@oxyhq/services 10.4.0 → 11.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.
Files changed (39) 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/mutations/useAccountMutations.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +83 -0
  23. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
  24. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +12 -8
  25. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  26. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +24 -0
  27. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +1 -0
  28. package/lib/typescript/module/ui/components/SignInModal.d.ts +10 -5
  29. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  30. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  31. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +83 -0
  32. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
  33. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +12 -8
  34. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  35. package/package.json +21 -33
  36. package/src/ui/components/AnotherDeviceQR.tsx +111 -0
  37. package/src/ui/components/SignInModal.tsx +142 -381
  38. package/src/ui/hooks/useOxyAuthSession.ts +556 -0
  39. package/src/ui/screens/OxyAuthScreen.tsx +61 -506
@@ -4,98 +4,36 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _react = require("react");
8
7
  var _reactNative = require("react-native");
9
- var _socket = _interopRequireDefault(require("socket.io-client"));
10
8
  var _theme = require("@oxyhq/bloom/theme");
11
9
  var _button = require("@oxyhq/bloom/button");
12
10
  var _loading = require("@oxyhq/bloom/loading");
13
11
  var _OxyContext = require("../context/OxyContext.js");
14
- var _reactNativeQrcodeSvg = _interopRequireDefault(require("react-native-qrcode-svg"));
15
12
  var _OxyLogo = _interopRequireDefault(require("../components/OxyLogo.js"));
16
- var _core = require("@oxyhq/core");
17
- var _deviceFlowSignIn = require("../../utils/deviceFlowSignIn.js");
13
+ var _AnotherDeviceQR = _interopRequireDefault(require("../components/AnotherDeviceQR.js"));
14
+ var _useOxyAuthSession = require("../hooks/useOxyAuthSession.js");
18
15
  var _jsxRuntime = require("react/jsx-runtime");
19
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
20
17
  /**
21
- * OxyAuthScreen - Sign in with Oxy
22
- *
23
- * This screen is used by OTHER apps in the Oxy ecosystem to authenticate users.
24
- * It presents two options:
25
- * 1. Scan QR code with Oxy Accounts app
26
- * 2. Open the Oxy Auth web flow
27
- *
28
- * Uses WebSocket for real-time authorization updates (with polling fallback).
29
- * The Oxy Accounts app is where users manage their cryptographic identity.
30
- * This screen should NOT be used within the Accounts app itself.
18
+ * OxyAuthScreen - Sign in with Oxy (native bottom-sheet container)
19
+ *
20
+ * Used by OTHER apps in the Oxy ecosystem to authenticate users. The primary
21
+ * action is the one-tap "Continue with Oxy" approval flow (opens the Oxy Auth
22
+ * web flow with a deep-link `redirect_uri` so the app→app return path can
23
+ * complete the sign-in). The QR code is demoted to a collapsed "Sign in on
24
+ * another device" disclosure — you can't scan your own screen.
25
+ *
26
+ * ALL of the auth-session machinery (session-token creation, QR data, socket +
27
+ * polling, the native deep-link return path, waiting/error/retry state, the
28
+ * open-auth handler, and cleanup) lives in the shared `useOxyAuthSession` hook,
29
+ * which the web `SignInModal` also consumes — neither container re-implements
30
+ * the transport. The Oxy Accounts app is where users manage their cryptographic
31
+ * identity; this screen should NOT be used within the Accounts app itself.
31
32
  */
32
33
 
