Skip to content

feat: show a clear error when trying to use CAAPI untunnelled in localhost#3504

Merged
fredericoo merged 10 commits intomainfrom
fb-better-feedback-for-cap
Mar 3, 2026
Merged

feat: show a clear error when trying to use CAAPI untunnelled in localhost#3504
fredericoo merged 10 commits intomainfrom
fb-better-feedback-for-cap

Conversation

@fredericoo
Copy link
Copy Markdown
Contributor

@fredericoo fredericoo commented Feb 25, 2026

WHY are these changes introduced?

Local development has a bad failure mode for Customer Account OAuth:

When a developer opens /account on localhost without tunneling, the app redirects into the OAuth flow and eventually fails with a redirect URI error from Shopify. The error is late and unclear, so it does not tell developers what they need to do next.

WHAT is this pull request doing?

  • throws an error and offers a way out for users: image
  • zero impact in bundle size: because NODE_ENV is static at build time, we can get rid of the code path entirely when bundling with vite

HOW to test your changes?

  1. Install deps and build Hydrogen package:
    • pnpm --dir packages/hydrogen build
  2. Run unit tests:
    • pnpm --dir packages/hydrogen test -- src/customer/customer.test.ts
  3. Start a dev app without customer-account tunneling and visit:
    • http://localhost:3000/account
  4. Verify result:
    • You should not be redirected to Shopify OAuth.
    • You should see a 400 error page with guidance to use --customer-account-push and a *.tryhydrogen.dev URL.
  5. Start dev with tunneling (--customer-account-push) and open the provided *.tryhydrogen.dev URL.
  6. Verify /account continues into normal Customer Account login flow.

Post-merge steps

None.

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or noteworthy changes
  • I've added tests to cover my changes
  • I've added or updated the documentation

@shopify
Copy link
Copy Markdown
Contributor

shopify Bot commented Feb 25, 2026

Oxygen deployed a preview of your fb-better-feedback-for-cap branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment March 3, 202612:48 PM

Learn more about Hydrogen's GitHub integration.

@fredericoo fredericoo marked this pull request as ready for review February 25, 2026 16:00
@fredericoo fredericoo requested a review from a team as a code owner February 25, 2026 16:00
Copy link
Copy Markdown
Contributor

@kdaviduik kdaviduik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

massive DX improvement ❤️

