@oxyhq/services 9.0.0 → 10.1.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.
Files changed (77) hide show
  1. package/lib/commonjs/ui/components/OxyProvider.js +2 -2
  2. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  3. package/lib/commonjs/ui/components/SignInModal.js +26 -12
  4. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  5. package/lib/commonjs/ui/context/OxyContext.js +35 -19
  6. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  7. package/lib/commonjs/ui/hooks/useWebSSO.js +7 -8
  8. package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
  9. package/lib/commonjs/ui/screens/OxyAuthScreen.js +65 -23
  10. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  11. package/lib/commonjs/utils/deviceFlowSignIn.js +55 -0
  12. package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -0
  13. package/lib/commonjs/utils/silentGuardKey.js +54 -0
  14. package/lib/commonjs/utils/silentGuardKey.js.map +1 -0
  15. package/lib/module/ui/components/OxyProvider.js +2 -2
  16. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  17. package/lib/module/ui/components/SignInModal.js +26 -12
  18. package/lib/module/ui/components/SignInModal.js.map +1 -1
  19. package/lib/module/ui/context/OxyContext.js +35 -19
  20. package/lib/module/ui/context/OxyContext.js.map +1 -1
  21. package/lib/module/ui/hooks/useWebSSO.js +7 -8
  22. package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
  23. package/lib/module/ui/screens/OxyAuthScreen.js +65 -23
  24. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  25. package/lib/module/utils/deviceFlowSignIn.js +51 -0
  26. package/lib/module/utils/deviceFlowSignIn.js.map +1 -0
  27. package/lib/module/utils/silentGuardKey.js +49 -0
  28. package/lib/module/utils/silentGuardKey.js.map +1 -0
  29. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +13 -9
  31. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
  34. package/lib/typescript/commonjs/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/ui/types/navigation.d.ts +8 -6
  38. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +61 -0
  40. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -0
  41. package/lib/typescript/commonjs/utils/silentGuardKey.d.ts +31 -0
  42. package/lib/typescript/commonjs/utils/silentGuardKey.d.ts.map +1 -0
  43. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  44. package/lib/typescript/module/ui/context/OxyContext.d.ts +13 -9
  45. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  46. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  47. package/lib/typescript/module/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
  48. package/lib/typescript/module/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
  49. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
  50. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  51. package/lib/typescript/module/ui/types/navigation.d.ts +8 -6
  52. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  53. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +61 -0
  54. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -0
  55. package/lib/typescript/module/utils/silentGuardKey.d.ts +31 -0
  56. package/lib/typescript/module/utils/silentGuardKey.d.ts.map +1 -0
  57. package/package.json +2 -2
  58. package/src/ui/components/OxyProvider.tsx +2 -2
  59. package/src/ui/components/SignInModal.tsx +26 -12
  60. package/src/ui/context/OxyContext.tsx +50 -33
  61. package/src/ui/hooks/useWebSSO.ts +7 -8
  62. package/src/ui/screens/OxyAuthScreen.tsx +65 -22
  63. package/src/ui/types/navigation.ts +8 -6
  64. package/src/utils/__tests__/deviceFlowSignIn.test.ts +104 -0
  65. package/src/utils/__tests__/silentGuardKey.test.ts +82 -0
  66. package/src/utils/deviceFlowSignIn.ts +76 -0
  67. package/src/utils/silentGuardKey.ts +46 -0
  68. package/lib/commonjs/ui/utils/appName.js +0 -62
  69. package/lib/commonjs/ui/utils/appName.js.map +0 -1
  70. package/lib/module/ui/utils/appName.js +0 -59
  71. package/lib/module/ui/utils/appName.js.map +0 -1
  72. package/lib/typescript/commonjs/ui/utils/appName.d.ts +0 -22
  73. package/lib/typescript/commonjs/ui/utils/appName.d.ts.map +0 -1
  74. package/lib/typescript/module/ui/utils/appName.d.ts +0 -22
  75. package/lib/typescript/module/ui/utils/appName.d.ts.map +0 -1
  76. package/src/ui/utils/__tests__/appName.test.ts +0 -52
  77. package/src/ui/utils/appName.ts +0 -62
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @jest-environment node
3
+ *
4
+ * Regression coverage for the NATIVE device-flow sign-in bug:
5
+ *
6
+ * On native, tapping "Sign In with Oxy" opens `OxyAuthScreen`, which creates
7
+ * a device-flow AuthSession and opens auth.oxy.so/authorize. The user signs
8
+ * in successfully, the API authorizes the session and notifies the client via
9
+ * the auth-session socket — but the screen then called `switchSession`
10
+ * DIRECTLY without first claiming the bearer with the secret `sessionToken`.
11
+ * `switchSession` -> `getTokenBySession` (`GET /session/token/:id`) requires a
12
+ * bearer the client did not yet hold, so it 401'd: the session was authorized
13
+ * server-side but the app never became authenticated ("nothing happens").
14
+ *
15
+ * The web `SignInModal` already claimed first; the native screen did not.
16
+ * `completeDeviceFlowSignIn` consolidates the claim->switch sequence so both
17
+ * paths are identical. These tests pin the ORDER (claim before switch) and the
18
+ * fail-fast behaviour.
19
+ */
20
+
21
+ import type { User } from '@oxyhq/core';
22
+ import {
23
+ completeDeviceFlowSignIn,
24
+ type DeviceFlowClient,
25
+ } from '../deviceFlowSignIn';
26
+
27
+ const SESSION_ID = 'session-id-123';
28
+ const SESSION_TOKEN = 'a'.repeat(32);
29
+ const USER = { id: 'user-1', username: 'nate', privacySettings: {} } as User;
30
+
31
+ describe('completeDeviceFlowSignIn', () => {
32
+ it('claims the sessionToken BEFORE switching the session', async () => {
33
+ const order: string[] = [];
34
+
35
+ const oxyServices: DeviceFlowClient = {
36
+ claimSessionByToken: jest.fn(async (token: string) => {
37
+ expect(token).toBe(SESSION_TOKEN);
38
+ order.push('claim');
39
+ }),
40
+ };
41
+ const switchSession = jest.fn(async (sessionId: string): Promise<User> => {
42
+ expect(sessionId).toBe(SESSION_ID);
43
+ order.push('switch');
44
+ return USER;
45
+ });
46
+
47
+ const user = await completeDeviceFlowSignIn({
48
+ oxyServices,
49
+ sessionId: SESSION_ID,
50
+ sessionToken: SESSION_TOKEN,
51
+ switchSession,
52
+ });
53
+
54
+ expect(order).toEqual(['claim', 'switch']);
55
+ expect(oxyServices.claimSessionByToken).toHaveBeenCalledWith(SESSION_TOKEN);
56
+ expect(switchSession).toHaveBeenCalledWith(SESSION_ID);
57
+ expect(user).toBe(USER);
58
+ });
59
+
60
+ it('does NOT switch the session when the claim fails (the native regression)', async () => {
61
+ const claimError = new Error('claim failed (401)');
62
+ const oxyServices: DeviceFlowClient = {
63
+ claimSessionByToken: jest.fn(async () => {
64
+ throw claimError;
65
+ }),
66
+ };
67
+ const switchSession = jest.fn(async (): Promise<User> => USER);
68
+
69
+ await expect(
70
+ completeDeviceFlowSignIn({
71
+ oxyServices,
72
+ sessionId: SESSION_ID,
73
+ sessionToken: SESSION_TOKEN,
74
+ switchSession,
75
+ }),
76
+ ).rejects.toThrow('claim failed (401)');
77
+
78
+ // The bearer was never planted, so we must not attempt the bearer-protected
79
+ // switch — surfacing the failure to the caller instead.
80
+ expect(switchSession).not.toHaveBeenCalled();
81
+ });
82
+
83
+ it('propagates a switchSession failure after a successful claim', async () => {
84
+ const switchError = new Error('session invalid');
85
+ const oxyServices: DeviceFlowClient = {
86
+ claimSessionByToken: jest.fn(async () => undefined),
87
+ };
88
+ const switchSession = jest.fn(async (): Promise<User> => {
89
+ throw switchError;
90
+ });
91
+
92
+ await expect(
93
+ completeDeviceFlowSignIn({
94
+ oxyServices,
95
+ sessionId: SESSION_ID,
96
+ sessionToken: SESSION_TOKEN,
97
+ switchSession,
98
+ }),
99
+ ).rejects.toThrow('session invalid');
100
+
101
+ expect(oxyServices.claimSessionByToken).toHaveBeenCalledTimes(1);
102
+ expect(switchSession).toHaveBeenCalledTimes(1);
103
+ });
104
+ });
@@ -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
+ });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared, pure orchestration for completing the cross-app device-flow sign-in
3
+ * (the QR-code / "Open Oxy Auth" path used on native and web).
4
+ *
5
+ * THE BUG THIS FIXES (native): once another authenticated device approves the
6
+ * pending AuthSession, the originating client is notified (socket / poll /
7
+ * deep-link) with the authorized `sessionId`. Before any session-management
8
+ * code can use it, the client MUST exchange the secret 128-bit `sessionToken`
9
+ * (held only by this client, generated for THIS flow) for the first access
10
+ * token via `claimSessionByToken` — the device-flow equivalent of OAuth's
11
+ * code-for-token exchange (RFC 8628 §3.4).
12
+ *
13
+ * Skipping the claim leaves the SDK with NO bearer token, so the subsequent
14
+ * `switchSession` -> `getTokenBySession` (`GET /session/token/:id`) call 401s
15
+ * against the C1-hardened API: the session is authorized server-side but the
16
+ * app never becomes authenticated and the UI sits "Waiting for
17
+ * authorization..." forever. The web `SignInModal` already claimed first; the
18
+ * native `OxyAuthScreen` did not. Consolidating the claim→switch sequence here
19
+ * keeps both paths identical and unit-testable, and prevents future drift.
20
+ */
21
+
22
+ import type { User } from '@oxyhq/core';
23
+
24
+ /**
25
+ * The minimal `OxyServices` surface this orchestration needs. Kept as a
26
+ * structural type (rather than importing the full client) so the helper is
27
+ * trivially unit-testable with a stub and never pulls the RN/Expo runtime into
28
+ * a test bundle.
29
+ */
30
+ export interface DeviceFlowClient {
31
+ /**
32
+ * Exchange the device-flow `sessionToken` for the first access + refresh
33
+ * token, planting them on the client. Single-use; replay is rejected by the
34
+ * API. No bearer required — the high-entropy `sessionToken` IS the credential.
35
+ */
36
+ claimSessionByToken: (sessionToken: string) => Promise<unknown>;
37
+ }
38
+
39
+ export interface CompleteDeviceFlowSignInOptions {
40
+ /** The OxyServices client (or any object exposing `claimSessionByToken`). */
41
+ oxyServices: DeviceFlowClient;
42
+ /** The authorized device session id, delivered by the socket / poll / link. */
43
+ sessionId: string;
44
+ /**
45
+ * The secret `sessionToken` generated for THIS flow and registered via
46
+ * `POST /auth/session/create`. Required to claim the first access token.
47
+ */
48
+ sessionToken: string;
49
+ /**
50
+ * The session-management `switchSession` from `useOxy()`. Hydrates the
51
+ * activated session (validates, fetches the user, persists, updates state).
52
+ * Runs AFTER the bearer is planted so its bearer-protected calls succeed.
53
+ */
54
+ switchSession: (sessionId: string) => Promise<User>;
55
+ }
56
+
57
+ /**
58
+ * Complete a device-flow sign-in: claim the first access token with the secret
59
+ * `sessionToken` (planting the bearer), then hydrate the session via
60
+ * `switchSession`. Returns the authenticated user.
61
+ *
62
+ * Throws if either the claim or the switch fails; callers surface a retry UI.
63
+ */
64
+ export async function completeDeviceFlowSignIn({
65
+ oxyServices,
66
+ sessionId,
67
+ sessionToken,
68
+ switchSession,
69
+ }: CompleteDeviceFlowSignInOptions): Promise<User> {
70
+ // 1) Plant the bearer + refresh tokens. Without this the bearer-protected
71
+ // `getTokenBySession` inside `switchSession` 401s (the native regression).
72
+ await oxyServices.claimSessionByToken(sessionToken);
73
+
74
+ // 2) Bearer is now planted — hydrate the session through the normal path.
75
+ return switchSession(sessionId);
76
+ }
@@ -0,0 +1,46 @@
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
+ /**
19
+ * Read `window.location.origin` safely on every platform.
20
+ *
21
+ * Returns the browser origin on web, and the sentinel `'no-origin'` anywhere
22
+ * `window.location` is absent (React Native, SSR/Node). Never throws.
23
+ */
24
+ export function safeWindowOrigin(): string {
25
+ if (typeof window !== 'undefined' && typeof window.location !== 'undefined') {
26
+ return window.location.origin;
27
+ }
28
+ return 'no-origin';
29
+ }
30
+
31
+ /**
32
+ * Build the stable `origin|baseURL` signature for the silent-SSO run-once
33
+ * guard. Two providers pointed at the same API from the same origin share one
34
+ * attempt. `getBaseURL` is invoked defensively (it may be absent or throw on a
35
+ * partially-initialised client); any failure degrades to an empty baseURL.
36
+ */
37
+ export function buildSilentGuardKey(getBaseURL?: () => string | undefined): string {
38
+ const origin = safeWindowOrigin();
39
+ let baseURL = '';
40
+ try {
41
+ baseURL = getBaseURL?.() ?? '';
42
+ } catch {
43
+ baseURL = '';
44
+ }
45
+ return `${origin}|${baseURL}`;
46
+ }
@@ -1,62 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.resolveAppDisplayName = resolveAppDisplayName;
7
- var _reactNative = require("react-native");
8
- /**
9
- * The `storageKeyPrefix` default applied by `OxyContextProvider`. When the
10
- * consumer never overrides it, the prefix carries no app-identity signal and
11
- * must NOT be used to derive a display name (it would surface "Oxy_session").
12
- */
13
- const DEFAULT_STORAGE_KEY_PREFIX = 'oxy_session';
14
-
15
- /**
16
- * Capitalize the first character of a non-empty string. Used to turn a lower
17
- * case `storageKeyPrefix` (e.g. `"mention"`) into a presentable label
18
- * (`"Mention"`). Pure; leaves the remainder untouched so multi-word or already
19
- * capitalized values are preserved.
20
- */
21
- function capitalizeFirst(value) {
22
- return value.charAt(0).toUpperCase() + value.slice(1);
23
- }
24
-
25
- /**
26
- * Resolve a human-readable application display name for the consent / sign-in
27
- * UI shown by the central Oxy auth experience (e.g. "Mention wants to access
28
- * your Oxy account"). This is sent as the `appId` field on
29
- * `POST /auth/session/create` and rendered verbatim by the auth consent page.
30
- *
31
- * Resolution order (first non-empty wins):
32
- * 1. An explicit `appName` declared by the consumer on `OxyProvider`.
33
- * 2. The capitalized `storageKeyPrefix` — but only when the consumer actually
34
- * overrode the default. Apps already pass a brand-shaped prefix
35
- * (`"mention"`, `"homiio"`, …) so this gives most apps a correct name with
36
- * zero extra config.
37
- * 3. On web only, a meaningful `document.title` (trimmed). This rescues
38
- * zero-config web apps that set a page title but no prefix.
39
- * 4. `Platform.OS` as the terminal fallback. On web this yields the historical
40
- * `"web"` value — now reached ONLY when an app supplies neither an explicit
41
- * name, a custom prefix, nor a document title.
42
- *
43
- * The result is never empty.
44
- */
45
- function resolveAppDisplayName(appName, storageKeyPrefix) {
46
- const explicit = appName?.trim();
47
- if (explicit) {
48
- return explicit;
49
- }
50
- const prefix = storageKeyPrefix?.trim();
51
- if (prefix && prefix !== DEFAULT_STORAGE_KEY_PREFIX) {
52
- return capitalizeFirst(prefix);
53
- }
54
- if (_reactNative.Platform.OS === 'web' && typeof document !== 'undefined') {
55
- const title = document.title?.trim();
56
- if (title) {
57
- return title;
58
- }
59
- }
60
- return _reactNative.Platform.OS;
61
- }
62
- //# sourceMappingURL=appName.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_reactNative","require","DEFAULT_STORAGE_KEY_PREFIX","capitalizeFirst","value","charAt","toUpperCase","slice","resolveAppDisplayName","appName","storageKeyPrefix","explicit","trim","prefix","Platform","OS","document","title"],"sourceRoot":"../../../../src","sources":["ui/utils/appName.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA,MAAMC,0BAA0B,GAAG,aAAa;;AAEhD;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,eAAeA,CAACC,KAAa,EAAU;EAC9C,OAAOA,KAAK,CAACC,MAAM,CAAC,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC,GAAGF,KAAK,CAACG,KAAK,CAAC,CAAC,CAAC;AACvD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,qBAAqBA,CACnCC,OAA2B,EAC3BC,gBAAoC,EAC5B;EACR,MAAMC,QAAQ,GAAGF,OAAO,EAAEG,IAAI,CAAC,CAAC;EAChC,IAAID,QAAQ,EAAE;IACZ,OAAOA,QAAQ;EACjB;EAEA,MAAME,MAAM,GAAGH,gBAAgB,EAAEE,IAAI,CAAC,CAAC;EACvC,IAAIC,MAAM,IAAIA,MAAM,KAAKX,0BAA0B,EAAE;IACnD,OAAOC,eAAe,CAACU,MAAM,CAAC;EAChC;EAEA,IAAIC,qBAAQ,CAACC,EAAE,KAAK,KAAK,IAAI,OAAOC,QAAQ,KAAK,WAAW,EAAE;IAC5D,MAAMC,KAAK,GAAGD,QAAQ,CAACC,KAAK,EAAEL,IAAI,CAAC,CAAC;IACpC,IAAIK,KAAK,EAAE;MACT,OAAOA,KAAK;IACd;EACF;EAEA,OAAOH,qBAAQ,CAACC,EAAE;AACpB","ignoreList":[]}
@@ -1,59 +0,0 @@
1
- "use strict";
2
-
3
- import { Platform } from 'react-native';
4
-
5
- /**
6
- * The `storageKeyPrefix` default applied by `OxyContextProvider`. When the
7
- * consumer never overrides it, the prefix carries no app-identity signal and
8
- * must NOT be used to derive a display name (it would surface "Oxy_session").
9
- */
10
- const DEFAULT_STORAGE_KEY_PREFIX = 'oxy_session';
11
-
12
- /**
13
- * Capitalize the first character of a non-empty string. Used to turn a lower
14
- * case `storageKeyPrefix` (e.g. `"mention"`) into a presentable label
15
- * (`"Mention"`). Pure; leaves the remainder untouched so multi-word or already
16
- * capitalized values are preserved.
17
- */
18
- function capitalizeFirst(value) {
19
- return value.charAt(0).toUpperCase() + value.slice(1);
20
- }
21
-
22
- /**
23
- * Resolve a human-readable application display name for the consent / sign-in
24
- * UI shown by the central Oxy auth experience (e.g. "Mention wants to access
25
- * your Oxy account"). This is sent as the `appId` field on
26
- * `POST /auth/session/create` and rendered verbatim by the auth consent page.
27
- *
28
- * Resolution order (first non-empty wins):
29
- * 1. An explicit `appName` declared by the consumer on `OxyProvider`.
30
- * 2. The capitalized `storageKeyPrefix` — but only when the consumer actually
31
- * overrode the default. Apps already pass a brand-shaped prefix
32
- * (`"mention"`, `"homiio"`, …) so this gives most apps a correct name with
33
- * zero extra config.
34
- * 3. On web only, a meaningful `document.title` (trimmed). This rescues
35
- * zero-config web apps that set a page title but no prefix.
36
- * 4. `Platform.OS` as the terminal fallback. On web this yields the historical
37
- * `"web"` value — now reached ONLY when an app supplies neither an explicit
38
- * name, a custom prefix, nor a document title.
39
- *
40
- * The result is never empty.
41
- */
42
- export function resolveAppDisplayName(appName, storageKeyPrefix) {
43
- const explicit = appName?.trim();
44
- if (explicit) {
45
- return explicit;
46
- }
47
- const prefix = storageKeyPrefix?.trim();
48
- if (prefix && prefix !== DEFAULT_STORAGE_KEY_PREFIX) {
49
- return capitalizeFirst(prefix);
50
- }
51
- if (Platform.OS === 'web' && typeof document !== 'undefined') {
52
- const title = document.title?.trim();
53
- if (title) {
54
- return title;
55
- }
56
- }
57
- return Platform.OS;
58
- }
59
- //# sourceMappingURL=appName.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["Platform","DEFAULT_STORAGE_KEY_PREFIX","capitalizeFirst","value","charAt","toUpperCase","slice","resolveAppDisplayName","appName","storageKeyPrefix","explicit","trim","prefix","OS","document","title"],"sourceRoot":"../../../../src","sources":["ui/utils/appName.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;;AAEvC;AACA;AACA;AACA;AACA;AACA,MAAMC,0BAA0B,GAAG,aAAa;;AAEhD;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,eAAeA,CAACC,KAAa,EAAU;EAC9C,OAAOA,KAAK,CAACC,MAAM,CAAC,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC,GAAGF,KAAK,CAACG,KAAK,CAAC,CAAC,CAAC;AACvD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,qBAAqBA,CACnCC,OAA2B,EAC3BC,gBAAoC,EAC5B;EACR,MAAMC,QAAQ,GAAGF,OAAO,EAAEG,IAAI,CAAC,CAAC;EAChC,IAAID,QAAQ,EAAE;IACZ,OAAOA,QAAQ;EACjB;EAEA,MAAME,MAAM,GAAGH,gBAAgB,EAAEE,IAAI,CAAC,CAAC;EACvC,IAAIC,MAAM,IAAIA,MAAM,KAAKX,0BAA0B,EAAE;IACnD,OAAOC,eAAe,CAACU,MAAM,CAAC;EAChC;EAEA,IAAIZ,QAAQ,CAACa,EAAE,KAAK,KAAK,IAAI,OAAOC,QAAQ,KAAK,WAAW,EAAE;IAC5D,MAAMC,KAAK,GAAGD,QAAQ,CAACC,KAAK,EAAEJ,IAAI,CAAC,CAAC;IACpC,IAAII,KAAK,EAAE;MACT,OAAOA,KAAK;IACd;EACF;EAEA,OAAOf,QAAQ,CAACa,EAAE;AACpB","ignoreList":[]}
@@ -1,22 +0,0 @@
1
- /**
2
- * Resolve a human-readable application display name for the consent / sign-in
3
- * UI shown by the central Oxy auth experience (e.g. "Mention wants to access
4
- * your Oxy account"). This is sent as the `appId` field on
5
- * `POST /auth/session/create` and rendered verbatim by the auth consent page.
6
- *
7
- * Resolution order (first non-empty wins):
8
- * 1. An explicit `appName` declared by the consumer on `OxyProvider`.
9
- * 2. The capitalized `storageKeyPrefix` — but only when the consumer actually
10
- * overrode the default. Apps already pass a brand-shaped prefix
11
- * (`"mention"`, `"homiio"`, …) so this gives most apps a correct name with
12
- * zero extra config.
13
- * 3. On web only, a meaningful `document.title` (trimmed). This rescues
14
- * zero-config web apps that set a page title but no prefix.
15
- * 4. `Platform.OS` as the terminal fallback. On web this yields the historical
16
- * `"web"` value — now reached ONLY when an app supplies neither an explicit
17
- * name, a custom prefix, nor a document title.
18
- *
19
- * The result is never empty.
20
- */
21
- export declare function resolveAppDisplayName(appName: string | undefined, storageKeyPrefix: string | undefined): string;
22
- //# sourceMappingURL=appName.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appName.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/appName.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,MAAM,CAmBR"}
@@ -1,22 +0,0 @@
1
- /**
2
- * Resolve a human-readable application display name for the consent / sign-in
3
- * UI shown by the central Oxy auth experience (e.g. "Mention wants to access
4
- * your Oxy account"). This is sent as the `appId` field on
5
- * `POST /auth/session/create` and rendered verbatim by the auth consent page.
6
- *
7
- * Resolution order (first non-empty wins):
8
- * 1. An explicit `appName` declared by the consumer on `OxyProvider`.
9
- * 2. The capitalized `storageKeyPrefix` — but only when the consumer actually
10
- * overrode the default. Apps already pass a brand-shaped prefix
11
- * (`"mention"`, `"homiio"`, …) so this gives most apps a correct name with
12
- * zero extra config.
13
- * 3. On web only, a meaningful `document.title` (trimmed). This rescues
14
- * zero-config web apps that set a page title but no prefix.
15
- * 4. `Platform.OS` as the terminal fallback. On web this yields the historical
16
- * `"web"` value — now reached ONLY when an app supplies neither an explicit
17
- * name, a custom prefix, nor a document title.
18
- *
19
- * The result is never empty.
20
- */
21
- export declare function resolveAppDisplayName(appName: string | undefined, storageKeyPrefix: string | undefined): string;
22
- //# sourceMappingURL=appName.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appName.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/appName.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,MAAM,CAmBR"}
@@ -1,52 +0,0 @@
1
- import { resolveAppDisplayName } from '../appName';
2
-
3
- // The shared react-native mock pins `Platform.OS` to 'web', which is exactly
4
- // the platform on which the historical "web wants to access your Oxy account"
5
- // regression occurred. These tests assert the resolution order that prevents it.
6
-
7
- describe('resolveAppDisplayName', () => {
8
- const originalTitle = typeof document !== 'undefined' ? document.title : '';
9
-
10
- afterEach(() => {
11
- if (typeof document !== 'undefined') {
12
- document.title = originalTitle;
13
- }
14
- });
15
-
16
- it('prefers an explicit appName, trimmed', () => {
17
- expect(resolveAppDisplayName(' Mention ', 'oxy_session')).toBe('Mention');
18
- });
19
-
20
- it('explicit appName wins over a custom storageKeyPrefix', () => {
21
- expect(resolveAppDisplayName('Mention', 'homiio')).toBe('Mention');
22
- });
23
-
24
- it('capitalizes a custom storageKeyPrefix when no appName is given', () => {
25
- expect(resolveAppDisplayName(undefined, 'mention')).toBe('Mention');
26
- });
27
-
28
- it('ignores the default storageKeyPrefix (never surfaces "Oxy_session")', () => {
29
- if (typeof document !== 'undefined') {
30
- document.title = '';
31
- }
32
- expect(resolveAppDisplayName(undefined, 'oxy_session')).toBe('web');
33
- });
34
-
35
- it('falls back to document.title on web when no name or custom prefix is set', () => {
36
- if (typeof document !== 'undefined') {
37
- document.title = 'Homiio';
38
- }
39
- expect(resolveAppDisplayName(undefined, 'oxy_session')).toBe('Homiio');
40
- });
41
-
42
- it('falls back to the platform only when nothing else is available', () => {
43
- if (typeof document !== 'undefined') {
44
- document.title = '';
45
- }
46
- expect(resolveAppDisplayName(undefined, undefined)).toBe('web');
47
- });
48
-
49
- it('treats a whitespace-only appName as absent', () => {
50
- expect(resolveAppDisplayName(' ', 'mention')).toBe('Mention');
51
- });
52
- });
@@ -1,62 +0,0 @@
1
- import { Platform } from 'react-native';
2
-
3
- /**
4
- * The `storageKeyPrefix` default applied by `OxyContextProvider`. When the
5
- * consumer never overrides it, the prefix carries no app-identity signal and
6
- * must NOT be used to derive a display name (it would surface "Oxy_session").
7
- */
8
- const DEFAULT_STORAGE_KEY_PREFIX = 'oxy_session';
9
-
10
- /**
11
- * Capitalize the first character of a non-empty string. Used to turn a lower
12
- * case `storageKeyPrefix` (e.g. `"mention"`) into a presentable label
13
- * (`"Mention"`). Pure; leaves the remainder untouched so multi-word or already
14
- * capitalized values are preserved.
15
- */
16
- function capitalizeFirst(value: string): string {
17
- return value.charAt(0).toUpperCase() + value.slice(1);
18
- }
19
-
20
- /**
21
- * Resolve a human-readable application display name for the consent / sign-in
22
- * UI shown by the central Oxy auth experience (e.g. "Mention wants to access
23
- * your Oxy account"). This is sent as the `appId` field on
24
- * `POST /auth/session/create` and rendered verbatim by the auth consent page.
25
- *
26
- * Resolution order (first non-empty wins):
27
- * 1. An explicit `appName` declared by the consumer on `OxyProvider`.
28
- * 2. The capitalized `storageKeyPrefix` — but only when the consumer actually
29
- * overrode the default. Apps already pass a brand-shaped prefix
30
- * (`"mention"`, `"homiio"`, …) so this gives most apps a correct name with
31
- * zero extra config.
32
- * 3. On web only, a meaningful `document.title` (trimmed). This rescues
33
- * zero-config web apps that set a page title but no prefix.
34
- * 4. `Platform.OS` as the terminal fallback. On web this yields the historical
35
- * `"web"` value — now reached ONLY when an app supplies neither an explicit
36
- * name, a custom prefix, nor a document title.
37
- *
38
- * The result is never empty.
39
- */
40
- export function resolveAppDisplayName(
41
- appName: string | undefined,
42
- storageKeyPrefix: string | undefined,
43
- ): string {
44
- const explicit = appName?.trim();
45
- if (explicit) {
46
- return explicit;
47
- }
48
-
49
- const prefix = storageKeyPrefix?.trim();
50
- if (prefix && prefix !== DEFAULT_STORAGE_KEY_PREFIX) {
51
- return capitalizeFirst(prefix);
52
- }
53
-
54
- if (Platform.OS === 'web' && typeof document !== 'undefined') {
55
- const title = document.title?.trim();
56
- if (title) {
57
- return title;
58
- }
59
- }
60
-
61
- return Platform.OS;
62
- }