@faire-auth/expo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Faire Auth Expo Plugin
2
+
3
+ This plugin integrates Faire Auth with Expo, allowing you to easily add authentication to your Expo (React Native) applications. It supports both Expo native and web apps.
4
+
5
+ ## Installation
6
+
7
+ To get started, install the necessary packages:
8
+
9
+ ```bash
10
+ # Using npm
11
+ npm install faire-auth @faire-auth/expo
12
+
13
+ # Using yarn
14
+ yarn add faire-auth @faire-auth/expo
15
+
16
+ # Using pnpm
17
+ pnpm add faire-auth @faire-auth/expo
18
+
19
+ # Using bun
20
+ bun add faire-auth @faire-auth/expo
21
+ ```
22
+
23
+ You will also need to install `expo-secure-store` for secure session and cookie storage in your Expo app:
24
+
25
+ ```bash
26
+ npm install expo-secure-store
27
+ # or
28
+ yarn add expo-secure-store
29
+ # or
30
+ pnpm add expo-secure-store
31
+ # or
32
+ bun add expo-secure-store
33
+ ```
34
+
35
+ ## Basic Usage
36
+
37
+ ### Configure the Faire Auth Backend
38
+
39
+ Ensure you have a Faire Auth backend set up. You can follow the main [Installation Guide](https://www.faire-auth.com/docs/installation).
40
+
41
+ Then, add the Expo plugin to your Faire Auth server configuration (e.g., in your `auth.ts` or `lib/auth.ts` file):
42
+
43
+ ```typescript
44
+ // lib/auth.ts
45
+ import { faireAuth } from "faire-auth";
46
+ import { expo } from "@faire-auth/expo"; // Import the server plugin
47
+
48
+ export const auth = faireAuth({
49
+ // ...your other Faire Auth options
50
+ baseURL: "http://localhost:8081", // The base URL of your application server where the routes are mounted.
51
+ plugins: [expo()], // Add the Expo server plugin
52
+ emailAndPassword: {
53
+ enabled: true,
54
+ },
55
+ // Add other configurations like trustedOrigins
56
+ trustedOrigins: ["myapp://"] // Replace "myapp" with your app's scheme
57
+ });
58
+ ```
59
+
60
+ ### Initialize the Faire Auth Client in Expo
61
+
62
+ In your Expo app, initialize the client (e.g., in `lib/auth-client.ts`):
63
+
64
+ ```typescript
65
+ // lib/auth-client.ts
66
+ import { createAuthClient } from "faire-auth/react";
67
+ import { expoClient } from "@faire-auth/expo/client"; // Import the client plugin
68
+ import * as SecureStore from "expo-secure-store";
69
+
70
+ export const authClient = createAuthClient({
71
+ baseURL: "http://localhost:8081", // Your Faire Auth backend URL
72
+ plugins: [
73
+ expoClient({
74
+ scheme: "myapp", // Your app's scheme (defined in app.json)
75
+ storagePrefix: "myapp", // A prefix for storage keys
76
+ storage: SecureStore, // Pass SecureStore for token storage
77
+ })
78
+ ]
79
+ });
80
+
81
+ // You can also export specific methods if you prefer:
82
+ // export const { signIn, signUp, useSession } = authClient;
83
+ ```
84
+ Make sure your app's scheme (e.g., "myapp") is defined in your `app.json`.
85
+
86
+ ## Documentation
87
+
88
+ For more detailed information and advanced configurations, please refer to the documentation:
89
+
90
+ - **Main Faire Auth Installation:** [https://www.faire-auth.com/docs/installation](https://www.faire-auth.com/docs/installation)
91
+ - **Expo Integration Guide:** [https://www.faire-auth.com/docs/integrations/expo](https://www.faire-auth.com/docs/integrations/expo)
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,196 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let expo_linking = require("expo-linking");
25
+ expo_linking = __toESM(expo_linking);
26
+ let react_native = require("react-native");
27
+ let expo_constants = require("expo-constants");
28
+ expo_constants = __toESM(expo_constants);
29
+ let faire_auth = require("faire-auth");
30
+
31
+ //#region src/client.ts
32
+ function parseSetCookieHeader(header) {
33
+ const cookieMap = /* @__PURE__ */ new Map();
34
+ splitSetCookieHeader(header).forEach((cookie) => {
35
+ const [nameValue, ...attributes] = cookie.split(";").map((p) => p.trim());
36
+ if (!nameValue) return;
37
+ const [name, ...valueParts] = nameValue.split("=");
38
+ if (!name) return;
39
+ const cookieObj = { value: valueParts.join("=") };
40
+ attributes.forEach((attr) => {
41
+ const [attrName, ...attrValueParts] = attr.split("=");
42
+ if (!attrName) return;
43
+ const attrValue = attrValueParts.join("=");
44
+ cookieObj[attrName.toLowerCase()] = attrValue;
45
+ });
46
+ cookieMap.set(name, cookieObj);
47
+ });
48
+ return cookieMap;
49
+ }
50
+ function splitSetCookieHeader(setCookie) {
51
+ const parts = [];
52
+ let buffer = "";
53
+ let i = 0;
54
+ while (i < setCookie.length) {
55
+ const char = setCookie[i];
56
+ if (char === ",") {
57
+ const recent = buffer.toLowerCase();
58
+ const hasExpires = recent.includes("expires=");
59
+ const hasGmt = /gmt/i.test(recent);
60
+ if (hasExpires && !hasGmt) {
61
+ buffer += char;
62
+ i += 1;
63
+ continue;
64
+ }
65
+ if (buffer.trim().length > 0) {
66
+ parts.push(buffer.trim());
67
+ buffer = "";
68
+ }
69
+ i += 1;
70
+ if (setCookie[i] === " ") i += 1;
71
+ continue;
72
+ }
73
+ buffer += char;
74
+ i += 1;
75
+ }
76
+ if (buffer.trim().length > 0) parts.push(buffer.trim());
77
+ return parts;
78
+ }
79
+ function getSetCookie(header, prevCookie) {
80
+ const parsed = parseSetCookieHeader(header);
81
+ let toSetCookie = {};
82
+ parsed.forEach((cookie, key) => {
83
+ const expiresAt = cookie["expires"];
84
+ const maxAge = cookie["max-age"];
85
+ const expires = maxAge ? new Date(Date.now() + Number(maxAge) * 1e3) : expiresAt ? new Date(String(expiresAt)) : null;
86
+ toSetCookie[key] = {
87
+ value: cookie["value"],
88
+ expires: expires ? expires.toISOString() : null
89
+ };
90
+ });
91
+ if (prevCookie) try {
92
+ toSetCookie = {
93
+ ...JSON.parse(prevCookie),
94
+ ...toSetCookie
95
+ };
96
+ } catch {}
97
+ return JSON.stringify(toSetCookie);
98
+ }
99
+ function getCookie(cookie) {
100
+ let parsed = {};
101
+ try {
102
+ parsed = JSON.parse(cookie);
103
+ } catch (e) {}
104
+ return Object.entries(parsed).reduce((acc, [key, value], idx) => {
105
+ if (value.expires && new Date(value.expires) < /* @__PURE__ */ new Date()) return acc;
106
+ return `${idx !== 0 ? `${acc}; ` : ""}${key}=${value.value}`;
107
+ }, "");
108
+ }
109
+ function getOrigin(scheme) {
110
+ return expo_linking.createURL("", { scheme });
111
+ }
112
+ const expoClient = ({ storage, disableCache, scheme: rawScheme = expo_constants.default.expoConfig?.scheme || expo_constants.default.platform?.scheme, storagePrefix = "faire-auth" }) => {
113
+ let store = null;
114
+ const cookieName = `${storagePrefix}_cookie`;
115
+ const localCacheName = `${storagePrefix}_session_data`;
116
+ const isWeb = react_native.Platform.OS === "web";
117
+ const scheme = Array.isArray(rawScheme) ? rawScheme[0] : rawScheme;
118
+ if (!scheme && !isWeb) throw new Error("Scheme not found in app.json. Please provide a scheme in the options.");
119
+ return {
120
+ id: "expo",
121
+ getActions(_, $store) {
122
+ store = $store;
123
+ return { getCookie: () => {
124
+ return getCookie(storage.getItem(cookieName) || "{}");
125
+ } };
126
+ },
127
+ fetchPlugins: [{
128
+ id: "expo",
129
+ name: "Expo",
130
+ hooks: { async onSuccess(context) {
131
+ if (isWeb) return;
132
+ const setCookie = context.response.headers.get("set-cookie");
133
+ if (setCookie) {
134
+ const prevCookie = await storage.getItem(cookieName);
135
+ const toSetCookie = getSetCookie(setCookie || "", prevCookie ?? void 0);
136
+ await storage.setItem(cookieName, toSetCookie);
137
+ store?.notify("$sessionSignal");
138
+ }
139
+ if (context.request.url.toString().includes("/get-session") && !disableCache && context.data?.data) storage.setItem(localCacheName, JSON.stringify(context.data.data));
140
+ if (context.data?.data.redirect && (context.request.url.toString().includes("/sign-in") || context.request.url.toString().includes("/link-social")) && !context.request?.body.includes("idToken")) {
141
+ const callbackURL = JSON.parse(context.request.body)?.callbackURL;
142
+ const to = callbackURL?.startsWith("/") ? expo_linking.createURL(callbackURL, { scheme }) : callbackURL;
143
+ const signInURL = context.data?.data.url;
144
+ let Browser = void 0;
145
+ try {
146
+ Browser = await import("expo-web-browser");
147
+ } catch (error) {
148
+ throw new Error("\"expo-web-browser\" is not installed as a dependency!", { cause: error });
149
+ }
150
+ const result = await Browser.openAuthSessionAsync(signInURL, to);
151
+ if (result.type !== "success") return;
152
+ const url = new URL(result.url);
153
+ const cookie = String(url.searchParams.get("cookie"));
154
+ if (!cookie) return;
155
+ storage.setItem(cookieName, getSetCookie(cookie));
156
+ store?.notify("$sessionSignal");
157
+ }
158
+ } },
159
+ async init(url, options) {
160
+ if (isWeb) return {
161
+ url,
162
+ options
163
+ };
164
+ options ||= {};
165
+ const cookie = getCookie(storage.getItem(cookieName) || "{}");
166
+ options.credentials = "omit";
167
+ options.headers = (0, faire_auth.mergeHeaders)(options.headers, {
168
+ cookie,
169
+ "expo-origin": getOrigin(scheme),
170
+ "x-skip-oauth-proxy": "true"
171
+ });
172
+ if (url.includes("/sign-out")) {
173
+ await storage.setItem(cookieName, "{}");
174
+ store?.atoms.session?.set({
175
+ ...store.atoms.session.get(),
176
+ data: null,
177
+ error: null,
178
+ isPending: false
179
+ });
180
+ storage.setItem(localCacheName, "{}");
181
+ }
182
+ return {
183
+ url,
184
+ options
185
+ };
186
+ }
187
+ }]
188
+ };
189
+ };
190
+
191
+ //#endregion
192
+ exports.__toESM = __toESM;
193
+ exports.expoClient = expoClient;
194
+ exports.getCookie = getCookie;
195
+ exports.getSetCookie = getSetCookie;
196
+ exports.parseSetCookieHeader = parseSetCookieHeader;
@@ -0,0 +1,119 @@
1
+ import * as _better_fetch_fetch0 from "@better-fetch/fetch";
2
+ import { BetterFetchOption } from "@better-fetch/fetch";
3
+ import { Store } from "faire-auth/types";
4
+
5
+ //#region src/client.d.ts
6
+ interface CookieAttributes {
7
+ value: string;
8
+ expires?: Date;
9
+ "max-age"?: number;
10
+ domain?: string;
11
+ path?: string;
12
+ secure?: boolean;
13
+ httpOnly?: boolean;
14
+ sameSite?: "Strict" | "Lax" | "None";
15
+ }
16
+ declare function parseSetCookieHeader(header: string): Map<string, CookieAttributes>;
17
+ interface ExpoClientOptions {
18
+ scheme?: string;
19
+ storage: {
20
+ setItem: (key: string, value: string) => any;
21
+ getItem: (key: string) => string | null;
22
+ };
23
+ storagePrefix?: string;
24
+ disableCache?: boolean;
25
+ }
26
+ declare function getSetCookie(header: string, prevCookie?: string): string;
27
+ declare function getCookie(cookie: string): string;
28
+ declare const expoClient: ({
29
+ storage,
30
+ disableCache,
31
+ scheme: rawScheme,
32
+ storagePrefix
33
+ }: ExpoClientOptions) => {
34
+ id: "expo";
35
+ getActions(_: _better_fetch_fetch0.BetterFetch, $store: Store): {
36
+ /**
37
+ * Get the stored cookie.
38
+ *
39
+ * You can use this to get the cookie stored in the device and use it in your fetch
40
+ * requests.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const cookie = client.getCookie();
45
+ * fetch("https://api.example.com", {
46
+ * headers: {
47
+ * cookie,
48
+ * },
49
+ * });
50
+ */
51
+ getCookie: () => string;
52
+ };
53
+ fetchPlugins: {
54
+ id: string;
55
+ name: string;
56
+ hooks: {
57
+ onSuccess(context: _better_fetch_fetch0.SuccessContext<any>): Promise<void>;
58
+ };
59
+ init(url: string, options: {
60
+ method?: string;
61
+ redirect?: RequestRedirect;
62
+ headers?: HeadersInit & (HeadersInit | {
63
+ accept: "application/json" | "text/plain" | "application/octet-stream";
64
+ "content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
65
+ authorization: "Bearer" | "Basic";
66
+ });
67
+ cache?: RequestCache;
68
+ credentials?: RequestCredentials;
69
+ integrity?: string;
70
+ keepalive?: boolean;
71
+ mode?: RequestMode;
72
+ priority?: RequestPriority;
73
+ referrer?: string;
74
+ referrerPolicy?: ReferrerPolicy;
75
+ signal?: AbortSignal | null;
76
+ window?: null;
77
+ onRequest?: <T extends Record<string, any>>(context: _better_fetch_fetch0.RequestContext<T>) => Promise<_better_fetch_fetch0.RequestContext | void> | _better_fetch_fetch0.RequestContext | void;
78
+ onResponse?: (context: _better_fetch_fetch0.ResponseContext) => Promise<Response | void | _better_fetch_fetch0.ResponseContext> | Response | _better_fetch_fetch0.ResponseContext | void;
79
+ onSuccess?: (context: _better_fetch_fetch0.SuccessContext<any>) => Promise<void> | void;
80
+ onError?: (context: _better_fetch_fetch0.ErrorContext) => Promise<void> | void;
81
+ onRetry?: (response: _better_fetch_fetch0.ResponseContext) => Promise<void> | void;
82
+ hookOptions?: {
83
+ cloneResponse?: boolean;
84
+ };
85
+ timeout?: number;
86
+ customFetchImpl?: _better_fetch_fetch0.FetchEsque;
87
+ plugins?: _better_fetch_fetch0.BetterFetchPlugin[];
88
+ baseURL?: string;
89
+ throw?: boolean;
90
+ auth?: {
91
+ type: "Bearer";
92
+ token: string | Promise<string | undefined> | (() => string | Promise<string | undefined> | undefined) | undefined;
93
+ } | {
94
+ type: "Basic";
95
+ username: string | (() => string | undefined) | undefined;
96
+ password: string | (() => string | undefined) | undefined;
97
+ } | {
98
+ type: "Custom";
99
+ prefix: string | (() => string | undefined) | undefined;
100
+ value: string | (() => string | undefined) | undefined;
101
+ };
102
+ body?: any;
103
+ query?: any;
104
+ params?: any;
105
+ duplex?: "full" | "half";
106
+ jsonParser?: (text: string) => Promise<any> | any;
107
+ retry?: _better_fetch_fetch0.RetryOptions;
108
+ retryAttempt?: number;
109
+ output?: _better_fetch_fetch0.StandardSchemaV1 | typeof Blob | typeof File;
110
+ errorSchema?: _better_fetch_fetch0.StandardSchemaV1;
111
+ disableValidation?: boolean;
112
+ } | undefined): Promise<{
113
+ url: string;
114
+ options: BetterFetchOption;
115
+ }>;
116
+ }[];
117
+ };
118
+ //#endregion
119
+ export { expoClient, getCookie, getSetCookie, parseSetCookieHeader };
@@ -0,0 +1,119 @@
1
+ import * as _better_fetch_fetch0 from "@better-fetch/fetch";
2
+ import { BetterFetchOption } from "@better-fetch/fetch";
3
+ import { Store } from "faire-auth/types";
4
+
5
+ //#region src/client.d.ts
6
+ interface CookieAttributes {
7
+ value: string;
8
+ expires?: Date;
9
+ "max-age"?: number;
10
+ domain?: string;
11
+ path?: string;
12
+ secure?: boolean;
13
+ httpOnly?: boolean;
14
+ sameSite?: "Strict" | "Lax" | "None";
15
+ }
16
+ declare function parseSetCookieHeader(header: string): Map<string, CookieAttributes>;
17
+ interface ExpoClientOptions {
18
+ scheme?: string;
19
+ storage: {
20
+ setItem: (key: string, value: string) => any;
21
+ getItem: (key: string) => string | null;
22
+ };
23
+ storagePrefix?: string;
24
+ disableCache?: boolean;
25
+ }
26
+ declare function getSetCookie(header: string, prevCookie?: string): string;
27
+ declare function getCookie(cookie: string): string;
28
+ declare const expoClient: ({
29
+ storage,
30
+ disableCache,
31
+ scheme: rawScheme,
32
+ storagePrefix
33
+ }: ExpoClientOptions) => {
34
+ id: "expo";
35
+ getActions(_: _better_fetch_fetch0.BetterFetch, $store: Store): {
36
+ /**
37
+ * Get the stored cookie.
38
+ *
39
+ * You can use this to get the cookie stored in the device and use it in your fetch
40
+ * requests.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const cookie = client.getCookie();
45
+ * fetch("https://api.example.com", {
46
+ * headers: {
47
+ * cookie,
48
+ * },
49
+ * });
50
+ */
51
+ getCookie: () => string;
52
+ };
53
+ fetchPlugins: {
54
+ id: string;
55
+ name: string;
56
+ hooks: {
57
+ onSuccess(context: _better_fetch_fetch0.SuccessContext<any>): Promise<void>;
58
+ };
59
+ init(url: string, options: {
60
+ method?: string;
61
+ redirect?: RequestRedirect;
62
+ headers?: HeadersInit & (HeadersInit | {
63
+ accept: "application/json" | "text/plain" | "application/octet-stream";
64
+ "content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
65
+ authorization: "Bearer" | "Basic";
66
+ });
67
+ cache?: RequestCache;
68
+ credentials?: RequestCredentials;
69
+ integrity?: string;
70
+ keepalive?: boolean;
71
+ mode?: RequestMode;
72
+ priority?: RequestPriority;
73
+ referrer?: string;
74
+ referrerPolicy?: ReferrerPolicy;
75
+ signal?: AbortSignal | null;
76
+ window?: null;
77
+ onRequest?: <T extends Record<string, any>>(context: _better_fetch_fetch0.RequestContext<T>) => Promise<_better_fetch_fetch0.RequestContext | void> | _better_fetch_fetch0.RequestContext | void;
78
+ onResponse?: (context: _better_fetch_fetch0.ResponseContext) => Promise<Response | void | _better_fetch_fetch0.ResponseContext> | Response | _better_fetch_fetch0.ResponseContext | void;
79
+ onSuccess?: (context: _better_fetch_fetch0.SuccessContext<any>) => Promise<void> | void;
80
+ onError?: (context: _better_fetch_fetch0.ErrorContext) => Promise<void> | void;
81
+ onRetry?: (response: _better_fetch_fetch0.ResponseContext) => Promise<void> | void;
82
+ hookOptions?: {
83
+ cloneResponse?: boolean;
84
+ };
85
+ timeout?: number;
86
+ customFetchImpl?: _better_fetch_fetch0.FetchEsque;
87
+ plugins?: _better_fetch_fetch0.BetterFetchPlugin[];
88
+ baseURL?: string;
89
+ throw?: boolean;
90
+ auth?: {
91
+ type: "Bearer";
92
+ token: string | Promise<string | undefined> | (() => string | Promise<string | undefined> | undefined) | undefined;
93
+ } | {
94
+ type: "Basic";
95
+ username: string | (() => string | undefined) | undefined;
96
+ password: string | (() => string | undefined) | undefined;
97
+ } | {
98
+ type: "Custom";
99
+ prefix: string | (() => string | undefined) | undefined;
100
+ value: string | (() => string | undefined) | undefined;
101
+ };
102
+ body?: any;
103
+ query?: any;
104
+ params?: any;
105
+ duplex?: "full" | "half";
106
+ jsonParser?: (text: string) => Promise<any> | any;
107
+ retry?: _better_fetch_fetch0.RetryOptions;
108
+ retryAttempt?: number;
109
+ output?: _better_fetch_fetch0.StandardSchemaV1 | typeof Blob | typeof File;
110
+ errorSchema?: _better_fetch_fetch0.StandardSchemaV1;
111
+ disableValidation?: boolean;
112
+ } | undefined): Promise<{
113
+ url: string;
114
+ options: BetterFetchOption;
115
+ }>;
116
+ }[];
117
+ };
118
+ //#endregion
119
+ export { expoClient, getCookie, getSetCookie, parseSetCookieHeader };
@@ -0,0 +1,167 @@
1
+ import * as Linking from "expo-linking";
2
+ import { Platform } from "react-native";
3
+ import Constants from "expo-constants";
4
+ import { mergeHeaders } from "faire-auth";
5
+
6
+ //#region src/client.ts
7
+ function parseSetCookieHeader(header) {
8
+ const cookieMap = /* @__PURE__ */ new Map();
9
+ splitSetCookieHeader(header).forEach((cookie) => {
10
+ const [nameValue, ...attributes] = cookie.split(";").map((p) => p.trim());
11
+ if (!nameValue) return;
12
+ const [name, ...valueParts] = nameValue.split("=");
13
+ if (!name) return;
14
+ const cookieObj = { value: valueParts.join("=") };
15
+ attributes.forEach((attr) => {
16
+ const [attrName, ...attrValueParts] = attr.split("=");
17
+ if (!attrName) return;
18
+ const attrValue = attrValueParts.join("=");
19
+ cookieObj[attrName.toLowerCase()] = attrValue;
20
+ });
21
+ cookieMap.set(name, cookieObj);
22
+ });
23
+ return cookieMap;
24
+ }
25
+ function splitSetCookieHeader(setCookie) {
26
+ const parts = [];
27
+ let buffer = "";
28
+ let i = 0;
29
+ while (i < setCookie.length) {
30
+ const char = setCookie[i];
31
+ if (char === ",") {
32
+ const recent = buffer.toLowerCase();
33
+ const hasExpires = recent.includes("expires=");
34
+ const hasGmt = /gmt/i.test(recent);
35
+ if (hasExpires && !hasGmt) {
36
+ buffer += char;
37
+ i += 1;
38
+ continue;
39
+ }
40
+ if (buffer.trim().length > 0) {
41
+ parts.push(buffer.trim());
42
+ buffer = "";
43
+ }
44
+ i += 1;
45
+ if (setCookie[i] === " ") i += 1;
46
+ continue;
47
+ }
48
+ buffer += char;
49
+ i += 1;
50
+ }
51
+ if (buffer.trim().length > 0) parts.push(buffer.trim());
52
+ return parts;
53
+ }
54
+ function getSetCookie(header, prevCookie) {
55
+ const parsed = parseSetCookieHeader(header);
56
+ let toSetCookie = {};
57
+ parsed.forEach((cookie, key) => {
58
+ const expiresAt = cookie["expires"];
59
+ const maxAge = cookie["max-age"];
60
+ const expires = maxAge ? new Date(Date.now() + Number(maxAge) * 1e3) : expiresAt ? new Date(String(expiresAt)) : null;
61
+ toSetCookie[key] = {
62
+ value: cookie["value"],
63
+ expires: expires ? expires.toISOString() : null
64
+ };
65
+ });
66
+ if (prevCookie) try {
67
+ toSetCookie = {
68
+ ...JSON.parse(prevCookie),
69
+ ...toSetCookie
70
+ };
71
+ } catch {}
72
+ return JSON.stringify(toSetCookie);
73
+ }
74
+ function getCookie(cookie) {
75
+ let parsed = {};
76
+ try {
77
+ parsed = JSON.parse(cookie);
78
+ } catch (e) {}
79
+ return Object.entries(parsed).reduce((acc, [key, value], idx) => {
80
+ if (value.expires && new Date(value.expires) < /* @__PURE__ */ new Date()) return acc;
81
+ return `${idx !== 0 ? `${acc}; ` : ""}${key}=${value.value}`;
82
+ }, "");
83
+ }
84
+ function getOrigin(scheme) {
85
+ return Linking.createURL("", { scheme });
86
+ }
87
+ const expoClient = ({ storage, disableCache, scheme: rawScheme = Constants.expoConfig?.scheme || Constants.platform?.scheme, storagePrefix = "faire-auth" }) => {
88
+ let store = null;
89
+ const cookieName = `${storagePrefix}_cookie`;
90
+ const localCacheName = `${storagePrefix}_session_data`;
91
+ const isWeb = Platform.OS === "web";
92
+ const scheme = Array.isArray(rawScheme) ? rawScheme[0] : rawScheme;
93
+ if (!scheme && !isWeb) throw new Error("Scheme not found in app.json. Please provide a scheme in the options.");
94
+ return {
95
+ id: "expo",
96
+ getActions(_, $store) {
97
+ store = $store;
98
+ return { getCookie: () => {
99
+ return getCookie(storage.getItem(cookieName) || "{}");
100
+ } };
101
+ },
102
+ fetchPlugins: [{
103
+ id: "expo",
104
+ name: "Expo",
105
+ hooks: { async onSuccess(context) {
106
+ if (isWeb) return;
107
+ const setCookie = context.response.headers.get("set-cookie");
108
+ if (setCookie) {
109
+ const prevCookie = await storage.getItem(cookieName);
110
+ const toSetCookie = getSetCookie(setCookie || "", prevCookie ?? void 0);
111
+ await storage.setItem(cookieName, toSetCookie);
112
+ store?.notify("$sessionSignal");
113
+ }
114
+ if (context.request.url.toString().includes("/get-session") && !disableCache && context.data?.data) storage.setItem(localCacheName, JSON.stringify(context.data.data));
115
+ if (context.data?.data.redirect && (context.request.url.toString().includes("/sign-in") || context.request.url.toString().includes("/link-social")) && !context.request?.body.includes("idToken")) {
116
+ const callbackURL = JSON.parse(context.request.body)?.callbackURL;
117
+ const to = callbackURL?.startsWith("/") ? Linking.createURL(callbackURL, { scheme }) : callbackURL;
118
+ const signInURL = context.data?.data.url;
119
+ let Browser = void 0;
120
+ try {
121
+ Browser = await import("expo-web-browser");
122
+ } catch (error) {
123
+ throw new Error("\"expo-web-browser\" is not installed as a dependency!", { cause: error });
124
+ }
125
+ const result = await Browser.openAuthSessionAsync(signInURL, to);
126
+ if (result.type !== "success") return;
127
+ const url = new URL(result.url);
128
+ const cookie = String(url.searchParams.get("cookie"));
129
+ if (!cookie) return;
130
+ storage.setItem(cookieName, getSetCookie(cookie));
131
+ store?.notify("$sessionSignal");
132
+ }
133
+ } },
134
+ async init(url, options) {
135
+ if (isWeb) return {
136
+ url,
137
+ options
138
+ };
139
+ options ||= {};
140
+ const cookie = getCookie(storage.getItem(cookieName) || "{}");
141
+ options.credentials = "omit";
142
+ options.headers = mergeHeaders(options.headers, {
143
+ cookie,
144
+ "expo-origin": getOrigin(scheme),
145
+ "x-skip-oauth-proxy": "true"
146
+ });
147
+ if (url.includes("/sign-out")) {
148
+ await storage.setItem(cookieName, "{}");
149
+ store?.atoms.session?.set({
150
+ ...store.atoms.session.get(),
151
+ data: null,
152
+ error: null,
153
+ isPending: false
154
+ });
155
+ storage.setItem(localCacheName, "{}");
156
+ }
157
+ return {
158
+ url,
159
+ options
160
+ };
161
+ }
162
+ }]
163
+ };
164
+ };
165
+
166
+ //#endregion
167
+ export { expoClient, getCookie, getSetCookie, parseSetCookieHeader };