Skip to content

fix(mobile): gate auth boot on region and kill hardcoded cal.com URLs#83

Merged
dhairyashiil merged 2 commits intodevin/1776757080-eu-region-followupfrom
devin/1776885463-eu-mobile-helpers
Apr 22, 2026
Merged

fix(mobile): gate auth boot on region and kill hardcoded cal.com URLs#83
dhairyashiil merged 2 commits intodevin/1776757080-eu-region-followupfrom
devin/1776885463-eu-mobile-helpers

Conversation

@dhairyashiil
Copy link
Copy Markdown
Member

@dhairyashiil dhairyashiil commented Apr 22, 2026

Summary

Stacked on top of #81. Finishes the mobile side of the EU-region audit:

  • AuthContext boot race. Two separate mount effects previously let checkAuthState() fire on the US-default oauthService before preloadRegion() 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 awaits preloadRegion(), rebuilds oauthService (and rebinds setupRefreshTokenFunction) if the preloaded region differs from the initial one, and only then calls checkAuthState(activeService). handleOAuthAuth and checkAuthState now take the service as an explicit arg so we don't depend on post-setState timing.
  • Stable callback refs. Wrapped logout and refreshToken in logoutRef / refreshTokenRef so callbacks registered on the shared CalComAPIService singleton always read the latest impls, even though the boot effect runs only once.
  • Service rebind on region change. subscribeRegion listener and the preload-mismatch branch both call setupRefreshTokenFunction(next) so token refresh stays wired to whichever service is currently active.
  • Login tap race. LoginScreen now tracks regionPreloadPending and disables the Continue CTA (and no-ops its handler) until preloadRegion() 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.
  • EU OAuth fallback warning (outcome-based). Dev-only, fires at most once per JS session from inside 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 _EU vars are missing but the shared _EU vars carry EU credentials through. Documented all EU + per-browser env vars in apps/mobile/.env.example.
  • Helpers + hardcoded URL cleanup. Added getCalSupportUrl() and getCalHelpUrl(slug) to apps/mobile/utils/region.ts. Both currently return cal.com globally because go.cal.eu / cal.eu/help don't exist — single-line flip the day Cal mirrors them. Replaced the ten hardcoded cal.com call sites flagged by the audit (alerts copy, Support row in More, nine AdvancedTab learnMoreUrls, one RecurringTab learnMoreUrl) and deleted the now-stale TODO(eu-region) blocks.
  • Typecheck unblock. booking-actions.ts:138 meetingUrl: getMeetingUrl(booking) ?? undefined so the string | null return narrows cleanly into NormalizedBooking.meetingUrl: string | undefined. bun --filter mobile typecheck now passes.
  • CI grep guardrail. New apps/mobile/scripts/check-no-cal-hostnames.sh (word-boundary regex, skips comment lines, allowlists utils/region.ts / utils/booking.ts / the env template / itself) wired into root bun run lint:all as bun run check:no-cal-hostnames.

Out of scope (flagged in the locked plan): webAuth.ts region gating and any extension-side EU issues — those live on the #81 branch this PR stacks onto. Known tradeoff: clearAuth still closes over oauthService from React state, so a live region swap while logged in could briefly call clearTokensFromExtension() 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

  • Cold-start EU: set region to EU, kill the app, relaunch, and confirm the very first authenticated request goes to api.cal.eu (no US flicker). Main correctness claim of the boot-effect collapse.
  • Login tap race: hard-relaunch on a slow-storage device and try to mash the Continue button as fast as possible — the CTA should remain disabled (greyed) until the region picker resolves to the saved value, and no OAuth should start before then.
  • Live region swap: in More → Region, flip US ↔ EU and confirm the refresh-token callback still fires on subsequent token expiry (i.e. setupRefreshTokenFunction got rebound after subscribeRegion).
  • Helpers: Support row and any "Learn more" link in Advanced / Recurring tabs both still navigate to cal.com (intentional — helpers default global). Trigger showNotAvailableAlert and confirm copy reads "This feature is not available in the app yet. To use, please visit Cal.com."
  • EU OAuth fallback warning: run with EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_EU set but per-browser _EU vars unset, switch to EU — should stay silent (shared _EU covers the per-browser arm). Then unset the shared _EU pair — 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

  • Biome's useExhaustiveDependencies fires on the mount-only boot effect and on handleOAuthAuth; both have a biome-ignore with a rationale explaining the intent.
  • The guardrail script lives at apps/mobile/scripts/check-no-cal-hostnames.sh and is registered at the root package.json rather than apps/mobile/package.json since lint:all lives at the root.
  • No tests added: apps/mobile has no test harness; the CI grep is the regression net.

Link to Devin session: https://app.devin.ai/sessions/ccbfee87cc954cba9a475b12845c6b5d
Requested by: @dhairyashiil

… 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.
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

Deployment failed with the following error:

You don't have permission to create a Preview Deployment for this Vercel project: cal-companion-mcp.

View Documentation: https://vercel.com/docs/accounts/team-members-and-roles

@github-actions github-actions Bot added the config Changes to project configuration files label Apr 22, 2026
…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.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
cal-companion-chat Ignored Ignored Apr 22, 2026 7:50pm

Request Review

@dhairyashiil dhairyashiil marked this pull request as ready for review April 22, 2026 19:50
@dhairyashiil dhairyashiil requested a review from a team as a code owner April 22, 2026 19:50
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

@dhairyashiil dhairyashiil merged commit fb10e6c into devin/1776757080-eu-region-followup Apr 22, 2026
8 of 10 checks passed
devin-ai-integration Bot pushed a commit that referenced this pull request Apr 22, 2026
Main contained squash-merged #80 (EU region support); #81 branch had the
same work as individual commits plus #81/#83 follow-ups. Resolved 7
textual conflicts by keeping HEAD in each case — HEAD already contained
every semantic change from main. Merge result is bit-identical to
pre-merge HEAD.
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Changes to project configuration files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant