@authowl/react-native 0.2.1

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/dist/index.js ADDED
@@ -0,0 +1,1160 @@
1
+ // src/client.ts
2
+ import {
3
+ createNativeAuthClient,
4
+ decodePublishableKey,
5
+ getPublicConfig,
6
+ resolveConfig
7
+ } from "@authowl/core/native";
8
+
9
+ // src/cookie-jar.ts
10
+ import { sessionCookieName } from "@authowl/core/native";
11
+ function sessionStorageKey(projectId) {
12
+ return `authowl.session.${projectId}`;
13
+ }
14
+ function readSetCookie(header, name) {
15
+ if (!header) return null;
16
+ const entries = typeof header === "string" ? header.split("\n") : header;
17
+ for (const entry of entries) {
18
+ const trimmed = entry.trim();
19
+ const separator = trimmed.indexOf("=");
20
+ if (separator === -1) continue;
21
+ if (trimmed.slice(0, separator).trim() !== name) continue;
22
+ const value = trimmed.slice(separator + 1).split(";", 1)[0] ?? "";
23
+ return value;
24
+ }
25
+ return null;
26
+ }
27
+ function setCookieValues(headers) {
28
+ const withGetter = headers;
29
+ if (typeof withGetter.getSetCookie === "function") {
30
+ return withGetter.getSetCookie();
31
+ }
32
+ return headers.get("set-cookie");
33
+ }
34
+ function withSessionCookie(headers, name, value) {
35
+ const existing = headers.get("cookie");
36
+ const pair = `${name}=${value}`;
37
+ headers.set("cookie", existing ? `${existing}; ${pair}` : pair);
38
+ }
39
+ function createCookieJarFetch(options) {
40
+ const { storage, projectId, secure } = options;
41
+ const baseFetch = options.fetchImpl ?? globalThis.fetch;
42
+ if (typeof baseFetch !== "function") {
43
+ throw new Error(
44
+ "@authowl/react-native requires a global fetch. Pass `fetchImpl` if your runtime lacks one."
45
+ );
46
+ }
47
+ const cookieName = sessionCookieName(projectId, { secure });
48
+ const storageKey = sessionStorageKey(projectId);
49
+ return async function cookieJarFetch(input, init) {
50
+ const useSessionJar = init?.credentials !== "omit";
51
+ const stored = useSessionJar ? await storage.getItem(storageKey) : null;
52
+ const inherited = init?.headers ?? (typeof input === "object" && input !== null && "headers" in input ? input.headers : void 0);
53
+ const headers = new Headers(inherited);
54
+ if (stored) withSessionCookie(headers, cookieName, stored);
55
+ const response = await baseFetch(input, { ...init, headers });
56
+ const issued = useSessionJar ? readSetCookie(setCookieValues(response.headers), cookieName) : null;
57
+ if (issued !== null) {
58
+ if (issued === "") await storage.removeItem(storageKey);
59
+ else await storage.setItem(storageKey, issued);
60
+ }
61
+ return response;
62
+ };
63
+ }
64
+
65
+ // src/client.ts
66
+ function createAuthOwlNative(config) {
67
+ const { projectId } = decodePublishableKey(config.publishableKey);
68
+ const secure = new URL(config.apiUrl).protocol === "https:";
69
+ const resolved = resolveConfig({
70
+ publishableKey: config.publishableKey,
71
+ apiUrl: config.apiUrl,
72
+ fetch: createCookieJarFetch({
73
+ storage: config.storage,
74
+ projectId,
75
+ secure,
76
+ fetchImpl: config.fetchImpl
77
+ })
78
+ });
79
+ const client = config.passkeys ? createNativeAuthClient(resolved, config.onSessionMutation, config.passkeys) : createNativeAuthClient(resolved, config.onSessionMutation);
80
+ return { client, projectId, getPublicConfig: () => getPublicConfig(resolved) };
81
+ }
82
+
83
+ // src/oauth.ts
84
+ function signInWithSocialIdToken(client, options) {
85
+ return client.signIn.social(options);
86
+ }
87
+
88
+ // src/provider.tsx
89
+ import {
90
+ createContext,
91
+ createElement,
92
+ useCallback,
93
+ useContext,
94
+ useEffect,
95
+ useMemo,
96
+ useState,
97
+ useSyncExternalStore
98
+ } from "react";
99
+ import { createMembershipHas } from "@authowl/core/native";
100
+ var AuthOwlContext = createContext(null);
101
+ function AuthOwlProvider(props) {
102
+ const {
103
+ children,
104
+ publishableKey,
105
+ apiUrl,
106
+ storage,
107
+ fetchImpl,
108
+ onSessionMutation,
109
+ passkeys,
110
+ locale = "en"
111
+ } = props;
112
+ const native = useMemo(() => {
113
+ return createAuthOwlNative({
114
+ publishableKey,
115
+ apiUrl,
116
+ storage,
117
+ fetchImpl,
118
+ onSessionMutation,
119
+ passkeys
120
+ });
121
+ }, [publishableKey, apiUrl, storage, fetchImpl, onSessionMutation, passkeys]);
122
+ const [publicConfig, setPublicConfig] = useState(null);
123
+ const [publicConfigState, setPublicConfigState] = useState("loading");
124
+ useEffect(() => {
125
+ let active = true;
126
+ setPublicConfig(null);
127
+ setPublicConfigState("loading");
128
+ native.getPublicConfig().then((config) => {
129
+ if (!active) return;
130
+ setPublicConfig(config);
131
+ setPublicConfigState("ready");
132
+ }).catch(() => {
133
+ if (!active) return;
134
+ setPublicConfig(null);
135
+ setPublicConfigState("error");
136
+ });
137
+ return () => {
138
+ active = false;
139
+ };
140
+ }, [native]);
141
+ const value = useMemo(() => ({
142
+ client: native.client,
143
+ projectId: native.projectId,
144
+ locale,
145
+ publicConfig,
146
+ publicConfigState
147
+ }), [native, locale, publicConfig, publicConfigState]);
148
+ return createElement(AuthOwlContext.Provider, { value }, children);
149
+ }
150
+ function useAuthOwlContext() {
151
+ const value = useContext(AuthOwlContext);
152
+ if (value === null) {
153
+ throw new Error("AuthOwl hooks must be used inside an <AuthOwlProvider>.");
154
+ }
155
+ return value;
156
+ }
157
+ function useAuthOwlLocale() {
158
+ return useAuthOwlContext().locale;
159
+ }
160
+ function useAuthOwlClient() {
161
+ return useAuthOwlContext().client;
162
+ }
163
+ function usePublicConfig() {
164
+ const { publicConfig, publicConfigState } = useAuthOwlContext();
165
+ return {
166
+ data: publicConfig,
167
+ state: publicConfigState,
168
+ isLoading: publicConfigState === "loading"
169
+ };
170
+ }
171
+ function useSession() {
172
+ const { client } = useAuthOwlContext();
173
+ const store = client.sessionStore;
174
+ return useSyncExternalStore(
175
+ (listener) => store.subscribe(listener),
176
+ () => store.getSnapshot(),
177
+ () => store.getSnapshot()
178
+ );
179
+ }
180
+ function useAuth() {
181
+ const { client } = useAuthOwlContext();
182
+ const state = useSession();
183
+ const membership = state.data?.session.membership ?? null;
184
+ const { has, hasPermission } = useMemo(
185
+ () => createMembershipHas(membership),
186
+ [membership]
187
+ );
188
+ return {
189
+ isLoaded: !state.isPending,
190
+ isSignedIn: state.data !== null,
191
+ user: state.data?.user ?? null,
192
+ session: state.data?.session ?? null,
193
+ signOut: async () => {
194
+ await client.signOut();
195
+ },
196
+ has,
197
+ hasPermission
198
+ };
199
+ }
200
+ function useUser() {
201
+ return useSession().data?.user ?? null;
202
+ }
203
+ function useSocialSignIn() {
204
+ const { client } = useAuthOwlContext();
205
+ return useCallback(
206
+ (options) => signInWithSocialIdToken(client, options),
207
+ [client]
208
+ );
209
+ }
210
+
211
+ // src/components/SignIn.tsx
212
+ import { useState as useState2 } from "react";
213
+ import { Text as Text2, View as View2 } from "react-native";
214
+
215
+ // src/i18n.ts
216
+ import { useMemo as useMemo2 } from "react";
217
+ import {
218
+ directionFor,
219
+ formatMessage,
220
+ resolveServerError
221
+ } from "@authowl/core/i18n";
222
+ function useT() {
223
+ const locale = useAuthOwlLocale();
224
+ return useMemo2(
225
+ () => (key, params) => formatMessage(locale, key, params),
226
+ [locale]
227
+ );
228
+ }
229
+ function useLocale() {
230
+ const locale = useAuthOwlLocale();
231
+ return useMemo2(() => ({ locale, direction: directionFor(locale) }), [locale]);
232
+ }
233
+ function useServerError() {
234
+ const locale = useAuthOwlLocale();
235
+ const t = useT();
236
+ return useMemo2(
237
+ () => (error, fallback) => resolveServerError(locale, error, t(fallback)),
238
+ [locale, t]
239
+ );
240
+ }
241
+
242
+ // src/components/SignIn.tsx
243
+ import { resolveProjectCapabilities } from "@authowl/core/native";
244
+
245
+ // src/components/primitives.tsx
246
+ import { useMemo as useMemo3 } from "react";
247
+ import {
248
+ ActivityIndicator,
249
+ Pressable,
250
+ Text,
251
+ TextInput,
252
+ View
253
+ } from "react-native";
254
+
255
+ // src/components/theme.ts
256
+ import { StyleSheet } from "react-native";
257
+ var defaultTheme = {
258
+ accent: "#F5B84C",
259
+ accentText: "#241703",
260
+ link: "#624A1E",
261
+ text: "#111827",
262
+ mutedText: "#6b7280",
263
+ background: "#ffffff",
264
+ surface: "#f9fafb",
265
+ border: "#d1d5db",
266
+ danger: "#b91c1c",
267
+ radius: 10,
268
+ spacing: 12
269
+ };
270
+ var darkTheme = {
271
+ ...defaultTheme,
272
+ link: "#F5B84C",
273
+ text: "#f9fafb",
274
+ mutedText: "#9ca3af",
275
+ background: "#111827",
276
+ surface: "#1f2937",
277
+ border: "#374151",
278
+ danger: "#fca5a5"
279
+ };
280
+ function createStyles(theme) {
281
+ return StyleSheet.create({
282
+ container: { gap: theme.spacing, backgroundColor: theme.background },
283
+ title: { fontSize: 22, fontWeight: "600", color: theme.text },
284
+ label: { fontSize: 13, fontWeight: "500", color: theme.mutedText },
285
+ field: { gap: 4 },
286
+ input: {
287
+ borderWidth: 1,
288
+ borderColor: theme.border,
289
+ borderRadius: theme.radius,
290
+ paddingHorizontal: theme.spacing,
291
+ paddingVertical: theme.spacing * 0.75,
292
+ fontSize: 16,
293
+ color: theme.text,
294
+ backgroundColor: theme.surface
295
+ },
296
+ inputInvalid: { borderColor: theme.danger },
297
+ button: {
298
+ borderRadius: theme.radius,
299
+ paddingVertical: theme.spacing,
300
+ alignItems: "center",
301
+ backgroundColor: theme.accent
302
+ },
303
+ buttonDisabled: { opacity: 0.5 },
304
+ buttonText: { color: theme.accentText, fontSize: 16, fontWeight: "600" },
305
+ link: { color: theme.link ?? theme.accent, fontSize: 14 },
306
+ consentRow: {
307
+ flexDirection: "row",
308
+ alignItems: "flex-start",
309
+ gap: 8
310
+ },
311
+ consentToggle: {
312
+ minWidth: 24,
313
+ height: 19,
314
+ alignItems: "center",
315
+ justifyContent: "center"
316
+ },
317
+ consentBox: {
318
+ width: 18,
319
+ height: 18,
320
+ alignItems: "center",
321
+ justifyContent: "center",
322
+ borderWidth: 1,
323
+ borderColor: theme.border,
324
+ borderRadius: 4,
325
+ backgroundColor: theme.surface
326
+ },
327
+ consentBoxChecked: {
328
+ borderColor: theme.accent,
329
+ backgroundColor: theme.accent
330
+ },
331
+ consentBoxDisabled: { opacity: 0.55 },
332
+ consentCheck: {
333
+ color: theme.accentText,
334
+ fontSize: 13,
335
+ fontWeight: "700",
336
+ lineHeight: 16
337
+ },
338
+ consentText: {
339
+ flex: 1,
340
+ color: theme.text,
341
+ fontSize: 13,
342
+ lineHeight: 19
343
+ },
344
+ consentLink: {
345
+ color: theme.link ?? theme.accent,
346
+ textDecorationLine: "underline"
347
+ },
348
+ error: { color: theme.danger, fontSize: 14 }
349
+ });
350
+ }
351
+
352
+ // src/components/primitives.tsx
353
+ import { jsx, jsxs } from "react/jsx-runtime";
354
+ function useStyles(theme = defaultTheme) {
355
+ return useMemo3(() => createStyles(theme), [theme]);
356
+ }
357
+ function Field({
358
+ label,
359
+ value,
360
+ onChangeText,
361
+ theme = defaultTheme,
362
+ placeholder,
363
+ secure = false,
364
+ invalid = false,
365
+ editable = true,
366
+ testID,
367
+ autoComplete,
368
+ keyboardType,
369
+ maxLength,
370
+ onSubmitEditing
371
+ }) {
372
+ const styles = useStyles(theme);
373
+ return /* @__PURE__ */ jsxs(View, { style: styles.field, children: [
374
+ /* @__PURE__ */ jsx(Text, { style: styles.label, children: label }),
375
+ /* @__PURE__ */ jsx(
376
+ TextInput,
377
+ {
378
+ style: invalid ? [styles.input, styles.inputInvalid] : styles.input,
379
+ value,
380
+ onChangeText,
381
+ placeholder,
382
+ placeholderTextColor: theme.mutedText,
383
+ secureTextEntry: secure,
384
+ editable,
385
+ testID,
386
+ autoCapitalize: "none",
387
+ autoCorrect: false,
388
+ autoComplete,
389
+ keyboardType,
390
+ maxLength,
391
+ accessibilityLabel: label,
392
+ onSubmitEditing
393
+ }
394
+ )
395
+ ] });
396
+ }
397
+ function SubmitButton({
398
+ label,
399
+ busyLabel,
400
+ onPress,
401
+ busy = false,
402
+ disabled = false,
403
+ theme = defaultTheme,
404
+ testID
405
+ }) {
406
+ const styles = useStyles(theme);
407
+ const blocked = busy || disabled;
408
+ return /* @__PURE__ */ jsxs(
409
+ Pressable,
410
+ {
411
+ style: blocked ? [styles.button, styles.buttonDisabled] : styles.button,
412
+ onPress,
413
+ disabled: blocked,
414
+ testID,
415
+ accessibilityRole: "button",
416
+ accessibilityState: { disabled: blocked, busy },
417
+ children: [
418
+ busy ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.accentText }) : null,
419
+ /* @__PURE__ */ jsx(Text, { style: styles.buttonText, children: busy ? busyLabel : label })
420
+ ]
421
+ }
422
+ );
423
+ }
424
+ function FormError({
425
+ message,
426
+ theme = defaultTheme,
427
+ testID = "authowl-error"
428
+ }) {
429
+ const styles = useStyles(theme);
430
+ if (!message) return null;
431
+ return /* @__PURE__ */ jsx(View, { accessibilityLiveRegion: "polite", children: /* @__PURE__ */ jsx(Text, { style: styles.error, testID, children: message }) });
432
+ }
433
+
434
+ // src/components/SignIn.tsx
435
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
436
+ function SignIn({
437
+ onSignedIn,
438
+ onSecondFactorRequired,
439
+ onForgotPassword,
440
+ theme = defaultTheme
441
+ }) {
442
+ const t = useT();
443
+ const toMessage = useServerError();
444
+ const client = useAuthOwlClient();
445
+ const config = usePublicConfig();
446
+ const capabilities = resolveProjectCapabilities(config.data);
447
+ const styles = useStyles(theme);
448
+ const [email, setEmail] = useState2("");
449
+ const [password, setPassword] = useState2("");
450
+ const [busy, setBusy] = useState2(false);
451
+ const [error, setError] = useState2(null);
452
+ const canSubmit = email.trim().length > 0 && password.length > 0;
453
+ async function submit() {
454
+ if (busy || !canSubmit) return;
455
+ setBusy(true);
456
+ setError(null);
457
+ try {
458
+ const result = await client.signIn.email({ email: email.trim(), password });
459
+ if (result.error !== null) {
460
+ setError(toMessage(result.error, "signIn.error.failed"));
461
+ return;
462
+ }
463
+ if (result.data !== null && "twoFactorRedirect" in result.data) {
464
+ onSecondFactorRequired?.();
465
+ return;
466
+ }
467
+ onSignedIn?.();
468
+ } catch {
469
+ setError(t("signIn.error.failed"));
470
+ } finally {
471
+ setBusy(false);
472
+ }
473
+ }
474
+ if (config.isLoading || config.data !== null && !capabilities.passwordSignIn) return null;
475
+ return /* @__PURE__ */ jsxs2(View2, { style: styles.container, testID: "authowl-signin", children: [
476
+ /* @__PURE__ */ jsx2(Text2, { style: styles.title, children: t("signIn.title") }),
477
+ /* @__PURE__ */ jsx2(
478
+ Field,
479
+ {
480
+ label: t("common.emailLabel"),
481
+ value: email,
482
+ onChangeText: setEmail,
483
+ theme,
484
+ editable: !busy,
485
+ testID: "authowl-signin-email",
486
+ autoComplete: "email",
487
+ keyboardType: "email-address"
488
+ }
489
+ ),
490
+ /* @__PURE__ */ jsx2(
491
+ Field,
492
+ {
493
+ label: t("common.passwordLabel"),
494
+ value: password,
495
+ onChangeText: setPassword,
496
+ theme,
497
+ secure: true,
498
+ editable: !busy,
499
+ testID: "authowl-signin-password",
500
+ autoComplete: "current-password",
501
+ onSubmitEditing: submit
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsx2(FormError, { message: error, theme }),
505
+ /* @__PURE__ */ jsx2(
506
+ SubmitButton,
507
+ {
508
+ label: t("signIn.submit"),
509
+ busyLabel: t("signIn.submitPending"),
510
+ onPress: submit,
511
+ busy,
512
+ disabled: !canSubmit,
513
+ theme,
514
+ testID: "authowl-signin-submit"
515
+ }
516
+ ),
517
+ onForgotPassword ? /* @__PURE__ */ jsx2(Text2, { style: styles.link, onPress: onForgotPassword, testID: "authowl-signin-forgot", children: t("signIn.forgotLink") }) : null
518
+ ] });
519
+ }
520
+
521
+ // src/components/SignUp.tsx
522
+ import { useEffect as useEffect2, useState as useState3 } from "react";
523
+ import { Linking, Pressable as Pressable2, Text as Text3, View as View3 } from "react-native";
524
+ import { resolveProjectCapabilities as resolveProjectCapabilities2 } from "@authowl/core/native";
525
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
526
+ function SignUp({ onSignedUp, structuredName = false, theme = defaultTheme }) {
527
+ const t = useT();
528
+ const { direction } = useLocale();
529
+ const toMessage = useServerError();
530
+ const client = useAuthOwlClient();
531
+ const config = usePublicConfig();
532
+ const capabilities = resolveProjectCapabilities2(config.data);
533
+ const styles = useStyles(theme);
534
+ const [email, setEmail] = useState3("");
535
+ const [password, setPassword] = useState3("");
536
+ const [name, setName] = useState3("");
537
+ const [firstName, setFirstName] = useState3("");
538
+ const [lastName, setLastName] = useState3("");
539
+ const [busy, setBusy] = useState3(false);
540
+ const [error, setError] = useState3(null);
541
+ const [acceptedConsent, setAcceptedConsent] = useState3(false);
542
+ const legal = config.data?.legal;
543
+ useEffect2(() => {
544
+ setAcceptedConsent(false);
545
+ }, [legal?.version]);
546
+ const useStructuredName = structuredName || capabilities.firstLastName;
547
+ const displayName = useStructuredName ? [firstName.trim(), lastName.trim()].filter(Boolean).join(" ") : name.trim();
548
+ const canSubmit = email.trim().length > 0 && password.length >= capabilities.passwordMinLength && password.length <= capabilities.passwordMaxLength && displayName.length > 0 && (!legal?.required || acceptedConsent);
549
+ async function submit() {
550
+ if (busy || !canSubmit) return;
551
+ setBusy(true);
552
+ setError(null);
553
+ const result = await client.signUp.email({
554
+ email: email.trim(),
555
+ password,
556
+ name: displayName,
557
+ ...legal?.required ? { consentVersion: legal.version } : {},
558
+ ...useStructuredName ? { firstName: firstName.trim(), lastName: lastName.trim() } : {}
559
+ });
560
+ setBusy(false);
561
+ if (result.error !== null) {
562
+ setError(toMessage(result.error, "signUp.error.failed"));
563
+ return;
564
+ }
565
+ onSignedUp?.({ sessionCreated: result.data?.sessionCreated === true });
566
+ }
567
+ if (config.isLoading || config.data !== null && !capabilities.passwordSignUp) return null;
568
+ const termsUrl = legal?.termsUrl;
569
+ const privacyUrl = legal?.privacyUrl;
570
+ const consentDocuments = [
571
+ termsUrl ? t("consent.termsOfService") : null,
572
+ privacyUrl ? t("consent.privacyPolicy") : null
573
+ ].filter((value) => value !== null).join(t("consent.docJoiner"));
574
+ const consentTemplate = t("signUp.consentLabel");
575
+ const consentMarker = consentTemplate.indexOf("{links}");
576
+ const consentPrefix = consentMarker === -1 ? consentTemplate : consentTemplate.slice(0, consentMarker);
577
+ const consentSuffix = consentMarker === -1 ? "" : consentTemplate.slice(consentMarker + "{links}".length);
578
+ return /* @__PURE__ */ jsxs3(View3, { style: styles.container, testID: "authowl-signup", children: [
579
+ /* @__PURE__ */ jsx3(Text3, { style: styles.title, children: t("signUp.title") }),
580
+ useStructuredName ? /* @__PURE__ */ jsxs3(Fragment, { children: [
581
+ /* @__PURE__ */ jsx3(
582
+ Field,
583
+ {
584
+ label: t("signUp.firstNameLabel"),
585
+ value: firstName,
586
+ onChangeText: setFirstName,
587
+ theme,
588
+ editable: !busy,
589
+ testID: "authowl-signup-first-name",
590
+ autoComplete: "given-name"
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsx3(
594
+ Field,
595
+ {
596
+ label: t("signUp.lastNameLabel"),
597
+ value: lastName,
598
+ onChangeText: setLastName,
599
+ theme,
600
+ editable: !busy,
601
+ testID: "authowl-signup-last-name",
602
+ autoComplete: "family-name"
603
+ }
604
+ )
605
+ ] }) : /* @__PURE__ */ jsx3(
606
+ Field,
607
+ {
608
+ label: t("signUp.nameLabel"),
609
+ value: name,
610
+ onChangeText: setName,
611
+ theme,
612
+ editable: !busy,
613
+ testID: "authowl-signup-name",
614
+ autoComplete: "name"
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsx3(
618
+ Field,
619
+ {
620
+ label: t("common.emailLabel"),
621
+ value: email,
622
+ onChangeText: setEmail,
623
+ theme,
624
+ editable: !busy,
625
+ testID: "authowl-signup-email",
626
+ autoComplete: "email",
627
+ keyboardType: "email-address"
628
+ }
629
+ ),
630
+ /* @__PURE__ */ jsx3(
631
+ Field,
632
+ {
633
+ label: t("common.passwordLabel"),
634
+ value: password,
635
+ onChangeText: setPassword,
636
+ theme,
637
+ secure: true,
638
+ editable: !busy,
639
+ testID: "authowl-signup-password",
640
+ autoComplete: "new-password",
641
+ maxLength: capabilities.passwordMaxLength,
642
+ onSubmitEditing: submit
643
+ }
644
+ ),
645
+ legal?.required ? /* @__PURE__ */ jsxs3(View3, { style: [styles.consentRow, { direction }], children: [
646
+ /* @__PURE__ */ jsx3(
647
+ Pressable2,
648
+ {
649
+ onPress: () => setAcceptedConsent((accepted) => !accepted),
650
+ disabled: busy,
651
+ hitSlop: 8,
652
+ style: styles.consentToggle,
653
+ testID: "authowl-signup-consent",
654
+ accessibilityRole: "checkbox",
655
+ accessibilityState: { checked: acceptedConsent },
656
+ accessibilityLabel: t("signUp.consentLabel", { links: consentDocuments }),
657
+ children: /* @__PURE__ */ jsx3(
658
+ View3,
659
+ {
660
+ style: [
661
+ styles.consentBox,
662
+ acceptedConsent && styles.consentBoxChecked,
663
+ busy && styles.consentBoxDisabled
664
+ ],
665
+ testID: "authowl-signup-consent-box",
666
+ children: acceptedConsent ? /* @__PURE__ */ jsx3(Text3, { style: styles.consentCheck, children: "\u2713" }) : null
667
+ }
668
+ )
669
+ }
670
+ ),
671
+ /* @__PURE__ */ jsxs3(Text3, { style: styles.consentText, children: [
672
+ consentPrefix,
673
+ termsUrl ? /* @__PURE__ */ jsx3(
674
+ Text3,
675
+ {
676
+ style: styles.consentLink,
677
+ onPress: () => void Linking.openURL(termsUrl),
678
+ testID: "authowl-signup-terms",
679
+ children: t("consent.termsOfService")
680
+ }
681
+ ) : null,
682
+ termsUrl && privacyUrl ? t("consent.docJoiner") : null,
683
+ privacyUrl ? /* @__PURE__ */ jsx3(
684
+ Text3,
685
+ {
686
+ style: styles.consentLink,
687
+ onPress: () => void Linking.openURL(privacyUrl),
688
+ testID: "authowl-signup-privacy",
689
+ children: t("consent.privacyPolicy")
690
+ }
691
+ ) : null,
692
+ consentSuffix
693
+ ] })
694
+ ] }) : null,
695
+ /* @__PURE__ */ jsx3(FormError, { message: error, theme }),
696
+ /* @__PURE__ */ jsx3(
697
+ SubmitButton,
698
+ {
699
+ label: t("signUp.submit"),
700
+ busyLabel: t("signUp.submitPending"),
701
+ onPress: submit,
702
+ busy,
703
+ disabled: !canSubmit,
704
+ theme,
705
+ testID: "authowl-signup-submit"
706
+ }
707
+ )
708
+ ] });
709
+ }
710
+
711
+ // src/components/EmailOtpForm.tsx
712
+ import { useState as useState4 } from "react";
713
+ import { Text as Text4, View as View4 } from "react-native";
714
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
715
+ function EmailOtpForm({ onSignedIn, theme = defaultTheme }) {
716
+ const t = useT();
717
+ const toMessage = useServerError();
718
+ const client = useAuthOwlClient();
719
+ const styles = useStyles(theme);
720
+ const [stage, setStage] = useState4("email");
721
+ const [email, setEmail] = useState4("");
722
+ const [code, setCode] = useState4("");
723
+ const [busy, setBusy] = useState4(false);
724
+ const [error, setError] = useState4(null);
725
+ async function requestCode() {
726
+ if (busy || email.trim().length === 0) return;
727
+ setBusy(true);
728
+ setError(null);
729
+ const result = await client.emailOtp.sendVerificationOtp({
730
+ email: email.trim(),
731
+ type: "sign-in"
732
+ });
733
+ setBusy(false);
734
+ if (result.error !== null) {
735
+ setError(toMessage(result.error, "emailOtp.error.sendFailed"));
736
+ return;
737
+ }
738
+ setStage("code");
739
+ }
740
+ async function verifyCode() {
741
+ if (busy || code.trim().length === 0) return;
742
+ setBusy(true);
743
+ setError(null);
744
+ const result = await client.signIn.emailOtp({ email: email.trim(), otp: code.trim() });
745
+ setBusy(false);
746
+ if (result.error !== null) {
747
+ setError(toMessage(result.error, "emailOtp.error.invalidCode"));
748
+ return;
749
+ }
750
+ onSignedIn?.();
751
+ }
752
+ function changeEmail() {
753
+ setStage("email");
754
+ setCode("");
755
+ setError(null);
756
+ }
757
+ if (stage === "email") {
758
+ return /* @__PURE__ */ jsxs4(View4, { style: styles.container, testID: "authowl-emailotp", children: [
759
+ /* @__PURE__ */ jsx4(
760
+ Field,
761
+ {
762
+ label: t("common.emailLabel"),
763
+ value: email,
764
+ onChangeText: setEmail,
765
+ theme,
766
+ editable: !busy,
767
+ testID: "authowl-emailotp-email",
768
+ autoComplete: "email",
769
+ keyboardType: "email-address",
770
+ onSubmitEditing: requestCode
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsx4(FormError, { message: error, theme }),
774
+ /* @__PURE__ */ jsx4(
775
+ SubmitButton,
776
+ {
777
+ label: t("emailOtp.requestSubmit"),
778
+ busyLabel: t("common.sending"),
779
+ onPress: requestCode,
780
+ busy,
781
+ disabled: email.trim().length === 0,
782
+ theme,
783
+ testID: "authowl-emailotp-request"
784
+ }
785
+ )
786
+ ] });
787
+ }
788
+ return /* @__PURE__ */ jsxs4(View4, { style: styles.container, testID: "authowl-emailotp", children: [
789
+ /* @__PURE__ */ jsx4(
790
+ Field,
791
+ {
792
+ label: t("emailOtp.codeLabel", { email: email.trim() }),
793
+ value: code,
794
+ onChangeText: setCode,
795
+ theme,
796
+ editable: !busy,
797
+ testID: "authowl-emailotp-code",
798
+ keyboardType: "number-pad",
799
+ autoComplete: "one-time-code",
800
+ onSubmitEditing: verifyCode
801
+ }
802
+ ),
803
+ /* @__PURE__ */ jsx4(FormError, { message: error, theme }),
804
+ /* @__PURE__ */ jsx4(
805
+ SubmitButton,
806
+ {
807
+ label: t("emailOtp.verifySubmit"),
808
+ busyLabel: t("common.verifying"),
809
+ onPress: verifyCode,
810
+ busy,
811
+ disabled: code.trim().length === 0,
812
+ theme,
813
+ testID: "authowl-emailotp-verify"
814
+ }
815
+ ),
816
+ /* @__PURE__ */ jsx4(Text4, { style: styles.link, onPress: changeEmail, testID: "authowl-emailotp-change", children: t("emailOtp.changeEmail") })
817
+ ] });
818
+ }
819
+
820
+ // src/components/OrganizationSwitcher.tsx
821
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef, useState as useState5 } from "react";
822
+ import { Pressable as Pressable3, Text as Text5, View as View5 } from "react-native";
823
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
824
+ function OrganizationSwitcher({
825
+ onSwitched,
826
+ allowPersonal = false,
827
+ theme = defaultTheme
828
+ }) {
829
+ const t = useT();
830
+ const toMessage = useServerError();
831
+ const client = useAuthOwlClient();
832
+ const config = usePublicConfig();
833
+ const session = useSession();
834
+ const styles = useStyles(theme);
835
+ const [organizations, setOrganizations] = useState5(null);
836
+ const [switching, setSwitching] = useState5(null);
837
+ const [error, setError] = useState5(null);
838
+ const activeId = session.data?.session.activeOrganizationId ?? null;
839
+ const userId = session.data?.user.id ?? null;
840
+ const signedIn = userId !== null;
841
+ const requestGeneration = useRef(0);
842
+ const load = useCallback2(async () => {
843
+ const generation = ++requestGeneration.current;
844
+ setError(null);
845
+ const result = await client.organization.list();
846
+ if (generation !== requestGeneration.current) return;
847
+ if (result.error !== null) {
848
+ setError(toMessage(result.error, "organization.error.load"));
849
+ return;
850
+ }
851
+ setOrganizations(result.data ?? []);
852
+ }, [client, toMessage]);
853
+ useEffect3(() => {
854
+ requestGeneration.current += 1;
855
+ setOrganizations(null);
856
+ setError(null);
857
+ if (userId === null) return;
858
+ void load();
859
+ return () => {
860
+ requestGeneration.current += 1;
861
+ };
862
+ }, [userId, load]);
863
+ async function switchTo(organization) {
864
+ if (switching !== null) return;
865
+ setSwitching(organization?.id ?? "personal");
866
+ setError(null);
867
+ const result = await client.organization.setActive({
868
+ organizationId: organization?.id ?? null
869
+ });
870
+ setSwitching(null);
871
+ if (result.error !== null) {
872
+ setError(toMessage(result.error, "organization.switcher.error"));
873
+ return;
874
+ }
875
+ onSwitched?.(result.data ?? null);
876
+ }
877
+ if (config.isLoading || config.data !== null && !config.data.organizations) return null;
878
+ if (!signedIn) {
879
+ return /* @__PURE__ */ jsx5(View5, { style: styles.container, testID: "authowl-orgswitcher", children: /* @__PURE__ */ jsx5(Text5, { style: styles.label, children: t("organization.signedOut") }) });
880
+ }
881
+ if (organizations === null && error === null) {
882
+ return /* @__PURE__ */ jsx5(View5, { style: styles.container, testID: "authowl-orgswitcher", children: /* @__PURE__ */ jsx5(Text5, { style: styles.label, children: t("organization.loading") }) });
883
+ }
884
+ return /* @__PURE__ */ jsxs5(View5, { style: styles.container, testID: "authowl-orgswitcher", children: [
885
+ /* @__PURE__ */ jsx5(Text5, { style: styles.label, children: t("organization.switcher.label") }),
886
+ allowPersonal ? /* @__PURE__ */ jsx5(
887
+ OrganizationRow,
888
+ {
889
+ label: t("organization.personal"),
890
+ selected: activeId === null,
891
+ busy: switching === "personal",
892
+ disabled: switching !== null,
893
+ onPress: () => void switchTo(null),
894
+ testID: "authowl-orgswitcher-personal",
895
+ theme
896
+ }
897
+ ) : null,
898
+ (organizations ?? []).map((organization) => /* @__PURE__ */ jsx5(
899
+ OrganizationRow,
900
+ {
901
+ label: organization.name,
902
+ selected: organization.id === activeId,
903
+ busy: switching === organization.id,
904
+ disabled: switching !== null,
905
+ onPress: () => void switchTo(organization),
906
+ testID: `authowl-orgswitcher-${organization.id}`,
907
+ theme
908
+ },
909
+ organization.id
910
+ )),
911
+ /* @__PURE__ */ jsx5(FormError, { message: error, theme, testID: "authowl-orgswitcher-error" }),
912
+ error !== null ? /* @__PURE__ */ jsx5(
913
+ Text5,
914
+ {
915
+ style: styles.link,
916
+ onPress: () => void load(),
917
+ testID: "authowl-orgswitcher-retry",
918
+ children: t("organization.retry")
919
+ }
920
+ ) : null
921
+ ] });
922
+ }
923
+ function OrganizationRow({
924
+ label,
925
+ selected,
926
+ busy,
927
+ disabled,
928
+ onPress,
929
+ testID,
930
+ theme
931
+ }) {
932
+ const styles = useStyles(theme);
933
+ return /* @__PURE__ */ jsx5(
934
+ Pressable3,
935
+ {
936
+ onPress,
937
+ disabled: disabled || selected,
938
+ testID,
939
+ accessibilityRole: "button",
940
+ accessibilityState: { disabled: disabled || selected, busy },
941
+ children: /* @__PURE__ */ jsx5(Text5, { style: selected ? [styles.label, { color: theme.accent }] : styles.label, children: label })
942
+ }
943
+ );
944
+ }
945
+
946
+ // src/components/PasskeyEnrollment.tsx
947
+ import { useState as useState6 } from "react";
948
+ import { Text as Text6, View as View6 } from "react-native";
949
+ import { resolveProjectCapabilities as resolveProjectCapabilities3 } from "@authowl/core/native";
950
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
951
+ function usePasskeyClient() {
952
+ const client = useAuthOwlClient();
953
+ return "addPasskey" in client.passkey ? client : null;
954
+ }
955
+ function PasskeyEnrollment({
956
+ name,
957
+ onEnrolled,
958
+ onSkip,
959
+ theme = defaultTheme
960
+ }) {
961
+ const t = useT();
962
+ const toMessage = useServerError();
963
+ const client = usePasskeyClient();
964
+ const config = usePublicConfig();
965
+ const capabilities = resolveProjectCapabilities3(config.data);
966
+ const styles = useStyles(theme);
967
+ const [busy, setBusy] = useState6(false);
968
+ const [error, setError] = useState6(null);
969
+ if (client === null || config.isLoading || config.data !== null && !capabilities.passkeyAdd) {
970
+ return null;
971
+ }
972
+ async function enrol() {
973
+ if (busy || client === null) return;
974
+ setBusy(true);
975
+ setError(null);
976
+ const result = await client.passkey.addPasskey(name === void 0 ? {} : { name });
977
+ setBusy(false);
978
+ if (result.error !== null) {
979
+ setError(toMessage(result.error, "signUp.error.passkeyFailed"));
980
+ return;
981
+ }
982
+ onEnrolled?.();
983
+ }
984
+ return /* @__PURE__ */ jsxs6(View6, { style: styles.container, testID: "authowl-passkey-enrollment", children: [
985
+ /* @__PURE__ */ jsx6(Text6, { style: styles.title, children: t("signUp.passkeyTitle") }),
986
+ /* @__PURE__ */ jsx6(Text6, { style: styles.label, children: t("signUp.passkeyDescription") }),
987
+ /* @__PURE__ */ jsx6(FormError, { message: error, theme, testID: "authowl-passkey-error" }),
988
+ /* @__PURE__ */ jsx6(
989
+ SubmitButton,
990
+ {
991
+ label: t("signUp.passkeySubmit"),
992
+ busyLabel: t("passkey.waiting"),
993
+ onPress: () => void enrol(),
994
+ busy,
995
+ theme,
996
+ testID: "authowl-passkey-submit"
997
+ }
998
+ ),
999
+ onSkip ? /* @__PURE__ */ jsx6(Text6, { style: styles.link, onPress: onSkip, testID: "authowl-passkey-skip", children: t("signUp.passkeySkip") }) : null
1000
+ ] });
1001
+ }
1002
+ function PasskeySignInButton({
1003
+ onSignedIn,
1004
+ theme = defaultTheme
1005
+ }) {
1006
+ const t = useT();
1007
+ const toMessage = useServerError();
1008
+ const client = usePasskeyClient();
1009
+ const config = usePublicConfig();
1010
+ const capabilities = resolveProjectCapabilities3(config.data);
1011
+ const styles = useStyles(theme);
1012
+ const [busy, setBusy] = useState6(false);
1013
+ const [error, setError] = useState6(null);
1014
+ if (client === null || config.isLoading || config.data !== null && !capabilities.passkeySignIn) return null;
1015
+ async function signIn() {
1016
+ if (busy || client === null) return;
1017
+ setBusy(true);
1018
+ setError(null);
1019
+ const result = await client.signIn.passkey();
1020
+ setBusy(false);
1021
+ if (result.error !== null) {
1022
+ setError(toMessage(result.error, "passkey.error.signInFailed"));
1023
+ return;
1024
+ }
1025
+ onSignedIn?.();
1026
+ }
1027
+ return /* @__PURE__ */ jsxs6(View6, { style: styles.container, testID: "authowl-passkey-signin", children: [
1028
+ /* @__PURE__ */ jsx6(
1029
+ SubmitButton,
1030
+ {
1031
+ label: t("passkey.signInButton"),
1032
+ busyLabel: t("passkey.waiting"),
1033
+ onPress: () => void signIn(),
1034
+ busy,
1035
+ theme,
1036
+ testID: "authowl-passkey-signin-submit"
1037
+ }
1038
+ ),
1039
+ /* @__PURE__ */ jsx6(FormError, { message: error, theme, testID: "authowl-passkey-signin-error" })
1040
+ ] });
1041
+ }
1042
+
1043
+ // src/passkeys.ts
1044
+ import {
1045
+ createPasskeyClient
1046
+ } from "@authowl/core/native";
1047
+ function createNativePasskeys(adapter) {
1048
+ return (http, sessionChanged) => createPasskeyClient(http, sessionChanged, {
1049
+ register: ({ optionsJSON }) => adapter.register(optionsJSON),
1050
+ authenticate: ({ optionsJSON }) => adapter.authenticate(optionsJSON),
1051
+ errorCode: adapter.errorCode?.bind(adapter)
1052
+ });
1053
+ }
1054
+
1055
+ // src/components/SocialButtons.tsx
1056
+ import { useState as useState7 } from "react";
1057
+ import { View as View7 } from "react-native";
1058
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1059
+ function SocialButtons({
1060
+ providers,
1061
+ onSignedIn,
1062
+ theme = defaultTheme
1063
+ }) {
1064
+ const t = useT();
1065
+ const toMessage = useServerError();
1066
+ const client = useAuthOwlClient();
1067
+ const styles = useStyles(theme);
1068
+ const [pending, setPending] = useState7(null);
1069
+ const [error, setError] = useState7(null);
1070
+ async function signIn(provider) {
1071
+ if (pending !== null) return;
1072
+ setPending(provider.id);
1073
+ setError(null);
1074
+ try {
1075
+ const idToken = await provider.getIdToken();
1076
+ if (idToken === null) return;
1077
+ const result = await client.signIn.social({ provider: provider.id, idToken });
1078
+ if (result.error !== null) {
1079
+ setError(toMessage(result.error, "social.error.startFailed"));
1080
+ return;
1081
+ }
1082
+ onSignedIn?.();
1083
+ } catch {
1084
+ setError(t("social.error.startFailed"));
1085
+ } finally {
1086
+ setPending(null);
1087
+ }
1088
+ }
1089
+ if (providers.length === 0) return null;
1090
+ return /* @__PURE__ */ jsxs7(View7, { style: styles.container, testID: "authowl-social", children: [
1091
+ providers.map((provider) => /* @__PURE__ */ jsx7(
1092
+ SubmitButton,
1093
+ {
1094
+ label: t("social.continueWith", { provider: provider.label }),
1095
+ busyLabel: t("social.redirecting"),
1096
+ onPress: () => {
1097
+ void signIn(provider);
1098
+ },
1099
+ busy: pending === provider.id,
1100
+ disabled: pending !== null && pending !== provider.id,
1101
+ theme,
1102
+ testID: `authowl-social-${provider.id}`
1103
+ },
1104
+ provider.id
1105
+ )),
1106
+ /* @__PURE__ */ jsx7(FormError, { message: error, theme, testID: "authowl-social-error" })
1107
+ ] });
1108
+ }
1109
+
1110
+ // src/storage.ts
1111
+ var MemoryStorage = class {
1112
+ entries = /* @__PURE__ */ new Map();
1113
+ async getItem(key) {
1114
+ return this.entries.get(key) ?? null;
1115
+ }
1116
+ async setItem(key, value) {
1117
+ this.entries.set(key, value);
1118
+ }
1119
+ async removeItem(key) {
1120
+ this.entries.delete(key);
1121
+ }
1122
+ };
1123
+
1124
+ // src/index.ts
1125
+ import { sessionCookieName as sessionCookieName2 } from "@authowl/core/native";
1126
+ export {
1127
+ AuthOwlProvider,
1128
+ EmailOtpForm,
1129
+ Field,
1130
+ FormError,
1131
+ MemoryStorage,
1132
+ OrganizationSwitcher,
1133
+ PasskeyEnrollment,
1134
+ PasskeySignInButton,
1135
+ SignIn,
1136
+ SignUp,
1137
+ SocialButtons,
1138
+ SubmitButton,
1139
+ createAuthOwlNative,
1140
+ createCookieJarFetch,
1141
+ createNativePasskeys,
1142
+ createStyles,
1143
+ darkTheme,
1144
+ defaultTheme,
1145
+ readSetCookie,
1146
+ sessionCookieName2 as sessionCookieName,
1147
+ sessionStorageKey,
1148
+ signInWithSocialIdToken,
1149
+ useAuth,
1150
+ useAuthOwlClient,
1151
+ useAuthOwlLocale,
1152
+ useLocale,
1153
+ usePublicConfig,
1154
+ useServerError,
1155
+ useSession,
1156
+ useSocialSignIn,
1157
+ useStyles,
1158
+ useT,
1159
+ useUser
1160
+ };