@oxyhq/services 18.1.0 → 19.0.1
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/context/OxyContext.js +49 -93
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +15 -29
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/session/authStore.js +12 -50
- package/lib/commonjs/ui/session/authStore.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +6 -11
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/tokenTransport.js +3 -4
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +49 -93
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +15 -29
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/session/authStore.js +13 -51
- package/lib/module/ui/session/authStore.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +6 -11
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/tokenTransport.js +3 -4
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +15 -6
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +4 -5
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/authStore.d.ts +11 -15
- package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +6 -10
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +3 -4
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +15 -6
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +4 -5
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/authStore.d.ts +11 -15
- package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +6 -10
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +3 -4
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/ui/context/OxyContext.tsx +57 -100
- package/src/ui/context/__tests__/OxyContext.coldBoot.test.tsx +13 -12
- package/src/ui/context/hooks/useAuthOperations.ts +18 -27
- package/src/ui/session/__tests__/createSessionClient.test.ts +3 -1
- package/src/ui/session/__tests__/tokenTransport.test.ts +6 -5
- package/src/ui/session/authStore.ts +12 -55
- package/src/ui/session/createSessionClient.ts +5 -10
- package/src/ui/session/tokenTransport.ts +3 -4
|
@@ -2,6 +2,7 @@ import type React from 'react';
|
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import { OxyServices } from '@oxyhq/core';
|
|
4
4
|
import type { User, ApiError, SessionLoginResponse, AccountNode, CreateAccountInput, ClientSession, AccountDialogController, AccountDialogView } from '@oxyhq/core';
|
|
5
|
+
import type { SecurityAlert } from '@oxyhq/contracts';
|
|
5
6
|
import type { UseFollowHook } from '../hooks/useFollow.types.js';
|
|
6
7
|
import { useLanguageManagement } from '../hooks/useLanguageManagement.js';
|
|
7
8
|
import type { RouteName } from '../navigation/routes.js';
|
|
@@ -40,7 +41,7 @@ export interface OxyContextState {
|
|
|
40
41
|
*
|
|
41
42
|
* Commits a successful session into context state through the SAME path the
|
|
42
43
|
* QR device-flow and cold boot use (so `isAuthenticated` / `user` update and
|
|
43
|
-
* the
|
|
44
|
+
* the zero-cookie device credential is persisted). Returns a discriminated result
|
|
44
45
|
* so the caller can branch on the two-factor-required case — which creates NO
|
|
45
46
|
* session; the caller completes the 2FA challenge with the returned
|
|
46
47
|
* `loginToken` via {@link OxyContextState.completeTwoFactorSignIn}.
|
|
@@ -52,18 +53,22 @@ export interface OxyContextState {
|
|
|
52
53
|
/**
|
|
53
54
|
* Complete a 2FA-gated password sign-in started by {@link signInWithPassword}.
|
|
54
55
|
* Presents the short-lived `loginToken` with a TOTP `token` or a `backupCode`;
|
|
55
|
-
* on success the session is committed exactly like a one-step sign-in.
|
|
56
|
+
* on success the session is committed exactly like a one-step sign-in. Returns
|
|
57
|
+
* any `securityAlert` the server attached so the caller can show the same
|
|
58
|
+
* "New sign-in detected" acknowledgement as the one-step path.
|
|
56
59
|
*/
|
|
57
60
|
completeTwoFactorSignIn: (params: {
|
|
58
61
|
loginToken: string;
|
|
59
62
|
token?: string;
|
|
60
63
|
backupCode?: string;
|
|
61
64
|
deviceName?: string;
|
|
62
|
-
}) => Promise<
|
|
65
|
+
}) => Promise<{
|
|
66
|
+
securityAlert?: SecurityAlert;
|
|
67
|
+
}>;
|
|
63
68
|
/**
|
|
64
69
|
* Commit a session obtained out-of-band (the "Sign in with Oxy" QR device
|
|
65
|
-
* flow). Plants tokens, persists the
|
|
66
|
-
* account into the device set, and hydrates the full user profile.
|
|
70
|
+
* flow). Plants tokens, persists the zero-cookie device credential, registers
|
|
71
|
+
* the account into the device set, and hydrates the full user profile.
|
|
67
72
|
*/
|
|
68
73
|
handleWebSession: (session: SessionLoginResponse) => Promise<void>;
|
|
69
74
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
@@ -134,7 +139,10 @@ export interface OxyContextState {
|
|
|
134
139
|
* Result of {@link OxyContextState.signInWithPassword}.
|
|
135
140
|
*
|
|
136
141
|
* `'ok'` — the password was accepted and the session committed (so
|
|
137
|
-
* `isAuthenticated` / `user` are updated and the
|
|
142
|
+
* `isAuthenticated` / `user` are updated and the device credential persisted).
|
|
143
|
+
* `securityAlert` is present when the server flagged this sign-in as anomalous
|
|
144
|
+
* (new device / location) — the caller shows a "New sign-in detected"
|
|
145
|
+
* acknowledgement before proceeding; the session is already committed.
|
|
138
146
|
*
|
|
139
147
|
* `'2fa_required'` — the account has two-factor auth enabled, so NO session was
|
|
140
148
|
* created. Complete the challenge with the returned short-lived `loginToken`
|
|
@@ -142,6 +150,7 @@ export interface OxyContextState {
|
|
|
142
150
|
*/
|
|
143
151
|
export type PasswordSignInResult = {
|
|
144
152
|
status: 'ok';
|
|
153
|
+
securityAlert?: SecurityAlert;
|
|
145
154
|
} | {
|
|
146
155
|
status: '2fa_required';
|
|
147
156
|
loginToken: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EACV,IAAI,EACJ,QAAQ,EACR,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,aAAa,EAGb,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
|
|
@@ -9,10 +9,9 @@ export interface UseAuthOperationsOptions {
|
|
|
9
9
|
/**
|
|
10
10
|
* The device-first persisted auth-state store. On EXPLICIT full sign-out the
|
|
11
11
|
* session blob is cleared (`store.clear()`) so a reload's cold boot finds no
|
|
12
|
-
*
|
|
13
|
-
* persisted so the next boot
|
|
14
|
-
*
|
|
15
|
-
* cleared here.
|
|
12
|
+
* device credential to restore; on sign-in the returned zero-cookie device
|
|
13
|
+
* credential (`deviceId` + `deviceSecret`) is persisted so the next boot
|
|
14
|
+
* warm-restores without a redirect.
|
|
16
15
|
*/
|
|
17
16
|
store: AuthStateStore;
|
|
18
17
|
activeSessionId: string | null;
|
|
@@ -51,7 +50,7 @@ export interface UseAuthOperationsResult {
|
|
|
51
50
|
logoutAll: () => Promise<void>;
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
|
-
* Clear the persisted
|
|
53
|
+
* Clear the persisted device credential on an explicit full sign-out.
|
|
55
54
|
* Best-effort and non-blocking: sign-out must never fail because a storage
|
|
56
55
|
* write threw. Exported so `OxyContext`'s zero-account branch (a REMOTE full
|
|
57
56
|
* sign-out pushed over the socket) runs the EXACT same cleanup as the local
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuthOperations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/context/hooks/useAuthOperations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAIvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC
|
|
1
|
+
{"version":3,"file":"useAuthOperations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/context/hooks/useAuthOperations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAIvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC;;;;;;OAMG;IACH,KAAK,EAAE,cAAc,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IACnF,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,0JAA0J;IAC1J,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAC;IAC7B,+MAA+M;IAC/M,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,uBAAuB,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,uBAAuB;IACtC,uCAAuC;IACvC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,kCAAkC;IAClC,MAAM,EAAE,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,+BAA+B;IAC/B,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAMD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,cAAc,EACrB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,GAClD,IAAI,CAIN;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,wRAmB/B,wBAAwB,KAAG,uBAqR7B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAccountMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useAccountMutations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,IAAI,EACJ,eAAe,EAChB,MAAM,aAAa,CAAC;AAerB;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;
|
|
1
|
+
{"version":3,"file":"useAccountMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useAccountMutations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Platform `AuthStateStore` for `@oxyhq/services`.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The zero-cookie device model persists the durable device credential
|
|
5
|
+
* (`deviceId` + `deviceSecret`) per origin (web) / per device (native), and the
|
|
6
|
+
* SDK re-mints the access token from that credential on cold boot. `@oxyhq/core`
|
|
7
|
+
* owns the store shape + logic (`createWebAuthStateStore` /
|
|
8
|
+
* `createNativeAuthStateStore`); this module only supplies the platform storage
|
|
9
|
+
* seam:
|
|
9
10
|
*
|
|
10
11
|
* - web → `createWebAuthStateStore()` (localStorage, in-memory fallback).
|
|
11
|
-
* - native → `createNativeAuthStateStore(secureKV)` for the SESSION blob
|
|
12
|
-
*
|
|
13
|
-
* long-lived DEVICE token delegated to `KeyManager`'s shared keychain
|
|
14
|
-
* (`group.so.oxy.shared`) instead of per-app storage — so an in-app login on
|
|
15
|
-
* any native Oxy app attributes to the SAME server-side DeviceSession.
|
|
12
|
+
* - native → `createNativeAuthStateStore(secureKV)` for the SESSION blob over
|
|
13
|
+
* `expo-secure-store` (AsyncStorage fallback).
|
|
16
14
|
*
|
|
17
15
|
* `expo-secure-store` is loaded via a runtime-computed dynamic import (the same
|
|
18
16
|
* optional-native-module pattern `OxyProvider` uses for netinfo / keyboard
|
|
@@ -23,11 +21,9 @@ import { type AuthStateStore } from '@oxyhq/core';
|
|
|
23
21
|
/**
|
|
24
22
|
* Build the platform {@link AuthStateStore} for this runtime.
|
|
25
23
|
*
|
|
26
|
-
* Native
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* a locked/failed shared-keychain read degrades to "no device token" rather
|
|
30
|
-
* than throwing out of cold boot / refresh.
|
|
24
|
+
* Native persists the SESSION blob (`deviceId` + `deviceSecret` + cached access
|
|
25
|
+
* token) per-app in SecureStore; the SDK re-mints the access token from the
|
|
26
|
+
* device credential on the next cold boot.
|
|
31
27
|
*/
|
|
32
28
|
export declare function createPlatformAuthStateStore(): AuthStateStore;
|
|
33
29
|
//# sourceMappingURL=authStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/authStore.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"authStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/authStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAGL,KAAK,cAAc,EAEpB,MAAM,aAAa,CAAC;AAmFrB;;;;;;GAMG;AACH,wBAAgB,4BAA4B,IAAI,cAAc,CAM7D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SessionClient, createSessionClientHost, type AuthStateStore, type OxyServices
|
|
1
|
+
import { SessionClient, createSessionClientHost, type AuthStateStore, type OxyServices } from '@oxyhq/core';
|
|
2
2
|
/**
|
|
3
3
|
* Wire a `SessionClient` for `@oxyhq/services`.
|
|
4
4
|
*
|
|
@@ -11,22 +11,18 @@ import { SessionClient, createSessionClientHost, type AuthStateStore, type OxySe
|
|
|
11
11
|
* core's lazy dynamic import of a bare specifier — bundler-fragile in
|
|
12
12
|
* Metro/Expo-web against the published core dist.
|
|
13
13
|
* - the device-first {@link createTokenTransport}, which mints a fallback token
|
|
14
|
-
*
|
|
14
|
+
* from the persisted device credential (`store` → `deviceId` + `deviceSecret`).
|
|
15
15
|
*
|
|
16
16
|
* `onUnauthenticated` fires when an applied device state has zero accounts (a
|
|
17
17
|
* device signout-all): the provider clears the persisted store + local state so
|
|
18
18
|
* a reload does not try to restore a dead session. The host is returned
|
|
19
19
|
* alongside the client so the caller can call `host.setCurrentAccountId(...)`
|
|
20
20
|
* as the active account changes.
|
|
21
|
+
*
|
|
22
|
+
* The realtime socket is bearer-only: a signed-out tab cannot join any device
|
|
23
|
+
* room, so there is no signed-out socket wiring.
|
|
21
24
|
*/
|
|
22
|
-
export declare function createSessionClient(oxyServices: OxyServices, store: AuthStateStore, onUnauthenticated?: () => void
|
|
23
|
-
/**
|
|
24
|
-
* Optional signed-out realtime wiring: `signedOutSocketAuth` (open the socket
|
|
25
|
-
* while signed out — web returns `true` to ride the `oxy_device` cookie,
|
|
26
|
-
* native returns the shared device token) and `onSessionAppeared` (self-acquire
|
|
27
|
-
* when a sibling signs in on this device).
|
|
28
|
-
*/
|
|
29
|
-
extra?: Pick<SessionClientOptions, 'signedOutSocketAuth' | 'onSessionAppeared'>): {
|
|
25
|
+
export declare function createSessionClient(oxyServices: OxyServices, store: AuthStateStore, onUnauthenticated?: () => void): {
|
|
30
26
|
client: SessionClient;
|
|
31
27
|
host: ReturnType<typeof createSessionClientHost>;
|
|
32
28
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSessionClient.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/createSessionClient.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"createSessionClient.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/createSessionClient.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,cAAc,EACrB,iBAAiB,CAAC,EAAE,MAAM,IAAI,GAC7B;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;CAClD,CAKA"}
|
|
@@ -5,10 +5,9 @@ import { type AuthStateStore, type OxyServices, type TokenTransport } from '@oxy
|
|
|
5
5
|
* `ensureActiveToken` is the fallback the client uses when a `session_state`
|
|
6
6
|
* push arrived WITHOUT an embedded `activeToken` and the app currently holds no
|
|
7
7
|
* bearer. It mints one through the ONE unified refresh path
|
|
8
|
-
* (`refreshPersistedSession`)
|
|
9
|
-
* (`
|
|
10
|
-
*
|
|
11
|
-
* persisted refresh token is the durable web credential.
|
|
8
|
+
* (`refreshPersistedSession`), which presents the persisted zero-cookie device
|
|
9
|
+
* credential (`deviceId` + `deviceSecret`) at `POST /session/device/token`. The
|
|
10
|
+
* persisted device credential is the sole durable restore credential.
|
|
12
11
|
*
|
|
13
12
|
* Concurrent pushes coalesce onto one in-flight mint. A failure is logged and
|
|
14
13
|
* swallowed: this method must never throw out (it runs inside the socket state
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenTransport.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/tokenTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAGrB
|
|
1
|
+
{"version":3,"file":"tokenTransport.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/tokenTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,cAAc,GACpB,cAAc,CAsChB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.1",
|
|
4
4
|
"description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -106,10 +106,11 @@
|
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
"dependencies": {
|
|
109
|
-
"@oxyhq/contracts": "0.
|
|
109
|
+
"@oxyhq/contracts": "0.13.0",
|
|
110
|
+
"color": "^4.2.3"
|
|
110
111
|
},
|
|
111
112
|
"devDependencies": {
|
|
112
|
-
"@oxyhq/core": "
|
|
113
|
+
"@oxyhq/core": "9.0.0",
|
|
113
114
|
"nativewind": "5.0.0-preview.3",
|
|
114
115
|
"react-native-css": "^3.0.0",
|
|
115
116
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
@@ -153,7 +154,7 @@
|
|
|
153
154
|
"peerDependencies": {
|
|
154
155
|
"@expo/vector-icons": "^15.0.3",
|
|
155
156
|
"@oxyhq/bloom": ">=0.16.0",
|
|
156
|
-
"@oxyhq/core": "^
|
|
157
|
+
"@oxyhq/core": "^9.0.0",
|
|
157
158
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
158
159
|
"@react-native-community/netinfo": "^11.4.1",
|
|
159
160
|
"@tanstack/query-async-storage-persister": "^5.101",
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
createAccountDialogController,
|
|
32
32
|
logger as loggerUtil,
|
|
33
33
|
} from '@oxyhq/core';
|
|
34
|
+
import type { SecurityAlert } from '@oxyhq/contracts';
|
|
34
35
|
import {
|
|
35
36
|
registerAccountDialogControls,
|
|
36
37
|
notifyAccountDialogVisibility,
|
|
@@ -99,7 +100,7 @@ export interface OxyContextState {
|
|
|
99
100
|
*
|
|
100
101
|
* Commits a successful session into context state through the SAME path the
|
|
101
102
|
* QR device-flow and cold boot use (so `isAuthenticated` / `user` update and
|
|
102
|
-
* the
|
|
103
|
+
* the zero-cookie device credential is persisted). Returns a discriminated result
|
|
103
104
|
* so the caller can branch on the two-factor-required case — which creates NO
|
|
104
105
|
* session; the caller completes the 2FA challenge with the returned
|
|
105
106
|
* `loginToken` via {@link OxyContextState.completeTwoFactorSignIn}.
|
|
@@ -113,19 +114,21 @@ export interface OxyContextState {
|
|
|
113
114
|
/**
|
|
114
115
|
* Complete a 2FA-gated password sign-in started by {@link signInWithPassword}.
|
|
115
116
|
* Presents the short-lived `loginToken` with a TOTP `token` or a `backupCode`;
|
|
116
|
-
* on success the session is committed exactly like a one-step sign-in.
|
|
117
|
+
* on success the session is committed exactly like a one-step sign-in. Returns
|
|
118
|
+
* any `securityAlert` the server attached so the caller can show the same
|
|
119
|
+
* "New sign-in detected" acknowledgement as the one-step path.
|
|
117
120
|
*/
|
|
118
121
|
completeTwoFactorSignIn: (params: {
|
|
119
122
|
loginToken: string;
|
|
120
123
|
token?: string;
|
|
121
124
|
backupCode?: string;
|
|
122
125
|
deviceName?: string;
|
|
123
|
-
}) => Promise<
|
|
126
|
+
}) => Promise<{ securityAlert?: SecurityAlert }>;
|
|
124
127
|
|
|
125
128
|
/**
|
|
126
129
|
* Commit a session obtained out-of-band (the "Sign in with Oxy" QR device
|
|
127
|
-
* flow). Plants tokens, persists the
|
|
128
|
-
* account into the device set, and hydrates the full user profile.
|
|
130
|
+
* flow). Plants tokens, persists the zero-cookie device credential, registers
|
|
131
|
+
* the account into the device set, and hydrates the full user profile.
|
|
129
132
|
*/
|
|
130
133
|
handleWebSession: (session: SessionLoginResponse) => Promise<void>;
|
|
131
134
|
|
|
@@ -212,14 +215,17 @@ const OxyContext = createContext<OxyContextState | null>(null);
|
|
|
212
215
|
* Result of {@link OxyContextState.signInWithPassword}.
|
|
213
216
|
*
|
|
214
217
|
* `'ok'` — the password was accepted and the session committed (so
|
|
215
|
-
* `isAuthenticated` / `user` are updated and the
|
|
218
|
+
* `isAuthenticated` / `user` are updated and the device credential persisted).
|
|
219
|
+
* `securityAlert` is present when the server flagged this sign-in as anomalous
|
|
220
|
+
* (new device / location) — the caller shows a "New sign-in detected"
|
|
221
|
+
* acknowledgement before proceeding; the session is already committed.
|
|
216
222
|
*
|
|
217
223
|
* `'2fa_required'` — the account has two-factor auth enabled, so NO session was
|
|
218
224
|
* created. Complete the challenge with the returned short-lived `loginToken`
|
|
219
225
|
* via {@link OxyContextState.completeTwoFactorSignIn}.
|
|
220
226
|
*/
|
|
221
227
|
export type PasswordSignInResult =
|
|
222
|
-
| { status: 'ok' }
|
|
228
|
+
| { status: 'ok'; securityAlert?: SecurityAlert }
|
|
223
229
|
| { status: '2fa_required'; loginToken: string };
|
|
224
230
|
|
|
225
231
|
export interface OxyContextProviderProps {
|
|
@@ -272,16 +278,15 @@ const DEFAULT_SESSION_VALIDITY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
272
278
|
*/
|
|
273
279
|
const SESSION_HANDOFF_DEADLINE = 6000;
|
|
274
280
|
|
|
275
|
-
/** The internal commit input — a session plus the
|
|
276
|
-
* not on the public `SessionLoginResponse
|
|
277
|
-
*
|
|
281
|
+
/** The internal commit input — a session plus the zero-cookie device credential
|
|
282
|
+
* (`deviceId` + `deviceSecret`) that is not on the public `SessionLoginResponse`.
|
|
283
|
+
* All extras optional so `SessionLoginResponse` is assignable. */
|
|
278
284
|
interface CommitInput {
|
|
279
285
|
sessionId: string;
|
|
280
286
|
accessToken?: string;
|
|
281
|
-
refreshToken?: string;
|
|
282
287
|
deviceId?: string;
|
|
283
|
-
/** Rotating device secret (
|
|
284
|
-
*
|
|
288
|
+
/** Rotating device secret (zero-cookie transport); persisted with `deviceId`
|
|
289
|
+
* so the cold boot can mint via `POST /session/device/token`. */
|
|
285
290
|
deviceSecret?: string;
|
|
286
291
|
expiresAt?: string;
|
|
287
292
|
userId?: string;
|
|
@@ -371,9 +376,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
371
376
|
}
|
|
372
377
|
const oxyServices = oxyServicesRef.current;
|
|
373
378
|
|
|
374
|
-
// The device-first persisted auth-state store (per-origin
|
|
375
|
-
// web; SecureStore session blob
|
|
376
|
-
//
|
|
379
|
+
// The device-first persisted auth-state store (per-origin zero-cookie device
|
|
380
|
+
// credential on web; SecureStore session blob on native). Built ONCE per
|
|
381
|
+
// provider mount.
|
|
377
382
|
const authStoreRef = useRef<AuthStateStore | null>(null);
|
|
378
383
|
if (!authStoreRef.current) {
|
|
379
384
|
authStoreRef.current = createPlatformAuthStateStore();
|
|
@@ -548,19 +553,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
548
553
|
const markAuthResolvedRef = useRef(markAuthResolved);
|
|
549
554
|
markAuthResolvedRef.current = markAuthResolved;
|
|
550
555
|
|
|
551
|
-
// Re-boot indirection: the SessionClient is built in the ref initializer below
|
|
552
|
-
// (before `runColdBoot` is defined), but its `onSessionAppeared` must re-run the
|
|
553
|
-
// cold boot to self-acquire when a sibling signs in on this device. A ref bridges
|
|
554
|
-
// the ordering; it is assigned right after `runColdBoot` is declared.
|
|
555
|
-
const runColdBootRef = useRef<(() => Promise<void>) | null>(null);
|
|
556
|
-
|
|
557
556
|
// Server-authoritative device session client. Built ONCE per `oxyServices`
|
|
558
|
-
// instance. `onUnauthenticated` (a device signout-all pushed over the
|
|
559
|
-
// or bootstrapped as zero-account) clears the
|
|
560
|
-
// a reload does not try to restore a session
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
//
|
|
557
|
+
// instance. `onUnauthenticated` (a device signout-all pushed over the
|
|
558
|
+
// bearer-authenticated socket, or bootstrapped as zero-account) clears the
|
|
559
|
+
// persisted store + local state so a reload does not try to restore a session
|
|
560
|
+
// the device no longer has. The realtime socket is bearer-only now: a
|
|
561
|
+
// signed-out tab cannot join any device room, so there is no signed-out sync
|
|
562
|
+
// wiring here.
|
|
564
563
|
const sessionClientPairRef = useRef<ReturnType<typeof createSessionClient> | null>(null);
|
|
565
564
|
if (!sessionClientPairRef.current) {
|
|
566
565
|
sessionClientPairRef.current = createSessionClient(
|
|
@@ -572,22 +571,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
572
571
|
logger('Failed to clear local state on remote sign-out', clearError);
|
|
573
572
|
});
|
|
574
573
|
},
|
|
575
|
-
{
|
|
576
|
-
signedOutSocketAuth: async () => {
|
|
577
|
-
// Web (`*.oxy.so`): the first-party `oxy_device` cookie rides the
|
|
578
|
-
// same-site handshake automatically — just connect.
|
|
579
|
-
if (isWebBrowser()) return true;
|
|
580
|
-
// Native (no cookie jar): present the shared device token if we have one.
|
|
581
|
-
try {
|
|
582
|
-
return (await authStore.loadDeviceToken()) ?? false;
|
|
583
|
-
} catch {
|
|
584
|
-
return false;
|
|
585
|
-
}
|
|
586
|
-
},
|
|
587
|
-
onSessionAppeared: () => {
|
|
588
|
-
void runColdBootRef.current?.();
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
574
|
);
|
|
592
575
|
}
|
|
593
576
|
const { client: sessionClient, host: sessionClientHost } = sessionClientPairRef.current;
|
|
@@ -681,7 +664,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
681
664
|
}
|
|
682
665
|
}
|
|
683
666
|
await clearSessionState();
|
|
684
|
-
// Explicit FULL wipe: also drop the persisted
|
|
667
|
+
// Explicit FULL wipe: also drop the persisted device credential so a reload
|
|
685
668
|
// finds nothing to restore.
|
|
686
669
|
await authStore.clear();
|
|
687
670
|
useAccountStore.getState().reset();
|
|
@@ -739,10 +722,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
739
722
|
}, [logger, oxyServices]);
|
|
740
723
|
|
|
741
724
|
// Unified in-session refresh (SDK-owned; every RP inherits it). Installs the
|
|
742
|
-
// ONE core refresh handler (
|
|
743
|
-
//
|
|
744
|
-
// ~60s before expiry and on tab-focus. Replaces the deleted
|
|
745
|
-
// `inSessionTokenRefresh` module.
|
|
725
|
+
// ONE core refresh handler (re-mint from the persisted zero-cookie device
|
|
726
|
+
// credential via `POST /session/device/token`) plus the proactive scheduler
|
|
727
|
+
// that refreshes ~60s before expiry and on tab-focus. Replaces the deleted
|
|
728
|
+
// services-local `inSessionTokenRefresh` module.
|
|
746
729
|
useEffect(() => {
|
|
747
730
|
const dispose = installAuthRefreshHandler({ oxy: oxyServices, store: authStore });
|
|
748
731
|
const scheduler = startTokenRefreshScheduler(oxyServices);
|
|
@@ -754,32 +737,27 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
754
737
|
|
|
755
738
|
// ── Session commit funnel ────────────────────────────────────────────────
|
|
756
739
|
// The single place a session becomes committed: plant tokens, persist the
|
|
757
|
-
//
|
|
758
|
-
// device set, and hydrate the full user.
|
|
759
|
-
// (`handleWebSession`), password sign-in, 2FA
|
|
740
|
+
// zero-cookie device credential (`deviceId` + `deviceSecret`), register the
|
|
741
|
+
// account into the server-authoritative device set, and hydrate the full user.
|
|
742
|
+
// Used by the QR device flow (`handleWebSession`), password sign-in, 2FA
|
|
743
|
+
// completion, and the cold boot.
|
|
760
744
|
const commitSession = useCallback(
|
|
761
745
|
async (input: CommitInput, options: { activate: boolean }): Promise<void> => {
|
|
762
746
|
if (input.accessToken) {
|
|
763
747
|
oxyServices.setTokens(input.accessToken);
|
|
764
748
|
}
|
|
765
749
|
|
|
766
|
-
// Persist the durable blob when
|
|
767
|
-
// (
|
|
768
|
-
// this is idempotent.
|
|
769
|
-
//
|
|
770
|
-
if (input.
|
|
750
|
+
// Persist the durable blob when the zero-cookie device credential
|
|
751
|
+
// (`deviceId` + `deviceSecret`) is present. The cold boot has usually
|
|
752
|
+
// persisted already; this is idempotent. Without the credential there is
|
|
753
|
+
// nothing durable to persist and the session lives only for this runtime.
|
|
754
|
+
if (input.deviceId && input.deviceSecret && input.userId) {
|
|
771
755
|
try {
|
|
772
|
-
const deviceToken = (await authStore.loadDeviceToken()) ?? undefined;
|
|
773
756
|
const next: PersistedAuthState = {
|
|
774
757
|
sessionId: input.sessionId,
|
|
775
|
-
refreshToken: input.refreshToken,
|
|
776
758
|
userId: input.userId,
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// cold boot can restore via the zero-cookie mint. Both are additive —
|
|
780
|
-
// absent on cookie-lane sign-ins, which fall back to the refresh family.
|
|
781
|
-
...(input.deviceId ? { deviceId: input.deviceId } : {}),
|
|
782
|
-
...(input.deviceSecret ? { deviceSecret: input.deviceSecret } : {}),
|
|
759
|
+
deviceId: input.deviceId,
|
|
760
|
+
deviceSecret: input.deviceSecret,
|
|
783
761
|
...(input.accessToken ? { accessToken: input.accessToken } : {}),
|
|
784
762
|
...(input.expiresAt ? { expiresAt: input.expiresAt } : {}),
|
|
785
763
|
};
|
|
@@ -867,9 +845,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
867
845
|
{
|
|
868
846
|
sessionId: session.sessionId,
|
|
869
847
|
accessToken: session.accessToken,
|
|
870
|
-
// deviceFlow threads
|
|
871
|
-
//
|
|
872
|
-
refreshToken: (session as { refreshToken?: string }).refreshToken,
|
|
848
|
+
// deviceFlow threads the rotating deviceSecret on the runtime object
|
|
849
|
+
// even though `SessionLoginResponse` does not type it.
|
|
873
850
|
deviceSecret: (session as { deviceSecret?: string }).deviceSecret,
|
|
874
851
|
deviceId: session.deviceId,
|
|
875
852
|
expiresAt: session.expiresAt,
|
|
@@ -944,9 +921,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
944
921
|
password: string,
|
|
945
922
|
opts?: { deviceName?: string; deviceFingerprint?: string },
|
|
946
923
|
): Promise<PasswordSignInResult> => {
|
|
947
|
-
const deviceToken = (await authStore.loadDeviceToken()) ?? undefined;
|
|
948
924
|
const result = await oxyServices.passwordSignIn(identifier, password, {
|
|
949
|
-
deviceToken,
|
|
950
925
|
deviceName: opts?.deviceName,
|
|
951
926
|
deviceFingerprint: opts?.deviceFingerprint,
|
|
952
927
|
});
|
|
@@ -958,7 +933,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
958
933
|
{
|
|
959
934
|
sessionId: result.sessionId,
|
|
960
935
|
accessToken: result.accessToken,
|
|
961
|
-
refreshToken: result.refreshToken,
|
|
962
936
|
deviceSecret: result.deviceSecret,
|
|
963
937
|
deviceId: result.deviceId,
|
|
964
938
|
expiresAt: result.expiresAt,
|
|
@@ -967,9 +941,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
967
941
|
},
|
|
968
942
|
{ activate: true },
|
|
969
943
|
);
|
|
970
|
-
return { status: 'ok' };
|
|
944
|
+
return { status: 'ok', ...(result.securityAlert ? { securityAlert: result.securityAlert } : {}) };
|
|
971
945
|
},
|
|
972
|
-
[oxyServices,
|
|
946
|
+
[oxyServices, commitSession],
|
|
973
947
|
);
|
|
974
948
|
|
|
975
949
|
const completeTwoFactorSignIn = useCallback(
|
|
@@ -978,20 +952,17 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
978
952
|
token?: string;
|
|
979
953
|
backupCode?: string;
|
|
980
954
|
deviceName?: string;
|
|
981
|
-
}): Promise<
|
|
982
|
-
const deviceToken = (await authStore.loadDeviceToken()) ?? undefined;
|
|
955
|
+
}): Promise<{ securityAlert?: SecurityAlert }> => {
|
|
983
956
|
const result = await oxyServices.completeTwoFactorSignIn({
|
|
984
957
|
loginToken: params.loginToken,
|
|
985
958
|
token: params.token,
|
|
986
959
|
backupCode: params.backupCode,
|
|
987
|
-
deviceToken,
|
|
988
960
|
deviceName: params.deviceName,
|
|
989
961
|
});
|
|
990
962
|
await commitSession(
|
|
991
963
|
{
|
|
992
964
|
sessionId: result.sessionId,
|
|
993
965
|
accessToken: result.accessToken,
|
|
994
|
-
refreshToken: result.refreshToken,
|
|
995
966
|
deviceSecret: result.deviceSecret,
|
|
996
967
|
deviceId: result.deviceId,
|
|
997
968
|
expiresAt: result.expiresAt,
|
|
@@ -1000,8 +971,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1000
971
|
},
|
|
1001
972
|
{ activate: true },
|
|
1002
973
|
);
|
|
974
|
+
return { securityAlert: result.securityAlert };
|
|
1003
975
|
},
|
|
1004
|
-
[oxyServices,
|
|
976
|
+
[oxyServices, commitSession],
|
|
1005
977
|
);
|
|
1006
978
|
|
|
1007
979
|
// ── Cold boot ────────────────────────────────────────────────────────────
|
|
@@ -1042,19 +1014,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1042
1014
|
markAuthResolvedRef.current();
|
|
1043
1015
|
},
|
|
1044
1016
|
onSignedOut: () => {
|
|
1017
|
+
// The realtime socket is bearer-only: a signed-out tab cannot join any
|
|
1018
|
+
// device room, so there is no signed-out socket to start here.
|
|
1045
1019
|
markAuthResolvedRef.current();
|
|
1046
|
-
// Open the signed-out realtime channel so this idle tab joins its
|
|
1047
|
-
// `device:<id>` room (web `oxy_device` cookie / native device token)
|
|
1048
|
-
// and self-acquires when a sibling signs in. Idempotent, best-effort.
|
|
1049
|
-
void sessionClient.start().catch((startError) => {
|
|
1050
|
-
if (__DEV__) {
|
|
1051
|
-
loggerUtil.debug(
|
|
1052
|
-
'signed-out socket start failed (non-fatal)',
|
|
1053
|
-
{ component: 'OxyContext', method: 'runColdBoot' },
|
|
1054
|
-
startError as unknown,
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
1020
|
},
|
|
1059
1021
|
onStepError: (id, error) => {
|
|
1060
1022
|
if (__DEV__) {
|
|
@@ -1078,20 +1040,16 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1078
1040
|
// Backstop: resolve on every exit path so the gate can never hang.
|
|
1079
1041
|
markAuthResolvedRef.current();
|
|
1080
1042
|
}
|
|
1081
|
-
}, [oxyServices, authStore
|
|
1082
|
-
// Bridge for `onSessionAppeared` (SessionClient) → re-run the cold boot to
|
|
1083
|
-
// self-acquire when a sibling signs in on this device while this tab is idle.
|
|
1084
|
-
runColdBootRef.current = runColdBoot;
|
|
1043
|
+
}, [oxyServices, authStore]);
|
|
1085
1044
|
|
|
1086
1045
|
useEffect(() => {
|
|
1087
1046
|
if (initialized) {
|
|
1088
1047
|
return;
|
|
1089
1048
|
}
|
|
1090
1049
|
// IdP mode (`coldBoot={false}`): this provider is NOT the ecosystem session
|
|
1091
|
-
// authority, so it never runs the device-first restore
|
|
1092
|
-
// signed
|
|
1093
|
-
//
|
|
1094
|
-
// is no boot spinner; a deliberate sign-in still commits a normal session.
|
|
1050
|
+
// authority, so it never runs the device-first restore. Resolve auth
|
|
1051
|
+
// immediately as signed out so there is no boot spinner; a deliberate sign-in
|
|
1052
|
+
// still commits a normal session.
|
|
1095
1053
|
if (!coldBoot) {
|
|
1096
1054
|
setInitialized(true);
|
|
1097
1055
|
markAuthResolved();
|
|
@@ -1215,7 +1173,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1215
1173
|
{
|
|
1216
1174
|
sessionId: result.sessionId,
|
|
1217
1175
|
accessToken: result.accessToken,
|
|
1218
|
-
refreshToken: (result as { refreshToken?: string }).refreshToken,
|
|
1219
1176
|
deviceSecret: (result as { deviceSecret?: string }).deviceSecret,
|
|
1220
1177
|
deviceId: result.deviceId,
|
|
1221
1178
|
expiresAt: result.expiresAt,
|
|
@@ -1379,7 +1336,7 @@ const LOADING_STATE: OxyContextState = {
|
|
|
1379
1336
|
getPublicKey: () => Promise.resolve(null),
|
|
1380
1337
|
signIn: () => rejectMissingProvider<User>(),
|
|
1381
1338
|
signInWithPassword: () => rejectMissingProvider<PasswordSignInResult>(),
|
|
1382
|
-
completeTwoFactorSignIn: () => rejectMissingProvider<
|
|
1339
|
+
completeTwoFactorSignIn: () => rejectMissingProvider<{ securityAlert?: SecurityAlert }>(),
|
|
1383
1340
|
handleWebSession: () => rejectMissingProvider<void>(),
|
|
1384
1341
|
logout: () => rejectMissingProvider<void>(),
|
|
1385
1342
|
logoutAll: () => rejectMissingProvider<void>(),
|