@bobfrankston/rmfmail 1.2.188 → 1.2.190
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TODO.md +10 -7
- package/client/android-bootstrap.bundle.js +66 -47
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +22 -0
- package/client/app.bundle.js.map +3 -3
- package/client/components/calendar-sidebar.js +12 -1
- package/client/components/calendar-sidebar.js.map +1 -1
- package/client/components/calendar-sidebar.ts +8 -1
- package/client/components/message-viewer.js +19 -0
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +17 -0
- package/client/compose/compose.bundle.js +4 -2
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/editor.js +11 -2
- package/client/compose/editor.js.map +1 -1
- package/client/compose/editor.ts +11 -2
- package/client/package.json +1 -1
- package/docs/TODO-prereorg-snapshot-2026-07-15.md +1130 -0
- package/package.json +5 -5
- package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.js +84 -50
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +86 -51
- package/packages/mailx-store-web/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-33816 → node_modules.npmglobalize-stash-12376}/.package-lock.json +0 -0
|
@@ -1024,61 +1024,80 @@ async function refreshAccessToken(refreshToken: string): Promise<{ access_token:
|
|
|
1024
1024
|
// ── Token provider (browser OAuth, same as desktop) ──
|
|
1025
1025
|
|
|
1026
1026
|
function createNativeTokenProvider(email: string): () => Promise<string> {
|
|
1027
|
-
return
|
|
1028
|
-
//
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1027
|
+
return () => {
|
|
1028
|
+
// C158: single-flight per email. Startup fires several concurrent
|
|
1029
|
+
// token requests (GDrive reconcile, contacts sync, syncAll) — with
|
|
1030
|
+
// no cached token each one launched its OWN browser-consent intent
|
|
1031
|
+
// (three "Starting OAuth flow" in one boot, 2026-07-22 logit trail).
|
|
1032
|
+
// Concurrent callers now share one resolution. Keyed on a window
|
|
1033
|
+
// global, not a module local, because the double-init bug that
|
|
1034
|
+
// exposed this also loads the module twice — two module instances
|
|
1035
|
+
// must still share the guard.
|
|
1036
|
+
const w = window as any;
|
|
1037
|
+
const inflight: Map<string, Promise<string>> = w.__rmfOAuthInflight ||= new Map();
|
|
1038
|
+
const key = canonEmail(email);
|
|
1039
|
+
const existing = inflight.get(key);
|
|
1040
|
+
if (existing) return existing;
|
|
1041
|
+
const p = fetchTokenForEmail(email).finally(() => inflight.delete(key));
|
|
1042
|
+
inflight.set(key, p);
|
|
1043
|
+
return p;
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
async function fetchTokenForEmail(email: string): Promise<string> {
|
|
1048
|
+
// Check cached token first
|
|
1049
|
+
const cached = await getCachedToken(email);
|
|
1050
|
+
if (cached?.access_token) {
|
|
1051
|
+
const expiresAt = cached.expires_at || 0;
|
|
1052
|
+
const bufferMs = 5 * 60 * 1000; // 5 min buffer
|
|
1053
|
+
if (Date.now() < expiresAt - bufferMs) {
|
|
1054
|
+
return cached.access_token;
|
|
1055
|
+
}
|
|
1056
|
+
// Try refresh
|
|
1057
|
+
if (cached.refresh_token) {
|
|
1058
|
+
try {
|
|
1059
|
+
console.log(`[oauth] Refreshing token for ${email}`);
|
|
1060
|
+
const refreshed = await refreshAccessToken(cached.refresh_token);
|
|
1061
|
+
const token = {
|
|
1062
|
+
access_token: refreshed.access_token,
|
|
1063
|
+
refresh_token: cached.refresh_token,
|
|
1064
|
+
expires_at: Date.now() + refreshed.expires_in * 1000,
|
|
1065
|
+
};
|
|
1066
|
+
await setCachedToken(email, token);
|
|
1067
|
+
return token.access_token;
|
|
1068
|
+
} catch (e: any) {
|
|
1069
|
+
console.warn(`[oauth] Refresh failed: ${e.message}, starting new flow`);
|
|
1051
1070
|
}
|
|
1052
1071
|
}
|
|
1072
|
+
}
|
|
1053
1073
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1074
|
+
// No valid token — start browser OAuth flow
|
|
1075
|
+
const bridge = (window as any)._nativeBridge;
|
|
1076
|
+
if (!bridge?.app?.startOAuth) {
|
|
1077
|
+
throw new Error("No native OAuth bridge");
|
|
1078
|
+
}
|
|
1059
1079
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
};
|
|
1078
|
-
await setCachedToken(email, token);
|
|
1079
|
-
console.log(`[oauth] Token obtained for ${email}`);
|
|
1080
|
-
return token.access_token;
|
|
1080
|
+
const authUrl = `${OAUTH_CLIENT.authUri}?` + new URLSearchParams({
|
|
1081
|
+
client_id: OAUTH_CLIENT.clientId,
|
|
1082
|
+
redirect_uri: OAUTH_CLIENT.redirectUri,
|
|
1083
|
+
response_type: "code",
|
|
1084
|
+
scope: OAUTH_SCOPES,
|
|
1085
|
+
access_type: "offline",
|
|
1086
|
+
prompt: "consent",
|
|
1087
|
+
login_hint: email,
|
|
1088
|
+
}).toString();
|
|
1089
|
+
|
|
1090
|
+
console.log(`[oauth] Starting browser consent for ${email}`);
|
|
1091
|
+
const code = await bridge.app.startOAuth(authUrl);
|
|
1092
|
+
const tokens = await exchangeCodeForTokens(code);
|
|
1093
|
+
const token = {
|
|
1094
|
+
access_token: tokens.access_token,
|
|
1095
|
+
refresh_token: tokens.refresh_token,
|
|
1096
|
+
expires_at: Date.now() + tokens.expires_in * 1000,
|
|
1081
1097
|
};
|
|
1098
|
+
await setCachedToken(email, token);
|
|
1099
|
+
console.log(`[oauth] Token obtained for ${email}`);
|
|
1100
|
+
return token.access_token;
|
|
1082
1101
|
}
|
|
1083
1102
|
|
|
1084
1103
|
// ── GDrive folder lookup ──
|
|
@@ -1339,7 +1358,23 @@ async function waitForNativeBridge(timeoutMs: number = 5000): Promise<void> {
|
|
|
1339
1358
|
});
|
|
1340
1359
|
}
|
|
1341
1360
|
|
|
1342
|
-
export
|
|
1361
|
+
export function initAndroid(): Promise<void> {
|
|
1362
|
+
// C158: idempotency guard. The 2026-07-22 fold/unfold logit trail showed
|
|
1363
|
+
// ONE WebView reload executing the boot module TWICE — duplicate "bridge
|
|
1364
|
+
// installed", duplicate GDrive lookups, tripled OAuth launches. The
|
|
1365
|
+
// guard lives on window (not a module local) so it holds even when the
|
|
1366
|
+
// module itself is instantiated twice (bundle + package-path specifiers
|
|
1367
|
+
// resolve to distinct module instances).
|
|
1368
|
+
const w = window as any;
|
|
1369
|
+
if (w.__rmfInitAndroid) {
|
|
1370
|
+
console.warn("[android] initAndroid called again — duplicate suppressed (C158)");
|
|
1371
|
+
vlog("C158: duplicate initAndroid call suppressed");
|
|
1372
|
+
return w.__rmfInitAndroid;
|
|
1373
|
+
}
|
|
1374
|
+
return w.__rmfInitAndroid = initAndroidOnce();
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
async function initAndroidOnce(): Promise<void> {
|
|
1343
1378
|
console.log("[android] Initializing mailx (main-thread mode)...");
|
|
1344
1379
|
|
|
1345
1380
|
// Main-thread path: async I/O (fetch, TCP bridge) doesn't block the UI,
|