@pylo/react 0.7.0 → 0.8.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 +5 -107
- package/dist/chunk-3WSGP2SO.js +483 -0
- package/dist/{chunk-VUY6OTMP.js → chunk-A6UGLYBT.js} +1 -1
- package/dist/chunk-KOUVF33C.js +540 -0
- package/dist/chunk-R3IJ4HH6.js +540 -0
- package/dist/chunk-RCW4MW4F.js +540 -0
- package/dist/{chunk-SBMMJYJT.js → chunk-XCHM5XBY.js} +129 -7
- package/dist/chunk-XR5CLHMS.js +470 -0
- package/dist/chunk-ZWYBNMHS.js +500 -0
- package/dist/hooks.js +2 -2
- package/dist/index.d.ts +19 -6
- package/dist/index.js +4 -3
- package/dist/provider-X8jgqOeN.d.ts +74 -0
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/package.json +4 -4
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
extractErrorMessage,
|
|
19
19
|
isUnauthorizedError,
|
|
20
20
|
shouldRefreshToken,
|
|
21
|
+
safeRedirectPath,
|
|
21
22
|
LOGIN_MUTATION,
|
|
23
|
+
REDEEM_LOGIN_HANDOFF_MUTATION,
|
|
22
24
|
REFRESH_TOKEN_MUTATION
|
|
23
25
|
} from "@pylo/auth";
|
|
24
26
|
function createSessionStore(options) {
|
|
@@ -41,6 +43,11 @@ function createSessionStore(options) {
|
|
|
41
43
|
await options.storage.setItem(REFRESH_KEY, refresh2);
|
|
42
44
|
setState({ status: "signedIn", token: auth });
|
|
43
45
|
}
|
|
46
|
+
async function signIn(auth, refresh2) {
|
|
47
|
+
var _a2;
|
|
48
|
+
await persist(auth, refresh2);
|
|
49
|
+
(_a2 = options.onSignIn) == null ? void 0 : _a2.call(options);
|
|
50
|
+
}
|
|
44
51
|
async function clear() {
|
|
45
52
|
var _a2;
|
|
46
53
|
refreshToken = null;
|
|
@@ -51,6 +58,7 @@ function createSessionStore(options) {
|
|
|
51
58
|
}
|
|
52
59
|
async function runRefresh() {
|
|
53
60
|
await init();
|
|
61
|
+
if (state.status !== "signedIn") return null;
|
|
54
62
|
if (!refreshToken) {
|
|
55
63
|
await clear();
|
|
56
64
|
return null;
|
|
@@ -110,7 +118,7 @@ function createSessionStore(options) {
|
|
|
110
118
|
},
|
|
111
119
|
refresh,
|
|
112
120
|
async login(email, password) {
|
|
113
|
-
var _a2
|
|
121
|
+
var _a2;
|
|
114
122
|
const response = await graphqlRequest(
|
|
115
123
|
options.endpoint,
|
|
116
124
|
LOGIN_MUTATION,
|
|
@@ -138,14 +146,105 @@ function createSessionStore(options) {
|
|
|
138
146
|
};
|
|
139
147
|
}
|
|
140
148
|
const { auth_token, refresh_token } = response.data.login.data;
|
|
141
|
-
await
|
|
142
|
-
(_b = options.onSignIn) == null ? void 0 : _b.call(options);
|
|
149
|
+
await signIn(auth_token, refresh_token);
|
|
143
150
|
return { success: true, authToken: auth_token, refreshToken: refresh_token };
|
|
144
151
|
},
|
|
152
|
+
async redeemHandoff(code, verifier) {
|
|
153
|
+
var _a2;
|
|
154
|
+
const response = await graphqlRequest(
|
|
155
|
+
options.endpoint,
|
|
156
|
+
REDEEM_LOGIN_HANDOFF_MUTATION,
|
|
157
|
+
{ input: { code, handoff_verifier: verifier } }
|
|
158
|
+
);
|
|
159
|
+
if (hasErrors(response) || !response.data) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: {
|
|
163
|
+
code: "SIGN_IN_FAILED",
|
|
164
|
+
message: (_a2 = extractErrorMessage(response.errors)) != null ? _a2 : "Sign-in failed"
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const { auth_token, refresh_token, redirect } = response.data.redeemLoginHandoff.data;
|
|
169
|
+
await signIn(auth_token, refresh_token);
|
|
170
|
+
const safe = safeRedirectPath(redirect);
|
|
171
|
+
return {
|
|
172
|
+
success: true,
|
|
173
|
+
authToken: auth_token,
|
|
174
|
+
refreshToken: refresh_token,
|
|
175
|
+
...safe !== null ? { redirect: safe } : {}
|
|
176
|
+
};
|
|
177
|
+
},
|
|
145
178
|
logout: clear
|
|
146
179
|
};
|
|
147
180
|
}
|
|
148
181
|
|
|
182
|
+
// src/session/social.ts
|
|
183
|
+
import {
|
|
184
|
+
buildSocialStartUrl,
|
|
185
|
+
createHandoffChallenge,
|
|
186
|
+
createHandoffVerifier,
|
|
187
|
+
parseHandoffCallback
|
|
188
|
+
} from "@pylo/auth";
|
|
189
|
+
function failed(code, message) {
|
|
190
|
+
return { success: false, error: { code, message } };
|
|
191
|
+
}
|
|
192
|
+
function createSocialSignIn(o) {
|
|
193
|
+
const verifierKey = `${o.keyPrefix}.handoff_verifier`;
|
|
194
|
+
function redeem(parsed, verifier) {
|
|
195
|
+
if (!parsed) return failed("SIGN_IN_FAILED", "No sign-in response in this URL");
|
|
196
|
+
if ("error" in parsed) return failed("SIGN_IN_FAILED", parsed.error);
|
|
197
|
+
if (!verifier) {
|
|
198
|
+
return failed("SIGN_IN_FAILED", "No sign-in was started in this browser session");
|
|
199
|
+
}
|
|
200
|
+
return o.store.redeemHandoff(parsed.code, verifier);
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
async signInWith(provider, { returnUrl, redirect, invite }) {
|
|
204
|
+
const verifier = createHandoffVerifier(o.crypto);
|
|
205
|
+
const challenge = await createHandoffChallenge(verifier, o.crypto);
|
|
206
|
+
const startUrl = buildSocialStartUrl({
|
|
207
|
+
endpoint: o.endpoint,
|
|
208
|
+
provider,
|
|
209
|
+
appId: o.appId,
|
|
210
|
+
returnUrl,
|
|
211
|
+
challenge,
|
|
212
|
+
...redirect !== void 0 ? { redirect } : {},
|
|
213
|
+
...invite !== void 0 ? { invite } : {}
|
|
214
|
+
});
|
|
215
|
+
if (o.openAuthSession) {
|
|
216
|
+
let finalUrl;
|
|
217
|
+
try {
|
|
218
|
+
finalUrl = await o.openAuthSession(startUrl, returnUrl);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return failed(
|
|
221
|
+
"SIGN_IN_FAILED",
|
|
222
|
+
error instanceof Error ? error.message : "Sign-in failed"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (finalUrl === null) return failed("CANCELLED", "Sign-in was cancelled");
|
|
226
|
+
return redeem(parseHandoffCallback(finalUrl), verifier);
|
|
227
|
+
}
|
|
228
|
+
sessionStorage.setItem(verifierKey, verifier);
|
|
229
|
+
location.assign(startUrl);
|
|
230
|
+
return new Promise(() => {
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
async completeSignIn(url = location.href) {
|
|
234
|
+
const verifier = sessionStorage.getItem(verifierKey);
|
|
235
|
+
sessionStorage.removeItem(verifierKey);
|
|
236
|
+
const parsed = parseHandoffCallback(url);
|
|
237
|
+
if (parsed) {
|
|
238
|
+
const clean = new URL(url);
|
|
239
|
+
clean.searchParams.delete("code");
|
|
240
|
+
clean.searchParams.delete("error");
|
|
241
|
+
history.replaceState(history.state, "", clean.toString());
|
|
242
|
+
}
|
|
243
|
+
return redeem(parsed, verifier);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
149
248
|
// src/session/storage.ts
|
|
150
249
|
function createMemoryStorage() {
|
|
151
250
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -249,10 +348,13 @@ var SIGNED_OUT = { status: "signedOut", token: null };
|
|
|
249
348
|
var NO_OP = () => () => {
|
|
250
349
|
};
|
|
251
350
|
function PyloProvider(props) {
|
|
252
|
-
var _a;
|
|
351
|
+
var _a, _b;
|
|
253
352
|
const queryClient = useQueryClient();
|
|
254
353
|
const endpoint = resolveEndpoint(props.endpoint);
|
|
255
354
|
const storeRef = useRef(null);
|
|
355
|
+
const socialRef = useRef(null);
|
|
356
|
+
const openAuthSessionRef = useRef(props.openAuthSession);
|
|
357
|
+
openAuthSessionRef.current = props.openAuthSession;
|
|
256
358
|
if (storeRef.current === null && !props.getToken) {
|
|
257
359
|
storeRef.current = createSessionStore({
|
|
258
360
|
endpoint,
|
|
@@ -262,8 +364,19 @@ function PyloProvider(props) {
|
|
|
262
364
|
onSignOut: () => queryClient.clear(),
|
|
263
365
|
onSignIn: () => void queryClient.invalidateQueries({ queryKey: ["pylo"] })
|
|
264
366
|
});
|
|
367
|
+
if (props.appId !== void 0) {
|
|
368
|
+
socialRef.current = createSocialSignIn({
|
|
369
|
+
endpoint,
|
|
370
|
+
appId: props.appId,
|
|
371
|
+
keyPrefix: (_b = props.keyPrefix) != null ? _b : "pylo",
|
|
372
|
+
store: storeRef.current,
|
|
373
|
+
...props.openAuthSession ? { openAuthSession: (url, returnUrl) => openAuthSessionRef.current(url, returnUrl) } : {},
|
|
374
|
+
...props.handoffCrypto ? { crypto: props.handoffCrypto } : {}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
265
377
|
}
|
|
266
378
|
const store = storeRef.current;
|
|
379
|
+
const social = socialRef.current;
|
|
267
380
|
const state = useSyncExternalStore(
|
|
268
381
|
store ? store.subscribe : NO_OP,
|
|
269
382
|
store ? store.getState : () => SIGNED_OUT,
|
|
@@ -283,8 +396,8 @@ function PyloProvider(props) {
|
|
|
283
396
|
getToken,
|
|
284
397
|
...store ? { onUnauthorized: () => store.refresh() } : {}
|
|
285
398
|
});
|
|
286
|
-
return { transport, endpoint, state, store, getToken };
|
|
287
|
-
}, [endpoint, state, store, isEscapeHatch]);
|
|
399
|
+
return { transport, endpoint, state, store, social, getToken };
|
|
400
|
+
}, [endpoint, state, store, social, isEscapeHatch]);
|
|
288
401
|
return /* @__PURE__ */ jsx(PyloContext.Provider, { value, children: props.children });
|
|
289
402
|
}
|
|
290
403
|
function usePyloTransport() {
|
|
@@ -304,7 +417,7 @@ function usePyloClient() {
|
|
|
304
417
|
);
|
|
305
418
|
}
|
|
306
419
|
function usePyloAuth() {
|
|
307
|
-
const { state, store, transport, getToken } = usePyloContext();
|
|
420
|
+
const { state, store, social, transport, getToken } = usePyloContext();
|
|
308
421
|
const isSignedIn = state.status === "signedIn";
|
|
309
422
|
const me = useQuery({
|
|
310
423
|
queryKey: ["pylo", "me"],
|
|
@@ -330,8 +443,17 @@ function usePyloAuth() {
|
|
|
330
443
|
}
|
|
331
444
|
await store.logout();
|
|
332
445
|
},
|
|
446
|
+
signInWith: async (provider, options) => requireSocial("signInWith").signInWith(provider, options),
|
|
447
|
+
completeSignIn: async (url) => requireSocial("completeSignIn").completeSignIn(url),
|
|
333
448
|
getToken
|
|
334
449
|
};
|
|
450
|
+
function requireSocial(method) {
|
|
451
|
+
if (!store) {
|
|
452
|
+
throw new Error(`${method} is unavailable when PyloProvider is given getToken`);
|
|
453
|
+
}
|
|
454
|
+
if (!social) throw new Error(`${method} requires PyloProvider's appId`);
|
|
455
|
+
return social;
|
|
456
|
+
}
|
|
335
457
|
}
|
|
336
458
|
|
|
337
459
|
export {
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// src/provider.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useSyncExternalStore
|
|
9
|
+
} from "react";
|
|
10
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
11
|
+
import { createPyloClient, resolveEndpoint } from "@pylo/core";
|
|
12
|
+
import { ME_QUERY } from "@pylo/auth";
|
|
13
|
+
|
|
14
|
+
// src/session/store.ts
|
|
15
|
+
import {
|
|
16
|
+
graphqlRequest,
|
|
17
|
+
hasErrors,
|
|
18
|
+
extractErrorMessage,
|
|
19
|
+
isUnauthorizedError,
|
|
20
|
+
shouldRefreshToken,
|
|
21
|
+
safeRedirectPath,
|
|
22
|
+
LOGIN_MUTATION,
|
|
23
|
+
REDEEM_LOGIN_HANDOFF_MUTATION,
|
|
24
|
+
REFRESH_TOKEN_MUTATION
|
|
25
|
+
} from "@pylo/auth";
|
|
26
|
+
function createSessionStore(options) {
|
|
27
|
+
var _a;
|
|
28
|
+
const prefix = (_a = options.keyPrefix) != null ? _a : "pylo";
|
|
29
|
+
const AUTH_KEY = `${prefix}.auth_token`;
|
|
30
|
+
const REFRESH_KEY = `${prefix}.refresh_token`;
|
|
31
|
+
let state = { status: "loading", token: null };
|
|
32
|
+
let refreshToken = null;
|
|
33
|
+
let inFlight = null;
|
|
34
|
+
let ready = null;
|
|
35
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
36
|
+
function setState(next) {
|
|
37
|
+
state = next;
|
|
38
|
+
for (const listener of listeners) listener();
|
|
39
|
+
}
|
|
40
|
+
async function persist(auth, refresh2) {
|
|
41
|
+
refreshToken = refresh2;
|
|
42
|
+
await options.storage.setItem(AUTH_KEY, auth);
|
|
43
|
+
await options.storage.setItem(REFRESH_KEY, refresh2);
|
|
44
|
+
setState({ status: "signedIn", token: auth });
|
|
45
|
+
}
|
|
46
|
+
async function signIn(auth, refresh2) {
|
|
47
|
+
var _a2;
|
|
48
|
+
await persist(auth, refresh2);
|
|
49
|
+
(_a2 = options.onSignIn) == null ? void 0 : _a2.call(options);
|
|
50
|
+
}
|
|
51
|
+
async function clear() {
|
|
52
|
+
var _a2;
|
|
53
|
+
refreshToken = null;
|
|
54
|
+
await options.storage.removeItem(AUTH_KEY);
|
|
55
|
+
await options.storage.removeItem(REFRESH_KEY);
|
|
56
|
+
setState({ status: "signedOut", token: null });
|
|
57
|
+
(_a2 = options.onSignOut) == null ? void 0 : _a2.call(options);
|
|
58
|
+
}
|
|
59
|
+
async function runRefresh() {
|
|
60
|
+
await init();
|
|
61
|
+
if (state.status !== "signedIn") return null;
|
|
62
|
+
if (!refreshToken) {
|
|
63
|
+
await clear();
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const response = await graphqlRequest(
|
|
67
|
+
options.endpoint,
|
|
68
|
+
REFRESH_TOKEN_MUTATION,
|
|
69
|
+
{ input: { refresh_token: refreshToken } }
|
|
70
|
+
);
|
|
71
|
+
if (hasErrors(response) || !response.data) {
|
|
72
|
+
if (isUnauthorizedError(response)) {
|
|
73
|
+
await clear();
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return state.token;
|
|
77
|
+
}
|
|
78
|
+
const { auth_token, refresh_token } = response.data.refreshToken.data;
|
|
79
|
+
await persist(auth_token, refresh_token);
|
|
80
|
+
return auth_token;
|
|
81
|
+
}
|
|
82
|
+
function init() {
|
|
83
|
+
ready != null ? ready : ready = (async () => {
|
|
84
|
+
try {
|
|
85
|
+
const [auth, stored] = await Promise.all([
|
|
86
|
+
options.storage.getItem(AUTH_KEY),
|
|
87
|
+
options.storage.getItem(REFRESH_KEY)
|
|
88
|
+
]);
|
|
89
|
+
refreshToken = stored;
|
|
90
|
+
setState(
|
|
91
|
+
auth ? { status: "signedIn", token: auth } : { status: "signedOut", token: null }
|
|
92
|
+
);
|
|
93
|
+
} catch {
|
|
94
|
+
refreshToken = null;
|
|
95
|
+
setState({ status: "signedOut", token: null });
|
|
96
|
+
}
|
|
97
|
+
})();
|
|
98
|
+
return ready;
|
|
99
|
+
}
|
|
100
|
+
function refresh() {
|
|
101
|
+
inFlight != null ? inFlight : inFlight = runRefresh().finally(() => {
|
|
102
|
+
inFlight = null;
|
|
103
|
+
});
|
|
104
|
+
return inFlight;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
getState: () => state,
|
|
108
|
+
subscribe(listener) {
|
|
109
|
+
listeners.add(listener);
|
|
110
|
+
return () => void listeners.delete(listener);
|
|
111
|
+
},
|
|
112
|
+
init,
|
|
113
|
+
async getToken() {
|
|
114
|
+
await init();
|
|
115
|
+
if (!state.token) return null;
|
|
116
|
+
if (shouldRefreshToken(state.token)) return refresh();
|
|
117
|
+
return state.token;
|
|
118
|
+
},
|
|
119
|
+
refresh,
|
|
120
|
+
async login(email, password) {
|
|
121
|
+
var _a2;
|
|
122
|
+
const response = await graphqlRequest(
|
|
123
|
+
options.endpoint,
|
|
124
|
+
LOGIN_MUTATION,
|
|
125
|
+
{
|
|
126
|
+
input: {
|
|
127
|
+
email,
|
|
128
|
+
password,
|
|
129
|
+
...options.appId !== void 0 ? { pylo_app_id: options.appId } : {}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
if (hasErrors(response)) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: {
|
|
137
|
+
code: "INVALID_CREDENTIALS",
|
|
138
|
+
message: (_a2 = extractErrorMessage(response.errors)) != null ? _a2 : "Login failed"
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (!response.data) {
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: { code: "SERVER_ERROR", message: "No data returned" }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const { auth_token, refresh_token } = response.data.login.data;
|
|
149
|
+
await signIn(auth_token, refresh_token);
|
|
150
|
+
return { success: true, authToken: auth_token, refreshToken: refresh_token };
|
|
151
|
+
},
|
|
152
|
+
async redeemHandoff(code, verifier) {
|
|
153
|
+
var _a2;
|
|
154
|
+
const response = await graphqlRequest(
|
|
155
|
+
options.endpoint,
|
|
156
|
+
REDEEM_LOGIN_HANDOFF_MUTATION,
|
|
157
|
+
{ input: { code, handoff_verifier: verifier } }
|
|
158
|
+
);
|
|
159
|
+
if (hasErrors(response) || !response.data) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: {
|
|
163
|
+
code: "SIGN_IN_FAILED",
|
|
164
|
+
message: (_a2 = extractErrorMessage(response.errors)) != null ? _a2 : "Sign-in failed"
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const { auth_token, refresh_token, redirect } = response.data.redeemLoginHandoff.data;
|
|
169
|
+
await signIn(auth_token, refresh_token);
|
|
170
|
+
const safe = safeRedirectPath(redirect);
|
|
171
|
+
return {
|
|
172
|
+
success: true,
|
|
173
|
+
authToken: auth_token,
|
|
174
|
+
refreshToken: refresh_token,
|
|
175
|
+
...safe !== null ? { redirect: safe } : {}
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
logout: clear
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/session/social.ts
|
|
183
|
+
import {
|
|
184
|
+
buildSocialStartUrl,
|
|
185
|
+
createHandoffChallenge,
|
|
186
|
+
createHandoffVerifier,
|
|
187
|
+
parseHandoffCallback
|
|
188
|
+
} from "@pylo/auth";
|
|
189
|
+
function failed(code, message) {
|
|
190
|
+
return { success: false, error: { code, message } };
|
|
191
|
+
}
|
|
192
|
+
function createSocialSignIn(o) {
|
|
193
|
+
const verifierKey = `${o.keyPrefix}.handoff_verifier`;
|
|
194
|
+
function redeem(parsed, verifier) {
|
|
195
|
+
if (!parsed) return failed("SIGN_IN_FAILED", "No sign-in response in this URL");
|
|
196
|
+
if ("error" in parsed) return failed("SIGN_IN_FAILED", parsed.error);
|
|
197
|
+
if (!verifier) {
|
|
198
|
+
return failed("SIGN_IN_FAILED", "No sign-in was started in this browser session");
|
|
199
|
+
}
|
|
200
|
+
return o.store.redeemHandoff(parsed.code, verifier);
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
async signInWith(provider, { returnUrl, redirect, invite }) {
|
|
204
|
+
const verifier = createHandoffVerifier(o.crypto);
|
|
205
|
+
const challenge = await createHandoffChallenge(verifier, o.crypto);
|
|
206
|
+
const startUrl = buildSocialStartUrl({
|
|
207
|
+
endpoint: o.endpoint,
|
|
208
|
+
provider,
|
|
209
|
+
appId: o.appId,
|
|
210
|
+
returnUrl,
|
|
211
|
+
challenge,
|
|
212
|
+
...redirect !== void 0 ? { redirect } : {},
|
|
213
|
+
...invite !== void 0 ? { invite } : {}
|
|
214
|
+
});
|
|
215
|
+
if (o.openAuthSession) {
|
|
216
|
+
let finalUrl;
|
|
217
|
+
try {
|
|
218
|
+
finalUrl = await o.openAuthSession(startUrl, returnUrl);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return failed(
|
|
221
|
+
"SIGN_IN_FAILED",
|
|
222
|
+
error instanceof Error ? error.message : "Sign-in failed"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (finalUrl === null) return failed("CANCELLED", "Sign-in was cancelled");
|
|
226
|
+
return redeem(parseHandoffCallback(finalUrl), verifier);
|
|
227
|
+
}
|
|
228
|
+
sessionStorage.setItem(verifierKey, verifier);
|
|
229
|
+
location.assign(startUrl);
|
|
230
|
+
return new Promise(() => {
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
async completeSignIn(url = location.href) {
|
|
234
|
+
const verifier = sessionStorage.getItem(verifierKey);
|
|
235
|
+
sessionStorage.removeItem(verifierKey);
|
|
236
|
+
const parsed = parseHandoffCallback(url);
|
|
237
|
+
if (parsed) {
|
|
238
|
+
const clean = new URL(url);
|
|
239
|
+
clean.searchParams.delete("code");
|
|
240
|
+
clean.searchParams.delete("error");
|
|
241
|
+
history.replaceState(history.state, "", clean.toString());
|
|
242
|
+
}
|
|
243
|
+
return redeem(parsed, verifier);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/session/storage.ts
|
|
249
|
+
function createMemoryStorage() {
|
|
250
|
+
const store = /* @__PURE__ */ new Map();
|
|
251
|
+
return {
|
|
252
|
+
getItem: async (key) => {
|
|
253
|
+
var _a;
|
|
254
|
+
return (_a = store.get(key)) != null ? _a : null;
|
|
255
|
+
},
|
|
256
|
+
setItem: async (key, value) => void store.set(key, value),
|
|
257
|
+
removeItem: async (key) => void store.delete(key)
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function createLocalStorageAdapter() {
|
|
261
|
+
let web;
|
|
262
|
+
try {
|
|
263
|
+
web = globalThis.localStorage;
|
|
264
|
+
} catch {
|
|
265
|
+
web = void 0;
|
|
266
|
+
}
|
|
267
|
+
if (!web) return createMemoryStorage();
|
|
268
|
+
const fallback = createMemoryStorage();
|
|
269
|
+
return {
|
|
270
|
+
getItem: async (key) => {
|
|
271
|
+
try {
|
|
272
|
+
const value = web.getItem(key);
|
|
273
|
+
if (value !== null) return value;
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
return fallback.getItem(key);
|
|
277
|
+
},
|
|
278
|
+
setItem: async (key, value) => {
|
|
279
|
+
try {
|
|
280
|
+
web.setItem(key, value);
|
|
281
|
+
} catch {
|
|
282
|
+
await fallback.setItem(key, value);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
removeItem: async (key) => {
|
|
286
|
+
try {
|
|
287
|
+
web.removeItem(key);
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
await fallback.removeItem(key);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/transport.ts
|
|
296
|
+
import {
|
|
297
|
+
graphqlRequest as graphqlRequest2,
|
|
298
|
+
hasErrors as hasErrors2,
|
|
299
|
+
extractErrorMessage as extractErrorMessage2,
|
|
300
|
+
isUnauthorizedError as isUnauthorizedError2
|
|
301
|
+
} from "@pylo/auth";
|
|
302
|
+
import { PyloError } from "@pylo/core";
|
|
303
|
+
function createDirectTransport(options) {
|
|
304
|
+
function send(query, variables, headers, token) {
|
|
305
|
+
return graphqlRequest2(
|
|
306
|
+
options.endpoint,
|
|
307
|
+
query,
|
|
308
|
+
variables,
|
|
309
|
+
{
|
|
310
|
+
...token !== null ? { token } : {},
|
|
311
|
+
...headers !== void 0 ? { headers } : {}
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return async (query, variables, headers) => {
|
|
316
|
+
var _a;
|
|
317
|
+
let response = await send(query, variables, headers, await options.getToken());
|
|
318
|
+
if (isUnauthorizedError2(response) && options.onUnauthorized) {
|
|
319
|
+
const token = await options.onUnauthorized();
|
|
320
|
+
if (token !== null) {
|
|
321
|
+
response = await send(query, variables, headers, token);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (hasErrors2(response)) {
|
|
325
|
+
throw new PyloError(
|
|
326
|
+
(_a = extractErrorMessage2(response.errors)) != null ? _a : "GraphQL request failed",
|
|
327
|
+
response.errors
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (!response.data) {
|
|
331
|
+
throw new PyloError("No data returned from GraphQL request");
|
|
332
|
+
}
|
|
333
|
+
return response.data;
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/provider.tsx
|
|
338
|
+
import { jsx } from "react/jsx-runtime";
|
|
339
|
+
var PyloContext = createContext(null);
|
|
340
|
+
function usePyloContext() {
|
|
341
|
+
const value = useContext(PyloContext);
|
|
342
|
+
if (!value) {
|
|
343
|
+
throw new Error("usePylo* must be used inside <PyloProvider>");
|
|
344
|
+
}
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
var SIGNED_OUT = { status: "signedOut", token: null };
|
|
348
|
+
var NO_OP = () => () => {
|
|
349
|
+
};
|
|
350
|
+
function PyloProvider(props) {
|
|
351
|
+
var _a, _b;
|
|
352
|
+
const queryClient = useQueryClient();
|
|
353
|
+
const endpoint = resolveEndpoint(props.endpoint);
|
|
354
|
+
const storeRef = useRef(null);
|
|
355
|
+
const socialRef = useRef(null);
|
|
356
|
+
const openAuthSessionRef = useRef(props.openAuthSession);
|
|
357
|
+
openAuthSessionRef.current = props.openAuthSession;
|
|
358
|
+
if (storeRef.current === null && !props.getToken) {
|
|
359
|
+
storeRef.current = createSessionStore({
|
|
360
|
+
endpoint,
|
|
361
|
+
storage: (_a = props.storage) != null ? _a : createLocalStorageAdapter(),
|
|
362
|
+
...props.appId !== void 0 ? { appId: props.appId } : {},
|
|
363
|
+
...props.keyPrefix !== void 0 ? { keyPrefix: props.keyPrefix } : {},
|
|
364
|
+
onSignOut: () => queryClient.clear(),
|
|
365
|
+
onSignIn: () => void queryClient.invalidateQueries({ queryKey: ["pylo"] })
|
|
366
|
+
});
|
|
367
|
+
if (props.appId !== void 0) {
|
|
368
|
+
socialRef.current = createSocialSignIn({
|
|
369
|
+
endpoint,
|
|
370
|
+
appId: props.appId,
|
|
371
|
+
keyPrefix: (_b = props.keyPrefix) != null ? _b : "pylo",
|
|
372
|
+
store: storeRef.current,
|
|
373
|
+
...props.openAuthSession ? {
|
|
374
|
+
openAuthSession: (url, returnUrl) => openAuthSessionRef.current(url, returnUrl)
|
|
375
|
+
} : {},
|
|
376
|
+
...props.handoffCrypto ? { crypto: props.handoffCrypto } : {}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const store = storeRef.current;
|
|
381
|
+
const social = socialRef.current;
|
|
382
|
+
const state = useSyncExternalStore(
|
|
383
|
+
store ? store.subscribe : NO_OP,
|
|
384
|
+
store ? store.getState : () => SIGNED_OUT,
|
|
385
|
+
store ? store.getState : () => SIGNED_OUT
|
|
386
|
+
);
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
void (store == null ? void 0 : store.init());
|
|
389
|
+
}, [store]);
|
|
390
|
+
const getTokenProp = props.getToken;
|
|
391
|
+
const getTokenRef = useRef(getTokenProp);
|
|
392
|
+
getTokenRef.current = getTokenProp;
|
|
393
|
+
const isEscapeHatch = getTokenProp !== void 0;
|
|
394
|
+
const value = useMemo(() => {
|
|
395
|
+
const getToken = isEscapeHatch ? () => getTokenRef.current() : () => store.getToken();
|
|
396
|
+
const transport = createDirectTransport({
|
|
397
|
+
endpoint,
|
|
398
|
+
getToken,
|
|
399
|
+
...store ? { onUnauthorized: () => store.refresh() } : {}
|
|
400
|
+
});
|
|
401
|
+
return { transport, endpoint, state, store, social, getToken };
|
|
402
|
+
}, [endpoint, state, store, social, isEscapeHatch]);
|
|
403
|
+
return /* @__PURE__ */ jsx(PyloContext.Provider, { value, children: props.children });
|
|
404
|
+
}
|
|
405
|
+
function usePyloTransport() {
|
|
406
|
+
return usePyloContext().transport;
|
|
407
|
+
}
|
|
408
|
+
function usePyloClient() {
|
|
409
|
+
const { endpoint, getToken } = usePyloContext();
|
|
410
|
+
return useMemo(
|
|
411
|
+
() => createPyloClient({
|
|
412
|
+
endpoint,
|
|
413
|
+
auth: async () => {
|
|
414
|
+
const token = await getToken();
|
|
415
|
+
return token !== null ? { token } : {};
|
|
416
|
+
}
|
|
417
|
+
}),
|
|
418
|
+
[endpoint, getToken]
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
function usePyloAuth() {
|
|
422
|
+
const { state, store, social, transport, getToken } = usePyloContext();
|
|
423
|
+
const isSignedIn = state.status === "signedIn";
|
|
424
|
+
const me = useQuery({
|
|
425
|
+
queryKey: ["pylo", "me"],
|
|
426
|
+
queryFn: async () => {
|
|
427
|
+
const data = await transport(ME_QUERY, {});
|
|
428
|
+
return data.me.current_user.data;
|
|
429
|
+
},
|
|
430
|
+
enabled: isSignedIn
|
|
431
|
+
});
|
|
432
|
+
return {
|
|
433
|
+
isLoading: state.status === "loading" || isSignedIn && me.isLoading,
|
|
434
|
+
isSignedIn,
|
|
435
|
+
user: me.data,
|
|
436
|
+
login: async (email, password) => {
|
|
437
|
+
if (!store) {
|
|
438
|
+
throw new Error("login is unavailable when PyloProvider is given getToken");
|
|
439
|
+
}
|
|
440
|
+
return store.login(email, password);
|
|
441
|
+
},
|
|
442
|
+
logout: async () => {
|
|
443
|
+
if (!store) {
|
|
444
|
+
throw new Error("logout is unavailable when PyloProvider is given getToken");
|
|
445
|
+
}
|
|
446
|
+
await store.logout();
|
|
447
|
+
},
|
|
448
|
+
signInWith: async (provider, options) => requireSocial("signInWith").signInWith(provider, options),
|
|
449
|
+
completeSignIn: async (url) => requireSocial("completeSignIn").completeSignIn(url),
|
|
450
|
+
getToken
|
|
451
|
+
};
|
|
452
|
+
function requireSocial(method) {
|
|
453
|
+
if (!store) {
|
|
454
|
+
throw new Error(`${method} is unavailable when PyloProvider is given getToken`);
|
|
455
|
+
}
|
|
456
|
+
if (!social) throw new Error(`${method} requires PyloProvider's appId`);
|
|
457
|
+
return social;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export {
|
|
462
|
+
createSessionStore,
|
|
463
|
+
createMemoryStorage,
|
|
464
|
+
createLocalStorageAdapter,
|
|
465
|
+
createDirectTransport,
|
|
466
|
+
PyloProvider,
|
|
467
|
+
usePyloTransport,
|
|
468
|
+
usePyloClient,
|
|
469
|
+
usePyloAuth
|
|
470
|
+
};
|