@oxyhq/services 8.7.0 → 10.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/lib/commonjs/ui/components/OxyProvider.js +2 -2
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/SignInModal.js +14 -3
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +30 -18
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useWebSSO.js +7 -8
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +14 -3
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/utils/silentGuardKey.js +54 -0
- package/lib/commonjs/utils/silentGuardKey.js.map +1 -0
- package/lib/module/ui/components/OxyProvider.js +2 -2
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/SignInModal.js +14 -3
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +30 -18
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useWebSSO.js +7 -8
- package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +14 -3
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/utils/silentGuardKey.js +49 -0
- package/lib/module/utils/silentGuardKey.js.map +1 -0
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +12 -8
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +8 -6
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts +31 -0
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +12 -8
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +8 -6
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/utils/silentGuardKey.d.ts +31 -0
- package/lib/typescript/module/utils/silentGuardKey.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/ui/components/OxyProvider.tsx +2 -2
- package/src/ui/components/SignInModal.tsx +14 -3
- package/src/ui/context/OxyContext.tsx +42 -29
- package/src/ui/hooks/useWebSSO.ts +7 -8
- package/src/ui/screens/OxyAuthScreen.tsx +14 -3
- package/src/ui/types/navigation.ts +8 -6
- package/src/utils/__tests__/silentGuardKey.test.ts +82 -0
- package/src/utils/silentGuardKey.ts +46 -0
- package/lib/commonjs/ui/utils/appName.js +0 -62
- package/lib/commonjs/ui/utils/appName.js.map +0 -1
- package/lib/module/ui/utils/appName.js +0 -59
- package/lib/module/ui/utils/appName.js.map +0 -1
- package/lib/typescript/commonjs/ui/utils/appName.d.ts +0 -22
- package/lib/typescript/commonjs/ui/utils/appName.d.ts.map +0 -1
- package/lib/typescript/module/ui/utils/appName.d.ts +0 -22
- package/lib/typescript/module/ui/utils/appName.d.ts.map +0 -1
- package/src/ui/utils/__tests__/appName.test.ts +0 -52
- package/src/ui/utils/appName.ts +0 -62
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, pure helpers for building the `origin|baseURL` signature used as the
|
|
3
|
+
* module-level run-once guard key for cold-boot silent-SSO probes
|
|
4
|
+
* (`silentColdBootKey` in `OxyContext`, `ssoSignature` in `useWebSSO`).
|
|
5
|
+
*
|
|
6
|
+
* NATIVE SAFETY (the bug this fixes): React Native aliases a global `window`
|
|
7
|
+
* (it points at the JS global object), so `typeof window !== 'undefined'` is
|
|
8
|
+
* `true` on native — but `window.location` is `undefined`. Reading
|
|
9
|
+
* `window.location.origin` after only a `typeof window` check therefore throws
|
|
10
|
+
* `TypeError: Cannot read property 'origin' of undefined` on native. Because
|
|
11
|
+
* the key is built UNCONDITIONALLY at the top of the cold-boot path (before its
|
|
12
|
+
* try/catch), that throw escaped session restore entirely and broke
|
|
13
|
+
* cross-session restore on native. Both prior copies of the guard had the same
|
|
14
|
+
* insufficient `typeof window` check and were prone to drift, so the read is
|
|
15
|
+
* consolidated here behind a guard that also verifies `window.location`.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Read `window.location.origin` safely on every platform.
|
|
19
|
+
*
|
|
20
|
+
* Returns the browser origin on web, and the sentinel `'no-origin'` anywhere
|
|
21
|
+
* `window.location` is absent (React Native, SSR/Node). Never throws.
|
|
22
|
+
*/
|
|
23
|
+
export declare function safeWindowOrigin(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Build the stable `origin|baseURL` signature for the silent-SSO run-once
|
|
26
|
+
* guard. Two providers pointed at the same API from the same origin share one
|
|
27
|
+
* attempt. `getBaseURL` is invoked defensively (it may be absent or throw on a
|
|
28
|
+
* partially-initialised client); any failure degrades to an empty baseURL.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildSilentGuardKey(getBaseURL?: () => string | undefined): string;
|
|
31
|
+
//# sourceMappingURL=silentGuardKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"silentGuardKey.d.ts","sourceRoot":"","sources":["../../../../src/utils/silentGuardKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,MAAM,CASjF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwD/B,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,oBAAoB,eAAqB,CAAC;AAEvD,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,GAAI,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAGxF,CAAC;AAEF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwD/B,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,oBAAoB,eAAqB,CAAC;AAEvD,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,GAAI,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAGxF,CAAC;AAEF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAsXxB,CAAC;AA8FF,eAAe,WAAW,CAAC"}
|
|
@@ -62,12 +62,16 @@ export interface OxyContextState {
|
|
|
62
62
|
clearAllAccountData: () => Promise<void>;
|
|
63
63
|
storageKeyPrefix: string;
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* `
|
|
65
|
+
* The app's Oxy OAuth client id / ApplicationCredential publicKey, as
|
|
66
|
+
* supplied via the `clientId` prop. Required for the cross-app device
|
|
67
|
+
* sign-in flow: the sign-in components send it to
|
|
68
|
+
* `POST /auth/session/create` so the API can identify the requesting app by
|
|
69
|
+
* its real registered client id (the consent identity is then resolved
|
|
70
|
+
* server-side and shown by the central auth web). `null` when the consuming
|
|
71
|
+
* app did not configure a client id — the device sign-in flow surfaces a
|
|
72
|
+
* configuration error in that case.
|
|
69
73
|
*/
|
|
70
|
-
|
|
74
|
+
clientId: string | null;
|
|
71
75
|
oxyServices: OxyServices;
|
|
72
76
|
useFollow?: UseFollowHook;
|
|
73
77
|
showBottomSheet?: (screenOrConfig: RouteName | {
|
|
@@ -90,10 +94,10 @@ export interface OxyContextProviderProps {
|
|
|
90
94
|
authRedirectUri?: string;
|
|
91
95
|
storageKeyPrefix?: string;
|
|
92
96
|
/**
|
|
93
|
-
*
|
|
94
|
-
* sign-in
|
|
97
|
+
* The app's Oxy OAuth client id / ApplicationCredential publicKey; required
|
|
98
|
+
* for the cross-app device sign-in flow. See {@link OxyContextState.clientId}.
|
|
95
99
|
*/
|
|
96
|
-
|
|
100
|
+
clientId?: string;
|
|
97
101
|
onAuthStateChange?: (user: User | null) => void;
|
|
98
102
|
onError?: (error: ApiError) => void;
|
|
99
103
|
}
|
|
@@ -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;AACf,OAAO,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
|
|
@@ -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;AAcrB;;GAEG;AACH,eAAO,MAAM,gBAAgB;;EA6F5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe;SAMQ,MAAM;WAAS,MAAM;WAAS,MAAM;WAAS,MAAM;;;EA4EtF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,UAAU,8BAA8B;IACtC,yCAAyC;IACzC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAClC;;;;OAIG;IACH,WAAW,EAAE,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB;sBAoFf,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;sBARnD,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;sBARnD,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;sBARnD,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"useAccountMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useAccountMutations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxD,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,2DAA2D;IAC3D,eAAe,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC5D,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AA8BD;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAI/B;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CAwHpC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA0F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,
|
|
1
|
+
{"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA0F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAwY5C,CAAC;AA6FF,eAAe,aAAa,CAAC"}
|
|
@@ -30,13 +30,15 @@ export interface OxyProviderProps {
|
|
|
30
30
|
onAuthStateChange?: (user: unknown) => void;
|
|
31
31
|
storageKeyPrefix?: string;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* The app's Oxy OAuth client id / ApplicationCredential publicKey.
|
|
34
|
+
* Required for the cross-app device sign-in flow: the QR / popup
|
|
35
|
+
* sign-in registers a device-flow session via `POST /auth/session/create`,
|
|
36
|
+
* which now identifies the requesting app by this real registered
|
|
37
|
+
* client id. The central Oxy auth experience resolves and renders the
|
|
38
|
+
* consent identity from it server-side. Without it the device sign-in
|
|
39
|
+
* flow cannot start.
|
|
38
40
|
*/
|
|
39
|
-
|
|
41
|
+
clientId?: string;
|
|
40
42
|
baseURL?: string;
|
|
41
43
|
authWebUrl?: string;
|
|
42
44
|
authRedirectUri?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKtD,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAE5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACxE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAG9C,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAGlC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAGjE,aAAa,CAAC,EAAE,SAAS,CAAC;IAG1B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAGnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IAMtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKtD,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAE5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACxE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAG9C,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAGlC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAGjE,aAAa,CAAC,EAAE,SAAS,CAAC;IAG1B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAGnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IAMtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC7B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, pure helpers for building the `origin|baseURL` signature used as the
|
|
3
|
+
* module-level run-once guard key for cold-boot silent-SSO probes
|
|
4
|
+
* (`silentColdBootKey` in `OxyContext`, `ssoSignature` in `useWebSSO`).
|
|
5
|
+
*
|
|
6
|
+
* NATIVE SAFETY (the bug this fixes): React Native aliases a global `window`
|
|
7
|
+
* (it points at the JS global object), so `typeof window !== 'undefined'` is
|
|
8
|
+
* `true` on native — but `window.location` is `undefined`. Reading
|
|
9
|
+
* `window.location.origin` after only a `typeof window` check therefore throws
|
|
10
|
+
* `TypeError: Cannot read property 'origin' of undefined` on native. Because
|
|
11
|
+
* the key is built UNCONDITIONALLY at the top of the cold-boot path (before its
|
|
12
|
+
* try/catch), that throw escaped session restore entirely and broke
|
|
13
|
+
* cross-session restore on native. Both prior copies of the guard had the same
|
|
14
|
+
* insufficient `typeof window` check and were prone to drift, so the read is
|
|
15
|
+
* consolidated here behind a guard that also verifies `window.location`.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Read `window.location.origin` safely on every platform.
|
|
19
|
+
*
|
|
20
|
+
* Returns the browser origin on web, and the sentinel `'no-origin'` anywhere
|
|
21
|
+
* `window.location` is absent (React Native, SSR/Node). Never throws.
|
|
22
|
+
*/
|
|
23
|
+
export declare function safeWindowOrigin(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Build the stable `origin|baseURL` signature for the silent-SSO run-once
|
|
26
|
+
* guard. Two providers pointed at the same API from the same origin share one
|
|
27
|
+
* attempt. `getBaseURL` is invoked defensively (it may be absent or throw on a
|
|
28
|
+
* partially-initialised client); any failure degrades to an empty baseURL.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildSilentGuardKey(getBaseURL?: () => string | undefined): string;
|
|
31
|
+
//# sourceMappingURL=silentGuardKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"silentGuardKey.d.ts","sourceRoot":"","sources":["../../../../src/utils/silentGuardKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,MAAM,CASjF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
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",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"peerDependencies": {
|
|
161
161
|
"@expo/vector-icons": "^15.0.3",
|
|
162
162
|
"@oxyhq/bloom": ">=0.5.0",
|
|
163
|
-
"@oxyhq/core": "^2.
|
|
163
|
+
"@oxyhq/core": "^3.2.0",
|
|
164
164
|
"@react-native-community/netinfo": "^11.4.1",
|
|
165
165
|
"@tanstack/query-async-storage-persister": "^5.100",
|
|
166
166
|
"@tanstack/query-sync-storage-persister": "^5.100",
|
|
@@ -103,7 +103,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
103
103
|
children,
|
|
104
104
|
onAuthStateChange,
|
|
105
105
|
storageKeyPrefix,
|
|
106
|
-
|
|
106
|
+
clientId,
|
|
107
107
|
baseURL,
|
|
108
108
|
authWebUrl,
|
|
109
109
|
authRedirectUri,
|
|
@@ -297,7 +297,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
297
297
|
authWebUrl={authWebUrl}
|
|
298
298
|
authRedirectUri={authRedirectUri}
|
|
299
299
|
storageKeyPrefix={storageKeyPrefix}
|
|
300
|
-
|
|
300
|
+
clientId={clientId}
|
|
301
301
|
onAuthStateChange={onAuthStateChange as OxyContextProviderProps['onAuthStateChange']}
|
|
302
302
|
>
|
|
303
303
|
{children}
|
|
@@ -93,7 +93,7 @@ const SignInModal: React.FC = () => {
|
|
|
93
93
|
|
|
94
94
|
const insets = useSafeAreaInsets();
|
|
95
95
|
const theme = useTheme();
|
|
96
|
-
const { oxyServices, switchSession,
|
|
96
|
+
const { oxyServices, switchSession, clientId } = useOxy();
|
|
97
97
|
|
|
98
98
|
const socketRef = useRef<Socket | null>(null);
|
|
99
99
|
const pollingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
@@ -266,6 +266,17 @@ const SignInModal: React.FC = () => {
|
|
|
266
266
|
setError(null);
|
|
267
267
|
isProcessingRef.current = false;
|
|
268
268
|
|
|
269
|
+
// The cross-app device sign-in flow identifies the requesting app by its
|
|
270
|
+
// real registered OAuth client id (ApplicationCredential publicKey).
|
|
271
|
+
// Without it the API cannot resolve the consent identity, so we fail
|
|
272
|
+
// fast with a clear configuration error rather than creating a session
|
|
273
|
+
// the server would reject.
|
|
274
|
+
if (!clientId) {
|
|
275
|
+
setError('This app is not configured for sign-in (missing clientId).');
|
|
276
|
+
setIsLoading(false);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
269
280
|
try {
|
|
270
281
|
const sessionToken = generateSessionToken();
|
|
271
282
|
const expiresAt = Date.now() + AUTH_SESSION_EXPIRY_MS;
|
|
@@ -273,7 +284,7 @@ const SignInModal: React.FC = () => {
|
|
|
273
284
|
await oxyServices.makeRequest('POST', '/auth/session/create', {
|
|
274
285
|
sessionToken,
|
|
275
286
|
expiresAt,
|
|
276
|
-
|
|
287
|
+
clientId,
|
|
277
288
|
}, { cache: false });
|
|
278
289
|
|
|
279
290
|
setAuthSession({ sessionToken, expiresAt });
|
|
@@ -284,7 +295,7 @@ const SignInModal: React.FC = () => {
|
|
|
284
295
|
} finally {
|
|
285
296
|
setIsLoading(false);
|
|
286
297
|
}
|
|
287
|
-
}, [oxyServices, connectSocket,
|
|
298
|
+
}, [oxyServices, connectSocket, clientId]);
|
|
288
299
|
|
|
289
300
|
// Generate a cryptographically random session token.
|
|
290
301
|
// 16 random bytes -> 32 hex chars (128 bits of entropy) — unguessable.
|
|
@@ -42,7 +42,6 @@ import { useDeviceManagement } from '../hooks/useDeviceManagement';
|
|
|
42
42
|
import { getStorageKeys, createPlatformStorage, type StorageInterface } from '../utils/storageHelpers';
|
|
43
43
|
import { isInvalidSessionError, isTimeoutOrNetworkError } from '../utils/errorHandlers';
|
|
44
44
|
import { readActiveAuthuser, writeActiveAuthuser } from '../utils/activeAuthuser';
|
|
45
|
-
import { resolveAppDisplayName } from '../utils/appName';
|
|
46
45
|
import type { RouteName } from '../navigation/routes';
|
|
47
46
|
import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSheetManager';
|
|
48
47
|
import { useQueryClient } from '@tanstack/react-query';
|
|
@@ -51,6 +50,7 @@ import { useAvatarPicker } from '../hooks/useAvatarPicker';
|
|
|
51
50
|
import { useAccountStore } from '../stores/accountStore';
|
|
52
51
|
import { logger as loggerUtil } from '@oxyhq/core';
|
|
53
52
|
import { useWebSSO, isWebBrowser } from '../hooks/useWebSSO';
|
|
53
|
+
import { buildSilentGuardKey } from '../../utils/silentGuardKey';
|
|
54
54
|
|
|
55
55
|
export interface OxyContextState {
|
|
56
56
|
user: User | null;
|
|
@@ -116,12 +116,16 @@ export interface OxyContextState {
|
|
|
116
116
|
clearAllAccountData: () => Promise<void>;
|
|
117
117
|
storageKeyPrefix: string;
|
|
118
118
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* `
|
|
119
|
+
* The app's Oxy OAuth client id / ApplicationCredential publicKey, as
|
|
120
|
+
* supplied via the `clientId` prop. Required for the cross-app device
|
|
121
|
+
* sign-in flow: the sign-in components send it to
|
|
122
|
+
* `POST /auth/session/create` so the API can identify the requesting app by
|
|
123
|
+
* its real registered client id (the consent identity is then resolved
|
|
124
|
+
* server-side and shown by the central auth web). `null` when the consuming
|
|
125
|
+
* app did not configure a client id — the device sign-in flow surfaces a
|
|
126
|
+
* configuration error in that case.
|
|
123
127
|
*/
|
|
124
|
-
|
|
128
|
+
clientId: string | null;
|
|
125
129
|
oxyServices: OxyServices;
|
|
126
130
|
useFollow?: UseFollowHook;
|
|
127
131
|
showBottomSheet?: (screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> }) => void;
|
|
@@ -149,10 +153,10 @@ export interface OxyContextProviderProps {
|
|
|
149
153
|
authRedirectUri?: string;
|
|
150
154
|
storageKeyPrefix?: string;
|
|
151
155
|
/**
|
|
152
|
-
*
|
|
153
|
-
* sign-in
|
|
156
|
+
* The app's Oxy OAuth client id / ApplicationCredential publicKey; required
|
|
157
|
+
* for the cross-app device sign-in flow. See {@link OxyContextState.clientId}.
|
|
154
158
|
*/
|
|
155
|
-
|
|
159
|
+
clientId?: string;
|
|
156
160
|
onAuthStateChange?: (user: User | null) => void;
|
|
157
161
|
onError?: (error: ApiError) => void;
|
|
158
162
|
}
|
|
@@ -181,14 +185,15 @@ const servicesSilentAttempted = new Set<string>();
|
|
|
181
185
|
* Build the `origin|baseURL` signature used as the silent-cold-boot guard key.
|
|
182
186
|
*/
|
|
183
187
|
function silentColdBootKey(oxyServices: OxyServices): string {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
// `buildSilentGuardKey` reads `window.location.origin` behind a guard that
|
|
189
|
+
// also verifies `window.location` exists. This is critical: it runs
|
|
190
|
+
// UNCONDITIONALLY at the top of `restoreSessionsFromStorage` (before the
|
|
191
|
+
// cold-boot try/catch) on EVERY platform, and React Native aliases a global
|
|
192
|
+
// `window` with NO `window.location`. Without that guard the read threw
|
|
193
|
+
// `Cannot read property 'origin' of undefined` on native, escaping the
|
|
194
|
+
// restore path so `markAuthResolved` never ran and stored-session restore was
|
|
195
|
+
// never reached.
|
|
196
|
+
return buildSilentGuardKey(() => oxyServices.getBaseURL?.());
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
/**
|
|
@@ -258,7 +263,12 @@ const COLD_BOOT_OVERALL_DEADLINE = 20000;
|
|
|
258
263
|
* off-browser.
|
|
259
264
|
*/
|
|
260
265
|
function isSameSiteIdP(idpOrigin: string): boolean {
|
|
261
|
-
|
|
266
|
+
// Native defines a global `window` but no `window.location`; guard the
|
|
267
|
+
// latter so reading `.hostname` can never throw off-browser. (Only reachable
|
|
268
|
+
// from the web-only visibility check, but kept robust for parity.)
|
|
269
|
+
if (typeof window === 'undefined' || typeof window.location === 'undefined') {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
262
272
|
let idpHostname: string;
|
|
263
273
|
try {
|
|
264
274
|
idpHostname = new URL(idpOrigin).hostname;
|
|
@@ -314,7 +324,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
314
324
|
authWebUrl,
|
|
315
325
|
authRedirectUri,
|
|
316
326
|
storageKeyPrefix = 'oxy_session',
|
|
317
|
-
|
|
327
|
+
clientId: clientIdProp,
|
|
318
328
|
onAuthStateChange,
|
|
319
329
|
onError,
|
|
320
330
|
}) => {
|
|
@@ -424,13 +434,16 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
424
434
|
|
|
425
435
|
const storageKeys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
|
|
426
436
|
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
)
|
|
437
|
+
// The app's Oxy OAuth client id surfaced on the context so the cross-app
|
|
438
|
+
// device sign-in components (SignInModal / OxyAuthScreen) can identify the
|
|
439
|
+
// requesting app to `POST /auth/session/create`. Normalized to a trimmed
|
|
440
|
+
// non-empty string, or `null` when the consumer did not configure one — the
|
|
441
|
+
// sign-in components surface a clear configuration error in that case rather
|
|
442
|
+
// than falling back to any display string.
|
|
443
|
+
const clientId = useMemo(() => {
|
|
444
|
+
const trimmed = clientIdProp?.trim();
|
|
445
|
+
return trimmed ? trimmed : null;
|
|
446
|
+
}, [clientIdProp]);
|
|
434
447
|
|
|
435
448
|
// Storage initialization.
|
|
436
449
|
//
|
|
@@ -1666,7 +1679,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1666
1679
|
clearSessionState,
|
|
1667
1680
|
clearAllAccountData,
|
|
1668
1681
|
storageKeyPrefix,
|
|
1669
|
-
|
|
1682
|
+
clientId,
|
|
1670
1683
|
oxyServices,
|
|
1671
1684
|
useFollow: useFollowHook,
|
|
1672
1685
|
showBottomSheet: showBottomSheetForContext,
|
|
@@ -1695,7 +1708,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1695
1708
|
logoutAllDeviceSessions,
|
|
1696
1709
|
oxyServices,
|
|
1697
1710
|
storageKeyPrefix,
|
|
1698
|
-
|
|
1711
|
+
clientId,
|
|
1699
1712
|
refreshSessionsWithUser,
|
|
1700
1713
|
sessions,
|
|
1701
1714
|
setLanguage,
|
|
@@ -1773,7 +1786,7 @@ const LOADING_STATE: OxyContextState = {
|
|
|
1773
1786
|
clearSessionState: () => rejectMissingProvider<void>(),
|
|
1774
1787
|
clearAllAccountData: () => rejectMissingProvider<void>(),
|
|
1775
1788
|
storageKeyPrefix: 'oxy_session',
|
|
1776
|
-
|
|
1789
|
+
clientId: null,
|
|
1777
1790
|
oxyServices: LOADING_STATE_OXY_SERVICES,
|
|
1778
1791
|
openAvatarPicker: () => {},
|
|
1779
1792
|
actingAs: null,
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { useEffect, useRef, useCallback } from 'react';
|
|
19
19
|
import type { OxyServices } from '@oxyhq/core';
|
|
20
20
|
import type { SessionLoginResponse } from '@oxyhq/core';
|
|
21
|
+
import { buildSilentGuardKey } from '../../utils/silentGuardKey';
|
|
21
22
|
|
|
22
23
|
interface UseWebSSOOptions {
|
|
23
24
|
oxyServices: OxyServices;
|
|
@@ -58,14 +59,12 @@ const silentSSOAttempted = new Set<string>();
|
|
|
58
59
|
* pointed at the same API from the same origin share one attempt.
|
|
59
60
|
*/
|
|
60
61
|
function ssoSignature(oxyServices: OxyServices): string {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
return `${origin}|${baseURL}`;
|
|
62
|
+
// Shared with `OxyContext.silentColdBootKey`. `buildSilentGuardKey` reads
|
|
63
|
+
// `window.location.origin` behind a guard that also verifies
|
|
64
|
+
// `window.location` exists — React Native aliases a global `window` with NO
|
|
65
|
+
// `window.location`, so a `typeof window`-only check would throw
|
|
66
|
+
// `Cannot read property 'origin' of undefined` off-browser.
|
|
67
|
+
return buildSilentGuardKey(() => oxyServices.getBaseURL?.());
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
@@ -119,7 +119,7 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
|
|
|
119
119
|
theme,
|
|
120
120
|
}) => {
|
|
121
121
|
const bloomTheme = useTheme();
|
|
122
|
-
const { oxyServices, signIn, switchSession,
|
|
122
|
+
const { oxyServices, signIn, switchSession, clientId } = useOxy();
|
|
123
123
|
|
|
124
124
|
const [authSession, setAuthSession] = useState<AuthSession | null>(null);
|
|
125
125
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -264,6 +264,17 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
|
|
|
264
264
|
setError(null);
|
|
265
265
|
isProcessingRef.current = false;
|
|
266
266
|
|
|
267
|
+
// The cross-app device sign-in flow identifies the requesting app by its
|
|
268
|
+
// real registered OAuth client id (ApplicationCredential publicKey).
|
|
269
|
+
// Without it the API cannot resolve the consent identity, so we fail fast
|
|
270
|
+
// with a clear configuration error rather than creating a session the
|
|
271
|
+
// server would reject.
|
|
272
|
+
if (!clientId) {
|
|
273
|
+
setError('This app is not configured for sign-in (missing clientId).');
|
|
274
|
+
setIsLoading(false);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
267
278
|
try {
|
|
268
279
|
// Generate a unique session token for this auth request
|
|
269
280
|
const sessionToken = generateSessionToken();
|
|
@@ -273,7 +284,7 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
|
|
|
273
284
|
await oxyServices.makeRequest('POST', '/auth/session/create', {
|
|
274
285
|
sessionToken,
|
|
275
286
|
expiresAt,
|
|
276
|
-
|
|
287
|
+
clientId,
|
|
277
288
|
}, { cache: false });
|
|
278
289
|
|
|
279
290
|
setAuthSession({ sessionToken, expiresAt });
|
|
@@ -286,7 +297,7 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
|
|
|
286
297
|
} finally {
|
|
287
298
|
setIsLoading(false);
|
|
288
299
|
}
|
|
289
|
-
}, [oxyServices, connectSocket,
|
|
300
|
+
}, [oxyServices, connectSocket, clientId]);
|
|
290
301
|
|
|
291
302
|
// Generate a random session token
|
|
292
303
|
const generateSessionToken = (): string => {
|
|
@@ -53,13 +53,15 @@ export interface OxyProviderProps {
|
|
|
53
53
|
onAuthStateChange?: (user: unknown) => void;
|
|
54
54
|
storageKeyPrefix?: string;
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
56
|
+
* The app's Oxy OAuth client id / ApplicationCredential publicKey.
|
|
57
|
+
* Required for the cross-app device sign-in flow: the QR / popup
|
|
58
|
+
* sign-in registers a device-flow session via `POST /auth/session/create`,
|
|
59
|
+
* which now identifies the requesting app by this real registered
|
|
60
|
+
* client id. The central Oxy auth experience resolves and renders the
|
|
61
|
+
* consent identity from it server-side. Without it the device sign-in
|
|
62
|
+
* flow cannot start.
|
|
61
63
|
*/
|
|
62
|
-
|
|
64
|
+
clientId?: string;
|
|
63
65
|
baseURL?: string;
|
|
64
66
|
authWebUrl?: string;
|
|
65
67
|
authRedirectUri?: string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*
|
|
4
|
+
* Regression coverage for the native session-restore crash:
|
|
5
|
+
*
|
|
6
|
+
* W [component:OxyContext]: Failed to restore sessions from storage
|
|
7
|
+
* [TypeError: Cannot read property 'origin' of undefined]
|
|
8
|
+
*
|
|
9
|
+
* `silentColdBootKey` (OxyContext) and `ssoSignature` (useWebSSO) both build an
|
|
10
|
+
* `origin|baseURL` guard signature UNCONDITIONALLY at the top of the cold-boot
|
|
11
|
+
* path, on every platform. React Native aliases a global `window` (so
|
|
12
|
+
* `typeof window !== 'undefined'` is `true`) but provides NO `window.location`.
|
|
13
|
+
* The previous `typeof window`-only guard then read `window.location.origin`
|
|
14
|
+
* and threw `Cannot read property 'origin' of undefined`, escaping session
|
|
15
|
+
* restore entirely. Both call sites now delegate to the shared, guarded
|
|
16
|
+
* `buildSilentGuardKey`, verified here under all three platform shapes.
|
|
17
|
+
*
|
|
18
|
+
* Runs in the `node` environment so `window` is genuinely controllable — under
|
|
19
|
+
* jsdom `window.location` is non-configurable and cannot be removed, so the
|
|
20
|
+
* native shape (window present, location absent) is not reproducible there.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { buildSilentGuardKey, safeWindowOrigin } from '../silentGuardKey';
|
|
24
|
+
|
|
25
|
+
describe('silentGuardKey native safety', () => {
|
|
26
|
+
const globalRef = globalThis as { window?: unknown };
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
delete globalRef.window;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('safeWindowOrigin', () => {
|
|
33
|
+
it('returns "no-origin" when there is no window (Node / SSR)', () => {
|
|
34
|
+
delete globalRef.window;
|
|
35
|
+
expect(safeWindowOrigin()).toBe('no-origin');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns "no-origin" on React Native (window present, no location)', () => {
|
|
39
|
+
// EXACT native shape: RN aliases a global `window` to the JS global, but
|
|
40
|
+
// there is no `window.location`. The old `typeof window`-only guard threw
|
|
41
|
+
// here; the new guard must return the sentinel without throwing.
|
|
42
|
+
globalRef.window = {};
|
|
43
|
+
expect(() => safeWindowOrigin()).not.toThrow();
|
|
44
|
+
expect(safeWindowOrigin()).toBe('no-origin');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns the browser origin on web', () => {
|
|
48
|
+
globalRef.window = { location: { origin: 'https://app.mention.earth' } };
|
|
49
|
+
expect(safeWindowOrigin()).toBe('https://app.mention.earth');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('buildSilentGuardKey', () => {
|
|
54
|
+
it('does not throw and composes "no-origin|" on React Native', () => {
|
|
55
|
+
globalRef.window = {};
|
|
56
|
+
const getBaseURL = () => 'https://api.mention.earth';
|
|
57
|
+
expect(() => buildSilentGuardKey(getBaseURL)).not.toThrow();
|
|
58
|
+
expect(buildSilentGuardKey(getBaseURL)).toBe('no-origin|https://api.mention.earth');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('composes "origin|baseURL" on web', () => {
|
|
62
|
+
globalRef.window = { location: { origin: 'https://app.mention.earth' } };
|
|
63
|
+
expect(buildSilentGuardKey(() => 'https://api.mention.earth')).toBe(
|
|
64
|
+
'https://app.mention.earth|https://api.mention.earth',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('degrades baseURL to empty when getBaseURL is absent', () => {
|
|
69
|
+
globalRef.window = {};
|
|
70
|
+
expect(buildSilentGuardKey()).toBe('no-origin|');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('degrades baseURL to empty when getBaseURL throws', () => {
|
|
74
|
+
globalRef.window = {};
|
|
75
|
+
const throwing = (): string => {
|
|
76
|
+
throw new Error('client not initialised');
|
|
77
|
+
};
|
|
78
|
+
expect(() => buildSilentGuardKey(throwing)).not.toThrow();
|
|
79
|
+
expect(buildSilentGuardKey(throwing)).toBe('no-origin|');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|