@oxyhq/services 10.4.0 → 10.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/commonjs/ui/components/AnotherDeviceQR.js +121 -0
  2. package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +1 -0
  3. package/lib/commonjs/ui/components/SignInModal.js +145 -356
  4. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/useOxyAuthSession.js +458 -0
  6. package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +1 -0
  7. package/lib/commonjs/ui/screens/OxyAuthScreen.js +62 -460
  8. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  9. package/lib/module/ui/components/AnotherDeviceQR.js +116 -0
  10. package/lib/module/ui/components/AnotherDeviceQR.js.map +1 -0
  11. package/lib/module/ui/components/SignInModal.js +149 -358
  12. package/lib/module/ui/components/SignInModal.js.map +1 -1
  13. package/lib/module/ui/hooks/useOxyAuthSession.js +452 -0
  14. package/lib/module/ui/hooks/useOxyAuthSession.js.map +1 -0
  15. package/lib/module/ui/screens/OxyAuthScreen.js +62 -460
  16. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  17. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +24 -0
  18. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +1 -0
  19. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +10 -5
  20. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  21. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +83 -0
  22. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
  23. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +12 -8
  24. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  25. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +24 -0
  26. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +1 -0
  27. package/lib/typescript/module/ui/components/SignInModal.d.ts +10 -5
  28. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  29. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +83 -0
  30. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
  31. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +12 -8
  32. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/ui/components/AnotherDeviceQR.tsx +111 -0
  35. package/src/ui/components/SignInModal.tsx +142 -381
  36. package/src/ui/hooks/useOxyAuthSession.ts +556 -0
  37. package/src/ui/screens/OxyAuthScreen.tsx +61 -506
@@ -0,0 +1,24 @@
1
+ /**
2
+ * AnotherDeviceQR — the collapsed "Sign in on another device" disclosure shared
3
+ * by both sign-in containers (`SignInModal` on web, `OxyAuthScreen` on native).
4
+ *
5
+ * The QR code is always DEMOTED below the platform-primary "Continue with Oxy"
6
+ * action: you cannot scan your own screen, so the QR is only useful for handing
7
+ * sign-in to a SECOND device (scan with the Oxy Accounts app there). It lives
8
+ * inside a tap-to-expand disclosure so it never competes with the primary CTA.
9
+ *
10
+ * The QR plate stays white intentionally — high contrast is required for
11
+ * reliable scanning regardless of the app theme.
12
+ */
13
+ import type React from 'react';
14
+ export interface AnotherDeviceQRProps {
15
+ /** The QR payload (`oxyauth://<token>`). Empty string renders nothing. */
16
+ qrData: string;
17
+ }
18
+ /**
19
+ * Collapsed disclosure that reveals a high-contrast QR for signing in on a
20
+ * second device with the Oxy Accounts app.
21
+ */
22
+ declare const AnotherDeviceQR: React.FC<AnotherDeviceQRProps>;
23
+ export default AnotherDeviceQR;
24
+ //# sourceMappingURL=AnotherDeviceQR.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnotherDeviceQR.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/AnotherDeviceQR.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;GAGG;AACH,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA0CnD,CAAC;AAqCF,eAAe,eAAe,CAAC"}
@@ -1,11 +1,16 @@
1
1
  /**
2
- * SignInModal - Full screen sign-in modal with QR code
2
+ * SignInModal - Web-first centered sign-in modal (Continue with Oxy)
3
3
  *
4
- * A semi-transparent full-screen modal that displays:
5
- * - QR code for scanning with Oxy Accounts app
6
- * - Button to open the Oxy Auth approval page
4
+ * A semi-transparent full-screen modal whose primary action is the one-tap
5
+ * "Continue with Oxy" approval flow. The QR code is demoted to a collapsed
6
+ * "Sign in on another device" disclosure (you can't scan your own screen).
7
7
  *
8
- * Animates with fade-in effect.
8
+ * ALL of the auth-session machinery (session-token creation, QR data, socket +
9
+ * polling, waiting/error/retry state, the open-auth handler, deep-link return,
10
+ * and cleanup) lives in the shared `useOxyAuthSession` hook, which the native
11
+ * `OxyAuthScreen` also consumes — neither container re-implements the transport.
12
+ *
13
+ * Animates with a fade + scale effect.
9
14
  */
