@sparkvault/sdk-mobile 0.1.3

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 (82) hide show
  1. package/README.md +186 -0
  2. package/dist/auth.d.ts +74 -0
  3. package/dist/auth.js +515 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/billing.d.ts +21 -0
  6. package/dist/billing.js +10 -0
  7. package/dist/billing.js.map +1 -0
  8. package/dist/client.d.ts +27 -0
  9. package/dist/client.js +36 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/config.d.ts +39 -0
  12. package/dist/config.js +69 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/encoding.d.ts +7 -0
  15. package/dist/encoding.js +113 -0
  16. package/dist/encoding.js.map +1 -0
  17. package/dist/entropy.d.ts +18 -0
  18. package/dist/entropy.js +35 -0
  19. package/dist/entropy.js.map +1 -0
  20. package/dist/errors.d.ts +46 -0
  21. package/dist/errors.js +72 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/folders.d.ts +15 -0
  24. package/dist/folders.js +54 -0
  25. package/dist/folders.js.map +1 -0
  26. package/dist/health.d.ts +19 -0
  27. package/dist/health.js +60 -0
  28. package/dist/health.js.map +1 -0
  29. package/dist/http.d.ts +45 -0
  30. package/dist/http.js +351 -0
  31. package/dist/http.js.map +1 -0
  32. package/dist/identity-dialog.d.ts +19 -0
  33. package/dist/identity-dialog.js +656 -0
  34. package/dist/identity-dialog.js.map +1 -0
  35. package/dist/index.d.ts +24 -0
  36. package/dist/index.js +15 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/ingots.d.ts +99 -0
  39. package/dist/ingots.js +365 -0
  40. package/dist/ingots.js.map +1 -0
  41. package/dist/mutex.d.ts +6 -0
  42. package/dist/mutex.js +20 -0
  43. package/dist/mutex.js.map +1 -0
  44. package/dist/push-tokens.d.ts +12 -0
  45. package/dist/push-tokens.js +15 -0
  46. package/dist/push-tokens.js.map +1 -0
  47. package/dist/sparks.d.ts +39 -0
  48. package/dist/sparks.js +32 -0
  49. package/dist/sparks.js.map +1 -0
  50. package/dist/tus.d.ts +24 -0
  51. package/dist/tus.js +202 -0
  52. package/dist/tus.js.map +1 -0
  53. package/dist/types.d.ts +406 -0
  54. package/dist/types.js +2 -0
  55. package/dist/types.js.map +1 -0
  56. package/dist/validation.d.ts +5 -0
  57. package/dist/validation.js +45 -0
  58. package/dist/validation.js.map +1 -0
  59. package/dist/vaults.d.ts +94 -0
  60. package/dist/vaults.js +106 -0
  61. package/dist/vaults.js.map +1 -0
  62. package/package.json +58 -0
  63. package/src/auth.ts +707 -0
  64. package/src/billing.ts +30 -0
  65. package/src/client.ts +51 -0
  66. package/src/config.ts +123 -0
  67. package/src/encoding.ts +150 -0
  68. package/src/entropy.ts +56 -0
  69. package/src/errors.ts +110 -0
  70. package/src/folders.ts +76 -0
  71. package/src/health.ts +81 -0
  72. package/src/http.ts +429 -0
  73. package/src/identity-dialog.tsx +955 -0
  74. package/src/index.ts +103 -0
  75. package/src/ingots.ts +593 -0
  76. package/src/mutex.ts +26 -0
  77. package/src/push-tokens.ts +26 -0
  78. package/src/sparks.ts +73 -0
  79. package/src/tus.ts +280 -0
  80. package/src/types.ts +487 -0
  81. package/src/validation.ts +49 -0
  82. package/src/vaults.ts +271 -0
@@ -0,0 +1,656 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { ActivityIndicator, Image, KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
4
+ const SUPPORTED_METHODS = new Set(['passkey', 'totp_email', 'totp_sms', 'totp_voice']);
5
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6
+ const E164_PHONE_REGEX = /^\+[1-9]\d{7,14}$/;
7
+ export function SparkVaultIdentityDialog({ client, visible, initialIdentity, initialIdentityType, passkeyProvider, onSuccess, onCancel, onError, }) {
8
+ const [step, setStep] = useState('loading');
9
+ const [config, setConfig] = useState(null);
10
+ const [identity, setIdentity] = useState(initialIdentity ?? '');
11
+ const [identityType, setIdentityType] = useState(initialIdentityType ?? 'email');
12
+ const [selectedMethod, setSelectedMethod] = useState(null);
13
+ const [kindling, setKindling] = useState(null);
14
+ const [expiresAt, setExpiresAt] = useState(null);
15
+ const [pin, setPin] = useState('');
16
+ const [countdown, setCountdown] = useState('');
17
+ const [isBusy, setIsBusy] = useState(false);
18
+ const [error, setError] = useState('');
19
+ const sessionRef = useRef(0);
20
+ const onSuccessRef = useRef(onSuccess);
21
+ const onCancelRef = useRef(onCancel);
22
+ const onErrorRef = useRef(onError);
23
+ const branding = config?.branding;
24
+ const effectiveTheme = branding?.themeMode ?? 'light';
25
+ const colors = effectiveTheme === 'dark' ? darkColors : lightColors;
26
+ const allowedTypes = useMemo(() => normalizeAllowedTypes(config), [config]);
27
+ const methodsForIdentity = useMemo(() => getMethodsForIdentity(config, identityType, passkeyProvider), [config, identityType, passkeyProvider]);
28
+ useEffect(() => {
29
+ onSuccessRef.current = onSuccess;
30
+ onCancelRef.current = onCancel;
31
+ onErrorRef.current = onError;
32
+ }, [onCancel, onError, onSuccess]);
33
+ useEffect(() => {
34
+ if (!visible) {
35
+ sessionRef.current += 1;
36
+ return;
37
+ }
38
+ let cancelled = false;
39
+ const sessionId = sessionRef.current + 1;
40
+ sessionRef.current = sessionId;
41
+ setStep('loading');
42
+ setError('');
43
+ setPin('');
44
+ setKindling(null);
45
+ setExpiresAt(null);
46
+ setSelectedMethod(null);
47
+ setIdentity(initialIdentity ?? '');
48
+ setIdentityType(initialIdentityType ?? 'email');
49
+ client.auth.getConfig()
50
+ .then((nextConfig) => {
51
+ if (cancelled || !isActiveSession(sessionRef, sessionId))
52
+ return;
53
+ setConfig(nextConfig);
54
+ setIdentityType(resolveInitialIdentityType(initialIdentity, initialIdentityType, normalizeAllowedTypes(nextConfig)));
55
+ setStep('identity');
56
+ })
57
+ .catch((err) => {
58
+ if (cancelled || !isActiveSession(sessionRef, sessionId))
59
+ return;
60
+ const nextError = asError(err);
61
+ setError(nextError.message);
62
+ setStep('error');
63
+ onErrorRef.current?.(nextError);
64
+ });
65
+ return () => {
66
+ cancelled = true;
67
+ };
68
+ }, [client, initialIdentity, initialIdentityType, visible]);
69
+ useEffect(() => {
70
+ if (!expiresAt || step !== 'totp') {
71
+ setCountdown('');
72
+ return;
73
+ }
74
+ const updateCountdown = () => {
75
+ const remaining = expiresAt - Math.floor(Date.now() / 1000);
76
+ setCountdown(remaining > 0 ? formatCountdown(remaining) : 'Expired');
77
+ };
78
+ updateCountdown();
79
+ const timer = setInterval(updateCountdown, 1000);
80
+ return () => clearInterval(timer);
81
+ }, [expiresAt, step]);
82
+ const finishWithToken = useCallback(async (result, sessionId) => {
83
+ const jwks = await client.auth.getJwks();
84
+ if (!isActiveSession(sessionRef, sessionId))
85
+ return;
86
+ await onSuccessRef.current({ ...result, jwks });
87
+ }, [client]);
88
+ const reportError = useCallback((err, fallback) => {
89
+ const nextError = asError(err, fallback);
90
+ setError(nextError.message);
91
+ onErrorRef.current?.(nextError);
92
+ }, []);
93
+ const handleCancel = useCallback(() => {
94
+ sessionRef.current += 1;
95
+ setIsBusy(false);
96
+ onCancelRef.current();
97
+ }, []);
98
+ const sendTotp = useCallback(async (method, targetIdentity = identity, targetIdentityType = identityType) => {
99
+ const totpMethod = toTotpMethod(method);
100
+ if (!totpMethod)
101
+ return;
102
+ const sessionId = sessionRef.current;
103
+ setIsBusy(true);
104
+ setError('');
105
+ setSelectedMethod(method);
106
+ try {
107
+ const response = await client.auth.sendTotp({
108
+ identity: targetIdentity,
109
+ identityType: targetIdentityType,
110
+ method: totpMethod,
111
+ });
112
+ if (!isActiveSession(sessionRef, sessionId))
113
+ return;
114
+ setKindling(response.kindling);
115
+ setExpiresAt(response.expires_at);
116
+ setPin('');
117
+ setStep('totp');
118
+ }
119
+ catch (err) {
120
+ if (!isActiveSession(sessionRef, sessionId))
121
+ return;
122
+ reportError(err, 'Failed to send verification code');
123
+ }
124
+ finally {
125
+ if (isActiveSession(sessionRef, sessionId)) {
126
+ setIsBusy(false);
127
+ }
128
+ }
129
+ }, [client, identity, identityType, reportError]);
130
+ const runPasskey = useCallback(async (targetIdentity = identity, targetIdentityType = identityType) => {
131
+ if (!passkeyProvider) {
132
+ setError('Passkeys are not available in this app.');
133
+ return;
134
+ }
135
+ const sessionId = sessionRef.current;
136
+ setIsBusy(true);
137
+ setError('');
138
+ setSelectedMethod('passkey');
139
+ setStep('passkey');
140
+ try {
141
+ const supported = await passkeyProvider.isSupported();
142
+ if (!supported) {
143
+ throw new Error('Passkeys are not supported on this device.');
144
+ }
145
+ if (!isActiveSession(sessionRef, sessionId))
146
+ return;
147
+ const challenge = await client.auth.getPasskeyAuthOptions(targetIdentity, targetIdentityType);
148
+ if (!isActiveSession(sessionRef, sessionId))
149
+ return;
150
+ const credential = await passkeyProvider.authenticate(challenge.options);
151
+ if (!isActiveSession(sessionRef, sessionId))
152
+ return;
153
+ if (!credential) {
154
+ throw new Error('Passkey authentication was cancelled.');
155
+ }
156
+ const result = await client.auth.completePasskeyAuthentication(credential, challenge.session);
157
+ await finishWithToken(result, sessionId);
158
+ }
159
+ catch (err) {
160
+ if (!isActiveSession(sessionRef, sessionId))
161
+ return;
162
+ reportError(err, 'Passkey authentication failed');
163
+ }
164
+ finally {
165
+ if (isActiveSession(sessionRef, sessionId)) {
166
+ setIsBusy(false);
167
+ }
168
+ }
169
+ }, [client, finishWithToken, identity, identityType, passkeyProvider, reportError]);
170
+ const handleIdentitySubmit = useCallback(async () => {
171
+ if (isBusy)
172
+ return;
173
+ const parsed = parseIdentity(identity, allowedTypes);
174
+ if (!parsed.ok) {
175
+ setError(parsed.error);
176
+ return;
177
+ }
178
+ const normalizedIdentity = parsed.identity;
179
+ const nextIdentityType = parsed.identityType;
180
+ const availableMethods = getMethodsForIdentity(config, nextIdentityType, passkeyProvider);
181
+ if (availableMethods.length === 0) {
182
+ setError('No supported sign-in methods are enabled for this identity type.');
183
+ return;
184
+ }
185
+ setIdentity(normalizedIdentity);
186
+ setIdentityType(nextIdentityType);
187
+ setError('');
188
+ setIsBusy(true);
189
+ const sessionId = sessionRef.current;
190
+ try {
191
+ if (availableMethods.includes('passkey')) {
192
+ const { hasPasskey } = await client.auth.checkPasskeyStatus(normalizedIdentity, nextIdentityType);
193
+ if (!isActiveSession(sessionRef, sessionId))
194
+ return;
195
+ if (hasPasskey) {
196
+ await runPasskey(normalizedIdentity, nextIdentityType);
197
+ return;
198
+ }
199
+ }
200
+ const totpMethods = availableMethods.filter((method) => method !== 'passkey');
201
+ if (totpMethods.length === 1) {
202
+ await sendTotp(totpMethods[0], normalizedIdentity, nextIdentityType);
203
+ return;
204
+ }
205
+ setStep('methods');
206
+ }
207
+ catch (err) {
208
+ if (!isActiveSession(sessionRef, sessionId))
209
+ return;
210
+ reportError(err, 'Failed to load sign-in methods');
211
+ }
212
+ finally {
213
+ if (isActiveSession(sessionRef, sessionId)) {
214
+ setIsBusy(false);
215
+ }
216
+ }
217
+ }, [allowedTypes, client, config, identity, isBusy, passkeyProvider, reportError, runPasskey, sendTotp]);
218
+ const handleMethodSelect = useCallback(async (method) => {
219
+ if (method === 'passkey') {
220
+ await runPasskey();
221
+ return;
222
+ }
223
+ await sendTotp(method);
224
+ }, [runPasskey, sendTotp]);
225
+ const handleVerifyTotp = useCallback(async () => {
226
+ if (!kindling || pin.length !== 6)
227
+ return;
228
+ const sessionId = sessionRef.current;
229
+ setIsBusy(true);
230
+ setError('');
231
+ try {
232
+ const result = await client.auth.verifyTotp({
233
+ kindling,
234
+ pin,
235
+ recipient: identity,
236
+ });
237
+ await finishWithToken(result, sessionId);
238
+ }
239
+ catch (err) {
240
+ if (!isActiveSession(sessionRef, sessionId))
241
+ return;
242
+ const apiError = err;
243
+ if (apiError.data?.kindling) {
244
+ setKindling(apiError.data.kindling);
245
+ }
246
+ if (apiError.data?.expires_at) {
247
+ setExpiresAt(apiError.data.expires_at);
248
+ }
249
+ setPin('');
250
+ reportError(err, 'Verification failed');
251
+ }
252
+ finally {
253
+ if (isActiveSession(sessionRef, sessionId)) {
254
+ setIsBusy(false);
255
+ }
256
+ }
257
+ }, [client, finishWithToken, identity, kindling, pin, reportError]);
258
+ const handleResend = useCallback(async () => {
259
+ if (!selectedMethod)
260
+ return;
261
+ await sendTotp(selectedMethod);
262
+ }, [selectedMethod, sendTotp]);
263
+ const handleRetryConfig = useCallback(() => {
264
+ const sessionId = sessionRef.current + 1;
265
+ sessionRef.current = sessionId;
266
+ setIsBusy(true);
267
+ setConfig(null);
268
+ setError('');
269
+ setStep('loading');
270
+ client.auth.getConfig()
271
+ .then((nextConfig) => {
272
+ if (!isActiveSession(sessionRef, sessionId))
273
+ return;
274
+ setConfig(nextConfig);
275
+ setIdentityType(resolveInitialIdentityType(initialIdentity, initialIdentityType, normalizeAllowedTypes(nextConfig)));
276
+ setStep('identity');
277
+ })
278
+ .catch((err) => {
279
+ if (!isActiveSession(sessionRef, sessionId))
280
+ return;
281
+ reportError(err, 'Failed to load sign-in options');
282
+ setStep('error');
283
+ })
284
+ .finally(() => {
285
+ if (isActiveSession(sessionRef, sessionId)) {
286
+ setIsBusy(false);
287
+ }
288
+ });
289
+ }, [client, initialIdentity, initialIdentityType, reportError]);
290
+ const headerLogo = getHeaderLogo(branding, effectiveTheme);
291
+ const companyName = branding?.companyName || 'SparkVault';
292
+ return (_jsx(Modal, { visible: visible, transparent: true, animationType: "fade", onRequestClose: handleCancel, children: _jsx(KeyboardAvoidingView, { behavior: Platform.OS === 'ios' ? 'padding' : undefined, style: [styles.backdrop, { backgroundColor: colors.backdrop }], children: _jsx(ScrollView, { contentContainerStyle: styles.scrollContent, keyboardShouldPersistTaps: "handled", children: _jsxs(View, { style: [styles.card, { backgroundColor: colors.card, borderColor: colors.border }], children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.brand, children: [headerLogo ? (_jsx(Image, { source: { uri: headerLogo }, style: styles.logo, resizeMode: "contain" })) : (_jsx(View, { style: [styles.logoFallback, { backgroundColor: colors.primarySoft }], children: _jsx(Text, { style: [styles.logoFallbackText, { color: colors.primary }], children: companyName.slice(0, 1).toUpperCase() }) })), _jsx(Text, { style: [styles.companyName, { color: colors.text }], children: companyName })] }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Close identity verification", onPress: handleCancel, hitSlop: 12, style: styles.closeButton, children: _jsx(Text, { style: [styles.closeText, { color: colors.muted }], children: "x" }) })] }), step === 'loading' && (_jsxs(View, { style: styles.centerContent, children: [_jsx(ActivityIndicator, { color: colors.primary }), _jsx(Text, { style: [styles.mutedText, { color: colors.muted }], children: "Loading sign-in options..." })] })), step === 'identity' && (_jsxs(View, { style: styles.body, children: [_jsx(Text, { style: [styles.title, { color: colors.text }], children: "Sign in" }), _jsx(Text, { style: [styles.subtitle, { color: colors.muted }], children: identitySubtitle(allowedTypes) }), _jsx(TextInput, { value: identity, onChangeText: (value) => {
293
+ setIdentity(value);
294
+ setError('');
295
+ if (allowedTypes.length > 1) {
296
+ setIdentityType(value.trim().startsWith('+') || /^\d/.test(value.trim()) ? 'phone' : 'email');
297
+ }
298
+ }, placeholder: identityPlaceholder(allowedTypes), placeholderTextColor: colors.placeholder, autoCapitalize: "none", autoCorrect: false, autoComplete: identityType === 'phone' ? 'tel' : 'email', keyboardType: identityType === 'phone' ? 'phone-pad' : 'email-address', editable: !isBusy, style: [
299
+ styles.input,
300
+ {
301
+ borderColor: error ? colors.error : colors.border,
302
+ color: colors.text,
303
+ backgroundColor: colors.input,
304
+ },
305
+ ] }), error ? _jsx(Text, { style: [styles.error, { color: colors.error }], children: error }) : null, _jsx(PrimaryButton, { label: "Continue", colors: colors, disabled: !identity.trim() || isBusy, loading: isBusy, onPress: handleIdentitySubmit })] })), step === 'methods' && (_jsxs(View, { style: styles.body, children: [_jsx(Text, { style: [styles.title, { color: colors.text }], children: "Choose a method" }), _jsx(Text, { style: [styles.subtitle, { color: colors.muted }], children: identity }), _jsx(View, { style: styles.methodList, children: methodsForIdentity.map((method) => (_jsxs(Pressable, { accessibilityRole: "button", onPress: () => handleMethodSelect(method), disabled: isBusy, style: ({ pressed }) => [
306
+ styles.methodButton,
307
+ {
308
+ borderColor: colors.border,
309
+ backgroundColor: pressed ? colors.pressed : colors.input,
310
+ opacity: isBusy ? 0.6 : 1,
311
+ },
312
+ ], children: [_jsx(Text, { style: [styles.methodTitle, { color: colors.text }], children: methodTitle(method) }), _jsx(Text, { style: [styles.methodDescription, { color: colors.muted }], children: methodDescription(method) })] }, method))) }), error ? _jsx(Text, { style: [styles.error, { color: colors.error }], children: error }) : null, _jsx(TextButton, { label: "Use a different identity", colors: colors, onPress: () => setStep('identity') })] })), step === 'passkey' && (_jsxs(View, { style: styles.body, children: [_jsx(Text, { style: [styles.title, { color: colors.text }], children: "Use your passkey" }), _jsx(Text, { style: [styles.subtitle, { color: colors.muted }], children: "Confirm this sign-in with your device passkey." }), _jsx(View, { style: [styles.passkeyIcon, { backgroundColor: colors.primarySoft }], children: _jsx(Text, { style: [styles.passkeyIconText, { color: colors.primary }], children: "key" }) }), isBusy ? _jsx(ActivityIndicator, { color: colors.primary }) : null, error ? _jsx(Text, { style: [styles.error, { color: colors.error }], children: error }) : null, _jsx(PrimaryButton, { label: "Try Again", colors: colors, disabled: isBusy, loading: isBusy, onPress: runPasskey }), fallbackTotpMethod(methodsForIdentity) ? (_jsx(TextButton, { label: fallbackLabel(fallbackTotpMethod(methodsForIdentity)), colors: colors, onPress: () => sendTotp(fallbackTotpMethod(methodsForIdentity)), disabled: isBusy })) : null] })), step === 'totp' && (_jsxs(View, { style: styles.body, children: [_jsx(Text, { style: [styles.title, { color: colors.text }], children: totpTitle(selectedMethod) }), _jsxs(Text, { style: [styles.subtitle, { color: colors.muted }], children: ["Enter the 6-digit code sent to ", '\n', _jsx(Text, { style: { color: colors.text, fontWeight: '700' }, children: identity })] }), _jsx(TextInput, { value: pin, onChangeText: (value) => {
313
+ setPin(value.replace(/\D/g, '').slice(0, 6));
314
+ setError('');
315
+ }, placeholder: "000000", placeholderTextColor: colors.placeholder, keyboardType: "number-pad", maxLength: 6, editable: !isBusy && countdown !== 'Expired', style: [
316
+ styles.pinInput,
317
+ {
318
+ borderColor: error ? colors.error : colors.border,
319
+ color: colors.text,
320
+ backgroundColor: colors.input,
321
+ },
322
+ ] }), _jsx(Text, { style: [styles.countdown, { color: countdown === 'Expired' ? colors.error : colors.muted }], children: countdown === 'Expired' ? 'Code expired' : `Expires in ${countdown}` }), error ? _jsx(Text, { style: [styles.error, { color: colors.error }], children: error }) : null, _jsx(PrimaryButton, { label: "Verify", colors: colors, disabled: pin.length !== 6 || isBusy || countdown === 'Expired', loading: isBusy, onPress: handleVerifyTotp }), _jsx(TextButton, { label: "Resend code", colors: colors, onPress: handleResend, disabled: isBusy })] })), step === 'error' && (_jsxs(View, { style: styles.body, children: [_jsx(Text, { style: [styles.title, { color: colors.text }], children: "Sign-in unavailable" }), _jsx(Text, { style: [styles.error, { color: colors.error }], children: error }), _jsx(PrimaryButton, { label: "Try Again", colors: colors, disabled: isBusy, loading: isBusy, onPress: handleRetryConfig })] }))] }) }) }) }));
323
+ }
324
+ function isActiveSession(sessionRef, sessionId) {
325
+ return sessionRef.current === sessionId;
326
+ }
327
+ function PrimaryButton({ label, colors, disabled, loading, onPress, }) {
328
+ return (_jsx(Pressable, { accessibilityRole: "button", onPress: onPress, disabled: disabled, style: ({ pressed }) => [
329
+ styles.primaryButton,
330
+ {
331
+ backgroundColor: disabled ? colors.muted : colors.primary,
332
+ opacity: pressed && !disabled ? 0.9 : 1,
333
+ },
334
+ ], children: loading ? (_jsx(ActivityIndicator, { color: colors.primaryText })) : (_jsx(Text, { style: [styles.primaryButtonText, { color: colors.primaryText }], children: label })) }));
335
+ }
336
+ function TextButton({ label, colors, disabled, onPress, }) {
337
+ return (_jsx(Pressable, { accessibilityRole: "button", onPress: onPress, disabled: disabled, style: styles.textButton, children: _jsx(Text, { style: [styles.textButtonLabel, { color: disabled ? colors.muted : colors.primary }], children: label }) }));
338
+ }
339
+ function normalizeAllowedTypes(config) {
340
+ const allowed = config?.allowedIdentityTypes?.filter((type) => type === 'email' || type === 'phone');
341
+ return allowed && allowed.length > 0 ? allowed : ['email'];
342
+ }
343
+ function resolveInitialIdentityType(identity, explicitType, allowedTypes) {
344
+ if (explicitType && allowedTypes.includes(explicitType))
345
+ return explicitType;
346
+ if (identity?.trim().startsWith('+') && allowedTypes.includes('phone'))
347
+ return 'phone';
348
+ return allowedTypes.includes('email') ? 'email' : 'phone';
349
+ }
350
+ function getMethodsForIdentity(config, identityType, passkeyProvider) {
351
+ const configuredMethods = config?.methods ?? ['totp_email'];
352
+ return configuredMethods.filter((method) => {
353
+ if (!SUPPORTED_METHODS.has(method))
354
+ return false;
355
+ if (method === 'passkey')
356
+ return identityType === 'email' && Boolean(passkeyProvider);
357
+ if (method === 'totp_email')
358
+ return identityType === 'email';
359
+ return identityType === 'phone';
360
+ });
361
+ }
362
+ function parseIdentity(value, allowedTypes) {
363
+ const trimmed = value.trim();
364
+ if (!trimmed) {
365
+ return { ok: false, error: 'Enter an identity to continue.' };
366
+ }
367
+ const inferredType = trimmed.startsWith('+') || /^\d/.test(trimmed) ? 'phone' : 'email';
368
+ if (!allowedTypes.includes(inferredType)) {
369
+ return {
370
+ ok: false,
371
+ error: inferredType === 'phone' ? 'Phone sign-in is not enabled for this app.' : 'Email sign-in is not enabled for this app.',
372
+ };
373
+ }
374
+ if (inferredType === 'email') {
375
+ const email = trimmed.toLowerCase();
376
+ if (!EMAIL_REGEX.test(email) || email.length > 254) {
377
+ return { ok: false, error: 'Enter a valid email address.' };
378
+ }
379
+ return { ok: true, identity: email, identityType: 'email' };
380
+ }
381
+ const phone = trimmed.startsWith('+') ? trimmed.replace(/[^\d+]/g, '') : `+${trimmed.replace(/\D/g, '')}`;
382
+ if (!E164_PHONE_REGEX.test(phone)) {
383
+ return { ok: false, error: 'Enter a valid phone number in international format.' };
384
+ }
385
+ return { ok: true, identity: phone, identityType: 'phone' };
386
+ }
387
+ function toTotpMethod(method) {
388
+ if (method === 'totp_email')
389
+ return 'email';
390
+ if (method === 'totp_sms')
391
+ return 'sms';
392
+ if (method === 'totp_voice')
393
+ return 'voice';
394
+ return null;
395
+ }
396
+ function fallbackTotpMethod(methods) {
397
+ return methods.find((method) => method !== 'passkey') ?? null;
398
+ }
399
+ function fallbackLabel(method) {
400
+ if (method === 'totp_sms')
401
+ return 'Use SMS code instead';
402
+ if (method === 'totp_voice')
403
+ return 'Use voice call instead';
404
+ return 'Use email code instead';
405
+ }
406
+ function methodTitle(method) {
407
+ if (method === 'passkey')
408
+ return 'Passkey';
409
+ if (method === 'totp_sms')
410
+ return 'Text message';
411
+ if (method === 'totp_voice')
412
+ return 'Voice call';
413
+ return 'Email code';
414
+ }
415
+ function methodDescription(method) {
416
+ if (method === 'passkey')
417
+ return 'Use Face ID, Touch ID, or your device passcode.';
418
+ if (method === 'totp_sms')
419
+ return 'Receive a one-time code by SMS.';
420
+ if (method === 'totp_voice')
421
+ return 'Receive a one-time code by phone call.';
422
+ return 'Receive a one-time code by email.';
423
+ }
424
+ function identitySubtitle(allowedTypes) {
425
+ if (allowedTypes.includes('email') && allowedTypes.includes('phone'))
426
+ return 'Enter your email or phone number to continue.';
427
+ if (allowedTypes.includes('phone'))
428
+ return 'Enter your phone number to continue.';
429
+ return 'Enter your email address to continue.';
430
+ }
431
+ function identityPlaceholder(allowedTypes) {
432
+ if (allowedTypes.includes('email') && allowedTypes.includes('phone'))
433
+ return 'name@company.com or +14155551234';
434
+ if (allowedTypes.includes('phone'))
435
+ return '+14155551234';
436
+ return 'name@company.com';
437
+ }
438
+ function totpTitle(method) {
439
+ if (method === 'totp_sms')
440
+ return 'Check your messages';
441
+ if (method === 'totp_voice')
442
+ return 'Check your phone';
443
+ return 'Check your email';
444
+ }
445
+ function getHeaderLogo(branding, theme) {
446
+ if (!branding)
447
+ return null;
448
+ return theme === 'dark'
449
+ ? branding.logoDark || branding.logoLight
450
+ : branding.logoLight || branding.logoDark;
451
+ }
452
+ function formatCountdown(seconds) {
453
+ const minutes = Math.floor(seconds / 60);
454
+ const remainingSeconds = seconds % 60;
455
+ return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
456
+ }
457
+ function asError(value, fallback = 'Something went wrong') {
458
+ if (value instanceof Error)
459
+ return value;
460
+ if (typeof value === 'string')
461
+ return new Error(value);
462
+ return new Error(fallback);
463
+ }
464
+ const lightColors = {
465
+ backdrop: 'rgba(15, 23, 42, 0.42)',
466
+ card: '#ffffff',
467
+ input: '#ffffff',
468
+ pressed: '#f8fafc',
469
+ text: '#0f172a',
470
+ muted: '#64748b',
471
+ placeholder: '#94a3b8',
472
+ border: '#e2e8f0',
473
+ primary: '#2563eb',
474
+ primaryText: '#ffffff',
475
+ primarySoft: '#dbeafe',
476
+ error: '#dc2626',
477
+ };
478
+ const darkColors = {
479
+ backdrop: 'rgba(2, 6, 23, 0.72)',
480
+ card: '#0f172a',
481
+ input: '#111827',
482
+ pressed: '#1e293b',
483
+ text: '#f8fafc',
484
+ muted: '#94a3b8',
485
+ placeholder: '#64748b',
486
+ border: '#334155',
487
+ primary: '#60a5fa',
488
+ primaryText: '#0f172a',
489
+ primarySoft: '#172554',
490
+ error: '#f87171',
491
+ };
492
+ const styles = StyleSheet.create({
493
+ backdrop: {
494
+ flex: 1,
495
+ },
496
+ scrollContent: {
497
+ flexGrow: 1,
498
+ justifyContent: 'center',
499
+ padding: 20,
500
+ },
501
+ card: {
502
+ borderWidth: StyleSheet.hairlineWidth,
503
+ borderRadius: 8,
504
+ padding: 20,
505
+ width: '100%',
506
+ maxWidth: 420,
507
+ alignSelf: 'center',
508
+ shadowColor: '#000000',
509
+ shadowOffset: { width: 0, height: 16 },
510
+ shadowOpacity: 0.18,
511
+ shadowRadius: 24,
512
+ elevation: 12,
513
+ },
514
+ header: {
515
+ minHeight: 40,
516
+ flexDirection: 'row',
517
+ alignItems: 'center',
518
+ justifyContent: 'space-between',
519
+ marginBottom: 18,
520
+ },
521
+ brand: {
522
+ minWidth: 0,
523
+ flex: 1,
524
+ flexDirection: 'row',
525
+ alignItems: 'center',
526
+ },
527
+ logo: {
528
+ width: 32,
529
+ height: 32,
530
+ marginRight: 10,
531
+ },
532
+ logoFallback: {
533
+ width: 32,
534
+ height: 32,
535
+ borderRadius: 8,
536
+ alignItems: 'center',
537
+ justifyContent: 'center',
538
+ marginRight: 10,
539
+ },
540
+ logoFallbackText: {
541
+ fontSize: 15,
542
+ fontWeight: '800',
543
+ },
544
+ companyName: {
545
+ flex: 1,
546
+ fontSize: 16,
547
+ fontWeight: '700',
548
+ },
549
+ closeButton: {
550
+ width: 32,
551
+ height: 32,
552
+ alignItems: 'center',
553
+ justifyContent: 'center',
554
+ },
555
+ closeText: {
556
+ fontSize: 20,
557
+ fontWeight: '500',
558
+ lineHeight: 22,
559
+ },
560
+ centerContent: {
561
+ minHeight: 180,
562
+ alignItems: 'center',
563
+ justifyContent: 'center',
564
+ gap: 12,
565
+ },
566
+ body: {
567
+ gap: 14,
568
+ },
569
+ title: {
570
+ fontSize: 24,
571
+ fontWeight: '800',
572
+ letterSpacing: 0,
573
+ },
574
+ subtitle: {
575
+ fontSize: 14,
576
+ lineHeight: 20,
577
+ },
578
+ mutedText: {
579
+ fontSize: 14,
580
+ },
581
+ input: {
582
+ minHeight: 48,
583
+ borderWidth: 1,
584
+ borderRadius: 8,
585
+ paddingHorizontal: 14,
586
+ fontSize: 16,
587
+ },
588
+ pinInput: {
589
+ height: 56,
590
+ borderWidth: 1,
591
+ borderRadius: 8,
592
+ paddingHorizontal: 14,
593
+ fontSize: 26,
594
+ fontWeight: '700',
595
+ letterSpacing: 0,
596
+ textAlign: 'center',
597
+ },
598
+ countdown: {
599
+ fontSize: 13,
600
+ textAlign: 'center',
601
+ },
602
+ error: {
603
+ fontSize: 13,
604
+ lineHeight: 18,
605
+ },
606
+ primaryButton: {
607
+ minHeight: 48,
608
+ borderRadius: 8,
609
+ alignItems: 'center',
610
+ justifyContent: 'center',
611
+ paddingHorizontal: 16,
612
+ },
613
+ primaryButtonText: {
614
+ fontSize: 15,
615
+ fontWeight: '700',
616
+ },
617
+ textButton: {
618
+ minHeight: 40,
619
+ alignItems: 'center',
620
+ justifyContent: 'center',
621
+ },
622
+ textButtonLabel: {
623
+ fontSize: 14,
624
+ fontWeight: '700',
625
+ },
626
+ methodList: {
627
+ gap: 10,
628
+ },
629
+ methodButton: {
630
+ borderWidth: 1,
631
+ borderRadius: 8,
632
+ padding: 14,
633
+ },
634
+ methodTitle: {
635
+ fontSize: 15,
636
+ fontWeight: '800',
637
+ marginBottom: 4,
638
+ },
639
+ methodDescription: {
640
+ fontSize: 13,
641
+ lineHeight: 18,
642
+ },
643
+ passkeyIcon: {
644
+ width: 76,
645
+ height: 76,
646
+ borderRadius: 38,
647
+ alignSelf: 'center',
648
+ alignItems: 'center',
649
+ justifyContent: 'center',
650
+ },
651
+ passkeyIconText: {
652
+ fontSize: 18,
653
+ fontWeight: '800',
654
+ },
655
+ });
656
+ //# sourceMappingURL=identity-dialog.js.map