@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/LICENSE +21 -0
- package/README.md +225 -0
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/dist/index.cjs +1193 -0
- package/dist/index.d.cts +654 -0
- package/dist/index.d.ts +654 -0
- package/dist/index.js +1160 -0
- package/package.json +69 -0
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
|
+
};
|