33
- const debug = (0, _core.createDebugLogger)('OxyAuthScreen');
34
- const OXY_ACCOUNTS_WEB_URL = 'https://accounts.oxy.so';
35
- const OXY_AUTH_WEB_URL = 'https://auth.oxy.so';
36
-
37
- // Auth session expiration (5 minutes)
38
- const AUTH_SESSION_EXPIRY_MS = 5 * 60 * 1000;
39
-
40
- // Polling interval (fallback if socket fails)
41
- const POLLING_INTERVAL_MS = 3000;
42
- const resolveAuthWebBaseUrl = (baseURL, authWebUrl) => {
43
- if (authWebUrl) {
44
- return authWebUrl;
45
- }
46
- try {
47
- const url = new URL(baseURL);
48
- if (url.port === '3001') {
49
- url.port = '3002';
50
- return url.origin;
51
- }
52
- if (url.hostname.startsWith('api.')) {
53
- url.hostname = `auth.${url.hostname.slice(4)}`;
54
- return url.origin;
55
- }
56
- } catch {
57
- // Ignore parsing errors, fall back to default.
58
- }
59
- return OXY_AUTH_WEB_URL;
60
- };
61
- const resolveAuthRedirectUri = async authRedirectUri => {
62
- if (authRedirectUri) {
63
- return authRedirectUri;
64
- }
65
- try {
66
- const initialUrl = await _reactNative.Linking.getInitialURL();
67
- if (!initialUrl) {
68
- return null;
69
- }
70
- const parsed = new URL(initialUrl);
71
- parsed.search = '';
72
- parsed.hash = '';
73
- return parsed.toString();
74
- } catch {
75
- return null;
76
- }
77
- };
78
- const getRedirectParams = url => {
79
- try {
80
- const parsed = new URL(url);
81
- const sessionId = parsed.searchParams.get('session_id') ?? undefined;
82
- const error = parsed.searchParams.get('error') ?? undefined;
83
- if (!sessionId && !error) {
84
- return null;
85
- }
86
- return {
87
- sessionId,
88
- error
89
- };
90
- } catch {
91
- return null;
92
- }
93
- };
94
34
  const OxyAuthScreen = ({
95
- navigate,
96
35
  goBack,
97
- onAuthenticated,
98
- theme
36
+ onAuthenticated
99
37
  }) => {
100
38
  const bloomTheme = (0, _theme.useTheme)();
101
39
  const {
@@ -103,307 +41,16 @@ const OxyAuthScreen = ({
103
41
  switchSession,
104
42
  clientId
105
43
  } = (0, _OxyContext.useOxy)();
106
- const [authSession, setAuthSession] = (0, _react.useState)(null);
107
- const [isLoading, setIsLoading] = (0, _react.useState)(true);
108
- const [error, setError] = (0, _react.useState)(null);
109
- const [isWaiting, setIsWaiting] = (0, _react.useState)(false);
110
- const [connectionType, setConnectionType] = (0, _react.useState)('socket');
111
- const socketRef = (0, _react.useRef)(null);
112
- const pollingIntervalRef = (0, _react.useRef)(null);
113
- const isProcessingRef = (0, _react.useRef)(false);
114
- const linkingHandledRef = (0, _react.useRef)(false);
115
-
116
- // Handle successful authorization.
117
- //
118
- // The auth-session socket / poll (or deep-link return) hands us the
119
- // authorized `sessionId`. Before any session-management code can touch it we
120
- // MUST first exchange the secret `sessionToken` (held only by this client,
121
- // generated for THIS flow) for the first access token via
122
- // `claimSessionByToken` — the device-flow equivalent of OAuth's
123
- // code-for-token exchange (RFC 8628 §3.4).
124
- //
125
- // Without that exchange the SDK has no bearer token — the session is
126
- // authorized server-side but the app never becomes authenticated and the
127
- // sheet sits "Waiting for authorization..." forever. Once
128
- // `claimSessionByToken` plants the tokens in the HttpService, the rest of the
129
- // session wiring flows through the normal `switchSession` path. This mirrors
130
- // `SignInModal`'s web flow exactly.
131
- const handleAuthSuccess = (0, _react.useCallback)(async (sessionId, sessionToken) => {
132
- if (isProcessingRef.current) return;
133
- isProcessingRef.current = true;
134
- try {
135
- // Claim the first access token with the secret sessionToken, then
136
- // hydrate the session. The claim step is what the native screen was
137
- // previously missing — see `completeDeviceFlowSignIn`. `switchSession`
138
- // is always provided by the context here; the helper requires it.
139
- if (!switchSession) {
140
- throw new Error('Session management unavailable');
141
- }
142
- const user = await (0, _deviceFlowSignIn.completeDeviceFlowSignIn)({
143
- oxyServices,
144
- sessionId,
145
- sessionToken,
146
- switchSession
147
- });
148
- if (onAuthenticated) {
149
- onAuthenticated(user);
150
- }
151
- } catch (err) {
152
- debug.error('Error completing auth:', err);
153
- setError('Authorization successful but failed to complete sign in. Please try again.');
154
- isProcessingRef.current = false;
155
- }
156
- }, [oxyServices, switchSession, onAuthenticated]);
157
-
158
- // Connect to socket for real-time updates
159
- const connectSocket = (0, _react.useCallback)(sessionToken => {
160
- const baseURL = oxyServices.getBaseURL();
161
-
162
- // Connect to the auth-session namespace (no authentication required)
163
- const socket = (0, _socket.default)(`${baseURL}/auth-session`, {
164
- transports: ['websocket', 'polling'],
165
- reconnection: true,
166
- reconnectionAttempts: 3,
167
- reconnectionDelay: 1000
168
- });
169
- socketRef.current = socket;
170
- socket.on('connect', () => {
171
- debug.log('Auth socket connected');
172
- // Join the room for this session token
173
- socket.emit('join', sessionToken);
174
- setConnectionType('socket');
175
- });
176
- socket.on('joined', () => {
177
- debug.log('Joined auth session room');
178
- });
179
- socket.on('auth_update', payload => {
180
- debug.log('Auth update received:', payload);
181
- if (payload.status === 'authorized' && payload.sessionId) {
182
- cleanup();
183
- // `sessionToken` is this flow's secret credential (in closure) — pass
184
- // it through so `handleAuthSuccess` can claim the first access token.
185
- handleAuthSuccess(payload.sessionId, sessionToken);
186
- } else if (payload.status === 'cancelled') {
187
- cleanup();
188
- setError('Authorization was denied.');
189
- } else if (payload.status === 'expired') {
190
- cleanup();
191
- setError('Session expired. Please try again.');
192
- }
193
- });
194
- socket.on('connect_error', err => {
195
- debug.log('Socket connection error, falling back to polling:', err instanceof Error ? err.message : null);
196
- // Realtime transport errored — reflect the honest connection state. The
197
- // poll is already running (started unconditionally in
198
- // `generateAuthSession`), so `startPolling` here is a no-op backstop.
199
- socket.disconnect();
200
- setConnectionType('polling');
201
- startPolling(sessionToken);
202
- });
203
- socket.on('disconnect', () => {
204
- debug.log('Auth socket disconnected');
205
- });
206
- }, [oxyServices, handleAuthSuccess]);
207
-
208
- // Start polling for authorization.
209
- //
210
- // Idempotent: if a poll interval is already running this is a no-op, so the
211
- // `connect_error` path (which also calls this) cannot stack a second interval
212
- // on top of the always-on poll started in `generateAuthSession`.
213
- const startPolling = (0, _react.useCallback)(sessionToken => {
214
- if (pollingIntervalRef.current) return;
215
- pollingIntervalRef.current = setInterval(async () => {
216
- if (isProcessingRef.current) return;
217
- try {
218
- const response = await oxyServices.makeRequest('GET', `/auth/session/status/${sessionToken}`, undefined, {
219
- cache: false
220
- });
221
- if (response.authorized && response.sessionId) {
222
- cleanup();
223
- // Pass the original sessionToken (in closure) through; the claim
224
- // exchange needs it to mint the first access token.
225
- handleAuthSuccess(response.sessionId, sessionToken);
226
- } else if (response.status === 'cancelled') {
227
- cleanup();
228
- setError('Authorization was denied.');
229
- } else if (response.status === 'expired') {
230
- cleanup();
231
- setError('Session expired. Please try again.');
232
- }
233
- } catch (err) {
234
- // Silent fail for polling - will retry
235
- debug.log('Auth polling error:', err);
236
- }
237
- }, POLLING_INTERVAL_MS);
238
- }, [oxyServices, handleAuthSuccess]);
239
-
240
- // Cleanup socket and polling
241
- const cleanup = (0, _react.useCallback)(() => {
242
- setIsWaiting(false);
243
- if (socketRef.current) {
244
- socketRef.current.disconnect();
245
- socketRef.current = null;
246
- }
247
- if (pollingIntervalRef.current) {
248
- clearInterval(pollingIntervalRef.current);
249
- pollingIntervalRef.current = null;
250
- }
251
- }, []);
252
-
253
- // Generate a new auth session
254
- const generateAuthSession = (0, _react.useCallback)(async () => {
255
- setIsLoading(true);
256
- setError(null);
257
- isProcessingRef.current = false;
258
-
259
- // The cross-app device sign-in flow identifies the requesting app by its
260
- // real registered OAuth client id (ApplicationCredential publicKey).
261
- // Without it the API cannot resolve the consent identity, so we fail fast
262
- // with a clear configuration error rather than creating a session the
263
- // server would reject.
264
- if (!clientId) {
265
- setError('This app is not configured for sign-in (missing clientId).');
266
- setIsLoading(false);
267
- return;
268
- }
269
- try {
270
- // Generate a unique session token for this auth request
271
- const sessionToken = generateSessionToken();
272
- const expiresAt = Date.now() + AUTH_SESSION_EXPIRY_MS;
273
-
274
- // Register the auth session with the server
275
- await oxyServices.makeRequest('POST', '/auth/session/create', {
276
- sessionToken,
277
- expiresAt,
278
- clientId
279
- }, {
280
- cache: false
281
- });
282
- setAuthSession({
283
- sessionToken,
284
- expiresAt
285
- });
286
- setIsWaiting(true);
287
-
288
- // Socket is the fast path; the poll is a transport-independent backstop
289
- // that guarantees completion even if the socket connects but silently
290
- // never delivers auth_update (RN transport / idle-timeout).
291
- connectSocket(sessionToken);
292
- startPolling(sessionToken);
293
- } catch (err) {
294
- setError((err instanceof Error ? err.message : null) || 'Failed to create auth session');
295
- } finally {
296
- setIsLoading(false);
297
- }
298
- }, [oxyServices, connectSocket, startPolling, clientId]);
299
-
300
- // Generate a random session token
301
- const generateSessionToken = () => {
302
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
303
- let result = '';
304
- for (let i = 0; i < 32; i++) {
305
- result += chars.charAt(Math.floor(Math.random() * chars.length));
306
- }
307
- return result;
308
- };
309
-
310
- // Clean up on unmount
311
- (0, _react.useEffect)(() => {
312
- return () => {
313
- cleanup();
314
- };
315
- }, [cleanup]);
316
-
317
- // Initialize auth session
318
- (0, _react.useEffect)(() => {
319
- generateAuthSession();
320
- }, []);
321
-
322
- // Check if session expired
323
- (0, _react.useEffect)(() => {
324
- if (authSession && Date.now() > authSession.expiresAt) {
325
- cleanup();
326
- setAuthSession(null);
327
- setError('Session expired. Please try again.');
328
- }
329
- }, [authSession, cleanup]);
330
-
331
- // Build the QR code data
332
- const getQRData = () => {
333
- if (!authSession) return '';
334
-
335
- // Format: oxyauth://{sessionToken}
336
- return `oxyauth://${authSession.sessionToken}`;
337
- };
338
-
339
- // Open Oxy Auth web flow
340
- const handleOpenAuth = (0, _react.useCallback)(async () => {
341
- if (!authSession) return;
342
- const authBaseUrl = resolveAuthWebBaseUrl(oxyServices.getBaseURL(), oxyServices.config?.authWebUrl);
343
- const webUrl = new URL('/authorize', authBaseUrl);
344
- webUrl.searchParams.set('token', authSession.sessionToken);
345
- const redirectUri = await resolveAuthRedirectUri(oxyServices.config?.authRedirectUri);
346
- if (redirectUri) {
347
- webUrl.searchParams.set('redirect_uri', redirectUri);
348
- }
349
- try {
350
- await _reactNative.Linking.openURL(webUrl.toString());
351
- } catch (err) {
352
- setError('Unable to open Oxy Auth. Please try again or use the QR code.');
353
- }
354
- }, [authSession, oxyServices]);
355
-
356
- // Refresh session
357
- const handleRefresh = (0, _react.useCallback)(() => {
358
- cleanup();
359
- generateAuthSession();
360
- }, [generateAuthSession, cleanup]);
361
- const handleAuthRedirect = (0, _react.useCallback)(url => {
362
- const params = getRedirectParams(url);
363
- if (!params) {
364
- return;
365
- }
366
- if (params.error) {
367
- cleanup();
368
- setError('Authorization was denied.');
369
- return;
370
- }
371
- if (params.sessionId) {
372
- // The deep-link return carries only `session_id` — the secret
373
- // `sessionToken` for this flow lives in component state (generated in
374
- // `generateAuthSession`). Without it we cannot claim the first access
375
- // token, so the flow would 401 in `handleAuthSuccess`. If it is somehow
376
- // unavailable, fall through to the socket/poll path (which carries the
377
- // token in closure) rather than attempting an unauthenticated claim.
378
- const flowSessionToken = authSession?.sessionToken;
379
- if (!flowSessionToken) {
380
- return;
381
- }
382
- cleanup();
383
- handleAuthSuccess(params.sessionId, flowSessionToken);
384
- }
385
- }, [authSession, cleanup, handleAuthSuccess]);
386
- (0, _react.useEffect)(() => {
387
- if (_reactNative.Platform.OS === 'web') {
388
- return;
389
- }
390
- const subscription = _reactNative.Linking.addEventListener('url', ({
391
- url
392
- }) => {
393
- linkingHandledRef.current = true;
394
- handleAuthRedirect(url);
395
- });
396
- _reactNative.Linking.getInitialURL().then(url => {
397
- if (url && !linkingHandledRef.current) {
398
- handleAuthRedirect(url);
399
- }
400
- }).catch(() => {
401
- // Ignore linking errors; auth will still resolve via socket/polling.
402
- });
403
- return () => {
404
- subscription.remove();
405
- };
406
- }, [handleAuthRedirect]);
44
+ const {
45
+ qrData,
46
+ isLoading,
47
+ error,
48
+ isWaiting,
49
+ openAuthApproval,
50
+ retry
51
+ } = (0, _useOxyAuthSession.useOxyAuthSession)(oxyServices, clientId, switchSession, {
52
+ onSignedIn: onAuthenticated
53
+ });
407
54
  if (isLoading) {
408
55
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
409
56
  style: [styles.container, {
@@ -429,11 +76,8 @@ const OxyAuthScreen = ({
429
76
  children: error
430
77
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
431
78
  variant: "primary",
432
- onPress: handleRefresh,
433
- style: {
434
- width: '100%',
435
- borderRadius: 12
436
- },
79
+ onPress: retry,
80
+ style: styles.primaryButton,
437
81
  children: "Try Again"
438
82
  })]
439
83
  });
@@ -450,84 +94,57 @@ const OxyAuthScreen = ({
450
94
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
451
95
  style: styles.title,
452
96
  className: "text-foreground",
453
- children: "Sign in with Oxy"
97
+ children: "Sign in to Oxy"
454
98
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
455
99
  style: styles.subtitle,
456
100
  className: "text-muted-foreground",
457
- children: "Use your Oxy identity to sign in securely"
458
- })]
459
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
460
- style: styles.qrContainer,
461
- className: "bg-secondary border-border",
462
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
463
- style: styles.qrWrapper,
464
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeQrcodeSvg.default, {
465
- value: getQRData(),
466
- size: 200,
467
- backgroundColor: "white",
468
- color: "black"
469
- })
470
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
471
- style: styles.qrHint,
472
- className: "text-muted-foreground",
473
- children: "Scan with Oxy Accounts app"
474
- })]
475
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
476
- style: styles.dividerContainer,
477
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
478
- style: styles.divider,
479
- className: "bg-border"
480
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
481
- style: styles.dividerText,
482
- className: "text-muted-foreground",
483
- children: "or"
484
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
485
- style: styles.divider,
486
- className: "bg-border"
101
+ children: "Continue with your Oxy identity to sign in securely"
487
102
  })]
488
103
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
489
104
  variant: "primary",
490
- onPress: handleOpenAuth,
105
+ onPress: openAuthApproval,
491
106
  icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyLogo.default, {
492
107
  variant: "icon",
493
108
  size: 20,
494
- fillColor: bloomTheme.colors.card,
109
+ fillColor: bloomTheme.colors.primaryForeground,
495
110
  style: styles.buttonIcon
496
111
  }),
497
- style: {
498
- width: '100%',
499
- borderRadius: 12
500
- },
501
- children: "Open Oxy Auth"
112
+ style: styles.primaryButton,
113
+ children: "Continue with Oxy"
502
114
  }), isWaiting && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
503
115
  style: styles.statusContainer,
504
116
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_loading.Loading, {
505
117
  size: "small",
506
- style: {
507
- flex: undefined
508
- }
118
+ style: styles.statusSpinner
509
119
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
510
120
  style: styles.statusText,
511
121
  className: "text-muted-foreground",
512
122
  children: "Waiting for authorization..."
513
123
  })]
124
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
125
+ style: styles.qrSection,
126
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_AnotherDeviceQR.default, {
127
+ qrData: qrData
128
+ })
514
129
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
515
130
  style: styles.footer,
516
131
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
517
132
  style: styles.footerText,
518
133
  className: "text-muted-foreground",