Comment thread packages/hydrogen/src/customer/customer.ts
Comment thread packages/hydrogen/src/customer/customer.test.ts
Comment thread packages/hydrogen/src/customer/customer.ts Outdated
Comment thread packages/hydrogen/src/customer/customer.ts Outdated
throw new Response(
[
'Customer Account API OAuth requires a Hydrogen tunnel in local development.',
'Run `shopify hydrogen dev --customer-account-push`.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: these commands can also be aliased as just h2 dev rather than shopify hydrogen dev. i don't think the message needs to be updated but if you can think of a good way to communicate that either one works, go for it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats very fair

i made it more generic just mentioning to use the flag. if they got to this screen it means they know how to start the dev server, and however they did, they can just add the flag to the end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep exactly, i think that's super reasonable!

Copy link
Copy Markdown
Contributor

@kdaviduik kdaviduik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i trust you, though i'm not able to 🎩 this - i'm seeing this error. both with mock shop and a shop linked. i will investigate more tomorrow - this is also happening to be on main

Image

@fredericoo
Copy link
Copy Markdown
Contributor Author

i trust you, though i'm not able to 🎩 this - i'm seeing this error. both with mock shop and a shop linked. i will investigate more tomorrow - this is also happening to be on main

Image

yeah this is a real issue: if you are logged in or have a token you get a 500 instead, working on it!

@fredericoo fredericoo changed the title feat: error when using CAAPI untunelled feat: error when using CAAPI untunnelled Feb 26, 2026
@binks-code-reviewer
Copy link
Copy Markdown

binks-code-reviewer Bot commented Feb 26, 2026

🤖 Code Review · #projects-dev-ai for questions
React with 👍/👎 or reply — all feedback helps improve the agent.

Complete - No issues

📋 History

✅ No issues → ✅ No issues → ✅ No issues → ✅ No issues → ✅ No issues

Comment thread packages/hydrogen/src/customer/customer.ts
Comment thread packages/hydrogen/src/customer/customer.ts Outdated
@fredericoo fredericoo force-pushed the fb-better-feedback-for-cap branch from a681ba2 to dceae08 Compare February 26, 2026 18:49
Comment thread packages/hydrogen/src/customer/types.ts Outdated
@fredericoo fredericoo requested a review from kdaviduik February 26, 2026 19:03
await customer.login();
} catch (error) {
const response = error as Response & {message?: string};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: these tests work correctly because the test file globally stubs Response with a mock class at lines 26-38 that stores the constructor's body argument as .message. This is a pre-existing test pattern, not something this PR introduced.

The tests are correct and pass as intended. However, .message is a non-standard property that only exists due to this mock - the standard Response Web API doesn't have it. If the test environment or mock strategy ever changes, these assertions would silently break. Using await response.text() would be the spec-compliant approach, but since this follows the existing pattern throughout the file, I'm fine with this shipping as-is.

Comment thread packages/hydrogen/src/customer/customer.ts Outdated
Comment thread packages/hydrogen/src/customer/customer.ts
throw new Response(
[
'Customer Account API OAuth requires a Hydrogen tunnel in local development.',
'Run `shopify hydrogen dev --customer-account-push`.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep exactly, i think that's super reasonable!

Comment thread packages/hydrogen/src/customer/customer.ts Outdated
Comment thread packages/hydrogen/src/customer/types.ts Outdated
@fredericoo fredericoo changed the title feat: error when using CAAPI untunnelled feat: show a clear error when trying to use CAAPI untunnelled in localhost Feb 27, 2026
@fredericoo fredericoo merged commit 1c19b87 into main Mar 3, 2026
15 checks passed
@fredericoo fredericoo deleted the fb-better-feedback-for-cap branch March 3, 2026 14:53
fredericoo added a commit that referenced this pull request Mar 31, 2026
The development-only tunnel domain check (throwIfNotTunnelled) added in
#3504 blocks legitimate HTTPS setups like ngrok and local HTTPS proxies.
Rather than loosening the check entirely (which re-introduces the DX gap
that #3504 was designed to prevent), this adds an explicit opt-out via
the useCustomAuthDomain boolean option.

When set to true, the tunnel check logs a warnOnce with actionable
guidance (including the redirect_uri to register) instead of throwing.
Default behavior is unchanged — developers without the opt-out still
get the original error directing them to use --customer-account-push.

The implementation uses a closure pattern (ensureTunnel) inside
createCustomerAccountClient to avoid pass-through parameters at all 6
call sites. The tunnel check was also extracted from defaultAuthStatusHandler
into the authStatusHandler closure for consistent call-site ownership.

Fixes #3619
fredericoo added a commit that referenced this pull request Mar 31, 2026
The development-only tunnel domain check (throwIfNotTunnelled) added in
#3504 blocks legitimate HTTPS setups like ngrok and local HTTPS proxies.
Rather than loosening the check entirely (which re-introduces the DX gap
that #3504 was designed to prevent), this adds an explicit opt-out via
the useCustomAuthDomain boolean option.

When set to true, the tunnel check logs a warnOnce with actionable
guidance (including the redirect_uri to register) instead of throwing.
Default behavior is unchanged — developers without the opt-out still
get the original error directing them to use --customer-account-push.

The implementation uses a closure pattern (ensureTunnel) inside
createCustomerAccountClient to avoid pass-through parameters at all 6
call sites. The tunnel check was also extracted from defaultAuthStatusHandler
into the authStatusHandler closure for consistent call-site ownership.

Fixes #3619
fredericoo added a commit that referenced this pull request Mar 31, 2026
…ck (#3642)

* feat(hydrogen): add useCustomAuthDomain opt-out for tunnel domain check

The development-only tunnel domain check (throwIfNotTunnelled) added in
#3504 blocks legitimate HTTPS setups like ngrok and local HTTPS proxies.
Rather than loosening the check entirely (which re-introduces the DX gap
that #3504 was designed to prevent), this adds an explicit opt-out via
the useCustomAuthDomain boolean option.

When set to true, the tunnel check logs a warnOnce with actionable
guidance (including the redirect_uri to register) instead of throwing.
Default behavior is unchanged — developers without the opt-out still
get the original error directing them to use --customer-account-push.

The implementation uses a closure pattern (ensureTunnel) inside
createCustomerAccountClient to avoid pass-through parameters at all 6
call sites. The tunnel check was also extracted from defaultAuthStatusHandler
into the authStatusHandler closure for consistent call-site ownership.

Fixes #3619

* refactor: rename throwIfNotTunnelled to checkTunnelDomain and flatten nesting

The function no longer exclusively throws — when useCustomAuthDomain is
enabled, it warns and returns. The new name better describes both code
paths. Early returns replace the 3-level-deep nesting with flat,
top-to-bottom guard clauses.

Addresses review feedback from @itsjustriley.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add production no-op test for useCustomAuthDomain

Verifies that checkTunnelDomain is inert outside of development —
the NODE_ENV !== 'development' early return must prevent any warning
or throw in production, regardless of the useCustomAuthDomain flag.
This guards against accidental removal of the environment gate.

Addresses review feedback from @itsjustriley.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add useCustomAuthDomain coverage for mutate, logout, and authorize

Completes tunnel-check test coverage across all six ensureTunnel call
sites. Previously only login, query, and handleAuthStatus were tested.
This ensures that any method-specific regression in the tunnel check
wiring would be caught.

Addresses review feedback from @itsjustriley.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants