@oxyhq/services 17.0.0 → 18.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 (81) hide show
  1. package/README.md +77 -98
  2. package/lib/commonjs/index.js +19 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/ui/components/OxyAccountDialog.js +62 -84
  5. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -1
  6. package/lib/commonjs/ui/components/OxyConsentScreen.js +461 -0
  7. package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -0
  8. package/lib/commonjs/ui/components/OxyProvider.js +3 -1
  9. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  10. package/lib/commonjs/ui/components/OxySignInButton.js +176 -7
  11. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  12. package/lib/commonjs/ui/components/oauthNavigation.js +73 -0
  13. package/lib/commonjs/ui/components/oauthNavigation.js.map +1 -0
  14. package/lib/commonjs/ui/context/OxyContext.js +31 -4
  15. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  16. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +11 -0
  17. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  18. package/lib/module/index.js +2 -2
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/ui/components/OxyAccountDialog.js +63 -85
  21. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -1
  22. package/lib/module/ui/components/OxyConsentScreen.js +454 -0
  23. package/lib/module/ui/components/OxyConsentScreen.js.map +1 -0
  24. package/lib/module/ui/components/OxyProvider.js +3 -1
  25. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  26. package/lib/module/ui/components/OxySignInButton.js +177 -7
  27. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  28. package/lib/module/ui/components/oauthNavigation.js +70 -0
  29. package/lib/module/ui/components/oauthNavigation.js.map +1 -0
  30. package/lib/module/ui/context/OxyContext.js +31 -4
  31. package/lib/module/ui/context/OxyContext.js.map +1 -1
  32. package/lib/module/ui/context/hooks/useAuthOperations.js +11 -0
  33. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  34. package/lib/typescript/commonjs/index.d.ts +4 -1
  35. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +18 -2
  37. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts +45 -0
  39. package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -0
  40. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +56 -0
  42. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts +36 -0
  44. package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts.map +1 -0
  45. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +14 -0
  46. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts +1 -1
  49. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/ui/types/navigation.d.ts +18 -0
  51. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  52. package/lib/typescript/module/index.d.ts +4 -1
  53. package/lib/typescript/module/index.d.ts.map +1 -1
  54. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +18 -2
  55. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -1
  56. package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts +45 -0
  57. package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -0
  58. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  59. package/lib/typescript/module/ui/components/OxySignInButton.d.ts +56 -0
  60. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  61. package/lib/typescript/module/ui/components/oauthNavigation.d.ts +36 -0
  62. package/lib/typescript/module/ui/components/oauthNavigation.d.ts.map +1 -0
  63. package/lib/typescript/module/ui/context/OxyContext.d.ts +14 -0
  64. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  65. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  66. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts +1 -1
  67. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  68. package/lib/typescript/module/ui/types/navigation.d.ts +18 -0
  69. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  70. package/package.json +4 -4
  71. package/src/index.ts +12 -1
  72. package/src/ui/components/OxyAccountDialog.tsx +60 -85
  73. package/src/ui/components/OxyConsentScreen.tsx +456 -0
  74. package/src/ui/components/OxyProvider.tsx +2 -0
  75. package/src/ui/components/OxySignInButton.tsx +222 -6
  76. package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +130 -0
  77. package/src/ui/components/oauthNavigation.ts +89 -0
  78. package/src/ui/context/OxyContext.tsx +44 -4
  79. package/src/ui/context/__tests__/OxyContext.coldBoot.test.tsx +165 -0
  80. package/src/ui/context/hooks/useAuthOperations.ts +7 -0
  81. package/src/ui/types/navigation.ts +18 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * OxyConsentScreen — the unified OAuth authorize/consent surface for
3
+ * `@oxyhq/services`.
4
+ *
5
+ * A PURE, presentational React Native component: it renders the resolved
6
+ * requesting-application identity, the permissions (scopes) being requested,
7
+ * the account that will authorize the request, and the allow/deny affordances —
8
+ * and delegates every decision back through {@link OxyConsentScreenProps.onAllow}
9
+ * / {@link OxyConsentScreenProps.onDeny}. It performs NO data fetching and owns
10
+ * NO session state: the caller resolves the application + scopes + user (the IdP
11
+ * from `GET /auth/session/approve-info/:code`, Console from its own request) and
12
+ * drives the `busy` / `error` flags. This is the RN/Bloom port of the web
13
+ * `consent-card` (`packages/auth`), so the two consent surfaces stay in visual
14
+ * and behavioral lockstep.
15
+ *
16
+ * Theming is `useTheme()` + a `StyleSheet` (the same approach as
17
+ * {@link OxyAccountDialog}) so it renders correctly in EVERY consumer, including
18
+ * apps that do not use NativeWind. Copy is localized through {@link useI18n}
19
+ * under the `consent.*` keys.
20
+ */
21
+ import { useCallback } from 'react';
22
+ import { Linking, Pressable, ScrollView, StyleSheet, View } from 'react-native';
23
+ import { logger } from '@oxyhq/core';
24
+ import { Avatar } from '@oxyhq/bloom/avatar';
25
+ import { Button } from '@oxyhq/bloom/button';
26
+ import { Text } from '@oxyhq/bloom/typography';
27
+ import { useTheme } from '@oxyhq/bloom/theme';
28
+ import { useI18n } from '../hooks/useI18n';
29
+ import OxyLogo from './OxyLogo';
30
+
31
+ /** The requesting application, resolved by the caller (never user-supplied raw). */
32
+ export interface OxyConsentApplication {
33
+ /** Display name of the requesting application. */
34
+ name: string;
35
+ /** Application icon — a URL or bare file id passed straight to `<Avatar source>`. */
36
+ iconUrl?: string;
37
+ /** Public website of the application, shown as a link when present. */
38
+ websiteUrl?: string;
39
+ /** Privacy policy URL, shown as a link when present. */
40
+ privacyPolicyUrl?: string;
41
+ /** Terms of service URL, shown as a link when present. */
42
+ termsUrl?: string;
43
+ /** Human-readable publisher, used for the "Published by …" provenance line. */
44
+ developerName?: string;
45
+ /** True for first-party / official Oxy applications. */
46
+ isOfficial?: boolean;
47
+ }
48
+
49
+ /** The account that will authorize the request (the currently signed-in user). */
50
+ export interface OxyConsentUser {
51
+ /** Real display name, when the profile has one. */
52
+ displayName?: string;
53
+ /** Normalized handle — the sanctioned fallback when `displayName` is absent. */
54
+ handle?: string;
55
+ /** Avatar — a URL or bare file id passed straight to `<Avatar source>`. */
56
+ avatarUri?: string;
57
+ }
58
+
59
+ export interface OxyConsentScreenProps {
60
+ /** The resolved requesting application. */
61
+ application: OxyConsentApplication;
62
+ /** OAuth scopes requested by the client (mapped to friendly labels). */
63
+ scopes: string[];
64
+ /** The account that will authorize the request, when known. */
65
+ user?: OxyConsentUser;
66
+ /** Approve handler — the caller mints the code / completes the flow. */
67
+ onAllow: () => void | Promise<void>;
68
+ /** Deny handler — the caller cancels the flow. */
69
+ onDeny: () => void;
70
+ /** True while a decision is in flight; disables both actions and spins Allow. */
71
+ busy?: boolean;
72
+ /** A blocking error message for the request, when present. */
73
+ error?: string | null;
74
+ }
75
+
76
+ /**
77
+ * Friendly-label i18n keys for the scopes the platform issues. Standard OIDC
78
+ * scopes (`openid` / `profile` / `email` / `offline_access`) and the Oxy scope
79
+ * set both map to a curated `consent.scopes.*` sentence. Unknown scopes fall
80
+ * back to the raw scope string so the user always sees something concrete.
81
+ */
82
+ const SCOPE_LABEL_KEYS: Record<string, string> = {
83
+ openid: 'consent.scopes.openid',
84
+ profile: 'consent.scopes.profile',
85
+ email: 'consent.scopes.email',
86
+ offline_access: 'consent.scopes.offlineAccess',
87
+ 'user:read': 'consent.scopes.userRead',
88
+ 'files:read': 'consent.scopes.filesRead',
89
+ 'files:write': 'consent.scopes.filesWrite',
90
+ 'files:delete': 'consent.scopes.filesDelete',
91
+ 'webhooks:receive': 'consent.scopes.webhooksReceive',
92
+ 'chat:completions': 'consent.scopes.chatCompletions',
93
+ 'models:read': 'consent.scopes.modelsRead',
94
+ 'federation:write': 'consent.scopes.federationWrite',
95
+ };
96
+
97
+ type Translate = ReturnType<typeof useI18n>['t'];
98
+
99
+ function scopeLabel(scope: string, t: Translate): string {
100
+ const key = SCOPE_LABEL_KEYS[scope];
101
+ return key ? t(key) : scope;
102
+ }
103
+
104
+ /** A tappable legal/website link that opens the URL in the platform browser. */
105
+ function ConsentLink({
106
+ label,
107
+ url,
108
+ testID,
109
+ color,
110
+ }: {
111
+ label: string;
112
+ url: string;
113
+ testID: string;
114
+ color: string;
115
+ }) {
116
+ const handlePress = useCallback(() => {
117
+ // `websiteUrl`/legal URLs are application-controlled metadata rendered on a
118
+ // high-trust surface: only ever hand web schemes to the OS.
119
+ let protocol: string;
120
+ try {
121
+ protocol = new URL(url).protocol;
122
+ } catch {
123
+ logger.warn('OxyConsentScreen: invalid link URL', { url });
124
+ return;
125
+ }
126
+ if (protocol !== 'https:' && protocol !== 'http:') {
127
+ logger.warn('OxyConsentScreen: blocked non-web link scheme', { url });
128
+ return;
129
+ }
130
+ Linking.openURL(url).catch((error) => {
131
+ logger.warn('OxyConsentScreen: could not open link', { url, error });
132
+ });
133
+ }, [url]);
134
+ return (
135
+ <Pressable testID={testID} onPress={handlePress} accessibilityRole="link" style={styles.link}>
136
+ <Text style={[styles.linkText, { color }]}>{label}</Text>
137
+ </Pressable>
138
+ );
139
+ }
140
+
141
+ export function OxyConsentScreen({
142
+ application,
143
+ scopes,
144
+ user,
145
+ onAllow,
146
+ onDeny,
147
+ busy = false,
148
+ error = null,
149
+ }: OxyConsentScreenProps) {
150
+ const theme = useTheme();
151
+ const { t } = useI18n();
152
+ const appName = application.name;
153
+ // Scopes key the permission rows — a duplicate in the request would mean
154
+ // duplicate React keys and redundant rows.
155
+ const uniqueScopes = [...new Set(scopes)];
156
+
157
+ const provenanceLabel = application.isOfficial
158
+ ? t('consent.provenance.official')
159
+ : application.developerName
160
+ ? t('consent.provenance.developer', { developer: application.developerName })
161
+ : t('consent.provenance.thirdParty');
162
+
163
+ // Display-name rule (D5): a real display name, else the normalized handle.
164
+ const accountName = user ? user.displayName?.trim() || user.handle : undefined;
165
+
166
+ const handleAllow = useCallback(() => {
167
+ void onAllow();
168
+ }, [onAllow]);
169
+
170
+ const showLegalLinks = Boolean(application.privacyPolicyUrl || application.termsUrl);
171
+
172
+ return (
173
+ <ScrollView
174
+ testID="oxy-consent-screen"
175
+ style={styles.root}
176
+ contentContainerStyle={styles.content}
177
+ >
178
+ {/* Header: requesting app ↔ Oxy identity */}
179
+ <View style={styles.header}>
180
+ <View style={styles.connection}>
181
+ <Avatar source={application.iconUrl} name={appName} size={56} />
182
+ <View style={styles.connector}>
183
+ <View style={[styles.dot, { backgroundColor: theme.colors.border }]} />
184
+ <View style={[styles.dot, { backgroundColor: theme.colors.border }]} />
185
+ <View style={[styles.dot, { backgroundColor: theme.colors.border }]} />
186
+ </View>
187
+ <View style={[styles.logoBadge, { backgroundColor: theme.colors.backgroundSecondary ?? theme.colors.card }]}>
188
+ <OxyLogo variant="icon" size={30} fillColor={theme.colors.primary} />
189
+ </View>
190
+ </View>
191
+ <Text style={[styles.title, { color: theme.colors.text }]}>
192
+ {t('consent.title', { app: appName })}
193
+ </Text>
194
+ <Text style={[styles.subtitle, { color: theme.colors.textSecondary }]}>
195
+ {t('consent.subtitle', { app: appName })}
196
+ </Text>
197
+ </View>
198
+
199
+ {/* Provenance */}
200
+ <View style={[styles.card, { borderColor: theme.colors.border, backgroundColor: theme.colors.card }]}>
201
+ <Text testID="consent-provenance" style={[styles.provenance, { color: theme.colors.text }]}>
202
+ {provenanceLabel}
203
+ </Text>
204
+ {application.websiteUrl ? (
205
+ <ConsentLink
206
+ testID="consent-link-website"
207
+ label={application.websiteUrl}
208
+ url={application.websiteUrl}
209
+ color={theme.colors.primary}
210
+ />
211
+ ) : null}
212
+ </View>
213
+
214
+ {/* Requested permissions */}
215
+ <View style={styles.section}>
216
+ <Text style={[styles.sectionLabel, { color: theme.colors.textSecondary }]}>
217
+ {t('consent.permissions.title')}
218
+ </Text>
219
+ <View style={[styles.card, { borderColor: theme.colors.border, backgroundColor: theme.colors.card }]}>
220
+ {uniqueScopes.length > 0 ? (
221
+ uniqueScopes.map((scope) => (
222
+ <View key={scope} testID={`consent-scope-${scope}`} style={styles.row}>
223
+ <View style={[styles.bullet, { backgroundColor: theme.colors.primary }]} />
224
+ <Text style={[styles.rowText, { color: theme.colors.text }]}>
225
+ {scopeLabel(scope, t)}
226
+ </Text>
227
+ </View>
228
+ ))
229
+ ) : (
230
+ <View testID="consent-scope-basic" style={styles.row}>
231
+ <View style={[styles.bullet, { backgroundColor: theme.colors.primary }]} />
232
+ <Text style={[styles.rowText, { color: theme.colors.text }]}>
233
+ {t('consent.permissions.basic')}
234
+ </Text>
235
+ </View>
236
+ )}
237
+ </View>
238
+ </View>
239
+
240
+ {/* Authorizing account */}
241
+ {user ? (
242
+ <View
243
+ testID="consent-account"
244
+ style={[styles.accountRow, { borderColor: theme.colors.border, backgroundColor: theme.colors.card }]}
245
+ >
246
+ <Avatar source={user.avatarUri} name={accountName} size={40} />
247
+ <View style={styles.accountText}>
248
+ <Text
249
+ testID="consent-account-name"
250
+ numberOfLines={1}
251
+ style={[styles.accountName, { color: theme.colors.text }]}
252
+ >
253
+ {accountName}
254
+ </Text>
255
+ {user.handle && user.handle !== accountName ? (
256
+ <Text numberOfLines={1} style={[styles.accountHandle, { color: theme.colors.textSecondary }]}>
257
+ {user.handle}
258
+ </Text>
259
+ ) : null}
260
+ </View>
261
+ </View>
262
+ ) : null}
263
+
264
+ {/* Legal links */}
265
+ {showLegalLinks ? (
266
+ <View style={styles.linksRow}>
267
+ {application.privacyPolicyUrl ? (
268
+ <ConsentLink
269
+ testID="consent-link-privacy"
270
+ label={t('consent.links.privacy')}
271
+ url={application.privacyPolicyUrl}
272
+ color={theme.colors.textSecondary}
273
+ />
274
+ ) : null}
275
+ {application.termsUrl ? (
276
+ <ConsentLink
277
+ testID="consent-link-terms"
278
+ label={t('consent.links.terms')}
279
+ url={application.termsUrl}
280
+ color={theme.colors.textSecondary}
281
+ />
282
+ ) : null}
283
+ </View>
284
+ ) : null}
285
+
286
+ {/* Blocking error */}
287
+ {error ? (
288
+ <View
289
+ testID="consent-error"
290
+ style={[styles.errorCard, { borderColor: theme.colors.error }]}
291
+ >
292
+ <Text style={[styles.errorText, { color: theme.colors.error }]}>{error}</Text>
293
+ </View>
294
+ ) : null}
295
+
296
+ {/* Decision actions */}
297
+ <View style={styles.actions}>
298
+ <Button
299
+ testID="consent-allow"
300
+ variant="primary"
301
+ onPress={handleAllow}
302
+ disabled={busy}
303
+ loading={busy}
304
+ style={styles.actionButton}
305
+ >
306
+ {t('consent.allow', { app: appName })}
307
+ </Button>
308
+ <Button
309
+ testID="consent-deny"
310
+ variant="ghost"
311
+ onPress={onDeny}
312
+ disabled={busy}
313
+ style={styles.actionButton}
314
+ >
315
+ {t('consent.deny')}
316
+ </Button>
317
+ </View>
318
+
319
+ <Text style={[styles.disclaimer, { color: theme.colors.textSecondary }]}>
320
+ {t('consent.disclaimer', { app: appName })}
321
+ </Text>
322
+ </ScrollView>
323
+ );
324
+ }
325
+
326
+ const styles = StyleSheet.create({
327
+ root: {
328
+ flex: 1,
329
+ },
330
+ content: {
331
+ padding: 20,
332
+ gap: 20,
333
+ },
334
+ header: {
335
+ alignItems: 'center',
336
+ gap: 12,
337
+ },
338
+ connection: {
339
+ flexDirection: 'row',
340
+ alignItems: 'center',
341
+ gap: 10,
342
+ },
343
+ connector: {
344
+ flexDirection: 'row',
345
+ alignItems: 'center',
346
+ gap: 4,
347
+ },
348
+ dot: {
349
+ width: 4,
350
+ height: 4,
351
+ borderRadius: 2,
352
+ },
353
+ logoBadge: {
354
+ width: 56,
355
+ height: 56,
356
+ borderRadius: 28,
357
+ alignItems: 'center',
358
+ justifyContent: 'center',
359
+ },
360
+ title: {
361
+ fontSize: 20,
362
+ fontWeight: '700',
363
+ textAlign: 'center',
364
+ },
365
+ subtitle: {
366
+ fontSize: 14,
367
+ lineHeight: 20,
368
+ textAlign: 'center',
369
+ },
370
+ section: {
371
+ gap: 8,
372
+ },
373
+ sectionLabel: {
374
+ fontSize: 12,
375
+ fontWeight: '600',
376
+ paddingHorizontal: 4,
377
+ },
378
+ card: {
379
+ borderWidth: StyleSheet.hairlineWidth,
380
+ borderRadius: 16,
381
+ padding: 12,
382
+ gap: 10,
383
+ },
384
+ provenance: {
385
+ fontSize: 14,
386
+ fontWeight: '600',
387
+ },
388
+ row: {
389
+ flexDirection: 'row',
390
+ alignItems: 'center',
391
+ gap: 10,
392
+ },
393
+ bullet: {
394
+ width: 6,
395
+ height: 6,
396
+ borderRadius: 3,
397
+ },
398
+ rowText: {
399
+ flex: 1,
400
+ fontSize: 14,
401
+ },
402
+ accountRow: {
403
+ flexDirection: 'row',
404
+ alignItems: 'center',
405
+ gap: 12,
406
+ borderWidth: StyleSheet.hairlineWidth,
407
+ borderRadius: 16,
408
+ padding: 12,
409
+ },
410
+ accountText: {
411
+ flex: 1,
412
+ minWidth: 0,
413
+ },
414
+ accountName: {
415
+ fontSize: 15,
416
+ fontWeight: '600',
417
+ },
418
+ accountHandle: {
419
+ fontSize: 13,
420
+ },
421
+ linksRow: {
422
+ flexDirection: 'row',
423
+ flexWrap: 'wrap',
424
+ gap: 16,
425
+ paddingHorizontal: 4,
426
+ },
427
+ link: {
428
+ alignSelf: 'flex-start',
429
+ },
430
+ linkText: {
431
+ fontSize: 13,
432
+ textDecorationLine: 'underline',
433
+ },
434
+ errorCard: {
435
+ borderWidth: StyleSheet.hairlineWidth,
436
+ borderRadius: 12,
437
+ padding: 12,
438
+ },
439
+ errorText: {
440
+ fontSize: 14,
441
+ },
442
+ actions: {
443
+ gap: 12,
444
+ },
445
+ actionButton: {
446
+ width: '100%',
447
+ },
448
+ disclaimer: {
449
+ fontSize: 12,
450
+ lineHeight: 18,
451
+ textAlign: 'center',
452
+ paddingHorizontal: 4,
453
+ },
454
+ });
455
+
456
+ export default OxyConsentScreen;
@@ -116,6 +116,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
116
116
  authRedirectUri,
117
117
  queryClient: providedQueryClient,
118
118
  requireAuth = 'off',
119
+ coldBoot = true,
119
120
  }) => {
120
121
 
121
122
  // Dynamic KeyboardProvider for native. Uses variable indirection
@@ -310,6 +311,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
310
311
  authRedirectUri={authRedirectUri}
311
312
  storageKeyPrefix={storageKeyPrefix}
312
313
  clientId={clientId}
314
+ coldBoot={coldBoot}
313
315
  onAuthStateChange={onAuthStateChange as OxyContextProviderProps['onAuthStateChange']}
314
316
  >
315
317
  {requireAuth === 'off' ? (