10
15
  import type React from 'react';
11
16
  export declare const showSignInModal: () => void;
@@ -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;AAyD/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,EAkYxB,CAAC;AA8FF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuB/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,EA8BxB,CAAC;AAgPF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * useOxyAuthSession — the single shared engine for the cross-app device-flow
3
+ * sign-in ("Sign in with Oxy") used by BOTH containers:
4
+ *
5
+ * - `SignInModal` (web centered modal)
6
+ * - `OxyAuthScreen` (native bottom sheet)
7
+ *
8
+ * Before this hook existed, those two files each re-implemented ~90% of the
9
+ * same auth-session machinery (session-token creation, QR data, socket.io
10
+ * `/auth-session` subscription, the HTTP polling fallback, waiting/error/retry
11
+ * state, the open-auth handler, and cleanup) — and they had drifted (notably a
12
+ * weaker `Math.random()` session token on native vs the crypto-secure one on
13
+ * web). This hook owns ALL of it ONCE, with a clean typed surface, so the
14
+ * containers only own their layout. The transport is functionally identical to
15
+ * the previous implementations — this is a structural de-duplication, not a
16
+ * behavior change.
17
+ *
18
+ * The native-only deep-link return path (`Linking` redirect handling) lives
19
+ * HERE, gated to native via `Platform.OS`, so the web container never carries
20
+ * native-only code while the two paths still cannot drift.
21
+ */
22
+ import type { OxyServices, User } from '@oxyhq/core';
23
+ /**
24
+ * Default destination for the "Create an Oxy account" / "Get Oxy Accounts"
25
+ * link, shared by both containers. Reused as-is from the original native
26
+ * `OxyAuthScreen` so no new route is invented.
27
+ */
28
+ export declare const OXY_ACCOUNTS_WEB_URL = "https://accounts.oxy.so";
29
+ /** Default central Oxy auth web origin used when no override can be resolved. */
30
+ export declare const OXY_AUTH_WEB_URL = "https://auth.oxy.so";
31
+ /** Auth session expiration (5 minutes). */
32
+ export declare const AUTH_SESSION_EXPIRY_MS: number;
33
+ /** Polling interval (fallback if the socket fails) in milliseconds. */
34
+ export declare const POLLING_INTERVAL_MS = 3000;
35
+ /** The active device-flow session this client created and is waiting on. */
36
+ export interface AuthSession {
37
+ /** The secret high-entropy token that IS the device-flow credential. */
38
+ sessionToken: string;
39
+ /** Epoch ms after which the server rejects this session. */
40
+ expiresAt: number;
41
+ }
42
+ export interface UseOxyAuthSessionOptions {
43
+ /**
44
+ * Called after a fully completed sign-in (bearer claimed, session hydrated).
45
+ * The web modal closes itself here; the native screen forwards the user to
46
+ * its `onAuthenticated` prop. The hook itself stays presentation-agnostic.
47
+ */
48
+ onSignedIn?: (user: User) => void;
49
+ }
50
+ export interface UseOxyAuthSessionResult {
51
+ /** The active device-flow session, or `null` before/while it is created. */
52
+ authSession: AuthSession | null;
53
+ /** The QR payload string (`oxyauth://<token>`), or `''` when no session. */
54
+ qrData: string;
55
+ /** `true` while the session is being created (initial spinner). */
56
+ isLoading: boolean;
57
+ /** A user-facing error message, or `null`. Drives the retry UI. */
58
+ error: string | null;
59
+ /** `true` once a session exists and we are awaiting authorization. */
60
+ isWaiting: boolean;
61
+ /**
62
+ * Open the central Oxy auth approval surface for THIS device-flow session.
63
+ * On web this opens a centered approval popup; on native it opens the system
64
+ * browser (carrying a `redirect_uri` so the deep-link return path can fire).
65
+ * This is the action behind the platform-primary "Continue with Oxy" button.
66
+ */
67
+ openAuthApproval: () => Promise<void>;
68
+ /** Tear down the current session and create a fresh one (the retry action). */
69
+ retry: () => void;
70
+ /** Disconnect the socket and stop polling. Idempotent. */
71
+ cleanup: () => void;
72
+ }
73
+ /**
74
+ * Owns the full device-flow auth-session lifecycle for one mounted sign-in
75
+ * surface. Both `SignInModal` and `OxyAuthScreen` consume this; neither
76
+ * re-implements the socket / polling / deep-link transport.
77
+ *
78
+ * Subscriptions (socket connect/disconnect, the poll interval, the native
79
+ * deep-link listener, and unmount cleanup) are the legitimate `useEffect` use
80
+ * WITH cleanup. No effect here computes derived UI state.
81
+ */
82
+ export declare function useOxyAuthSession(oxyServices: OxyServices, clientId: string | null, switchSession: ((sessionId: string) => Promise<User>) | undefined, options?: UseOxyAuthSessionOptions): UseOxyAuthSessionResult;
83
+ //# sourceMappingURL=useOxyAuthSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOxyAuthSession.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useOxyAuthSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAMrD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAE9D,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,wBAAwB,CAAC;AAEtD,2CAA2C;AAC3C,eAAO,MAAM,sBAAsB,QAAgB,CAAC;AAEpD,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAWD,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAoFD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,aAAa,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EACjE,OAAO,GAAE,wBAA6B,GACrC,uBAAuB,CA0WzB"}
@@ -1,14 +1,18 @@
1
1
  /**
2
- * OxyAuthScreen - Sign in with Oxy
2
+ * OxyAuthScreen - Sign in with Oxy (native bottom-sheet container)
3
3
  *
4
- * This screen is used by OTHER apps in the Oxy ecosystem to authenticate users.
5
- * It presents two options:
6
- * 1. Scan QR code with Oxy Accounts app
7
- * 2. Open the Oxy Auth web flow
4
+ * Used by OTHER apps in the Oxy ecosystem to authenticate users. The primary
5
+ * action is the one-tap "Continue with Oxy" approval flow (opens the Oxy Auth
6
+ * web flow with a deep-link `redirect_uri` so the app→app return path can
7
+ * complete the sign-in). The QR code is demoted to a collapsed "Sign in on
8
+ * another device" disclosure — you can't scan your own screen.
8
9
  *
9
- * Uses WebSocket for real-time authorization updates (with polling fallback).
10
- * The Oxy Accounts app is where users manage their cryptographic identity.
11
- * This screen should NOT be used within the Accounts app itself.
10
+ * ALL of the auth-session machinery (session-token creation, QR data, socket +
11
+ * polling, the native deep-link return path, waiting/error/retry state, the
12
+ * open-auth handler, and cleanup) lives in the shared `useOxyAuthSession` hook,
13
+ * which the web `SignInModal` also consumes — neither container re-implements
14
+ * the transport. The Oxy Accounts app is where users manage their cryptographic
15
+ * identity; this screen should NOT be used within the Accounts app itself.
12
16
  */