519
- children: ["Don't have Oxy Accounts?", ' ']
134
+ children: ["Don't have an Oxy account?", ' ']
520
135
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
521
- onPress: () => _reactNative.Linking.openURL(OXY_ACCOUNTS_WEB_URL),
136
+ onPress: () => _reactNative.Linking.openURL(_useOxyAuthSession.OXY_ACCOUNTS_WEB_URL),
137
+ accessibilityRole: "link",
522
138
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
523
139
  style: styles.footerLink,
524
140
  className: "text-primary",
525
- children: "Get it here"
141
+ children: "Create one"
526
142
  })
527
143
  })]
528
144
  }), goBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
529
145
  style: styles.cancelButton,
530
146
  onPress: goBack,
147
+ accessibilityRole: "button",
531
148
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
532
149
  style: styles.cancelText,
533
150
  className: "text-muted-foreground",
@@ -545,62 +162,47 @@ const styles = _reactNative.StyleSheet.create({
545
162
  },
546
163
  header: {
547
164
  alignItems: 'center',
548
- marginBottom: 32
165
+ marginBottom: 28
549
166
  },
550
167
  title: {
551
168
  fontSize: 24,
552
169
  fontWeight: 'bold',
553
- marginTop: 16
170
+ marginTop: 16,
171
+ textAlign: 'center'
554
172
  },
555
173
  subtitle: {
556
174
  fontSize: 14,
557
175
  marginTop: 8,
558
176
  textAlign: 'center'
559
177
  },
560
- qrContainer: {
561
- padding: 24,
562
- borderRadius: 16,
563
- borderWidth: 1,
564
- alignItems: 'center'
565
- },
566
- qrWrapper: {
567
- padding: 16,
568
- backgroundColor: 'white',
178
+ primaryButton: {
179
+ width: '100%',
569
180
  borderRadius: 12
570
181
  },
571
- qrHint: {
572
- marginTop: 16,
573
- fontSize: 12
574
- },
575
- dividerContainer: {
576
- flexDirection: 'row',
577
- alignItems: 'center',
578
- marginVertical: 24,
579
- width: '100%'
580
- },
581
- divider: {
582
- flex: 1,
583
- height: 1
584
- },
585
- dividerText: {
586
- marginHorizontal: 16,
587
- fontSize: 14
588
- },
589
182
  buttonIcon: {
590
183
  marginRight: 10
591
184
  },
592
185
  statusContainer: {
593
186
  flexDirection: 'row',
594
187
  alignItems: 'center',
595
- marginTop: 24
188
+ marginTop: 20
189
+ },
190
+ statusSpinner: {
191
+ flex: undefined
596
192
  },
597
193
  statusText: {
598
194
  marginLeft: 8,
599
195
  fontSize: 14
600
196
  },
197
+ qrSection: {
198
+ width: '100%',
199
+ marginTop: 24
200
+ },
601
201
  footer: {
602
202
  flexDirection: 'row',
603
- marginTop: 32
203
+ flexWrap: 'wrap',
204
+ justifyContent: 'center',
205
+ marginTop: 28
604
206
  },
605
207
  footerText: {
606
208
  fontSize: 14