+1
-8
frontend/components/Login.tsx
+1
-8
frontend/components/Login.tsx
···
68
68
e.preventDefault();
69
69
setError(null);
70
70
71
-
console.log("[Login] handleLogin called");
72
-
console.log("[Login] isStandalonePwa():", isStandalonePwa());
73
-
74
71
// Read directly from input in case Web Component updated it without firing input event
75
72
const currentHandle = inputRef.current?.value || handle;
76
73
···
98
95
99
96
// PWA mode: use popup OAuth to avoid losing PWA context
100
97
if (isStandalonePwa()) {
101
-
console.log("[PWA OAuth] Detected standalone PWA mode");
102
98
loginUrl += "&pwa=true";
103
99
try {
104
-
console.log("[PWA OAuth] Opening popup for:", loginUrl);
105
-
const result = await openOAuthPopup(loginUrl);
106
-
console.log("[PWA OAuth] Popup returned success:", result);
100
+
await openOAuthPopup(loginUrl);
107
101
// Success - reload to pick up the new session cookie
108
-
console.log("[PWA OAuth] Reloading page...");
109
102
globalThis.location.reload();
110
103
} catch (popupError) {
111
104
const message = popupError instanceof Error
+22
-52
frontend/utils/pwa.ts
+22
-52
frontend/utils/pwa.ts
···
7
7
*/
8
8
export function isStandalonePwa(): boolean {
9
9
// Check display-mode media query (works on Android Chrome)
10
-
const displayModeStandalone = globalThis.matchMedia &&
11
-
globalThis.matchMedia("(display-mode: standalone)").matches;
10
+
if (
11
+
globalThis.matchMedia &&
12
+
globalThis.matchMedia("(display-mode: standalone)").matches
13
+
) {
14
+
return true;
15
+
}
16
+
12
17
// Check iOS standalone mode
13
18
// deno-lint-ignore no-explicit-any
14
-
const iosStandalone = (globalThis.navigator as any).standalone === true;
19
+
if ((globalThis.navigator as any).standalone === true) {
20
+
return true;
21
+
}
22
+
15
23
// Check if launched from TWA (Trusted Web Activity on Android)
16
-
const isTwa = document.referrer.includes("android-app://");
24
+
if (document.referrer.includes("android-app://")) {
25
+
return true;
26
+
}
17
27
18
-
console.log("[PWA] Detection checks:", {
19
-
displayModeStandalone,
20
-
iosStandalone,
21
-
isTwa,
22
-
referrer: document.referrer,
23
-
});
24
-
25
-
return displayModeStandalone || iosStandalone || isTwa;
28
+
return false;
26
29
}
27
30
28
31
/**
···
36
39
export function openOAuthPopup(
37
40
loginUrl: string,
38
41
): Promise<{ did: string; handle: string }> {
39
-
console.log("[PWA OAuth] openOAuthPopup called with:", loginUrl);
40
-
41
42
return new Promise((resolve, reject) => {
42
-
console.log("[PWA OAuth] Inside promise, clearing previous result");
43
43
// Clear any previous OAuth result
44
44
localStorage.removeItem("pwa-oauth-result");
45
45
···
50
50
const top = Math.max(0, (screen.height - height) / 2);
51
51
52
52
// Open popup
53
-
console.log("[PWA OAuth] Opening popup window...");
54
53
const popup = globalThis.open(
55
54
loginUrl,
56
55
"oauth-popup",
57
56
`width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=yes,status=no`,
58
57
);
59
-
60
-
console.log("[PWA OAuth] Popup result:", popup ? "opened" : "blocked");
61
58
62
59
if (!popup) {
63
60
reject(
···
66
63
return;
67
64
}
68
65
69
-
console.log("[PWA OAuth] Setting up event listeners and polling...");
70
-
71
66
// Handle successful OAuth result
72
67
function handleSuccess(data: { did: string; handle: string }) {
73
-
console.log("[PWA OAuth] handleSuccess called with:", data);
74
68
cleanup();
75
69
localStorage.removeItem("pwa-oauth-result");
76
-
console.log("[PWA OAuth] Resolving promise and triggering reload...");
77
70
resolve(data);
78
71
}
79
72
···
101
94
102
95
// Poll localStorage frequently - the storage event doesn't always fire reliably
103
96
// especially after OAuth redirects through external providers
104
-
console.log("[PWA OAuth] Starting localStorage polling...");
105
-
let pollCount = 0;
106
-
107
-
// Keep reference for cleanup
108
97
let pollingStopped = false;
109
98
110
99
function pollForResult() {
111
-
if (pollingStopped) {
112
-
console.log("[PWA OAuth] Polling stopped");
113
-
return;
114
-
}
100
+
if (pollingStopped) return;
101
+
115
102
try {
116
-
pollCount++;
117
103
const result = localStorage.getItem("pwa-oauth-result");
118
-
// Log every 5th poll (every 1 second) to show we're still running
119
-
if (pollCount % 5 === 0) {
120
-
console.log(
121
-
"[PWA OAuth] Poll #" + pollCount + ", localStorage result:",
122
-
result ? "FOUND" : "empty",
123
-
);
124
-
}
125
104
if (result) {
126
-
console.log("[PWA OAuth] Found result in localStorage:", result);
127
105
const data = JSON.parse(result);
128
-
console.log("[PWA OAuth] Parsed data:", data);
129
106
if (data?.type === "oauth-callback" && data.success) {
130
-
console.log("[PWA OAuth] Calling handleSuccess");
131
107
handleSuccess({ did: data.did, handle: data.handle });
132
-
return; // Stop polling
108
+
return;
133
109
}
134
110
}
135
111
// Schedule next poll
136
112
setTimeout(pollForResult, 200);
137
-
} catch (e) {
138
-
console.error("[PWA OAuth] Poll error:", e);
113
+
} catch {
139
114
// Continue polling despite error
140
115
if (!pollingStopped) {
141
116
setTimeout(pollForResult, 200);
···
144
119
}
145
120
146
121
// Start polling immediately
147
-
console.log("[PWA OAuth] Starting first poll now...");
148
122
pollForResult();
149
-
console.log("[PWA OAuth] First poll executed");
150
123
151
124
// Don't rely on popup.closed - it returns true when popup navigates cross-origin
152
125
// Instead, use a timeout. OAuth should complete within 5 minutes max.
153
-
const OAUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
126
+
const OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
154
127
const startTime = Date.now();
155
128
156
129
const checkTimeout = setInterval(() => {
157
-
const elapsed = Date.now() - startTime;
158
-
if (elapsed > OAUTH_TIMEOUT_MS) {
159
-
console.log("[PWA OAuth] Timeout reached after 5 minutes");
130
+
if (Date.now() - startTime > OAUTH_TIMEOUT_MS) {
160
131
cleanup();
161
132
reject(new Error("Login timed out"));
162
133
clearInterval(checkTimeout);
163
134
}
164
-
}, 10000); // Check every 10 seconds
135
+
}, 10000);
165
136
166
137
function cleanup() {
167
-
console.log("[PWA OAuth] Cleanup called");
168
138
pollingStopped = true;
169
139
globalThis.removeEventListener("message", handleMessage);
170
140
globalThis.removeEventListener("storage", handleStorage);