@oxy.so/services 12.13.0 → 12.14.0
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/lib/commonjs/ui/boot/runProviderColdBoot.js +188 -88
- package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +116 -3
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/session/cachedBoot.js +95 -0
- package/lib/commonjs/ui/session/cachedBoot.js.map +1 -0
- package/lib/module/ui/boot/runProviderColdBoot.js +188 -88
- package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +117 -4
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/session/cachedBoot.js +92 -0
- package/lib/module/ui/session/cachedBoot.js.map +1 -0
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +24 -0
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/cachedBoot.d.ts +48 -0
- package/lib/typescript/commonjs/ui/session/cachedBoot.d.ts.map +1 -0
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +24 -0
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/cachedBoot.d.ts +48 -0
- package/lib/typescript/module/ui/session/cachedBoot.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/ui/boot/__tests__/cachedColdBoot.test.ts +187 -0
- package/src/ui/boot/runProviderColdBoot.ts +229 -95
- package/src/ui/context/OxyContext.tsx +146 -3
- package/src/ui/session/__tests__/cachedBoot.test.ts +32 -0
- package/src/ui/session/cachedBoot.ts +139 -0
|
@@ -82,9 +82,177 @@ async function detectOfflineHint() {
|
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
/** Starts the provider's fresh classification; a rejection is observed, not swallowed. */
|
|
86
|
+
function startClassification(opts) {
|
|
87
|
+
const classification = (async () => opts.resolveSessionLane?.() ?? resolveApplicationSessionLane(opts.oxyServices, opts.clientId))();
|
|
88
|
+
// Observed so a rejection that lands before it is awaited is not reported as
|
|
89
|
+
// unhandled; every caller still awaits it and sees the rejection.
|
|
90
|
+
classification.catch(() => undefined);
|
|
91
|
+
return classification;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** What the device lanes made of this holder: a session, a verdict against it, or no answer. */
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The device lanes of the boot (`runSessionColdBoot`), for a first boot or for
|
|
98
|
+
* the background check of a painted one.
|
|
99
|
+
*
|
|
100
|
+
* A first boot (`painted: false`) is on the critical path: its committed
|
|
101
|
+
* session resolves auth, bounded by {@link SESSION_HANDOFF_DEADLINE_MS}. A
|
|
102
|
+
* painted boot already resolved auth from the cache, so nothing waits on the
|
|
103
|
+
* handoff and the pure-local warm token is skipped: the check needs the mint's
|
|
104
|
+
* verdict, which a still-valid token cannot give.
|
|
105
|
+
*/
|
|
106
|
+
async function restoreDeviceSession(opts, painted) {
|
|
107
|
+
const {
|
|
108
|
+
oxyServices,
|
|
109
|
+
authStore,
|
|
110
|
+
sessionMode = 'account',
|
|
111
|
+
identity,
|
|
112
|
+
sessionClient,
|
|
113
|
+
syncDeviceCredentialToHost,
|
|
114
|
+
commitSession,
|
|
115
|
+
markAuthResolved
|
|
116
|
+
} = opts;
|
|
117
|
+
await opts.beforeDeviceBoot?.();
|
|
118
|
+
|
|
119
|
+
// Best-effort connectivity probe up front: an EXPLICIT offline verdict skips
|
|
120
|
+
// the two doomed network steps so routing settles immediately instead of
|
|
121
|
+
// burning the overall deadline on a mint that cannot succeed. Any ambiguity
|
|
122
|
+
// resolves to "online" — the network steps still run.
|
|
123
|
+
const offline = await detectOfflineHint();
|
|
124
|
+
let outcome = 'unreachable';
|
|
125
|
+
let holderRejected = false;
|
|
126
|
+
await runSessionColdBoot({
|
|
127
|
+
oxy: oxyServices,
|
|
128
|
+
store: authStore,
|
|
129
|
+
platform: {
|
|
130
|
+
isWeb: isWebBrowser(),
|
|
131
|
+
isNative: !isWebBrowser()
|
|
132
|
+
},
|
|
133
|
+
sessionMode,
|
|
134
|
+
identity,
|
|
135
|
+
// The device-wide shared credential slot, enabling the `shared-device-adopt`
|
|
136
|
+
// lane: a newly installed official app joins this device's existing session
|
|
137
|
+
// instead of falling back to signing a challenge with the Commons identity
|
|
138
|
+
// key. `null` on web, where each origin is its own device by design; the
|
|
139
|
+
// core lane is also skipped outright in `sessionMode: 'identity'`.
|
|
140
|
+
sharedDeviceCredential: createPlatformSharedDeviceCredentialStore() ?? undefined,
|
|
141
|
+
overallDeadlineMs: COLD_BOOT_OVERALL_DEADLINE_MS,
|
|
142
|
+
isOffline: () => offline,
|
|
143
|
+
requireServerVerdict: painted,
|
|
144
|
+
onHolderRejected: () => {
|
|
145
|
+
holderRejected = true;
|
|
146
|
+
},
|
|
147
|
+
onStepDeadline: stepId => {
|
|
148
|
+
loggerUtil.warn(`Cold-boot step "${stepId}" exceeded the ${COLD_BOOT_OVERALL_DEADLINE_MS}ms overall deadline — abandoned; routing proceeds signed-out`, {
|
|
149
|
+
component: 'runProviderColdBoot',
|
|
150
|
+
method: 'onStepDeadline'
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
onSession: async session => {
|
|
154
|
+
outcome = 'session';
|
|
155
|
+
// Mint already persisted `{deviceId, deviceSecret}` to the store; sync the
|
|
156
|
+
// in-memory SessionClient host so sockets + tab-focus re-mint can use it.
|
|
157
|
+
await syncDeviceCredentialToHost();
|
|
158
|
+
const handoff = commitSession({
|
|
159
|
+
sessionId: session.sessionId,
|
|
160
|
+
accessToken: session.accessToken,
|
|
161
|
+
userId: session.userId,
|
|
162
|
+
deviceState: session.state
|
|
163
|
+
}, {
|
|
164
|
+
activate: false
|
|
165
|
+
});
|
|
166
|
+
if (painted) {
|
|
167
|
+
// The account is already on screen; the commit refreshes its profile.
|
|
168
|
+
await handoff;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let handoffDeadlineId;
|
|
172
|
+
await Promise.race([handoff, new Promise(resolve => {
|
|
173
|
+
handoffDeadlineId = setTimeout(resolve, SESSION_HANDOFF_DEADLINE_MS);
|
|
174
|
+
})]).finally(() => {
|
|
175
|
+
if (handoffDeadlineId !== undefined) {
|
|
176
|
+
clearTimeout(handoffDeadlineId);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
markAuthResolved();
|
|
180
|
+
},
|
|
181
|
+
onSignedOut: async () => {
|
|
182
|
+
outcome = holderRejected ? 'rejected' : 'unreachable';
|
|
183
|
+
await syncDeviceCredentialToHost();
|
|
184
|
+
// A painted session is the caller's to end or keep (see verifyCachedSession).
|
|
185
|
+
if (painted) return;
|
|
186
|
+
const cred = await loadPersistedDeviceCredential(authStore);
|
|
187
|
+
if (cred) {
|
|
188
|
+
try {
|
|
189
|
+
await sessionClient.start();
|
|
190
|
+
} catch (socketError) {
|
|
191
|
+
if (__DEV__) {
|
|
192
|
+
loggerUtil.debug('Device socket start failed (non-fatal)', {
|
|
193
|
+
component: 'runProviderColdBoot'
|
|
194
|
+
}, socketError);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
markAuthResolved();
|
|
199
|
+
},
|
|
200
|
+
onStepError: (id, error) => {
|
|
201
|
+
if (__DEV__) {
|
|
202
|
+
loggerUtil.debug(`Cold-boot step "${id}" errored (non-fatal, falling through)`, {
|
|
203
|
+
component: 'runProviderColdBoot'
|
|
204
|
+
}, error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
await opts.afterDeviceBoot?.();
|
|
209
|
+
return outcome;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The background check of a painted session: the registry's classification and
|
|
214
|
+
* a mint with this holder's own credential (whose commit refreshes the profile).
|
|
215
|
+
* Each is asked until the server answers it. A verdict against the session
|
|
216
|
+
* signs out at that moment; no answer keeps the cached session on screen and
|
|
217
|
+
* hands the caller a retry.
|
|
218
|
+
*/
|
|
219
|
+
async function verifyCachedSession(opts, verified, classification) {
|
|
220
|
+
if (!verified.lane) {
|
|
221
|
+
let lane = null;
|
|
222
|
+
try {
|
|
223
|
+
lane = await classification;
|
|
224
|
+
} catch {
|
|
225
|
+
// The registry could not be asked (offline, timeout, 5xx). That is no
|
|
226
|
+
// verdict on this app: the cached `device` classification stands until it
|
|
227
|
+
// answers.
|
|
228
|
+
}
|
|
229
|
+
if (lane === 'oauth') {
|
|
230
|
+
await opts.onCachedSessionRejected?.();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
verified.lane = lane === 'device';
|
|
234
|
+
}
|
|
235
|
+
if (!verified.session) {
|
|
236
|
+
const outcome = await restoreDeviceSession(opts, true);
|
|
237
|
+
if (outcome === 'rejected') {
|
|
238
|
+
await opts.onCachedSessionRejected?.();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
verified.session = outcome === 'session';
|
|
242
|
+
}
|
|
243
|
+
if (!verified.lane || !verified.session) {
|
|
244
|
+
opts.onCachedSessionUnconfirmed?.(() => verifyCachedSession(opts, verified, verified.lane ? Promise.resolve('device') : startClassification(opts)));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
85
248
|
/**
|
|
86
249
|
* Device-first cold boot for `@oxy.so/services` providers.
|
|
87
250
|
*
|
|
251
|
+
* A returning session saved on this device (account mode, the app last
|
|
252
|
+
* classified `device`, its profile cached) paints first, with no network, and
|
|
253
|
+
* is then checked in the background — see `paintCachedSession`. Otherwise, a
|
|
254
|
+
* first boot:
|
|
255
|
+
*
|
|
88
256
|
* Ordered pipeline:
|
|
89
257
|
* 1. Complete an OAuth authorization-code return already on the URL (web)
|
|
90
258
|
* 2. `runSessionColdBoot` — device-secret mint (+ the native Commons-proof lane,
|
|
@@ -107,9 +275,6 @@ export async function runProviderColdBoot(opts) {
|
|
|
107
275
|
clientId,
|
|
108
276
|
authRedirectUri,
|
|
109
277
|
sessionMode = 'account',
|
|
110
|
-
identity,
|
|
111
|
-
sessionClient,
|
|
112
|
-
syncDeviceCredentialToHost,
|
|
113
278
|
commitSession,
|
|
114
279
|
markAuthResolved,
|
|
115
280
|
setTokenReady
|
|
@@ -118,13 +283,24 @@ export async function runProviderColdBoot(opts) {
|
|
|
118
283
|
setTokenReady(false);
|
|
119
284
|
try {
|
|
120
285
|
// Classification precedes every USE of a local secret, the shared credential
|
|
121
|
-
// and the identity key. Unavailable registry metadata fails closed
|
|
122
|
-
// offline
|
|
123
|
-
// overlaps the local work below.
|
|
124
|
-
const classification = (
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
|
|
286
|
+
// and the identity key. Unavailable registry metadata fails closed on a first
|
|
287
|
+
// boot, including offline. It starts now, before anything else, so its round
|
|
288
|
+
// trip overlaps the local work below.
|
|
289
|
+
const classification = startClassification(opts);
|
|
290
|
+
|
|
291
|
+
// A returning session saved on this device paints at once from the cache:
|
|
292
|
+
// the account, its cached content, auth resolved, no network on the way.
|
|
293
|
+
// The classification and the session are then checked in the background,
|
|
294
|
+
// and a verdict against either signs out at that moment. Identity-bound
|
|
295
|
+
// clients and OAuth returns keep the path below.
|
|
296
|
+
const freshLaunch = !identityBound && !hasOAuthReturnOnUrl();
|
|
297
|
+
if (freshLaunch && (await opts.paintCachedSession?.())) {
|
|
298
|
+
await verifyCachedSession(opts, {
|
|
299
|
+
lane: false,
|
|
300
|
+
session: false
|
|
301
|
+
}, classification);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
128
304
|
|
|
129
305
|
// The verdict decides HOW a session is restored, never WHETHER there is one.
|
|
130
306
|
// A web origin with nothing to restore and no OAuth return on the URL ends
|
|
@@ -132,7 +308,7 @@ export async function runProviderColdBoot(opts) {
|
|
|
132
308
|
// for an answer that cannot change the outcome. The classification still
|
|
133
309
|
// completes below and still gates every later sign-in
|
|
134
310
|
// (`ensureDeviceSessionLane` shares this same in-flight lookup).
|
|
135
|
-
if (
|
|
311
|
+
if (freshLaunch && isWebBrowser()) {
|
|
136
312
|
if (!(await hasRestorableState(authStore))) {
|
|
137
313
|
markAuthResolved();
|
|
138
314
|
const lane = await classification;
|
|
@@ -168,83 +344,7 @@ export async function runProviderColdBoot(opts) {
|
|
|
168
344
|
setTokenReady(true);
|
|
169
345
|
return;
|
|
170
346
|
}
|
|
171
|
-
await opts
|
|
172
|
-
|
|
173
|
-
// Best-effort connectivity probe up front: an EXPLICIT offline verdict skips
|
|
174
|
-
// the two doomed network steps so routing settles immediately instead of
|
|
175
|
-
// burning the overall deadline on a mint that cannot succeed. Any ambiguity
|
|
176
|
-
// resolves to "online" — the network steps still run.
|
|
177
|
-
const offline = await detectOfflineHint();
|
|
178
|
-
await runSessionColdBoot({
|
|
179
|
-
oxy: oxyServices,
|
|
180
|
-
store: authStore,
|
|
181
|
-
platform: {
|
|
182
|
-
isWeb: isWebBrowser(),
|
|
183
|
-
isNative: !isWebBrowser()
|
|
184
|
-
},
|
|
185
|
-
sessionMode,
|
|
186
|
-
identity,
|
|
187
|
-
// The device-wide shared credential slot, enabling the `shared-device-adopt`
|
|
188
|
-
// lane: a newly installed official app joins this device's existing session
|
|
189
|
-
// instead of falling back to signing a challenge with the Commons identity
|
|
190
|
-
// key. `null` on web, where each origin is its own device by design; the
|
|
191
|
-
// core lane is also skipped outright in `sessionMode: 'identity'`.
|
|
192
|
-
sharedDeviceCredential: createPlatformSharedDeviceCredentialStore() ?? undefined,
|
|
193
|
-
overallDeadlineMs: COLD_BOOT_OVERALL_DEADLINE_MS,
|
|
194
|
-
isOffline: () => offline,
|
|
195
|
-
onStepDeadline: stepId => {
|
|
196
|
-
loggerUtil.warn(`Cold-boot step "${stepId}" exceeded the ${COLD_BOOT_OVERALL_DEADLINE_MS}ms overall deadline — abandoned; routing proceeds signed-out`, {
|
|
197
|
-
component: 'runProviderColdBoot',
|
|
198
|
-
method: 'onStepDeadline'
|
|
199
|
-
});
|
|
200
|
-
},
|
|
201
|
-
onSession: async session => {
|
|
202
|
-
// Mint already persisted `{deviceId, deviceSecret}` to the store; sync the
|
|
203
|
-
// in-memory SessionClient host so sockets + tab-focus re-mint can use it.
|
|
204
|
-
await syncDeviceCredentialToHost();
|
|
205
|
-
const handoff = commitSession({
|
|
206
|
-
sessionId: session.sessionId,
|
|
207
|
-
accessToken: session.accessToken,
|
|
208
|
-
userId: session.userId,
|
|
209
|
-
deviceState: session.state
|
|
210
|
-
}, {
|
|
211
|
-
activate: false
|
|
212
|
-
});
|
|
213
|
-
let handoffDeadlineId;
|
|
214
|
-
await Promise.race([handoff, new Promise(resolve => {
|
|
215
|
-
handoffDeadlineId = setTimeout(resolve, SESSION_HANDOFF_DEADLINE_MS);
|
|
216
|
-
})]).finally(() => {
|
|
217
|
-
if (handoffDeadlineId !== undefined) {
|
|
218
|
-
clearTimeout(handoffDeadlineId);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
markAuthResolved();
|
|
222
|
-
},
|
|
223
|
-
onSignedOut: async () => {
|
|
224
|
-
await syncDeviceCredentialToHost();
|
|
225
|
-
const cred = await loadPersistedDeviceCredential(authStore);
|
|
226
|
-
if (cred) {
|
|
227
|
-
try {
|
|
228
|
-
await sessionClient.start();
|
|
229
|
-
} catch (socketError) {
|
|
230
|
-
if (__DEV__) {
|
|
231
|
-
loggerUtil.debug('Device socket start failed (non-fatal)', {
|
|
232
|
-
component: 'runProviderColdBoot'
|
|
233
|
-
}, socketError);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
markAuthResolved();
|
|
238
|
-
},
|
|
239
|
-
onStepError: (id, error) => {
|
|
240
|
-
if (__DEV__) {
|
|
241
|
-
loggerUtil.debug(`Cold-boot step "${id}" errored (non-fatal, falling through)`, {
|
|
242
|
-
component: 'runProviderColdBoot'
|
|
243
|
-
}, error);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
await opts.afterDeviceBoot?.();
|
|
347
|
+
await restoreDeviceSession(opts, false);
|
|
248
348
|
} catch (error) {
|
|
249
349
|
if (__DEV__) {
|
|
250
350
|
loggerUtil.error('Cold boot error', error instanceof Error ? error : new Error(String(error)), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["resolveApplicationSessionLane","logger","loggerUtil","runSessionColdBoot","loadPersistedDeviceCredential","createPlatformSharedDeviceCredentialStore","tryCompleteOAuthReturn","isWebBrowser","isNetConnectivityExplicitlyOffline","hasOAuthReturnOnUrl","search","globalThis","location","params","URLSearchParams","has","hasRestorableState","store","persisted","load","Boolean","accessToken","deviceSecret","SESSION_HANDOFF_DEADLINE_MS","COLD_BOOT_OVERALL_DEADLINE_MS","OFFLINE_PROBE_TIMEOUT_MS","detectOfflineHint","online","navigator","onLine","NetInfo","state","Promise","race","default","fetch","resolve","setTimeout","
|
|
1
|
+
{"version":3,"names":["resolveApplicationSessionLane","logger","loggerUtil","runSessionColdBoot","loadPersistedDeviceCredential","createPlatformSharedDeviceCredentialStore","tryCompleteOAuthReturn","isWebBrowser","isNetConnectivityExplicitlyOffline","hasOAuthReturnOnUrl","search","globalThis","location","params","URLSearchParams","has","hasRestorableState","store","persisted","load","Boolean","accessToken","deviceSecret","SESSION_HANDOFF_DEADLINE_MS","COLD_BOOT_OVERALL_DEADLINE_MS","OFFLINE_PROBE_TIMEOUT_MS","detectOfflineHint","online","navigator","onLine","NetInfo","state","Promise","race","default","fetch","resolve","setTimeout","startClassification","opts","classification","resolveSessionLane","oxyServices","clientId","catch","undefined","restoreDeviceSession","painted","authStore","sessionMode","identity","sessionClient","syncDeviceCredentialToHost","commitSession","markAuthResolved","beforeDeviceBoot","offline","outcome","holderRejected","oxy","platform","isWeb","isNative","sharedDeviceCredential","overallDeadlineMs","isOffline","requireServerVerdict","onHolderRejected","onStepDeadline","stepId","warn","component","method","onSession","session","handoff","sessionId","userId","deviceState","activate","handoffDeadlineId","finally","clearTimeout","onSignedOut","cred","start","socketError","__DEV__","debug","onStepError","id","error","afterDeviceBoot","verifyCachedSession","verified","lane","onCachedSessionRejected","onCachedSessionUnconfirmed","runProviderColdBoot","authRedirectUri","setTokenReady","identityBound","freshLaunch","paintCachedSession","onClassified","oauthCompleted","input","oauth","Error","String"],"sourceRoot":"../../../../src","sources":["ui/boot/runProviderColdBoot.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -21,7 +21,8 @@ import { runProviderColdBoot } from "../boot/runProviderColdBoot.js";
|
|
|
21
21
|
import { resolveApplicationSessionLane } from "../oauth/applicationSessionLane.js";
|
|
22
22
|
import { handleAuthError } from "../utils/errorHandlers.js";
|
|
23
23
|
import { hasPersistedSessionCredential, loadPersistedDeviceCredential } from "../utils/deviceCredential.js";
|
|
24
|
-
import { createTokenLossRecovery } from "../session/tokenLossRecovery.js";
|
|
24
|
+
import { createTokenLossRecovery, TOKEN_RECOVERY_BACKOFF_MS } from "../session/tokenLossRecovery.js";
|
|
25
|
+
import { createCachedBootStore } from "../session/cachedBoot.js";
|
|
25
26
|
import { bindAuthStoreToRuntime } from "../stores/authStore.js";
|
|
26
27
|
import { useLanguageManagement } from "../hooks/useLanguageManagement.js";
|
|
27
28
|
import { useSessionManagement } from "../hooks/useSessionManagement.js";
|
|
@@ -143,6 +144,7 @@ export const OxyRuntimeProvider = ({
|
|
|
143
144
|
clientId: clientIdProp,
|
|
144
145
|
services: oxyServices,
|
|
145
146
|
lane: null,
|
|
147
|
+
provisional: false,
|
|
146
148
|
pending: null
|
|
147
149
|
});
|
|
148
150
|
if (registeredLaneRef.current.clientId !== clientIdProp || registeredLaneRef.current.services !== oxyServices) {
|
|
@@ -150,6 +152,7 @@ export const OxyRuntimeProvider = ({
|
|
|
150
152
|
clientId: clientIdProp,
|
|
151
153
|
services: oxyServices,
|
|
152
154
|
lane: null,
|
|
155
|
+
provisional: false,
|
|
153
156
|
pending: null
|
|
154
157
|
};
|
|
155
158
|
}
|
|
@@ -272,6 +275,12 @@ export const OxyRuntimeProvider = ({
|
|
|
272
275
|
storageReadyRef.current = buildStorageDeferred();
|
|
273
276
|
}
|
|
274
277
|
const storageReady = storageReadyRef.current;
|
|
278
|
+
|
|
279
|
+
// What a returning session's first paint needs (the app's classification and
|
|
280
|
+
// the account's profile), kept in the same storage as the account query cache.
|
|
281
|
+
const cachedBoot = useMemo(() => storage ? createCachedBootStore(storage, storageKeyPrefix) : null, [storage, storageKeyPrefix]);
|
|
282
|
+
const cachedBootRef = useRef(cachedBoot);
|
|
283
|
+
cachedBootRef.current = cachedBoot;
|
|
275
284
|
useEffect(() => {
|
|
276
285
|
// `null` means the public provider owns initialization and has not resolved
|
|
277
286
|
// it yet. `undefined` means this internal provider was mounted directly and
|
|
@@ -419,10 +428,16 @@ export const OxyRuntimeProvider = ({
|
|
|
419
428
|
const selected = registeredLaneRef.current;
|
|
420
429
|
if (selected.clientId !== clientIdProp || selected.services !== oxyServices) throw new Error('Provider classification was superseded');
|
|
421
430
|
if (selected.pending) return selected.pending;
|
|
422
|
-
selected.lane = null;
|
|
431
|
+
if (!selected.provisional) selected.lane = null;
|
|
423
432
|
const pending = (async () => {
|
|
424
433
|
const lane = await resolveApplicationSessionLane(oxyServices, clientIdProp);
|
|
425
434
|
if (registeredLaneRef.current !== selected) throw new Error('Provider classification was superseded');
|
|
435
|
+
if (clientIdProp) void cachedBootRef.current?.saveLane(clientIdProp, lane);
|
|
436
|
+
if (selected.provisional) {
|
|
437
|
+
// The registry has answered: its verdict replaces the cached one now.
|
|
438
|
+
selected.provisional = false;
|
|
439
|
+
selected.lane = lane;
|
|
440
|
+
}
|
|
426
441
|
if (lane === 'oauth' && !hasIsolatedOAuthSession(runtime)) {
|
|
427
442
|
sessionClient.resetLocalState();
|
|
428
443
|
sessionClientHost.setDeviceCredential(null);
|
|
@@ -1075,6 +1090,100 @@ export const OxyRuntimeProvider = ({
|
|
|
1075
1090
|
if (!accountDialogOpen) clearSignInFlows(accountDialogControllerRef.current);
|
|
1076
1091
|
}, [accountDialogOpen]);
|
|
1077
1092
|
|
|
1093
|
+
// ── Cached first paint ───────────────────────────────────────────────────
|
|
1094
|
+
// Keep the profile the runtime holds for the signed-in account, so the next
|
|
1095
|
+
// cold boot can paint it before the network answers. Only for a device-lane
|
|
1096
|
+
// account session: an identity-bound client and an isolated OAuth session
|
|
1097
|
+
// always boot through the network. A sign-out deletes it.
|
|
1098
|
+
const cachedAccountJsonRef = useRef(null);
|
|
1099
|
+
useEffect(() => {
|
|
1100
|
+
if (!cachedBoot || identity) return;
|
|
1101
|
+
if (user === null) {
|
|
1102
|
+
if (cachedAccountJsonRef.current !== null) {
|
|
1103
|
+
cachedAccountJsonRef.current = null;
|
|
1104
|
+
void cachedBoot.clearAccount();
|
|
1105
|
+
}
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (registeredLaneRef.current.lane !== 'device' || hasIsolatedOAuthSession(runtime)) return;
|
|
1109
|
+
const json = JSON.stringify(user);
|
|
1110
|
+
if (json === cachedAccountJsonRef.current) return;
|
|
1111
|
+
cachedAccountJsonRef.current = json;
|
|
1112
|
+
void cachedBoot.saveAccount(user);
|
|
1113
|
+
}, [cachedBoot, identity, user, runtime]);
|
|
1114
|
+
|
|
1115
|
+
// Paint the session saved on this device: the bearer it left (when still
|
|
1116
|
+
// valid), the session mirror, the account from its cached profile, auth
|
|
1117
|
+
// resolved. Local reads only; the boot checks it all in the background.
|
|
1118
|
+
const paintCachedSession = useCallback(async () => {
|
|
1119
|
+
const cache = cachedBootRef.current;
|
|
1120
|
+
if (identity || !clientIdProp || !cache) return false;
|
|
1121
|
+
const cached = await cache.readSession(clientIdProp, authStore);
|
|
1122
|
+
const selected = registeredLaneRef.current;
|
|
1123
|
+
if (!cached || selected.clientId !== clientIdProp || selected.services !== oxyServices || selected.lane === 'oauth') {
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
1126
|
+
if (selected.lane === null) {
|
|
1127
|
+
// The cached verdict stands in for the registry until it answers, so the
|
|
1128
|
+
// refresh handler, lost-token recovery and sign-in treat this app as the
|
|
1129
|
+
// device lane it was on last run.
|
|
1130
|
+
selected.lane = 'device';
|
|
1131
|
+
selected.provisional = true;
|
|
1132
|
+
}
|
|
1133
|
+
if (cached.warmToken) oxyServices.session.setAccessToken(cached.warmToken);
|
|
1134
|
+
await syncDeviceCredentialToHost();
|
|
1135
|
+
const now = new Date();
|
|
1136
|
+
runtime.batch(() => {
|
|
1137
|
+
runtime.mergeSessions([{
|
|
1138
|
+
sessionId: cached.sessionId,
|
|
1139
|
+
deviceId: cached.deviceId,
|
|
1140
|
+
expiresAt: new Date(now.getTime() + DEFAULT_SESSION_VALIDITY_MS).toISOString(),
|
|
1141
|
+
lastActive: now.toISOString(),
|
|
1142
|
+
userId: cached.userId,
|
|
1143
|
+
isCurrent: true
|
|
1144
|
+
}], {
|
|
1145
|
+
merge: true
|
|
1146
|
+
});
|
|
1147
|
+
runtime.setActiveSessionId(cached.sessionId);
|
|
1148
|
+
runtime.setAccount(cached.user);
|
|
1149
|
+
});
|
|
1150
|
+
onAuthStateChangeRef.current?.(cached.user);
|
|
1151
|
+
markAuthResolvedRef.current();
|
|
1152
|
+
return true;
|
|
1153
|
+
}, [identity, clientIdProp, authStore, oxyServices, runtime, syncDeviceCredentialToHost]);
|
|
1154
|
+
|
|
1155
|
+
// A painted session the server could not be asked about yet stays on screen
|
|
1156
|
+
// and is asked again: on the lost-token backoff, and at once when the device
|
|
1157
|
+
// comes back online. Signed out meanwhile, there is nothing left to ask.
|
|
1158
|
+
const cachedSessionCheckRef = useRef({
|
|
1159
|
+
timer: null,
|
|
1160
|
+
attempt: 0,
|
|
1161
|
+
runNow: null
|
|
1162
|
+
});
|
|
1163
|
+
const scheduleCachedSessionCheck = useCallback(retry => {
|
|
1164
|
+
const check = cachedSessionCheckRef.current;
|
|
1165
|
+
if (check.timer !== null) clearTimeout(check.timer);
|
|
1166
|
+
const run = () => {
|
|
1167
|
+
if (check.timer !== null) clearTimeout(check.timer);
|
|
1168
|
+
check.timer = null;
|
|
1169
|
+
check.runNow = null;
|
|
1170
|
+
if (runtime.getSnapshot().account === null) return;
|
|
1171
|
+
retry().catch(retryError => {
|
|
1172
|
+
logger('Failed to check the cached session', retryError);
|
|
1173
|
+
});
|
|
1174
|
+
};
|
|
1175
|
+
const delay = TOKEN_RECOVERY_BACKOFF_MS[Math.min(check.attempt, TOKEN_RECOVERY_BACKOFF_MS.length - 1)];
|
|
1176
|
+
check.attempt += 1;
|
|
1177
|
+
check.runNow = run;
|
|
1178
|
+
check.timer = setTimeout(run, delay);
|
|
1179
|
+
}, [runtime, logger]);
|
|
1180
|
+
useEffect(() => () => {
|
|
1181
|
+
const check = cachedSessionCheckRef.current;
|
|
1182
|
+
if (check.timer !== null) clearTimeout(check.timer);
|
|
1183
|
+
check.timer = null;
|
|
1184
|
+
check.runNow = null;
|
|
1185
|
+
}, []);
|
|
1186
|
+
|
|
1078
1187
|
// ── Cold boot ────────────────────────────────────────────────────────────
|
|
1079
1188
|
// Device-first session restore via `runProviderColdBoot` (see boot/runProviderColdBoot.ts).
|
|
1080
1189
|
const runColdBoot = useCallback(async () => {
|
|
@@ -1100,12 +1209,15 @@ export const OxyRuntimeProvider = ({
|
|
|
1100
1209
|
syncDeviceCredentialToHost,
|
|
1101
1210
|
commitSession: (input, options) => commitSessionRef.current(input, options),
|
|
1102
1211
|
markAuthResolved: () => markAuthResolvedRef.current(),
|
|
1103
|
-
setTokenReady
|
|
1212
|
+
setTokenReady,
|
|
1213
|
+
paintCachedSession,
|
|
1214
|
+
onCachedSessionRejected: () => clearSessionStateRef.current(),
|
|
1215
|
+
onCachedSessionUnconfirmed: scheduleCachedSessionCheck
|
|
1104
1216
|
});
|
|
1105
1217
|
// The boot may have (re-)established the identity session or cleared a pin
|
|
1106
1218
|
// whose key no longer exists, so re-resolve rather than trusting the memo
|
|
1107
1219
|
// taken before it ran.
|
|
1108
|
-
}, [resolveRegisteredLane, oxyServices, authStore, clientIdProp, authRedirectUri, sessionMode, identity, syncDeviceCredentialToHost, sessionClient, setTokenReady]);
|
|
1220
|
+
}, [resolveRegisteredLane, oxyServices, authStore, clientIdProp, authRedirectUri, sessionMode, identity, syncDeviceCredentialToHost, sessionClient, setTokenReady, paintCachedSession, scheduleCachedSessionCheck]);
|
|
1109
1221
|
useEffect(() => {
|
|
1110
1222
|
if (initialized) {
|
|
1111
1223
|
return;
|
|
@@ -1188,6 +1300,7 @@ export const OxyRuntimeProvider = ({
|
|
|
1188
1300
|
if (!online || !wasOffline) {
|
|
1189
1301
|
return;
|
|
1190
1302
|
}
|
|
1303
|
+
cachedSessionCheckRef.current.runNow?.();
|
|
1191
1304
|
void (async () => {
|
|
1192
1305
|
if (hasIsolatedOAuthSession(runtime)) return;
|
|
1193
1306
|
if (oxyServices.session.accessToken || !sessionClientHost.getDeviceCredential()) {
|