13
17
  import type React from 'react';
14
18
  import type { BaseScreenProps } from '../types/navigation';
@@ -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;AA2F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAgb5C,CAAC;AA6FF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAS3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAyF5C,CAAC;AA8EF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * AnotherDeviceQR — the collapsed "Sign in on another device" disclosure shared
3
+ * by both sign-in containers (`SignInModal` on web, `OxyAuthScreen` on native).
4
+ *
5
+ * The QR code is always DEMOTED below the platform-primary "Continue with Oxy"
6
+ * action: you cannot scan your own screen, so the QR is only useful for handing
7
+ * sign-in to a SECOND device (scan with the Oxy Accounts app there). It lives
8
+ * inside a tap-to-expand disclosure so it never competes with the primary CTA.
9
+ *
10
+ * The QR plate stays white intentionally — high contrast is required for
11
+ * reliable scanning regardless of the app theme.
12
+ */
13
+ import type React from 'react';
14
+ export interface AnotherDeviceQRProps {
15
+ /** The QR payload (`oxyauth://<token>`). Empty string renders nothing. */
16
+ qrData: string;
17
+ }
18
+ /**
19
+ * Collapsed disclosure that reveals a high-contrast QR for signing in on a
20
+ * second device with the Oxy Accounts app.
21
+ */
22
+ declare const AnotherDeviceQR: React.FC<AnotherDeviceQRProps>;
23
+ export default AnotherDeviceQR;
24
+ //# sourceMappingURL=AnotherDeviceQR.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnotherDeviceQR.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/AnotherDeviceQR.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;GAGG;AACH,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA0CnD,CAAC;AAqCF,eAAe,eAAe,CAAC"}
@@ -1,11 +1,16 @@
1
1
  /**
2
- * SignInModal - Full screen sign-in modal with QR code
2
+ * SignInModal - Web-first centered sign-in modal (Continue with Oxy)
3
3
  *
4
- * A semi-transparent full-screen modal that displays:
5
- * - QR code for scanning with Oxy Accounts app
6
- * - Button to open the Oxy Auth approval page
4
+ * A semi-transparent full-screen modal whose primary action is the one-tap
5
+ * "Continue with Oxy" approval flow. The QR code is demoted to a collapsed
6
+ * "Sign in on another device" disclosure (you can't scan your own screen).
7
7
  *
8
- * Animates with fade-in effect.
8
+ * ALL of the auth-session machinery (session-token creation, QR data, socket +
9
+ * polling, waiting/error/retry state, the open-auth handler, deep-link return,
10
+ * and cleanup) lives in the shared `useOxyAuthSession` hook, which the native
11
+ * `OxyAuthScreen` also consumes — neither container re-implements the transport.
12
+ *
13
+ * Animates with a fade + scale effect.
9
14
  */
