Skip to content

Commit 398cb11

Browse files
committed
Use initializeAuth instead of getAuth for Firebase Auth in iOS; update web/lib/firebase/users.ts to avoid gapi iframe issue on capacitor://localhost. Increment iOS project version to 7.
1 parent 64abd8d commit 398cb11

4 files changed

Lines changed: 51 additions & 5 deletions

File tree

ios/App/App.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@
357357
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
358358
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
359359
CODE_SIGN_STYLE = Automatic;
360-
CURRENT_PROJECT_VERSION = 6;
360+
CURRENT_PROJECT_VERSION = 7;
361361
INFOPLIST_FILE = App/Info.plist;
362362
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
363363
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -378,7 +378,7 @@
378378
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
379379
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
380380
CODE_SIGN_STYLE = Automatic;
381-
CURRENT_PROJECT_VERSION = 6;
381+
CURRENT_PROJECT_VERSION = 7;
382382
INFOPLIST_FILE = App/Info.plist;
383383
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
384384
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";

ios/CLAUDE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ is in the [root CLAUDE.md](../CLAUDE.md).
2727
`WebViewLocalServer` already resolves `.html` itself. If you add a dynamic route, no change is
2828
needed — the router finds the bracketed file by scanning the directory.
2929

30+
- **Firebase Auth must be created with `initializeAuth`, not `getAuth`, inside the native shells.**
31+
`getAuth` installs the default popup/redirect resolver, which loads `apis.google.com/js/api.js` and
32+
builds a gapi iframe at startup. gapi cannot parse a non-http origin, so on `capacitor://localhost`
33+
it throws `evaluating 'gapi.iframes.getContext'` and auth never initialises — `onIdTokenChanged`
34+
never fires, `useUser()` stays `undefined`, and `pages/index.tsx` shows its loading animation
35+
forever with no visible error. See `web/lib/firebase/users.ts`. Android is unaffected only because
36+
Capacitor serves it from `https://localhost`; iOS cannot copy that, since the `capacitor://` scheme
37+
is what makes the origin secure for `getUserMedia` and `crypto.subtle`.
38+
3039
- **The iOS web build runs on macOS, so `scripts/build_web_view.sh` must stay BSD-tool-safe.** It
3140
strips `getStaticProps`/`getStaticPaths` from the pages in `SSG_PAGES` so dynamic routes export as
3241
plain `[username].html` / `blog/[slug].html` templates. That rename used GNU sed's `\b`, which BSD
@@ -61,6 +70,12 @@ is in the [root CLAUDE.md](../CLAUDE.md).
6170
`isNativeApp()` in `web/lib/util/webview.ts`. `isNativeApp()` is the default — the product is meant
6271
to look the same on both.
6372

73+
## Debugging the WebView from Linux
74+
75+
`ios/scripts/webview-eval.mjs` evaluates JS in the phone's WebView over `ios_webkit_debug_proxy`,
76+
without a browser — the fastest way to answer "what is the app actually seeing". Needs a build made
77+
with `IOS_WEB_DEBUG=1`; a stock Release build is never inspectable. See [README.md](README.md) §5.
78+
6479
## Editing the Xcode project without Xcode
6580

6681
`project.pbxproj`, `Info.plist` and `*.entitlements` are all text and are fine to edit directly.

ios/README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,21 @@ For the JS console, DOM and network — which is most of what matters in a WebVi
202202
Settings → Safari → Advanced → Web Inspector on the phone, then:
203203

204204
```bash
205-
ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html
206-
# open http://localhost:9221 and attach DevTools to the WKWebView
205+
ios_webkit_debug_proxy # then, in another terminal:
206+
node ios/scripts/webview-eval.mjs "location.href"
207+
node ios/scripts/webview-eval.mjs "Object.keys(Capacitor.Plugins).join(',')"
207208
```
208209

210+
`webview-eval.mjs` runs JavaScript in the WebView straight from the shell, which on Linux is far less
211+
trouble than the DevTools UI: the documented route is a `chrome-devtools://` URL, and current Chrome
212+
refuses to open that scheme from a link *or* the omnibox (it searches Google for it instead), while
213+
Firefox cannot open it at all. The script header documents the two protocol quirks it works around —
214+
commands must be wrapped in `Target.sendMessageToTarget`, and `awaitPromise` is unsupported, so use
215+
synchronous `XMLHttpRequest` or stash a result on `window` and read it back.
216+
217+
If you do want the full UI: open `http://localhost:9222` in **Chrome**, right-click the
218+
`capacitor://localhost` entry, Copy Link Address, and paste it into the address bar.
219+
209220
**A TestFlight build is not inspectable by default.** Capacitor sets `webView.isInspectable` only
210221
under `#if DEBUG`, and TestFlight ships Release — so the device advertises no inspectable page and the
211222
proxy's listing (`http://localhost:9222`) comes back empty, which reads as a broken proxy rather than a

web/lib/firebase/users.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
connectAuthEmulator,
1616
getAuth,
1717
GoogleAuthProvider,
18+
indexedDBLocalPersistence,
19+
initializeAuth,
1820
OAuthProvider,
1921
reauthenticateWithCredential,
2022
reauthenticateWithPopup,
@@ -32,7 +34,25 @@ dayjs.extend(utc)
3234

3335
export type {User}
3436

35-
export const auth = getAuth(app)
37+
/**
38+
* `getAuth()` installs Firebase's default popup/redirect resolver, which loads
39+
* `apis.google.com/js/api.js` and builds a gapi iframe as soon as auth initialises. gapi cannot parse
40+
* a non-http origin, so inside the iOS shell — served from `capacitor://localhost` — it throws
41+
* `undefined is not an object (evaluating 'gapi.iframes.getContext')` and takes auth initialisation
42+
* down with it. `onIdTokenChanged` then never fires, `useUser()` stays `undefined` forever, and
43+
* `pages/index.tsx` renders its loading animation for good. Nothing logs a failure the app can see.
44+
*
45+
* `initializeAuth` with no `popupRedirectResolver` skips that machinery entirely. The native shells
46+
* never need it: Google and Apple sign-in both go through `@capgo/capacitor-social-login` and
47+
* `signInWithCredential`. The browser keeps `getAuth`, where `signInWithPopup` is the whole point.
48+
*
49+
* Android happens to work either way — Capacitor serves it from `https://localhost`, which gapi
50+
* accepts. iOS cannot copy that: the scheme has to stay `capacitor://` for `getUserMedia` (voice
51+
* auto-fill) and `crypto.subtle` (the Sign-in-with-Apple nonce) to see a secure context.
52+
*/
53+
export const auth = isNativeApp()
54+
? initializeAuth(app, {persistence: indexedDBLocalPersistence})
55+
: getAuth(app)
3656

3757
if (IS_FIREBASE_EMULATOR) {
3858
connectAuthEmulator(auth, 'http://127.0.0.1:9099', {disableWarnings: true})

0 commit comments

Comments
 (0)