fix(mobile): gate auth boot on region and kill hardcoded cal.com URLs#83
Merged
dhairyashiil merged 2 commits intodevin/1776757080-eu-region-followupfrom Apr 22, 2026
Conversation
… add CI guardrail - Collapse AuthContext mount effects into one gated async boot so checkAuthState never fires before preloadRegion resolves; rebuild oauthService on region mismatch and rebind setupRefreshTokenFunction. - Add logoutRef / refreshTokenRef so callbacks wired into the shared CalComAPIService always hit the latest impls. - Warn once (dev-only) inside getBrowserSpecificOAuthConfig when EU region is selected but the per-browser EU client id / redirect URI is missing. - Document EU + per-browser OAuth env vars in .env.example. - Add getCalSupportUrl() and getCalHelpUrl(slug) to utils/region.ts; swap hardcoded cal.com URLs in alerts.ts, (more)/index.tsx, AdvancedTab.tsx, RecurringTab.tsx. - Add apps/mobile/scripts/check-no-cal-hostnames.sh and append it to root 'lint:all' so future hardcoded hostnames fail CI.
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Deployment failed with the following error: View Documentation: https://vercel.com/docs/accounts/team-members-and-roles |
…n, tighten EU OAuth fallback warn - booking-actions.ts: `meetingUrl: getMeetingUrl(booking) ?? undefined` so the `string | null` return narrows cleanly into the `string | undefined` NormalizedBooking field; mobile typecheck now exits 0. - LoginScreen.tsx: track `regionPreloadPending` and disable the Continue CTA (and no-op the handler) until `preloadRegion()` resolves, so a fast tap can't start OAuth against the default region while the saved region is still in flight. - oauthService.ts: refactor `maybeWarnEuFallback` to outcome-based detection — compute the US pair for the active browser and warn only when the final resolved (clientId, redirectUri) pair equals the US pair. Stays quiet when only per-browser `_EU` vars are missing but shared `_EU` vars carry EU credentials through.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
fb10e6c
into
devin/1776757080-eu-region-followup
8 of 10 checks passed
8 tasks
dhairyashiil
added a commit
that referenced
this pull request
Apr 24, 2026
…ension sync, and login picker polish (#81) * feat: add EU region support to companion apps Adds a Region Select (US / EU) to the mobile login screen and routes OAuth redirects, API calls, and deep links to the correct regional host (app.cal.com + api.cal.com vs app.cal.eu + api.cal.eu) based on the user's selection. The extension's host_permissions and background API_BASE_URL are also region-aware; the selected region is synced from the iframe alongside OAuth tokens so validateTokens + all background API calls hit the correct regional endpoint. Co-Authored-By: peer@cal.com <peer@cal.com> * feat: finish mobile region coverage and harden extension sync - Make mobile asset/icon + external link URLs region-aware via getCalAppUrl/getCalWebUrl - Clear cal_region on mobile logout to mirror extension behavior - Drop oauthService double-init in AuthContext - Validate and persist region on write in extension sync-oauth-tokens handler - TODO note on cal.com/help/* URLs pending EU mirror decision * fix: address review feedback on EU region followup - region.ts: drop localStorage.removeItem in clearRegion() (asymmetric with setRegion, which only writes through generalStorage) and always notify() so listeners aren't stale when clearRegion() runs before preloadRegion() - background/index.ts: hoist REGION_STORAGE_KEY constant to the top of the module and use it consistently (removes the TDZ footgun and aligns the two handlers that previously inlined the literal) - AuthContext: restore synchronous oauthService construction in the useState initializer (so consumers see a non-null service on first render and mount-time failures log immediately); the effect now only rebuilds when preloadRegion() returns a region different from the initial in-memory one, avoiding the double-init - getAvatarUrl: JSDoc examples reference getCalWebUrl() explicitly * fix: validate extension OAuth tokens against incoming region The sync-oauth-tokens handler persisted the region alongside the tokens, but validateTokens() read the region from chrome.storage.local before it was written, so a user switching regions would validate against the old API host and always fail. Thread the validated region through to validateTokens so it picks the right base URL directly; callers that don't yet know the region (refresh, resume) fall back to getApiBaseUrl(). * fix oauth * fix(mobile): gate auth boot on region and kill hardcoded cal.com URLs (#83) * fix(mobile): gate auth boot on region, warn on EU OAuth fallback, and add CI guardrail - Collapse AuthContext mount effects into one gated async boot so checkAuthState never fires before preloadRegion resolves; rebuild oauthService on region mismatch and rebind setupRefreshTokenFunction. - Add logoutRef / refreshTokenRef so callbacks wired into the shared CalComAPIService always hit the latest impls. - Warn once (dev-only) inside getBrowserSpecificOAuthConfig when EU region is selected but the per-browser EU client id / redirect URI is missing. - Document EU + per-browser OAuth env vars in .env.example. - Add getCalSupportUrl() and getCalHelpUrl(slug) to utils/region.ts; swap hardcoded cal.com URLs in alerts.ts, (more)/index.tsx, AdvancedTab.tsx, RecurringTab.tsx. - Add apps/mobile/scripts/check-no-cal-hostnames.sh and append it to root 'lint:all' so future hardcoded hostnames fail CI. * fix(mobile): unblock mobile typecheck, gate login CTA on preloadRegion, tighten EU OAuth fallback warn - booking-actions.ts: `meetingUrl: getMeetingUrl(booking) ?? undefined` so the `string | null` return narrows cleanly into the `string | undefined` NormalizedBooking field; mobile typecheck now exits 0. - LoginScreen.tsx: track `regionPreloadPending` and disable the Continue CTA (and no-op the handler) until `preloadRegion()` resolves, so a fast tap can't start OAuth against the default region while the saved region is still in flight. - oauthService.ts: refactor `maybeWarnEuFallback` to outcome-based detection — compute the US pair for the active browser and warn only when the final resolved (clientId, redirectUri) pair equals the US pair. Stays quiet when only per-browser `_EU` vars are missing but shared `_EU` vars carry EU credentials through. * refactor(mobile/oauth): share native redirect URI between US and EU clients Native Cal.com OAuth clients for both regions register the same `expo-wxt-app://oauth/callback` redirect URI, so the mobile code no longer needs an EU-specific `_REDIRECT_URI_EU`. Only the client ID differs by region on native. - getBrowserSpecificOAuthConfig: native branch now uses the shared EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI directly and emits a clearer one-shot dev warning when the EU client ID is missing (falls back to US client ID). Empty-string guard prevents a misleading warning when no US client is configured either. - Web/browser branches retain per-region redirect URI env support via the renamed maybeWarnEuFallbackWeb helper. - .env.example: drops the native REDIRECT_URI_EU entry and documents that the single native redirect URI must be allowlisted on both the US and EU Cal.com OAuth client records. * improve login screen * fix(mobile/ci): scan entire apps/mobile tree for hardcoded cal hostnames The check-no-cal-hostnames.sh guardrail claimed to enforce 'any file under apps/mobile' but only scanned six named subdirs (components, app, utils, services, hooks, contexts), leaving api/, config/, constants/, lib/, targets/, types/, widgets/, and top-level config files (app.json, etc.) silently exempt. New hardcoded cal.com / cal.eu strings in any of those locations would bypass CI. Switch to a whole-tree 'rg .' scan (rg respects .gitignore so node_modules and build artifacts are still skipped), drop the manual SEARCH_DIRS list, and update the allowlist regex to accept the './' prefix rg emits when invoked with a directory arg. Header comment updated to describe what the script actually enforces. * increase version --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: peer@cal.com <peer@cal.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on top of #81. Finishes the mobile side of the EU-region audit:
checkAuthState()fire on the US-defaultoauthServicebeforepreloadRegion()resolved the saved EU region, so the very first authenticated request could hit the wrong regional API. Collapsed them into one mount-only async effect that awaitspreloadRegion(), rebuildsoauthService(and rebindssetupRefreshTokenFunction) if the preloaded region differs from the initial one, and only then callscheckAuthState(activeService).handleOAuthAuthandcheckAuthStatenow take the service as an explicit arg so we don't depend on post-setStatetiming.logoutandrefreshTokeninlogoutRef/refreshTokenRefso callbacks registered on the sharedCalComAPIServicesingleton always read the latest impls, even though the boot effect runs only once.subscribeRegionlistener and the preload-mismatch branch both callsetupRefreshTokenFunction(next)so token refresh stays wired to whichever service is currently active.LoginScreennow tracksregionPreloadPendingand disables the Continue CTA (and no-ops its handler) untilpreloadRegion()resolves, so a fast tap can't kick off an OAuth flow against the default region while the persisted region is still loading from storage.getBrowserSpecificOAuthConfig. Computes the US pair for the active browser and warns only when the final resolved(clientId, redirectUri)equals the US pair — stays quiet when only per-browser_EUvars are missing but the shared_EUvars carry EU credentials through. Documented all EU + per-browser env vars inapps/mobile/.env.example.getCalSupportUrl()andgetCalHelpUrl(slug)toapps/mobile/utils/region.ts. Both currently returncal.comglobally becausego.cal.eu/cal.eu/helpdon't exist — single-line flip the day Cal mirrors them. Replaced the ten hardcodedcal.comcall sites flagged by the audit (alerts copy, Support row in More, nine AdvancedTablearnMoreUrls, one RecurringTablearnMoreUrl) and deleted the now-staleTODO(eu-region)blocks.booking-actions.ts:138meetingUrl: getMeetingUrl(booking) ?? undefinedso thestring | nullreturn narrows cleanly intoNormalizedBooking.meetingUrl: string | undefined.bun --filter mobile typechecknow passes.apps/mobile/scripts/check-no-cal-hostnames.sh(word-boundary regex, skips comment lines, allowlistsutils/region.ts/utils/booking.ts/ the env template / itself) wired into rootbun run lint:allasbun run check:no-cal-hostnames.Out of scope (flagged in the locked plan):
webAuth.tsregion gating and any extension-side EU issues — those live on the #81 branch this PR stacks onto. Known tradeoff:clearAuthstill closes overoauthServicefrom React state, so a live region swap while logged in could briefly callclearTokensFromExtension()against the previous service — tracked as follow-up; relevant only if the product ever allows rebinding in place without re-login.Review & Testing Checklist for Human
api.cal.eu(no US flicker). Main correctness claim of the boot-effect collapse.setupRefreshTokenFunctiongot rebound aftersubscribeRegion).cal.com(intentional — helpers default global). TriggershowNotAvailableAlertand confirm copy reads "This feature is not available in the app yet. To use, please visit Cal.com."EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_EUset but per-browser_EUvars unset, switch to EU — should stay silent (shared_EUcovers the per-browser arm). Then unset the shared_EUpair — should log a single[OAuth] EU region selected but the resolved client ID / redirect URI for <browser> matches the US pair…warning and not repeat.Notes
useExhaustiveDependenciesfires on the mount-only boot effect and onhandleOAuthAuth; both have abiome-ignorewith a rationale explaining the intent.apps/mobile/scripts/check-no-cal-hostnames.shand is registered at the rootpackage.jsonrather thanapps/mobile/package.jsonsincelint:alllives at the root.apps/mobilehas no test harness; the CI grep is the regression net.Link to Devin session: https://app.devin.ai/sessions/ccbfee87cc954cba9a475b12845c6b5d
Requested by: @dhairyashiil