10
15
  import type React from 'react';
11
16
  export declare const showSignInModal: () => void;
@@ -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;AAyD/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,EAkYxB,CAAC;AA8FF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuB/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,EA8BxB,CAAC;AAgPF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * useOxyAuthSession — the single shared engine for the cross-app device-flow
3
+ * sign-in ("Sign in with Oxy") used by BOTH containers:
4
+ *
5
+ * - `SignInModal` (web centered modal)
6
+ * - `OxyAuthScreen` (native bottom sheet)
7
+ *
8
+ * Before this hook existed, those two files each re-implemented ~90% of the
9
+ * same auth-session machinery (session-token creation, QR data, socket.io
10
+ * `/auth-session` subscription, the HTTP polling fallback, waiting/error/retry
11
+ * state, the open-auth handler, and cleanup) — and they had drifted (notably a
12
+ * weaker `Math.random()` session token on native vs the crypto-secure one on
13
+ * web). This hook owns ALL of it ONCE, with a clean typed surface, so the
14
+ * containers only own their layout. The transport is functionally identical to
15
+ * the previous implementations — this is a structural de-duplication, not a
16
+ * behavior change.
17
+ *
18
+ * The native-only deep-link return path (`Linking` redirect handling) lives
19
+ * HERE, gated to native via `Platform.OS`, so the web container never carries
20
+ * native-only code while the two paths still cannot drift.
21
+ */
22
+ import type { OxyServices, User } from '@oxyhq/core';
23
+ /**
24
+ * Default destination for the "Create an Oxy account" / "Get Oxy Accounts"
25
+ * link, shared by both containers. Reused as-is from the original native
26
+ * `OxyAuthScreen` so no new route is invented.
27
+ */
28
+ export declare const OXY_ACCOUNTS_WEB_URL = "https://accounts.oxy.so";
29
+ /** Default central Oxy auth web origin used when no override can be resolved. */
30
+ export declare const OXY_AUTH_WEB_URL = "https://auth.oxy.so";
31
+ /** Auth session expiration (5 minutes). */
32
+ export declare const AUTH_SESSION_EXPIRY_MS: number;
33
+ /** Polling interval (fallback if the socket fails) in milliseconds. */
34
+ export declare const POLLING_INTERVAL_MS = 3000;
35
+ /** The active device-flow session this client created and is waiting on. */
36
+ export interface AuthSession {
37
+ /** The secret high-entropy token that IS the device-flow credential. */
38
+ sessionToken: string;
39
+ /** Epoch ms after which the server rejects this session. */
40
+ expiresAt: number;
41
+ }
42
+ export interface UseOxyAuthSessionOptions {
43
+ /**
44
+ * Called after a fully completed sign-in (bearer claimed, session hydrated).
45
+ * The web modal closes itself here; the native screen forwards the user to
46
+ * its `onAuthenticated` prop. The hook itself stays presentation-agnostic.
47
+ */
48
+ onSignedIn?: (user: User) => void;
49
+ }
50
+ export interface UseOxyAuthSessionResult {
51
+ /** The active device-flow session, or `null` before/while it is created. */
52
+ authSession: AuthSession | null;
53
+ /** The QR payload string (`oxyauth://<token>`), or `''` when no session. */
54
+ qrData: string;
55
+ /** `true` while the session is being created (initial spinner). */
56
+ isLoading: boolean;
57
+ /** A user-facing error message, or `null`. Drives the retry UI. */
58
+ error: string | null;
59
+ /** `true` once a session exists and we are awaiting authorization. */
60
+ isWaiting: boolean;
61
+ /**
62
+ * Open the central Oxy auth approval surface for THIS device-flow session.
63
+ * On web this opens a centered approval popup; on native it opens the system
64
+ * browser (carrying a `redirect_uri` so the deep-link return path can fire).
65
+ * This is the action behind the platform-primary "Continue with Oxy" button.
66
+ */
67
+ openAuthApproval: () => Promise<void>;
68
+ /** Tear down the current session and create a fresh one (the retry action). */
69
+ retry: () => void;
70
+ /** Disconnect the socket and stop polling. Idempotent. */
71
+ cleanup: () => void;
72
+ }
73
+ /**
74
+ * Owns the full device-flow auth-session lifecycle for one mounted sign-in
75
+ * surface. Both `SignInModal` and `OxyAuthScreen` consume this; neither
76
+ * re-implements the socket / polling / deep-link transport.
77
+ *
78
+ * Subscriptions (socket connect/disconnect, the poll interval, the native
79
+ * deep-link listener, and unmount cleanup) are the legitimate `useEffect` use
80
+ * WITH cleanup. No effect here computes derived UI state.
81
+ */
82
+ export declare function useOxyAuthSession(oxyServices: OxyServices, clientId: string | null, switchSession: ((sessionId: string) => Promise<User>) | undefined, options?: UseOxyAuthSessionOptions): UseOxyAuthSessionResult;
83
+ //# sourceMappingURL=useOxyAuthSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOxyAuthSession.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useOxyAuthSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAMrD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAE9D,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,wBAAwB,CAAC;AAEtD,2CAA2C;AAC3C,eAAO,MAAM,sBAAsB,QAAgB,CAAC;AAEpD,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAWD,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAoFD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,aAAa,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EACjE,OAAO,GAAE,wBAA6B,GACrC,uBAAuB,CA0WzB"}
@@ -1,14 +1,18 @@
1
1
  /**
2
- * OxyAuthScreen - Sign in with Oxy
2
+ * OxyAuthScreen - Sign in with Oxy (native bottom-sheet container)
3
3
  *
4
- * This screen is used by OTHER apps in the Oxy ecosystem to authenticate users.
5
- * It presents two options:
6
- * 1. Scan QR code with Oxy Accounts app
7
- * 2. Open the Oxy Auth web flow
4
+ * Used by OTHER apps in the Oxy ecosystem to authenticate users. The primary
5
+ * action is the one-tap "Continue with Oxy" approval flow (opens the Oxy Auth
6
+ * web flow with a deep-link `redirect_uri` so the app→app return path can
7
+ * complete the sign-in). The QR code is demoted to a collapsed "Sign in on
8
+ * another device" disclosure — you can't scan your own screen.
8
9
  *
9
- * Uses WebSocket for real-time authorization updates (with polling fallback).
10
- * The Oxy Accounts app is where users manage their cryptographic identity.
11
- * This screen should NOT be used within the Accounts app itself.
10
+ * ALL of the auth-session machinery (session-token creation, QR data, socket +
11
+ * polling, the native deep-link return path, waiting/error/retry state, the
12
+ * open-auth handler, and cleanup) lives in the shared `useOxyAuthSession` hook,
13
+ * which the web `SignInModal` also consumes — neither container re-implements
14
+ * the transport. The Oxy Accounts app is where users manage their cryptographic
15
+ * identity; this screen should NOT be used within the Accounts app itself.
12
16
  */
