@oxyhq/core 8.1.0 → 9.0.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/boot/coldBootV2.js +22 -332
- package/dist/cjs/crypto/keyManager.js +0 -95
- package/dist/cjs/index.js +6 -19
- package/dist/cjs/mixins/OxyServices.auth.js +4 -7
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +19 -115
- package/dist/cjs/mixins/index.js +2 -3
- package/dist/cjs/session/SessionClient.js +29 -100
- package/dist/cjs/session/accountDialogController.js +0 -13
- package/dist/cjs/session/authStateStore.js +9 -89
- package/dist/cjs/session/createSessionClient.js +2 -9
- package/dist/cjs/session/refresh.js +46 -71
- package/dist/cjs/utils/registrableApex.js +2 -6
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/boot/coldBootV2.js +23 -330
- package/dist/esm/crypto/keyManager.js +0 -95
- package/dist/esm/index.js +7 -11
- package/dist/esm/mixins/OxyServices.auth.js +4 -7
- package/dist/esm/mixins/OxyServices.deviceBoot.js +20 -116
- package/dist/esm/mixins/index.js +2 -3
- package/dist/esm/session/SessionClient.js +29 -100
- package/dist/esm/session/accountDialogController.js +0 -13
- package/dist/esm/session/authStateStore.js +8 -88
- package/dist/esm/session/createSessionClient.js +2 -9
- package/dist/esm/session/refresh.js +46 -71
- package/dist/esm/utils/registrableApex.js +2 -6
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +3 -3
- package/dist/types/boot/coldBootV2.d.ts +28 -53
- package/dist/types/crypto/keyManager.d.ts +0 -21
- package/dist/types/index.d.ts +3 -5
- package/dist/types/mixins/OxyServices.auth.d.ts +4 -7
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +20 -60
- package/dist/types/session/SessionClient.d.ts +4 -38
- package/dist/types/session/accountDialogController.d.ts +1 -3
- package/dist/types/session/authStateStore.d.ts +32 -57
- package/dist/types/session/createSessionClient.d.ts +2 -9
- package/dist/types/session/refresh.d.ts +32 -28
- package/dist/types/utils/registrableApex.d.ts +2 -6
- package/package.json +2 -2
- package/src/HttpService.ts +3 -3
- package/src/boot/__tests__/coldBootV2.test.ts +140 -353
- package/src/boot/coldBootV2.ts +35 -391
- package/src/crypto/keyManager.ts +0 -101
- package/src/index.ts +7 -30
- package/src/mixins/OxyServices.auth.ts +5 -9
- package/src/mixins/OxyServices.deviceBoot.ts +19 -142
- package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +9 -96
- package/src/mixins/__tests__/onTokensChanged.test.ts +0 -1
- package/src/mixins/__tests__/passwordSignIn.test.ts +33 -9
- package/src/mixins/index.ts +2 -3
- package/src/session/SessionClient.ts +29 -120
- package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +113 -0
- package/src/session/__tests__/SessionClient.socket.test.ts +8 -8
- package/src/session/__tests__/authStateStore.test.ts +29 -42
- package/src/session/__tests__/refresh.test.ts +66 -52
- package/src/session/accountDialogController.ts +4 -18
- package/src/session/authStateStore.ts +32 -126
- package/src/session/createSessionClient.ts +2 -9
- package/src/session/refresh.ts +60 -92
- package/src/utils/registrableApex.ts +2 -6
- package/dist/cjs/boot/deviceBootReturn.js +0 -167
- package/dist/esm/boot/deviceBootReturn.js +0 -161
- package/dist/types/boot/deviceBootReturn.d.ts +0 -83
- package/src/boot/__tests__/deviceBootReturn.test.ts +0 -190
- package/src/boot/deviceBootReturn.ts +0 -210
- package/src/crypto/__tests__/sharedDeviceToken.test.ts +0 -24
- package/src/session/__tests__/SessionClient.signedOut.test.ts +0 -224
|
@@ -6,12 +6,13 @@ exports.createAuthRefreshHandler = createAuthRefreshHandler;
|
|
|
6
6
|
exports.installAuthRefreshHandler = installAuthRefreshHandler;
|
|
7
7
|
exports.startTokenRefreshScheduler = startTokenRefreshScheduler;
|
|
8
8
|
const platform_1 = require("../utils/platform");
|
|
9
|
+
const errorUtils_1 = require("../utils/errorUtils");
|
|
9
10
|
const loggerUtils_1 = require("../utils/loggerUtils");
|
|
10
11
|
/**
|
|
11
12
|
* Lead time (ms) before access-token expiry at which the proactive scheduler
|
|
12
|
-
*
|
|
13
|
-
* (60s) so the scheduled
|
|
14
|
-
*
|
|
13
|
+
* re-mints. Mirrors `HttpService`'s per-request `TOKEN_REFRESH_LEAD_SECONDS`
|
|
14
|
+
* (60s) so the scheduled re-mint and the request-time preflight use the same
|
|
15
|
+
* window — the scheduler just fires it during idle/background.
|
|
15
16
|
*/
|
|
16
17
|
exports.TOKEN_REFRESH_LEAD_MS = 60000;
|
|
17
18
|
/**
|
|
@@ -23,13 +24,13 @@ const MAX_TIMEOUT_DELAY_MS = 2147483647;
|
|
|
23
24
|
/**
|
|
24
25
|
* Floor (ms) on ANY scheduled delay. An already-expired / in-lead-window token
|
|
25
26
|
* computes a non-positive `exp − now − lead`; without this floor that becomes
|
|
26
|
-
* `setTimeout(…, 0)`, and a FAILING
|
|
27
|
+
* `setTimeout(…, 0)`, and a FAILING re-mint (offline / server error) would
|
|
27
28
|
* re-arm at 0 in the finally block → a tight 100%-CPU busy loop. The floor
|
|
28
29
|
* guarantees every re-arm yields the event loop.
|
|
29
30
|
*/
|
|
30
31
|
const MIN_SCHEDULE_DELAY_MS = 1000;
|
|
31
32
|
/**
|
|
32
|
-
* Backoff schedule (ms) applied when a scheduled
|
|
33
|
+
* Backoff schedule (ms) applied when a scheduled re-mint FAILS: first retry
|
|
33
34
|
* after {@link MIN_FAILURE_BACKOFF_MS}, doubling up to {@link MAX_FAILURE_BACKOFF_MS}.
|
|
34
35
|
* Reset to 0 on any success or token change. This is what converts the former
|
|
35
36
|
* zero-delay failure loop into a bounded, backing-off retry.
|
|
@@ -37,81 +38,55 @@ const MIN_SCHEDULE_DELAY_MS = 1000;
|
|
|
37
38
|
const MIN_FAILURE_BACKOFF_MS = 5000;
|
|
38
39
|
const MAX_FAILURE_BACKOFF_MS = 5 * 60000;
|
|
39
40
|
/**
|
|
40
|
-
*
|
|
41
|
-
* is permanently unusable — the family was revoked or a reuse was detected. On
|
|
42
|
-
* any of these the persisted store is CLEARED (the session is truly over);
|
|
43
|
-
* transient failures (network, 5xx) leave the store intact so a later attempt
|
|
44
|
-
* can still succeed.
|
|
45
|
-
*/
|
|
46
|
-
const REVOKED_REFRESH_CODES = new Set([
|
|
47
|
-
'invalid_grant',
|
|
48
|
-
'refresh_token_revoked',
|
|
49
|
-
'refresh_token_reuse',
|
|
50
|
-
'token_reuse',
|
|
51
|
-
'invalid_token',
|
|
52
|
-
]);
|
|
53
|
-
/** Does this error mean the refresh token is permanently dead (vs. transient)? */
|
|
54
|
-
function isRevokedRefreshError(error) {
|
|
55
|
-
if (!error || typeof error !== 'object') {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
const e = error;
|
|
59
|
-
if (e.status === 401 || e.status === 403) {
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
return typeof e.code === 'string' && REVOKED_REFRESH_CODES.has(e.code);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Rotate the persisted session and return the fresh access token, or `null`
|
|
41
|
+
* Re-mint the persisted session and return the fresh access token, or `null`
|
|
66
42
|
* when no arm could produce one.
|
|
67
43
|
*
|
|
68
|
-
* Arm 1 (`POST /
|
|
69
|
-
*
|
|
70
|
-
*
|
|
44
|
+
* Arm 1 (`POST /session/device/token`): if the store holds a `deviceId` +
|
|
45
|
+
* `deviceSecret`, mint — on success plant + persist the rotated secret. A 401
|
|
46
|
+
* means the secret is diverged (`invalid_device_secret`) or the device has no
|
|
47
|
+
* live session (`no_active_session`): drop the secret so the mint lane stops (or
|
|
48
|
+
* clear the store on web, where there is no fallback). A transient error leaves
|
|
49
|
+
* the store and returns `null`.
|
|
71
50
|
*
|
|
72
|
-
* Arm 2 (native shared-keychain): when
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
51
|
+
* Arm 2 (native shared-keychain): when the secret is absent or was just rejected,
|
|
52
|
+
* re-mint via `signInWithSharedIdentity` (which plants tokens). The shared
|
|
53
|
+
* keychain — not the per-origin store — is the durable native credential, so this
|
|
54
|
+
* arm does not write the store.
|
|
76
55
|
*/
|
|
77
56
|
async function refreshPersistedSession(deps) {
|
|
78
57
|
const { oxy, store } = deps;
|
|
79
58
|
const allowSharedKeyFallback = deps.allowSharedKeyFallback ?? (0, platform_1.isNative)();
|
|
80
59
|
const persisted = await store.load();
|
|
81
|
-
if (persisted?.
|
|
60
|
+
if (persisted?.deviceId && persisted?.deviceSecret) {
|
|
82
61
|
try {
|
|
83
|
-
const
|
|
84
|
-
oxy.setTokens(
|
|
62
|
+
const mint = await oxy.mintFromDeviceSecret(persisted.deviceId, persisted.deviceSecret);
|
|
63
|
+
oxy.setTokens(mint.accessToken);
|
|
64
|
+
const active = mint.state.accounts.find((a) => a.accountId === mint.state.activeAccountId);
|
|
85
65
|
const next = {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
accessToken:
|
|
90
|
-
expiresAt:
|
|
66
|
+
...persisted,
|
|
67
|
+
deviceId: mint.state.deviceId,
|
|
68
|
+
deviceSecret: mint.nextDeviceSecret,
|
|
69
|
+
accessToken: mint.accessToken,
|
|
70
|
+
expiresAt: mint.expiresAt,
|
|
71
|
+
...(active ? { sessionId: active.sessionId, userId: active.accountId } : {}),
|
|
91
72
|
};
|
|
92
|
-
if (persisted.deviceToken) {
|
|
93
|
-
next.deviceToken = persisted.deviceToken;
|
|
94
|
-
}
|
|
95
|
-
// The refresh response carries no device credentials — carry the persisted
|
|
96
|
-
// deviceId/deviceSecret (phase 2c) forward so a rotation never drops the
|
|
97
|
-
// zero-cookie mint lane (mirrors the deviceToken preservation above).
|
|
98
|
-
if (persisted.deviceId) {
|
|
99
|
-
next.deviceId = persisted.deviceId;
|
|
100
|
-
}
|
|
101
|
-
if (persisted.deviceSecret) {
|
|
102
|
-
next.deviceSecret = persisted.deviceSecret;
|
|
103
|
-
}
|
|
104
73
|
await store.save(next);
|
|
105
|
-
return
|
|
74
|
+
return mint.accessToken;
|
|
106
75
|
}
|
|
107
76
|
catch (error) {
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
77
|
+
if ((0, errorUtils_1.extractErrorStatus)(error) === 401) {
|
|
78
|
+
// Secret diverged / no active session. On a shared-key device drop only
|
|
79
|
+
// the secret so arm 2 below can recover; otherwise the session is over.
|
|
80
|
+
if (allowSharedKeyFallback) {
|
|
81
|
+
await store.save({ ...persisted, deviceSecret: undefined });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await store.clear();
|
|
85
|
+
}
|
|
86
|
+
// Fall through to the native shared-key arm.
|
|
112
87
|
}
|
|
113
88
|
else {
|
|
114
|
-
loggerUtils_1.logger.debug('Persisted
|
|
89
|
+
loggerUtils_1.logger.debug('Persisted deviceSecret mint failed (transient) — keeping store', { component: 'refresh', method: 'refreshPersistedSession' }, error);
|
|
115
90
|
return null;
|
|
116
91
|
}
|
|
117
92
|
}
|
|
@@ -124,7 +99,7 @@ async function refreshPersistedSession(deps) {
|
|
|
124
99
|
}
|
|
125
100
|
}
|
|
126
101
|
catch (error) {
|
|
127
|
-
loggerUtils_1.logger.debug('Shared-key
|
|
102
|
+
loggerUtils_1.logger.debug('Shared-key re-mint fallback failed', { component: 'refresh', method: 'refreshPersistedSession' }, error);
|
|
128
103
|
}
|
|
129
104
|
}
|
|
130
105
|
return null;
|
|
@@ -152,7 +127,7 @@ function installAuthRefreshHandler(deps) {
|
|
|
152
127
|
};
|
|
153
128
|
}
|
|
154
129
|
/**
|
|
155
|
-
* Start the proactive
|
|
130
|
+
* Start the proactive re-mint scheduler against `oxy`.
|
|
156
131
|
*
|
|
157
132
|
* Schedules a single timer to fire {@link TOKEN_REFRESH_LEAD_MS} before the
|
|
158
133
|
* current access token's `exp`, calling
|
|
@@ -160,18 +135,18 @@ function installAuthRefreshHandler(deps) {
|
|
|
160
135
|
* handler; deduped + cooldown-guarded). After every attempt it reschedules
|
|
161
136
|
* from the possibly-rotated token. It also reschedules whenever the token
|
|
162
137
|
* changes (a sign-out that clears the token cancels the timer) and, on web
|
|
163
|
-
* tab-focus,
|
|
138
|
+
* tab-focus, re-mints immediately if already inside the lead window (a
|
|
164
139
|
* long-hidden tab throttles timers, so the token can be expired on return).
|
|
165
140
|
*
|
|
166
141
|
* No-ops cleanly when there is no token or an opaque/no-`exp` token — the
|
|
167
|
-
* reactive 401 path stays the only
|
|
142
|
+
* reactive 401 path stays the only re-mint trigger in that case. The timer is
|
|
168
143
|
* `.unref?.()`-ed so it never keeps a Node/Jest event loop alive.
|
|
169
144
|
*/
|
|
170
145
|
function startTokenRefreshScheduler(oxy) {
|
|
171
146
|
let disposed = false;
|
|
172
147
|
let timer = null;
|
|
173
148
|
// 0 = no active backoff; grows on consecutive failures, resets on success /
|
|
174
|
-
// token change. Keeps a failing
|
|
149
|
+
// token change. Keeps a failing re-mint from re-arming at zero delay.
|
|
175
150
|
let failureBackoffMs = 0;
|
|
176
151
|
const clearTimer = () => {
|
|
177
152
|
if (timer !== null) {
|
|
@@ -184,10 +159,10 @@ function startTokenRefreshScheduler(oxy) {
|
|
|
184
159
|
clearTimer();
|
|
185
160
|
const clamped = Math.min(Math.max(delayMs, MIN_SCHEDULE_DELAY_MS), MAX_TIMEOUT_DELAY_MS);
|
|
186
161
|
timer = setTimeout(runRefresh, clamped);
|
|
187
|
-
// Never keep a Node/Jest event loop alive for a background
|
|
162
|
+
// Never keep a Node/Jest event loop alive for a background re-mint timer.
|
|
188
163
|
timer.unref?.();
|
|
189
164
|
};
|
|
190
|
-
/** Schedule the next
|
|
165
|
+
/** Schedule the next re-mint from the current token's expiry (the healthy path). */
|
|
191
166
|
const scheduleFromExpiry = () => {
|
|
192
167
|
clearTimer();
|
|
193
168
|
if (disposed || !oxy.getAccessToken()) {
|
|
@@ -5,12 +5,8 @@
|
|
|
5
5
|
* The client FAPI auto-detection helper was removed in the device-first cutover
|
|
6
6
|
* (which is why this file is now named for what it actually is, not the old
|
|
7
7
|
* `fapiAutoDetect`). What survives is the pure registrable-domain kernel, still
|
|
8
|
-
* used server
|
|
9
|
-
*
|
|
10
|
-
* `isSameSiteTrustedRequest`) and the `@oxyhq/core/server` CORS/re-export layer.
|
|
11
|
-
*
|
|
12
|
-
* `registrableApex` is NOT legacy — the device-first same-apex check is a live
|
|
13
|
-
* consumer, so this kernel stays regardless of the SSO/FedCM removal.
|
|
8
|
+
* used by the `@oxyhq/core/server` CORS layer (the `*.oxy.so` same-apex trust
|
|
9
|
+
* check) and its re-export surface.
|
|
14
10
|
*/
|
|
15
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
12
|
exports.registrableApex = registrableApex;
|