@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.
- package/lib/commonjs/ui/components/AnotherDeviceQR.js +121 -0
- package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +1 -0
- package/lib/commonjs/ui/components/SignInModal.js +145 -356
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js +458 -0
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +1 -0
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +62 -460
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/components/AnotherDeviceQR.js +116 -0
- package/lib/module/ui/components/AnotherDeviceQR.js.map +1 -0
- package/lib/module/ui/components/SignInModal.js +149 -358
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/hooks/useOxyAuthSession.js +452 -0
- package/lib/module/ui/hooks/useOxyAuthSession.js.map +1 -0
- package/lib/module/ui/screens/OxyAuthScreen.js +62 -460
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +24 -0
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +10 -5
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +83 -0
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +12 -8
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +24 -0
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/SignInModal.d.ts +10 -5
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +83 -0
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +12 -8
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/components/AnotherDeviceQR.tsx +111 -0
- package/src/ui/components/SignInModal.tsx +142 -381
- package/src/ui/hooks/useOxyAuthSession.ts +556 -0
- package/src/ui/screens/OxyAuthScreen.tsx +61 -506
|
@@ -8,39 +8,31 @@ var _react = require("react");
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
|
|
10
10
|
var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
|
|
11
|
-
var _socket = _interopRequireDefault(require("socket.io-client"));
|
|
12
|
-
var _reactNativeQrcodeSvg = _interopRequireDefault(require("react-native-qrcode-svg"));
|
|
13
11
|
var _theme = require("@oxyhq/bloom/theme");
|
|
14
12
|
var _button = require("@oxyhq/bloom/button");
|
|
15
13
|
var _loading = require("@oxyhq/bloom/loading");
|
|
16
14
|
var _OxyContext = require("../context/OxyContext.js");
|
|
17
15
|
var _OxyLogo = _interopRequireDefault(require("./OxyLogo.js"));
|
|
18
|
-
var
|
|
19
|
-
var
|
|
16
|
+
var _AnotherDeviceQR = _interopRequireDefault(require("./AnotherDeviceQR.js"));
|
|
17
|
+
var _useOxyAuthSession = require("../hooks/useOxyAuthSession.js");
|
|
20
18
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
21
19
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
22
20
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
23
21
|
/**
|
|
24
|
-
* SignInModal -
|
|
22
|
+
* SignInModal - Web-first centered sign-in modal (Continue with Oxy)
|
|
25
23
|
*
|
|
26
|
-
* A semi-transparent full-screen modal
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* A semi-transparent full-screen modal whose primary action is the one-tap
|
|
25
|
+
* "Continue with Oxy" approval flow. The QR code is demoted to a collapsed
|
|
26
|
+
* "Sign in on another device" disclosure (you can't scan your own screen).
|
|
29
27
|
*
|
|
30
|
-
*
|
|
28
|
+
* ALL of the auth-session machinery (session-token creation, QR data, socket +
|
|
29
|
+
* polling, waiting/error/retry state, the open-auth handler, deep-link return,
|
|
30
|
+
* and cleanup) lives in the shared `useOxyAuthSession` hook, which the native
|
|
31
|
+
* `OxyAuthScreen` also consumes — neither container re-implements the transport.
|
|
32
|
+
*
|
|
33
|
+
* Animates with a fade + scale effect.
|
|
31
34
|
*/
|
|
32
35
|
|
|
33
|
-
const debug = (0, _core.createDebugLogger)('SignInModal');
|
|
34
|
-
const {
|
|
35
|
-
width: SCREEN_WIDTH,
|
|
36
|
-
height: SCREEN_HEIGHT
|
|
37
|
-
} = _reactNative.Dimensions.get('window');
|
|
38
|
-
|
|
39
|
-
// Auth session expiration (5 minutes)
|
|
40
|
-
const AUTH_SESSION_EXPIRY_MS = 5 * 60 * 1000;
|
|
41
|
-
|
|
42
|
-
// Polling interval (fallback if socket fails)
|
|
43
|
-
const POLLING_INTERVAL_MS = 3000;
|
|
44
36
|
// Store for modal visibility with subscription support
|
|
45
37
|
let modalVisible = false;
|
|
46
38
|
let setModalVisibleCallback = null;
|
|
@@ -68,10 +60,6 @@ const subscribeToSignInModal = listener => {
|
|
|
68
60
|
exports.subscribeToSignInModal = subscribeToSignInModal;
|
|
69
61
|
const SignInModal = () => {
|
|
70
62
|
const [visible, setVisible] = (0, _react.useState)(false);
|
|
71
|
-
const [authSession, setAuthSession] = (0, _react.useState)(null);
|
|
72
|
-
const [isLoading, setIsLoading] = (0, _react.useState)(false);
|
|
73
|
-
const [error, setError] = (0, _react.useState)(null);
|
|
74
|
-
const [isWaiting, setIsWaiting] = (0, _react.useState)(false);
|
|
75
63
|
const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
|
|
76
64
|
const theme = (0, _theme.useTheme)();
|
|
77
65
|
const {
|
|
@@ -79,42 +67,59 @@ const SignInModal = () => {
|
|
|
79
67
|
switchSession,
|
|
80
68
|
clientId
|
|
81
69
|
} = (0, _OxyContext.useOxy)();
|
|
82
|
-
const socketRef = (0, _react.useRef)(null);
|
|
83
|
-
const pollingIntervalRef = (0, _react.useRef)(null);
|
|
84
|
-
const isProcessingRef = (0, _react.useRef)(false);
|
|
85
|
-
|
|
86
|
-
// Animation values
|
|
87
|
-
const opacity = (0, _reactNativeReanimated.useSharedValue)(0);
|
|
88
|
-
const scale = (0, _reactNativeReanimated.useSharedValue)(0.9);
|
|
89
70
|
|
|
90
|
-
// Register callback
|
|
71
|
+
// Register the imperative visibility callback.
|
|
91
72
|
(0, _react.useEffect)(() => {
|
|
92
73
|
setModalVisibleCallback = setVisible;
|
|
93
74
|
return () => {
|
|
94
75
|
setModalVisibleCallback = null;
|
|
95
76
|
};
|
|
96
77
|
}, []);
|
|
78
|
+
if (!visible) return null;
|
|
79
|
+
|
|
80
|
+
// The hook owns the whole auth-session lifecycle. Mounting it only while the
|
|
81
|
+
// modal is visible matches the previous behavior (session created on open,
|
|
82
|
+
// cleaned up on close) — the inner component is unmounted when `visible`
|
|
83
|
+
// flips false, which runs the hook's unmount cleanup.
|
|
84
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(SignInModalContent, {
|
|
85
|
+
theme: theme,
|
|
86
|
+
insets: insets,
|
|
87
|
+
oxyServices: oxyServices,
|
|
88
|
+
switchSession: switchSession,
|
|
89
|
+
clientId: clientId
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
const SignInModalContent = ({
|
|
93
|
+
theme,
|
|
94
|
+
insets,
|
|
95
|
+
oxyServices,
|
|
96
|
+
switchSession,
|
|
97
|
+
clientId
|
|
98
|
+
}) => {
|
|
99
|
+
const {
|
|
100
|
+
qrData,
|
|
101
|
+
isLoading,
|
|
102
|
+
error,
|
|
103
|
+
isWaiting,
|
|
104
|
+
openAuthApproval,
|
|
105
|
+
retry
|
|
106
|
+
} = (0, _useOxyAuthSession.useOxyAuthSession)(oxyServices, clientId, switchSession, {
|
|
107
|
+
onSignedIn: hideSignInModal
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Entrance animation.
|
|
111
|
+
const opacity = (0, _reactNativeReanimated.useSharedValue)(0);
|
|
112
|
+
const scale = (0, _reactNativeReanimated.useSharedValue)(0.96);
|
|
97
113
|
|
|
98
|
-
//
|
|
99
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: opacity and scale are Reanimated SharedValues (stable refs) that should not be listed as dependencies
|
|
114
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: opacity/scale are Reanimated SharedValues (stable refs) and must not be listed as deps; this runs once on mount to play the entrance.
|
|
100
115
|
(0, _react.useEffect)(() => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
generateAuthSession();
|
|
109
|
-
} else {
|
|
110
|
-
opacity.value = (0, _reactNativeReanimated.withTiming)(0, {
|
|
111
|
-
duration: 200
|
|
112
|
-
});
|
|
113
|
-
scale.value = (0, _reactNativeReanimated.withTiming)(0.9, {
|
|
114
|
-
duration: 200
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}, [visible]);
|
|
116
|
+
opacity.value = (0, _reactNativeReanimated.withTiming)(1, {
|
|
117
|
+
duration: 250
|
|
118
|
+
});
|
|
119
|
+
scale.value = (0, _reactNativeReanimated.withTiming)(1, {
|
|
120
|
+
duration: 250
|
|
121
|
+
});
|
|
122
|
+
}, []);
|
|
118
123
|
const backdropStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
|
|
119
124
|
opacity: opacity.value
|
|
120
125
|
}));
|
|
@@ -124,245 +129,14 @@ const SignInModal = () => {
|
|
|
124
129
|
scale: scale.value
|
|
125
130
|
}]
|
|
126
131
|
}));
|
|
127
|
-
|
|
128
|
-
// Handle successful authorization.
|
|
129
|
-
//
|
|
130
|
-
// The auth-session socket (or fallback poll) hands us the new
|
|
131
|
-
// `sessionId`. Before any session-management code can touch it we
|
|
132
|
-
// MUST first exchange the secret `sessionToken` (held only by this
|
|
133
|
-
// client) for the first access token via `claimSessionByToken` —
|
|
134
|
-
// this is the device-flow equivalent of OAuth's code-for-token
|
|
135
|
-
// exchange (RFC 8628 §3.4).
|
|
136
|
-
//
|
|
137
|
-
// Without that exchange the SDK has no bearer token and the app never
|
|
138
|
-
// becomes authenticated even though the session is authorized server-side.
|
|
139
|
-
// Once `claimSessionByToken` plants the tokens in the HttpService, the rest
|
|
140
|
-
// of the session wiring (state, persistence, language preference) flows
|
|
141
|
-
// through the normal `switchSession` path.
|
|
142
|
-
const handleAuthSuccess = (0, _react.useCallback)(async (sessionId, sessionToken) => {
|
|
143
|
-
if (isProcessingRef.current) return;
|
|
144
|
-
isProcessingRef.current = true;
|
|
145
|
-
try {
|
|
146
|
-
// Claim the first access token with the secret sessionToken, then
|
|
147
|
-
// hydrate the session. Shared with the native `OxyAuthScreen` via
|
|
148
|
-
// `completeDeviceFlowSignIn` so the two paths cannot drift.
|
|
149
|
-
if (!switchSession) {
|
|
150
|
-
throw new Error('Session management unavailable');
|
|
151
|
-
}
|
|
152
|
-
await (0, _deviceFlowSignIn.completeDeviceFlowSignIn)({
|
|
153
|
-
oxyServices,
|
|
154
|
-
sessionId,
|
|
155
|
-
sessionToken,
|
|
156
|
-
switchSession
|
|
157
|
-
});
|
|
158
|
-
hideSignInModal();
|
|
159
|
-
} catch (err) {
|
|
160
|
-
debug.error('Error completing auth:', err);
|
|
161
|
-
setError('Authorization successful but failed to complete sign in. Please try again.');
|
|
162
|
-
isProcessingRef.current = false;
|
|
163
|
-
}
|
|
164
|
-
}, [oxyServices, switchSession]);
|
|
165
|
-
|
|
166
|
-
// Cleanup socket and polling
|
|
167
|
-
const cleanup = (0, _react.useCallback)(() => {
|
|
168
|
-
setIsWaiting(false);
|
|
169
|
-
if (socketRef.current) {
|
|
170
|
-
socketRef.current.disconnect();
|
|
171
|
-
socketRef.current = null;
|
|
172
|
-
}
|
|
173
|
-
if (pollingIntervalRef.current) {
|
|
174
|
-
clearInterval(pollingIntervalRef.current);
|
|
175
|
-
pollingIntervalRef.current = null;
|
|
176
|
-
}
|
|
177
|
-
}, []);
|
|
178
|
-
|
|
179
|
-
// Connect to socket for real-time updates
|
|
180
|
-
const connectSocket = (0, _react.useCallback)(sessionToken => {
|
|
181
|
-
const baseURL = oxyServices.getBaseURL();
|
|
182
|
-
const socket = (0, _socket.default)(`${baseURL}/auth-session`, {
|
|
183
|
-
transports: ['websocket', 'polling'],
|
|
184
|
-
reconnection: true,
|
|
185
|
-
reconnectionAttempts: 3,
|
|
186
|
-
reconnectionDelay: 1000
|
|
187
|
-
});
|
|
188
|
-
socketRef.current = socket;
|
|
189
|
-
socket.on('connect', () => {
|
|
190
|
-
debug.log('Auth socket connected');
|
|
191
|
-
socket.emit('join', sessionToken);
|
|
192
|
-
});
|
|
193
|
-
socket.on('auth_update', payload => {
|
|
194
|
-
debug.log('Auth update received:', payload);
|
|
195
|
-
if (payload.status === 'authorized' && payload.sessionId) {
|
|
196
|
-
cleanup();
|
|
197
|
-
// `sessionToken` was generated by this client and is in scope
|
|
198
|
-
// via the `sessionToken` arg — pass it through so the claim
|
|
199
|
-
// call can exchange it for the first access token.
|
|
200
|
-
handleAuthSuccess(payload.sessionId, sessionToken);
|
|
201
|
-
} else if (payload.status === 'cancelled') {
|
|
202
|
-
cleanup();
|
|
203
|
-
setError('Authorization was denied.');
|
|
204
|
-
} else if (payload.status === 'expired') {
|
|
205
|
-
cleanup();
|
|
206
|
-
setError('Session expired. Please try again.');
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
socket.on('connect_error', err => {
|
|
210
|
-
debug.log('Socket connection error, falling back to polling:', err instanceof Error ? err.message : null);
|
|
211
|
-
socket.disconnect();
|
|
212
|
-
startPolling(sessionToken);
|
|
213
|
-
});
|
|
214
|
-
}, [oxyServices, handleAuthSuccess, cleanup]);
|
|
215
|
-
|
|
216
|
-
// Start polling for authorization.
|
|
217
|
-
//
|
|
218
|
-
// Idempotent: if a poll interval is already running this is a no-op, so the
|
|
219
|
-
// `connect_error` path (which also calls this) cannot stack a second
|
|
220
|
-
// interval on top of the always-on poll started in `generateAuthSession`.
|
|
221
|
-
const startPolling = (0, _react.useCallback)(sessionToken => {
|
|
222
|
-
if (pollingIntervalRef.current) return;
|
|
223
|
-
pollingIntervalRef.current = setInterval(async () => {
|
|
224
|
-
if (isProcessingRef.current) return;
|
|
225
|
-
try {
|
|
226
|
-
const response = await oxyServices.makeRequest('GET', `/auth/session/status/${sessionToken}`, undefined, {
|
|
227
|
-
cache: false
|
|
228
|
-
});
|
|
229
|
-
if (response.authorized && response.sessionId) {
|
|
230
|
-
cleanup();
|
|
231
|
-
// Pass the original sessionToken through; the claim
|
|
232
|
-
// exchange needs it to mint the first access token.
|
|
233
|
-
handleAuthSuccess(response.sessionId, sessionToken);
|
|
234
|
-
} else if (response.status === 'cancelled') {
|
|
235
|
-
cleanup();
|
|
236
|
-
setError('Authorization was denied.');
|
|
237
|
-
} else if (response.status === 'expired') {
|
|
238
|
-
cleanup();
|
|
239
|
-
setError('Session expired. Please try again.');
|
|
240
|
-
}
|
|
241
|
-
} catch (err) {
|
|
242
|
-
debug.log('Auth polling error:', err);
|
|
243
|
-
}
|
|
244
|
-
}, POLLING_INTERVAL_MS);
|
|
245
|
-
}, [oxyServices, handleAuthSuccess, cleanup]);
|
|
246
|
-
|
|
247
|
-
// Generate a new auth session
|
|
248
|
-
const generateAuthSession = (0, _react.useCallback)(async () => {
|
|
249
|
-
setIsLoading(true);
|
|
250
|
-
setError(null);
|
|
251
|
-
isProcessingRef.current = false;
|
|
252
|
-
|
|
253
|
-
// The cross-app device sign-in flow identifies the requesting app by its
|
|
254
|
-
// real registered OAuth client id (ApplicationCredential publicKey).
|
|
255
|
-
// Without it the API cannot resolve the consent identity, so we fail
|
|
256
|
-
// fast with a clear configuration error rather than creating a session
|
|
257
|
-
// the server would reject.
|
|
258
|
-
if (!clientId) {
|
|
259
|
-
setError('This app is not configured for sign-in (missing clientId).');
|
|
260
|
-
setIsLoading(false);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
try {
|
|
264
|
-
const sessionToken = generateSessionToken();
|
|
265
|
-
const expiresAt = Date.now() + AUTH_SESSION_EXPIRY_MS;
|
|
266
|
-
await oxyServices.makeRequest('POST', '/auth/session/create', {
|
|
267
|
-
sessionToken,
|
|
268
|
-
expiresAt,
|
|
269
|
-
clientId
|
|
270
|
-
}, {
|
|
271
|
-
cache: false
|
|
272
|
-
});
|
|
273
|
-
setAuthSession({
|
|
274
|
-
sessionToken,
|
|
275
|
-
expiresAt
|
|
276
|
-
});
|
|
277
|
-
setIsWaiting(true);
|
|
278
|
-
// Socket is the fast path; the poll is a transport-independent
|
|
279
|
-
// backstop that guarantees completion even if the socket connects
|
|
280
|
-
// but silently never delivers auth_update (RN transport /
|
|
281
|
-
// idle-timeout).
|
|
282
|
-
connectSocket(sessionToken);
|
|
283
|
-
startPolling(sessionToken);
|
|
284
|
-
} catch (err) {
|
|
285
|
-
setError((err instanceof Error ? err.message : null) || 'Failed to create auth session');
|
|
286
|
-
} finally {
|
|
287
|
-
setIsLoading(false);
|
|
288
|
-
}
|
|
289
|
-
}, [oxyServices, connectSocket, startPolling, clientId]);
|
|
290
|
-
|
|
291
|
-
// Generate a cryptographically random session token.
|
|
292
|
-
// 16 random bytes -> 32 hex chars (128 bits of entropy) — unguessable.
|
|
293
|
-
// crypto.getRandomValues is guaranteed available because importing
|
|
294
|
-
// @oxyhq/core installs a polyfill via expo-crypto on React Native.
|
|
295
|
-
const generateSessionToken = () => {
|
|
296
|
-
const bytes = new Uint8Array(16);
|
|
297
|
-
crypto.getRandomValues(bytes);
|
|
298
|
-
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
// Build the QR code data
|
|
302
|
-
const getQRData = () => {
|
|
303
|
-
if (!authSession) return '';
|
|
304
|
-
return `oxyauth://${authSession.sessionToken}`;
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
// Open Oxy Auth approval page for this device-flow session.
|
|
308
|
-
const handleOpenAuthApproval = (0, _react.useCallback)(async () => {
|
|
309
|
-
if (!authSession) return;
|
|
310
|
-
const baseURL = oxyServices.getBaseURL();
|
|
311
|
-
// Resolve auth web URL
|
|
312
|
-
let authWebUrl = oxyServices.config?.authWebUrl;
|
|
313
|
-
if (!authWebUrl) {
|
|
314
|
-
try {
|
|
315
|
-
const url = new URL(baseURL);
|
|
316
|
-
if (url.port === '3001') {
|
|
317
|
-
url.port = '3002';
|
|
318
|
-
authWebUrl = url.origin;
|
|
319
|
-
} else if (url.hostname.startsWith('api.')) {
|
|
320
|
-
url.hostname = `auth.${url.hostname.slice(4)}`;
|
|
321
|
-
authWebUrl = url.origin;
|
|
322
|
-
}
|
|
323
|
-
} catch {
|
|
324
|
-
authWebUrl = 'https://auth.oxy.so';
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
authWebUrl = authWebUrl || 'https://auth.oxy.so';
|
|
328
|
-
const webUrl = new URL('/authorize', authWebUrl);
|
|
329
|
-
webUrl.searchParams.set('token', authSession.sessionToken);
|
|
330
|
-
if (_reactNative.Platform.OS === 'web') {
|
|
331
|
-
// Open a separate approval window on web for the device-flow token.
|
|
332
|
-
const width = 500;
|
|
333
|
-
const height = 650;
|
|
334
|
-
const screenWidth = window.screen?.width ?? width;
|
|
335
|
-
const screenHeight = window.screen?.height ?? height;
|
|
336
|
-
const left = (screenWidth - width) / 2;
|
|
337
|
-
const top = (screenHeight - height) / 2;
|
|
338
|
-
window.open(webUrl.toString(), 'oxy-auth-approval', `width=${width},height=${height},left=${left},top=${top}`);
|
|
339
|
-
} else {
|
|
340
|
-
// Open in browser on native
|
|
341
|
-
_reactNative.Linking.openURL(webUrl.toString());
|
|
342
|
-
}
|
|
343
|
-
}, [authSession, oxyServices]);
|
|
344
|
-
|
|
345
|
-
// Refresh session
|
|
346
|
-
const handleRefresh = (0, _react.useCallback)(() => {
|
|
347
|
-
cleanup();
|
|
348
|
-
generateAuthSession();
|
|
349
|
-
}, [generateAuthSession, cleanup]);
|
|
350
|
-
|
|
351
|
-
// Handle close
|
|
352
132
|
const handleClose = (0, _react.useCallback)(() => {
|
|
353
|
-
cleanup();
|
|
354
133
|
hideSignInModal();
|
|
355
|
-
}, [
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return () => {
|
|
360
|
-
cleanup();
|
|
361
|
-
};
|
|
362
|
-
}, [cleanup]);
|
|
363
|
-
if (!visible) return null;
|
|
134
|
+
}, []);
|
|
135
|
+
const handleCreateAccount = (0, _react.useCallback)(() => {
|
|
136
|
+
_reactNative.Linking.openURL(_useOxyAuthSession.OXY_ACCOUNTS_WEB_URL);
|
|
137
|
+
}, []);
|
|
364
138
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
|
|
365
|
-
visible:
|
|
139
|
+
visible: true,
|
|
366
140
|
transparent: true,
|
|
367
141
|
animationType: "none",
|
|
368
142
|
statusBarTranslucent: true,
|
|
@@ -376,15 +150,22 @@ const SignInModal = () => {
|
|
|
376
150
|
onPress: handleClose,
|
|
377
151
|
activeOpacity: 1
|
|
378
152
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
|
|
379
|
-
style: [styles.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
153
|
+
style: [styles.card, {
|
|
154
|
+
backgroundColor: theme.colors.card,
|
|
155
|
+
paddingTop: insets.top + 24,
|
|
156
|
+
paddingBottom: insets.bottom + 24
|
|
157
|
+
}, contentStyle],
|
|
383
158
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
384
|
-
style: styles.closeButton,
|
|
159
|
+
style: [styles.closeButton, {
|
|
160
|
+
backgroundColor: theme.colors.backgroundSecondary
|
|
161
|
+
}],
|
|
385
162
|
onPress: handleClose,
|
|
163
|
+
accessibilityRole: "button",
|
|
164
|
+
accessibilityLabel: "Close sign in",
|
|
386
165
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
387
|
-
style: styles.closeButtonText,
|
|
166
|
+
style: [styles.closeButtonText, {
|
|
167
|
+
color: theme.colors.textSecondary
|
|
168
|
+
}],
|
|
388
169
|
children: "\xD7"
|
|
389
170
|
})
|
|
390
171
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
@@ -395,11 +176,11 @@ const SignInModal = () => {
|
|
|
395
176
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
396
177
|
className: "text-foreground",
|
|
397
178
|
style: styles.title,
|
|
398
|
-
children: "Sign in
|
|
179
|
+
children: "Sign in to Oxy"
|
|
399
180
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
400
181
|
className: "text-muted-foreground",
|
|
401
182
|
style: styles.subtitle,
|
|
402
|
-
children: "
|
|
183
|
+
children: "Continue with your Oxy identity to sign in securely"
|
|
403
184
|
})]
|
|
404
185
|
}), isLoading ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
405
186
|
style: styles.loadingContainer,
|
|
@@ -417,54 +198,54 @@ const SignInModal = () => {
|
|
|
417
198
|
style: styles.errorText,
|
|
418
199
|
children: error
|
|
419
200
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
|
|
420
|
-
|
|
201
|
+
variant: "primary",
|
|
202
|
+
onPress: retry,
|
|
203
|
+
style: styles.primaryButton,
|
|
421
204
|
children: "Try Again"
|
|
422
205
|
})]
|
|
423
206
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
424
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}],
|
|
428
|
-
children: authSession ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeQrcodeSvg.default, {
|
|
429
|
-
value: getQRData(),
|
|
430
|
-
size: 200,
|
|
431
|
-
backgroundColor: "white",
|
|
432
|
-
color: "black"
|
|
433
|
-
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_loading.Loading, {
|
|
434
|
-
size: "large"
|
|
435
|
-
})
|
|
436
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
437
|
-
style: styles.dividerContainer,
|
|
438
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
439
|
-
style: [styles.divider, {
|
|
440
|
-
backgroundColor: 'rgba(255,255,255,0.3)'
|
|
441
|
-
}]
|
|
442
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
443
|
-
style: [styles.dividerText, {
|
|
444
|
-
color: 'rgba(255,255,255,0.7)'
|
|
445
|
-
}],
|
|
446
|
-
children: "or"
|
|
447
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
448
|
-
style: [styles.divider, {
|
|
449
|
-
backgroundColor: 'rgba(255,255,255,0.3)'
|
|
450
|
-
}]
|
|
451
|
-
})]
|
|
452
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
|
|
453
|
-
onPress: handleOpenAuthApproval,
|
|
207
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
|
|
208
|
+
variant: "primary",
|
|
209
|
+
onPress: openAuthApproval,
|
|
454
210
|
icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyLogo.default, {
|
|
455
211
|
variant: "icon",
|
|
456
212
|
size: 20,
|
|
457
|
-
fillColor: theme.colors.
|
|
213
|
+
fillColor: theme.colors.primaryForeground,
|
|
214
|
+
style: styles.buttonIcon
|
|
458
215
|
}),
|
|
459
|
-
|
|
216
|
+
style: styles.primaryButton,
|
|
217
|
+
children: "Continue with Oxy"
|
|
460
218
|
}), isWaiting && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
461
219
|
style: styles.statusContainer,
|
|
462
220
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_loading.Loading, {
|
|
463
|
-
size: "small"
|
|
221
|
+
size: "small",
|
|
222
|
+
style: styles.statusSpinner
|
|
464
223
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
465
|
-
style: styles.statusText,
|
|
224
|
+
style: [styles.statusText, {
|
|
225
|
+
color: theme.colors.textSecondary
|
|
226
|
+
}],
|
|
466
227
|
children: "Waiting for authorization..."
|
|
467
228
|
})]
|
|
229
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
230
|
+
style: styles.qrSection,
|
|
231
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_AnotherDeviceQR.default, {
|
|
232
|
+
qrData: qrData
|
|
233
|
+
})
|
|
234
|
+
})]
|
|
235
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
236
|
+
style: styles.footer,
|
|
237
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
238
|
+
style: styles.footerText,
|
|
239
|
+
className: "text-muted-foreground",
|
|
240
|
+
children: ["Don't have an Oxy account?", ' ']
|
|
241
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
242
|
+
onPress: handleCreateAccount,
|
|
243
|
+
accessibilityRole: "link",
|
|
244
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
245
|
+
style: styles.footerLink,
|
|
246
|
+
className: "text-primary",
|
|
247
|
+
children: "Create one"
|
|
248
|
+
})
|
|
468
249
|
})]
|
|
469
250
|
})]
|
|
470
251
|
})]
|
|
@@ -477,11 +258,12 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
477
258
|
justifyContent: 'center',
|
|
478
259
|
alignItems: 'center'
|
|
479
260
|
},
|
|
480
|
-
|
|
261
|
+
card: {
|
|
481
262
|
width: '100%',
|
|
482
263
|
maxWidth: 400,
|
|
483
264
|
alignItems: 'center',
|
|
484
|
-
paddingHorizontal: 24
|
|
265
|
+
paddingHorizontal: 24,
|
|
266
|
+
borderRadius: 24
|
|
485
267
|
},
|
|
486
268
|
closeButton: {
|
|
487
269
|
position: 'absolute',
|
|
@@ -490,58 +272,52 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
490
272
|
width: 40,
|
|
491
273
|
height: 40,
|
|
492
274
|
borderRadius: 20,
|
|
493
|
-
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
494
275
|
justifyContent: 'center',
|
|
495
276
|
alignItems: 'center',
|
|
496
277
|
zIndex: 10
|
|
497
278
|
},
|
|
498
279
|
closeButtonText: {
|
|
499
|
-
color: 'white',
|
|
500
280
|
fontSize: 28,
|
|
501
281
|
fontWeight: '300',
|
|
502
282
|
lineHeight: 32
|
|
503
283
|
},
|
|
504
284
|
header: {
|
|
505
285
|
alignItems: 'center',
|
|
506
|
-
marginBottom:
|
|
286
|
+
marginBottom: 28
|
|
507
287
|
},
|
|
508
288
|
title: {
|
|
509
|
-
fontSize:
|
|
289
|
+
fontSize: 26,
|
|
510
290
|
fontWeight: 'bold',
|
|
511
|
-
marginTop: 16
|
|
291
|
+
marginTop: 16,
|
|
292
|
+
textAlign: 'center'
|
|
512
293
|
},
|
|
513
294
|
subtitle: {
|
|
514
295
|
fontSize: 15,
|
|
515
296
|
marginTop: 8,
|
|
516
297
|
textAlign: 'center'
|
|
517
298
|
},
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
borderRadius:
|
|
521
|
-
},
|
|
522
|
-
dividerContainer: {
|
|
523
|
-
flexDirection: 'row',
|
|
524
|
-
alignItems: 'center',
|
|
525
|
-
marginVertical: 24,
|
|
526
|
-
width: '100%'
|
|
527
|
-
},
|
|
528
|
-
divider: {
|
|
529
|
-
flex: 1,
|
|
530
|
-
height: 1
|
|
299
|
+
primaryButton: {
|
|
300
|
+
width: '100%',
|
|
301
|
+
borderRadius: 12
|
|
531
302
|
},
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
fontSize: 14
|
|
303
|
+
buttonIcon: {
|
|
304
|
+
marginRight: 10
|
|
535
305
|
},
|
|
536
306
|
statusContainer: {
|
|
537
307
|
flexDirection: 'row',
|
|
538
308
|
alignItems: 'center',
|
|
539
|
-
marginTop:
|
|
309
|
+
marginTop: 20
|
|
310
|
+
},
|
|
311
|
+
statusSpinner: {
|
|
312
|
+
flex: undefined
|
|
540
313
|
},
|
|
541
314
|
statusText: {
|
|
542
315
|
marginLeft: 8,
|
|
543
|
-
fontSize: 14
|
|
544
|
-
|
|
316
|
+
fontSize: 14
|
|
317
|
+
},
|
|
318
|
+
qrSection: {
|
|
319
|
+
width: '100%',
|
|
320
|
+
marginTop: 24
|
|
545
321
|
},
|
|
546
322
|
loadingContainer: {
|
|
547
323
|
alignItems: 'center',
|
|
@@ -560,6 +336,19 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
560
336
|
fontSize: 14,
|
|
561
337
|
textAlign: 'center',
|
|
562
338
|
marginBottom: 16
|
|
339
|
+
},
|
|
340
|
+
footer: {
|
|
341
|
+
flexDirection: 'row',
|
|
342
|
+
flexWrap: 'wrap',
|
|
343
|
+
justifyContent: 'center',
|
|
344
|
+
marginTop: 28
|
|
345
|
+
},
|
|
346
|
+
footerText: {
|
|
347
|
+
fontSize: 14
|
|
348
|
+
},
|
|
349
|
+
footerLink: {
|
|
350
|
+
fontSize: 14,
|
|
351
|
+
fontWeight: '600'
|
|
563
352
|
}
|
|
564
353
|
});
|
|
565
354
|
var _default = exports.default = SignInModal;
|