@oxyhq/core 5.4.2 → 5.5.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/index.js +2 -1
- package/dist/cjs/mixins/OxyServices.user.js +30 -8
- package/dist/cjs/session/SessionClient.js +4 -1
- package/dist/cjs/session/createSessionClient.js +8 -2
- package/dist/cjs/utils/ssoBounce.js +22 -0
- package/dist/cjs/utils/ssoReturn.js +8 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/mixins/OxyServices.user.js +30 -8
- package/dist/esm/session/SessionClient.js +4 -1
- package/dist/esm/session/createSessionClient.js +8 -2
- package/dist/esm/utils/ssoBounce.js +21 -0
- package/dist/esm/utils/ssoReturn.js +8 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.user.d.ts +32 -6
- package/dist/types/session/SessionClient.d.ts +13 -1
- package/dist/types/session/createSessionClient.d.ts +8 -1
- package/dist/types/utils/ssoBounce.d.ts +18 -0
- package/dist/types/utils/ssoReturn.d.ts +11 -1
- package/package.json +1 -1
- package/src/index.ts +5 -0
- package/src/mixins/OxyServices.user.ts +30 -8
- package/src/mixins/__tests__/userReadCacheBypass.test.ts +121 -0
- package/src/session/SessionClient.ts +17 -2
- package/src/session/__tests__/SessionClient.socketFactory.test.ts +79 -0
- package/src/session/createSessionClient.ts +9 -1
- package/src/utils/__tests__/ssoReturn.test.ts +25 -0
- package/src/utils/ssoBounce.ts +22 -0
- package/src/utils/ssoReturn.ts +18 -1
package/dist/esm/index.js
CHANGED
|
@@ -129,7 +129,7 @@ export { generateSsoState } from './mixins/OxyServices.sso.js';
|
|
|
129
129
|
// Post-claim durable-session establish hop (web device-flow / QR sign-in).
|
|
130
130
|
export { establishIdpSessionAfterClaim } from './utils/ssoEstablish.js';
|
|
131
131
|
// SSO bounce — per-origin sessionStorage keys, bounce URL builder, predicates
|
|
132
|
-
export { SSO_CALLBACK_PATH, SSO_GUARD_TTL_MS, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, ssoAttemptedKey, ssoPriorSessionKey, ssoSignedOutKey, ssoCallbackBootstrapKey, ssoNavigate, getSsoCallbackBootstrapScript, buildSsoBounceUrl, isCentralIdPOrigin, guardActive, silentRestoreSuppressed, allowSsoBounce, } from './utils/ssoBounce.js';
|
|
132
|
+
export { SSO_CALLBACK_PATH, SSO_GUARD_TTL_MS, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, ssoAttemptedKey, ssoPriorSessionKey, ssoSignedOutKey, ssoOutcomeKey, ssoCallbackBootstrapKey, ssoNavigate, getSsoCallbackBootstrapScript, buildSsoBounceUrl, isCentralIdPOrigin, guardActive, silentRestoreSuppressed, allowSsoBounce, } from './utils/ssoBounce.js';
|
|
133
133
|
export { runColdBoot } from './utils/coldBoot.js';
|
|
134
134
|
// ---------------------------------------------------------------------------
|
|
135
135
|
// Session sync (device-scoped multi-account session client)
|
|
@@ -16,12 +16,23 @@ export function OxyServicesUserMixin(Base) {
|
|
|
16
16
|
super(...args);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Get profile by username
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
* Get profile by username.
|
|
20
|
+
*
|
|
21
|
+
* @param username - The profile's username.
|
|
22
|
+
* @param options.cache - Defaults to `true` (5-minute TTL), matching prior
|
|
23
|
+
* behavior. Pass `{ cache: false }` to force a registry-fresh read: the
|
|
24
|
+
* request bypasses BOTH the cache lookup and the post-fetch cache write
|
|
25
|
+
* (see {@link HttpService.request}'s `cache` handling), so it neither
|
|
26
|
+
* serves nor overwrites any entry already cached for this key — a
|
|
27
|
+
* previously cached response (if one exists) is left in place until its
|
|
28
|
+
* own TTL expires or is explicitly invalidated elsewhere. Use this when a
|
|
29
|
+
* caller must observe a just-written change (e.g. a privacy/consent flag)
|
|
30
|
+
* that would otherwise be masked by the TTL window.
|
|
31
|
+
*/
|
|
32
|
+
async getProfileByUsername(username, options) {
|
|
22
33
|
try {
|
|
23
34
|
const user = await this.makeRequest('GET', `/profiles/username/${username}`, undefined, {
|
|
24
|
-
cache: true,
|
|
35
|
+
cache: options?.cache ?? true,
|
|
25
36
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
|
|
26
37
|
});
|
|
27
38
|
return normalizeUserIdentity(user);
|
|
@@ -204,12 +215,23 @@ export function OxyServicesUserMixin(Base) {
|
|
|
204
215
|
return users.map((user) => normalizeUserIdentity(user));
|
|
205
216
|
}
|
|
206
217
|
/**
|
|
207
|
-
* Get user by ID
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
* Get user by ID.
|
|
219
|
+
*
|
|
220
|
+
* @param userId - The target user's id.
|
|
221
|
+
* @param options.cache - Defaults to `true` (5-minute TTL), matching prior
|
|
222
|
+
* behavior. Pass `{ cache: false }` to force a registry-fresh read: the
|
|
223
|
+
* request bypasses BOTH the cache lookup and the post-fetch cache write
|
|
224
|
+
* (see {@link HttpService.request}'s `cache` handling), so it neither
|
|
225
|
+
* serves nor overwrites any entry already cached for this key — a
|
|
226
|
+
* previously cached response (if one exists) is left in place until its
|
|
227
|
+
* own TTL expires or is explicitly invalidated elsewhere. Use this when a
|
|
228
|
+
* caller must observe a just-written change (e.g. a privacy/consent flag)
|
|
229
|
+
* that would otherwise be masked by the TTL window.
|
|
230
|
+
*/
|
|
231
|
+
async getUserById(userId, options) {
|
|
210
232
|
try {
|
|
211
233
|
const user = await this.makeRequest('GET', `/users/${userId}`, undefined, {
|
|
212
|
-
cache: true,
|
|
234
|
+
cache: options?.cache ?? true,
|
|
213
235
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
214
236
|
});
|
|
215
237
|
return normalizeUserIdentity(user);
|
|
@@ -117,7 +117,10 @@ export class SessionClient {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
async connectSocket() {
|
|
120
|
-
|
|
120
|
+
// Prefer a statically-injected factory (services/auth-sdk bundle
|
|
121
|
+
// socket.io-client as a real dep); fall back to the lazy loader — and warn
|
|
122
|
+
// if THAT yields nothing — only when no factory was injected.
|
|
123
|
+
const io = this.options.socketFactory ?? (await getSocketIO());
|
|
121
124
|
if (!io) {
|
|
122
125
|
logger.warn('[SessionClient] no socket.io-client; running REST-only (no realtime sync)', { component: 'SessionClient' });
|
|
123
126
|
return;
|
|
@@ -15,9 +15,15 @@ import { createSessionClientHost } from './sessionClientHost.js';
|
|
|
15
15
|
* The host is returned alongside the client (not just the client) so the
|
|
16
16
|
* caller can call `host.setCurrentAccountId(...)` as the active account
|
|
17
17
|
* changes.
|
|
18
|
+
*
|
|
19
|
+
* `socketFactory` is the statically-injected `socket.io-client` `io` export.
|
|
20
|
+
* Consumers that bundle socket.io-client as a real dependency pass it so
|
|
21
|
+
* realtime sync never depends on core's lazy dynamic import of a bare
|
|
22
|
+
* specifier (bundler-fragile in Metro/Expo-web and Vite against the published
|
|
23
|
+
* dist). When omitted, the client falls back to the lazy loader.
|
|
18
24
|
*/
|
|
19
|
-
export function createSessionClient(oxyServices, transport) {
|
|
25
|
+
export function createSessionClient(oxyServices, transport, socketFactory) {
|
|
20
26
|
const host = createSessionClientHost(oxyServices);
|
|
21
|
-
const client = new SessionClient(host, { transport });
|
|
27
|
+
const client = new SessionClient(host, { transport, socketFactory });
|
|
22
28
|
return { client, host };
|
|
23
29
|
}
|
|
@@ -66,6 +66,7 @@ const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
|
|
|
66
66
|
const CALLBACK_BOOTSTRAP_KEY_PREFIX = 'oxy_sso_callback_bootstrap:';
|
|
67
67
|
const PRIOR_SESSION_KEY_PREFIX = 'oxy_sso_prior_session:';
|
|
68
68
|
const SIGNED_OUT_KEY_PREFIX = 'oxy_signed_out:';
|
|
69
|
+
const OUTCOME_KEY_PREFIX = 'oxy_sso_outcome:';
|
|
69
70
|
/** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
|
|
70
71
|
export function ssoStateKey(origin) {
|
|
71
72
|
return `${STATE_KEY_PREFIX}${origin}`;
|
|
@@ -145,6 +146,26 @@ export function ssoPriorSessionKey(origin) {
|
|
|
145
146
|
export function ssoSignedOutKey(origin) {
|
|
146
147
|
return `${SIGNED_OUT_KEY_PREFIX}${origin}`;
|
|
147
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Per-origin key holding the LAST consumed SSO-return outcome (`ok` | `none` |
|
|
151
|
+
* `error`, plus an optional machine-readable `reason` on the non-`ok` outcomes).
|
|
152
|
+
*
|
|
153
|
+
* Lives in per-tab `sessionStorage` like the other loop-breaker keys, and for
|
|
154
|
+
* the same reason: a `none`/`error` return HARD-navigates the RP off the
|
|
155
|
+
* internal callback path back to its real destination (a fresh document load),
|
|
156
|
+
* so the outcome an RP wants to render ("the central IdP had no session — show a
|
|
157
|
+
* branded sign-in screen instead of bouncing again") must survive that
|
|
158
|
+
* round-trip. The RP reads it on the destination load to decide whether an
|
|
159
|
+
* AUTOMATIC (guard-driven) sign-in should re-bounce or defer to a user gesture.
|
|
160
|
+
*
|
|
161
|
+
* Written as a small JSON blob (`{kind, reason?}`). Set whenever a return is
|
|
162
|
+
* consumed; cleared on a successful session commit and on an explicit
|
|
163
|
+
* user-gesture sign-in / full sign-out (so a deliberate retry is never
|
|
164
|
+
* suppressed by a prior automatic none/error).
|
|
165
|
+
*/
|
|
166
|
+
export function ssoOutcomeKey(origin) {
|
|
167
|
+
return `${OUTCOME_KEY_PREFIX}${origin}`;
|
|
168
|
+
}
|
|
148
169
|
/**
|
|
149
170
|
* Per-origin marker written by the pre-hydration callback bootstrap.
|
|
150
171
|
*
|
|
@@ -66,6 +66,14 @@ export function parseSsoReturnFragment(hash) {
|
|
|
66
66
|
result.code = code;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
else {
|
|
70
|
+
// A machine-readable reason accompanies a NON-`ok` outcome when the IdP
|
|
71
|
+
// supplies one. Success carries no reason, so it is only read here.
|
|
72
|
+
const reason = params.get('reason');
|
|
73
|
+
if (reason !== null && reason.length > 0) {
|
|
74
|
+
result.reason = reason;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
69
77
|
return result;
|
|
70
78
|
}
|
|
71
79
|
/**
|