13
17
  import type React from 'react';
14
18
  import type { BaseScreenProps } from '../types/navigation';
@@ -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;AA2F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAgb5C,CAAC;AA6FF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAS3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAyF5C,CAAC;AA8EF,eAAe,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "10.4.0",
3
+ "version": "10.5.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",
@@ -0,0 +1,111 @@
1
+ /**
2
+ * AnotherDeviceQR — the collapsed "Sign in on another device" disclosure shared
3
+ * by both sign-in containers (`SignInModal` on web, `OxyAuthScreen` on native).
4
+ *
5
+ * The QR code is always DEMOTED below the platform-primary "Continue with Oxy"
6
+ * action: you cannot scan your own screen, so the QR is only useful for handing
7
+ * sign-in to a SECOND device (scan with the Oxy Accounts app there). It lives
8
+ * inside a tap-to-expand disclosure so it never competes with the primary CTA.
9
+ *
10
+ * The QR plate stays white intentionally — high contrast is required for
11
+ * reliable scanning regardless of the app theme.
12
+ */
13
+
14
+ import type React from 'react';
15
+ import { useState, useCallback } from 'react';
16
+ import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
17
+ import QRCode from 'react-native-qrcode-svg';
18
+ import { useTheme } from '@oxyhq/bloom/theme';
19
+
20
+ export interface AnotherDeviceQRProps {
21
+ /** The QR payload (`oxyauth://<token>`). Empty string renders nothing. */
22
+ qrData: string;
23
+ }
24
+
25
+ /** Size, in px, of the rendered QR symbol. */
26
+ const QR_SIZE = 200;
27
+
28
+ /**
29
+ * Collapsed disclosure that reveals a high-contrast QR for signing in on a
30
+ * second device with the Oxy Accounts app.
31
+ */
32
+ const AnotherDeviceQR: React.FC<AnotherDeviceQRProps> = ({ qrData }) => {
33
+ const { colors } = useTheme();
34
+ const [expanded, setExpanded] = useState(false);
35
+
36
+ const toggle = useCallback(() => {
37
+ setExpanded((prev) => !prev);
38
+ }, []);
39
+
40
+ if (!qrData) {
41
+ return null;
42
+ }
43
+
44
+ return (
45
+ <View style={styles.container}>
46
+ <TouchableOpacity
47
+ style={styles.toggle}
48
+ onPress={toggle}
49
+ accessibilityRole="button"
50
+ accessibilityState={{ expanded }}
51
+ accessibilityHint="Reveals a QR code to sign in using the Oxy Accounts app on another device"
52
+ >
53
+ <Text style={styles.toggleText} className="text-muted-foreground">
54
+ Sign in on another device
55
+ </Text>
56
+ <Text style={styles.chevron} className="text-muted-foreground">
57
+ {expanded ? '✕' : '›'}
58
+ </Text>
59
+ </TouchableOpacity>
60
+
61
+ {expanded && (
62
+ <View style={styles.body}>
63
+ {/* The QR plate is intentionally white for scan reliability. */}
64
+ <View style={[styles.qrPlate, { backgroundColor: '#FFFFFF' }]}>
65
+ <QRCode value={qrData} size={QR_SIZE} backgroundColor="#FFFFFF" color="#000000" />
66
+ </View>
67
+ <Text style={[styles.hint, { color: colors.textSecondary }]}>
68
+ Scan with the Oxy Accounts app
69
+ </Text>
70
+ </View>
71
+ )}
72
+ </View>
73
+ );
74
+ };
75
+
76
+ const styles = StyleSheet.create({
77
+ container: {
78
+ width: '100%',
79
+ alignItems: 'center',
80
+ },
81
+ toggle: {
82
+ flexDirection: 'row',
83
+ alignItems: 'center',
84
+ justifyContent: 'center',
85
+ paddingVertical: 12,
86
+ paddingHorizontal: 8,
87
+ },
88
+ toggleText: {
89
+ fontSize: 14,
90
+ fontWeight: '600',
91
+ },
92
+ chevron: {
93
+ fontSize: 16,
94
+ marginLeft: 8,
95
+ },
96
+ body: {
97
+ alignItems: 'center',
98
+ marginTop: 8,
99
+ },
100
+ qrPlate: {
101
+ padding: 16,
102
+ borderRadius: 16,
103
+ },
104
+ hint: {
105
+ marginTop: 12,
106
+ fontSize: 12,
107
+ textAlign: 'center',
108
+ },
109
+ });
110
+
111
+ export default AnotherDeviceQR;