@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
|
@@ -0,0 +1,500 @@
|
|
|
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
|
+
await init();
|
|
155
|
+
let response;
|
|
156
|
+
try {
|
|
157
|
+
response = await graphqlRequest(
|
|
158
|
+
options.endpoint,
|
|
159
|
+
REDEEM_LOGIN_HANDOFF_MUTATION,
|
|
160
|
+
{ input: { code, handoff_verifier: verifier } }
|
|
161
|
+
);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
error: {
|
|
166
|
+
code: "SIGN_IN_FAILED",
|
|
167
|
+
message: error instanceof Error ? error.message : "Sign-in failed"
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (hasErrors(response) || !response.data) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: {
|
|
175
|
+
code: "SIGN_IN_FAILED",
|
|
176
|
+
message: (_a2 = extractErrorMessage(response.errors)) != null ? _a2 : "Sign-in failed"
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const { auth_token, refresh_token, redirect } = response.data.redeemLoginHandoff.data;
|
|
181
|
+
await signIn(auth_token, refresh_token);
|
|
182
|
+
const safe = safeRedirectPath(redirect);
|
|
183
|
+
return {
|
|
184
|
+
success: true,
|
|
185
|
+
authToken: auth_token,
|
|
186
|
+
refreshToken: refresh_token,
|
|
187
|
+
...safe !== null ? { redirect: safe } : {}
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
logout: clear
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/session/social.ts
|
|
195
|
+
import {
|
|
196
|
+
buildSocialStartUrl,
|
|
197
|
+
createHandoffChallenge,
|
|
198
|
+
createHandoffVerifier,
|
|
199
|
+
parseHandoffCallback
|
|
200
|
+
} from "@pylo/auth";
|
|
201
|
+
function failed(code, message) {
|
|
202
|
+
return { success: false, error: { code, message } };
|
|
203
|
+
}
|
|
204
|
+
function createSocialSignIn(o) {
|
|
205
|
+
const verifierKey = `${o.keyPrefix}.handoff_verifier`;
|
|
206
|
+
let completing = null;
|
|
207
|
+
function redeem(parsed, verifier) {
|
|
208
|
+
if (!parsed) return failed("SIGN_IN_FAILED", "No sign-in response in this URL");
|
|
209
|
+
if ("error" in parsed) return failed("SIGN_IN_FAILED", parsed.error);
|
|
210
|
+
if (!verifier) {
|
|
211
|
+
return failed("SIGN_IN_FAILED", "No sign-in was started in this browser session");
|
|
212
|
+
}
|
|
213
|
+
return o.store.redeemHandoff(parsed.code, verifier);
|
|
214
|
+
}
|
|
215
|
+
async function finish(url) {
|
|
216
|
+
try {
|
|
217
|
+
const verifier = sessionStorage.getItem(verifierKey);
|
|
218
|
+
sessionStorage.removeItem(verifierKey);
|
|
219
|
+
const parsed = parseHandoffCallback(url);
|
|
220
|
+
if (parsed && url === location.href) {
|
|
221
|
+
const clean = new URL(url);
|
|
222
|
+
clean.searchParams.delete("code");
|
|
223
|
+
clean.searchParams.delete("error");
|
|
224
|
+
history.replaceState(history.state, "", clean.toString());
|
|
225
|
+
}
|
|
226
|
+
return await redeem(parsed, verifier);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return failed("SIGN_IN_FAILED", error instanceof Error ? error.message : "Sign-in failed");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
async signInWith(provider, { returnUrl, redirect, invite }) {
|
|
233
|
+
let verifier;
|
|
234
|
+
let startUrl;
|
|
235
|
+
let finalUrl = null;
|
|
236
|
+
try {
|
|
237
|
+
verifier = await createHandoffVerifier(o.crypto);
|
|
238
|
+
const challenge = await createHandoffChallenge(verifier, o.crypto);
|
|
239
|
+
startUrl = buildSocialStartUrl({
|
|
240
|
+
endpoint: o.endpoint,
|
|
241
|
+
provider,
|
|
242
|
+
appId: o.appId,
|
|
243
|
+
returnUrl,
|
|
244
|
+
challenge,
|
|
245
|
+
...redirect !== void 0 ? { redirect } : {},
|
|
246
|
+
...invite !== void 0 ? { invite } : {}
|
|
247
|
+
});
|
|
248
|
+
if (o.openAuthSession) finalUrl = await o.openAuthSession(startUrl, returnUrl);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return failed(
|
|
251
|
+
"SIGN_IN_FAILED",
|
|
252
|
+
error instanceof Error ? error.message : "Sign-in failed"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (o.openAuthSession) {
|
|
256
|
+
if (finalUrl === null) return failed("CANCELLED", "Sign-in was cancelled");
|
|
257
|
+
return redeem(parseHandoffCallback(finalUrl), verifier);
|
|
258
|
+
}
|
|
259
|
+
sessionStorage.setItem(verifierKey, verifier);
|
|
260
|
+
location.assign(startUrl);
|
|
261
|
+
return new Promise(() => {
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
completeSignIn(url) {
|
|
265
|
+
if (o.openAuthSession) {
|
|
266
|
+
return Promise.resolve(
|
|
267
|
+
failed("SIGN_IN_FAILED", "signInWith already completes the sign-in in this environment")
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
completing != null ? completing : completing = finish(url != null ? url : location.href).finally(() => {
|
|
271
|
+
completing = null;
|
|
272
|
+
});
|
|
273
|
+
return completing;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/session/storage.ts
|
|
279
|
+
function createMemoryStorage() {
|
|
280
|
+
const store = /* @__PURE__ */ new Map();
|
|
281
|
+
return {
|
|
282
|
+
getItem: async (key) => {
|
|
283
|
+
var _a;
|
|
284
|
+
return (_a = store.get(key)) != null ? _a : null;
|
|
285
|
+
},
|
|
286
|
+
setItem: async (key, value) => void store.set(key, value),
|
|
287
|
+
removeItem: async (key) => void store.delete(key)
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function createLocalStorageAdapter() {
|
|
291
|
+
let web;
|
|
292
|
+
try {
|
|
293
|
+
web = globalThis.localStorage;
|
|
294
|
+
} catch {
|
|
295
|
+
web = void 0;
|
|
296
|
+
}
|
|
297
|
+
if (!web) return createMemoryStorage();
|
|
298
|
+
const fallback = createMemoryStorage();
|
|
299
|
+
return {
|
|
300
|
+
getItem: async (key) => {
|
|
301
|
+
try {
|
|
302
|
+
const value = web.getItem(key);
|
|
303
|
+
if (value !== null) return value;
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
return fallback.getItem(key);
|
|
307
|
+
},
|
|
308
|
+
setItem: async (key, value) => {
|
|
309
|
+
try {
|
|
310
|
+
web.setItem(key, value);
|
|
311
|
+
} catch {
|
|
312
|
+
await fallback.setItem(key, value);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
removeItem: async (key) => {
|
|
316
|
+
try {
|
|
317
|
+
web.removeItem(key);
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
await fallback.removeItem(key);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/transport.ts
|
|
326
|
+
import {
|
|
327
|
+
graphqlRequest as graphqlRequest2,
|
|
328
|
+
hasErrors as hasErrors2,
|
|
329
|
+
extractErrorMessage as extractErrorMessage2,
|
|
330
|
+
isUnauthorizedError as isUnauthorizedError2
|
|
331
|
+
} from "@pylo/auth";
|
|
332
|
+
import { PyloError } from "@pylo/core";
|
|
333
|
+
function createDirectTransport(options) {
|
|
334
|
+
function send(query, variables, headers, token) {
|
|
335
|
+
return graphqlRequest2(
|
|
336
|
+
options.endpoint,
|
|
337
|
+
query,
|
|
338
|
+
variables,
|
|
339
|
+
{
|
|
340
|
+
...token !== null ? { token } : {},
|
|
341
|
+
...headers !== void 0 ? { headers } : {}
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return async (query, variables, headers) => {
|
|
346
|
+
var _a;
|
|
347
|
+
let response = await send(query, variables, headers, await options.getToken());
|
|
348
|
+
if (isUnauthorizedError2(response) && options.onUnauthorized) {
|
|
349
|
+
const token = await options.onUnauthorized();
|
|
350
|
+
if (token !== null) {
|
|
351
|
+
response = await send(query, variables, headers, token);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (hasErrors2(response)) {
|
|
355
|
+
throw new PyloError(
|
|
356
|
+
(_a = extractErrorMessage2(response.errors)) != null ? _a : "GraphQL request failed",
|
|
357
|
+
response.errors
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
if (!response.data) {
|
|
361
|
+
throw new PyloError("No data returned from GraphQL request");
|
|
362
|
+
}
|
|
363
|
+
return response.data;
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/provider.tsx
|
|
368
|
+
import { jsx } from "react/jsx-runtime";
|
|
369
|
+
var PyloContext = createContext(null);
|
|
370
|
+
function usePyloContext() {
|
|
371
|
+
const value = useContext(PyloContext);
|
|
372
|
+
if (!value) {
|
|
373
|
+
throw new Error("usePylo* must be used inside <PyloProvider>");
|
|
374
|
+
}
|
|
375
|
+
return value;
|
|
376
|
+
}
|
|
377
|
+
var SIGNED_OUT = { status: "signedOut", token: null };
|
|
378
|
+
var NO_OP = () => () => {
|
|
379
|
+
};
|
|
380
|
+
function PyloProvider(props) {
|
|
381
|
+
var _a, _b;
|
|
382
|
+
const queryClient = useQueryClient();
|
|
383
|
+
const endpoint = resolveEndpoint(props.endpoint);
|
|
384
|
+
const storeRef = useRef(null);
|
|
385
|
+
const socialRef = useRef(null);
|
|
386
|
+
const openAuthSessionRef = useRef(props.openAuthSession);
|
|
387
|
+
openAuthSessionRef.current = props.openAuthSession;
|
|
388
|
+
if (storeRef.current === null && !props.getToken) {
|
|
389
|
+
storeRef.current = createSessionStore({
|
|
390
|
+
endpoint,
|
|
391
|
+
storage: (_a = props.storage) != null ? _a : createLocalStorageAdapter(),
|
|
392
|
+
...props.appId !== void 0 ? { appId: props.appId } : {},
|
|
393
|
+
...props.keyPrefix !== void 0 ? { keyPrefix: props.keyPrefix } : {},
|
|
394
|
+
onSignOut: () => queryClient.clear(),
|
|
395
|
+
onSignIn: () => void queryClient.invalidateQueries({ queryKey: ["pylo"] })
|
|
396
|
+
});
|
|
397
|
+
if (props.appId !== void 0) {
|
|
398
|
+
socialRef.current = createSocialSignIn({
|
|
399
|
+
endpoint,
|
|
400
|
+
appId: props.appId,
|
|
401
|
+
keyPrefix: (_b = props.keyPrefix) != null ? _b : "pylo",
|
|
402
|
+
store: storeRef.current,
|
|
403
|
+
...props.openAuthSession ? {
|
|
404
|
+
openAuthSession: (url, returnUrl) => openAuthSessionRef.current(url, returnUrl)
|
|
405
|
+
} : {},
|
|
406
|
+
...props.handoffCrypto ? { crypto: props.handoffCrypto } : {}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const store = storeRef.current;
|
|
411
|
+
const social = socialRef.current;
|
|
412
|
+
const state = useSyncExternalStore(
|
|
413
|
+
store ? store.subscribe : NO_OP,
|
|
414
|
+
store ? store.getState : () => SIGNED_OUT,
|
|
415
|
+
store ? store.getState : () => SIGNED_OUT
|
|
416
|
+
);
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
void (store == null ? void 0 : store.init());
|
|
419
|
+
}, [store]);
|
|
420
|
+
const getTokenProp = props.getToken;
|
|
421
|
+
const getTokenRef = useRef(getTokenProp);
|
|
422
|
+
getTokenRef.current = getTokenProp;
|
|
423
|
+
const isEscapeHatch = getTokenProp !== void 0;
|
|
424
|
+
const value = useMemo(() => {
|
|
425
|
+
const getToken = isEscapeHatch ? () => getTokenRef.current() : () => store.getToken();
|
|
426
|
+
const transport = createDirectTransport({
|
|
427
|
+
endpoint,
|
|
428
|
+
getToken,
|
|
429
|
+
...store ? { onUnauthorized: () => store.refresh() } : {}
|
|
430
|
+
});
|
|
431
|
+
return { transport, endpoint, state, store, social, getToken };
|
|
432
|
+
}, [endpoint, state, store, social, isEscapeHatch]);
|
|
433
|
+
return /* @__PURE__ */ jsx(PyloContext.Provider, { value, children: props.children });
|
|
434
|
+
}
|
|
435
|
+
function usePyloTransport() {
|
|
436
|
+
return usePyloContext().transport;
|
|
437
|
+
}
|
|
438
|
+
function usePyloClient() {
|
|
439
|
+
const { endpoint, getToken } = usePyloContext();
|
|
440
|
+
return useMemo(
|
|
441
|
+
() => createPyloClient({
|
|
442
|
+
endpoint,
|
|
443
|
+
auth: async () => {
|
|
444
|
+
const token = await getToken();
|
|
445
|
+
return token !== null ? { token } : {};
|
|
446
|
+
}
|
|
447
|
+
}),
|
|
448
|
+
[endpoint, getToken]
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
function usePyloAuth() {
|
|
452
|
+
const { state, store, social, transport, getToken } = usePyloContext();
|
|
453
|
+
const isSignedIn = state.status === "signedIn";
|
|
454
|
+
const me = useQuery({
|
|
455
|
+
queryKey: ["pylo", "me"],
|
|
456
|
+
queryFn: async () => {
|
|
457
|
+
const data = await transport(ME_QUERY, {});
|
|
458
|
+
return data.me.current_user.data;
|
|
459
|
+
},
|
|
460
|
+
enabled: isSignedIn
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
isLoading: state.status === "loading" || isSignedIn && me.isLoading,
|
|
464
|
+
isSignedIn,
|
|
465
|
+
user: me.data,
|
|
466
|
+
login: async (email, password) => {
|
|
467
|
+
if (!store) {
|
|
468
|
+
throw new Error("login is unavailable when PyloProvider is given getToken");
|
|
469
|
+
}
|
|
470
|
+
return store.login(email, password);
|
|
471
|
+
},
|
|
472
|
+
logout: async () => {
|
|
473
|
+
if (!store) {
|
|
474
|
+
throw new Error("logout is unavailable when PyloProvider is given getToken");
|
|
475
|
+
}
|
|
476
|
+
await store.logout();
|
|
477
|
+
},
|
|
478
|
+
signInWith: async (provider, options) => requireSocial("signInWith").signInWith(provider, options),
|
|
479
|
+
completeSignIn: async (url) => requireSocial("completeSignIn").completeSignIn(url),
|
|
480
|
+
getToken
|
|
481
|
+
};
|
|
482
|
+
function requireSocial(method) {
|
|
483
|
+
if (!store) {
|
|
484
|
+
throw new Error(`${method} is unavailable when PyloProvider is given getToken`);
|
|
485
|
+
}
|
|
486
|
+
if (!social) throw new Error(`${method} requires PyloProvider's appId`);
|
|
487
|
+
return social;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export {
|
|
492
|
+
createSessionStore,
|
|
493
|
+
createMemoryStorage,
|
|
494
|
+
createLocalStorageAdapter,
|
|
495
|
+
createDirectTransport,
|
|
496
|
+
PyloProvider,
|
|
497
|
+
usePyloTransport,
|
|
498
|
+
usePyloClient,
|
|
499
|
+
usePyloAuth
|
|
500
|
+
};
|
package/dist/hooks.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { EntityName, EntitySelect, EntityResult } from '@pylo/core';
|
|
2
2
|
export { AggregateFilterInput, AggregateFunction, AggregateGroupByInput, AggregateInput, AggregateInterval, AggregateIntervalUnit, AggregateMetricInput, AggregateOptions, AggregateResult, AggregateRow, AggregateRows, AggregateSortInput, AggregateSortKey, AggregateTotal, ByIdOptions, CallableEntityName, CreateUploadInput, CreateUploadUrlOptions, DimensionInput, DimensionValue, EntityAggregateApi, EntityCapability, EntityFields, EntityMethodName, EntityName, EntityNameWith, EntityRelationPath, EntityRelations, EntityResult, EntitySelect, EventAggregateOptions, EventGroupByInput, EventListFilterInput, EventListOptions, EventMetricInput, FieldName, FilesClient, FilterInput, HasCapability, IngestEvents, ListOptions, ListResult, Me, MetricValue, MutationRequestOptions, NumericFieldName, PaginationData, PaginationInput, PyloError, PyloEvent, PyloEventFieldValue, PyloEventFieldValuesOptions, PyloEventInput, PyloEventListResult, PyloEventProperty, PyloEventPropertyKeysOptions, PyloUploadedFile, QueryInput, QueryInputCondition, QueryOperator, RequestOptions, RowKeyOf, SearchValueInput, SortInput, SortOrder, SystemEntityName, TimeBucketInput, UploadAttachTarget, UploadOptions, UploadProgress, UploadSource, UploadUrl, UpsertInput, VirtualEntityName } from '@pylo/core';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
3
|
+
import { AuthResult, SocialProvider } from '@pylo/auth';
|
|
4
|
+
export { AuthResult, HandoffCrypto, PyloUser, SocialProvider } from '@pylo/auth';
|
|
5
|
+
import { P as PyloStorage, a as PyloAuth } from './provider-X8jgqOeN.js';
|
|
6
|
+
export { O as OpenAuthSession, b as PyloProvider, c as PyloProviderProps, S as SocialSignIn, d as SocialSignInResult, e as createLocalStorageAdapter, f as createMemoryStorage, u as usePyloClient, g as usePyloTransport } from './provider-X8jgqOeN.js';
|
|
7
7
|
export { D as DirectTransportOptions, T as Transport, c as createDirectTransport } from './transport-55cSrvQo.js';
|
|
8
8
|
export { InfiniteListOptions, ListHookResult, StartUploadOptions, UploadHookOptions, UploadHookResult, createPyloHooks } from './hooks.js';
|
|
9
9
|
import 'react/jsx-runtime';
|
|
@@ -22,7 +22,7 @@ interface SessionStoreOptions {
|
|
|
22
22
|
/** Namespaces the storage keys, for apps holding more than one session. */
|
|
23
23
|
keyPrefix?: string;
|
|
24
24
|
onSignOut?: () => void;
|
|
25
|
-
/** Called after a successful login, not after a refresh. */
|
|
25
|
+
/** Called after a successful login or handoff, not after a refresh. */
|
|
26
26
|
onSignIn?: () => void;
|
|
27
27
|
}
|
|
28
28
|
interface SessionStore {
|
|
@@ -34,6 +34,10 @@ interface SessionStore {
|
|
|
34
34
|
/** Forces a refresh regardless of expiry. Used after a rejected request. */
|
|
35
35
|
refresh(): Promise<string | null>;
|
|
36
36
|
login(email: string, password: string): Promise<AuthResult>;
|
|
37
|
+
/** Exchanges a social sign-in handoff code. A failure leaves any existing session intact. */
|
|
38
|
+
redeemHandoff(code: string, verifier: string): Promise<AuthResult & {
|
|
39
|
+
redirect?: string;
|
|
40
|
+
}>;
|
|
37
41
|
logout(): Promise<void>;
|
|
38
42
|
}
|
|
39
43
|
declare function createSessionStore(options: SessionStoreOptions): SessionStore;
|
|
@@ -83,5 +87,14 @@ type PyloSelect<E extends PyloEntity> = EntitySelect<RegisteredSchema, E>;
|
|
|
83
87
|
* `typeof <yourSelect>` (see {@link PyloSelect}) for an exact result type.
|
|
84
88
|
*/
|
|
85
89
|
type PyloResult<E extends PyloEntity, Sel extends PyloSelect<E>> = EntityResult<RegisteredSchema, E, Sel>;
|
|
90
|
+
/** Providers `signInWith` accepts. Codegen narrows it to those enabled for the app. */
|
|
91
|
+
type RegisteredSocialProvider = PyloRegister extends {
|
|
92
|
+
socialProvider: infer P extends string;
|
|
93
|
+
} ? P : SocialProvider;
|
|
94
|
+
/** Return URLs `signInWith` accepts. Codegen narrows it to the app's allowlist. */
|
|
95
|
+
type RegisteredSocialReturnUrl = PyloRegister extends {
|
|
96
|
+
socialReturnUrl: infer U extends string;
|
|
97
|
+
} ? U : string;
|
|
98
|
+
declare const usePyloAuth: () => PyloAuth<RegisteredSocialProvider, RegisteredSocialReturnUrl>;
|
|
86
99
|
|
|
87
|
-
export { type PyloEntity, type PyloRegister, type PyloResult, type PyloSelect, PyloStorage, type RegisteredSchema, type SessionState, type SessionStatus, type SessionStore, type SessionStoreOptions, createSessionStore };
|
|
100
|
+
export { PyloAuth, type PyloEntity, type PyloRegister, type PyloResult, type PyloSelect, PyloStorage, type RegisteredSchema, type RegisteredSocialProvider, type RegisteredSocialReturnUrl, type SessionState, type SessionStatus, type SessionStore, type SessionStoreOptions, createSessionStore, usePyloAuth };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPyloHooks
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RCW4MW4F.js";
|
|
4
4
|
import {
|
|
5
5
|
PyloProvider,
|
|
6
6
|
createDirectTransport,
|
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
usePyloAuth,
|
|
11
11
|
usePyloClient,
|
|
12
12
|
usePyloTransport
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ZWYBNMHS.js";
|
|
14
14
|
|
|
15
15
|
// src/index.ts
|
|
16
16
|
import { PyloError } from "@pylo/core";
|
|
17
|
+
var usePyloAuth2 = usePyloAuth;
|
|
17
18
|
export {
|
|
18
19
|
PyloError,
|
|
19
20
|
PyloProvider,
|
|
@@ -22,7 +23,7 @@ export {
|
|
|
22
23
|
createMemoryStorage,
|
|
23
24
|
createPyloHooks,
|
|
24
25
|
createSessionStore,
|
|
25
|
-
usePyloAuth,
|
|
26
|
+
usePyloAuth2 as usePyloAuth,
|
|
26
27
|
usePyloClient,
|
|
27
28
|
usePyloTransport
|
|
28
29
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { PyloClient } from '@pylo/core';
|
|
4
|
+
import { SocialProvider, AuthResult, PyloUser, HandoffCrypto } from '@pylo/auth';
|
|
5
|
+
import { T as Transport } from './transport-55cSrvQo.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The three operations a session needs from a key-value store. Async because
|
|
9
|
+
* the native implementations are: Expo's SecureStore has no synchronous API.
|
|
10
|
+
*/
|
|
11
|
+
interface PyloStorage {
|
|
12
|
+
getItem(key: string): Promise<string | null>;
|
|
13
|
+
setItem(key: string, value: string): Promise<void>;
|
|
14
|
+
removeItem(key: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
declare function createMemoryStorage(): PyloStorage;
|
|
17
|
+
/**
|
|
18
|
+
* Persists to `localStorage`, falling back to memory where it is missing or
|
|
19
|
+
* blocked — server rendering, and Safari's private mode, which throws on write.
|
|
20
|
+
* Falling back means the session lasts the page's lifetime rather than failing
|
|
21
|
+
* outright, so a read consults memory whenever the web store comes up empty.
|
|
22
|
+
*/
|
|
23
|
+
declare function createLocalStorageAdapter(): PyloStorage;
|
|
24
|
+
|
|
25
|
+
type SocialSignInResult = AuthResult & {
|
|
26
|
+
redirect?: string;
|
|
27
|
+
};
|
|
28
|
+
type OpenAuthSession = (url: string, returnUrl: string) => Promise<string | null>;
|
|
29
|
+
interface SocialSignIn<P extends string = SocialProvider, U extends string = string> {
|
|
30
|
+
/**
|
|
31
|
+
* On the web this navigates away and never resolves; finish on the return page
|
|
32
|
+
* with `completeSignIn`. With `openAuthSession` it resolves once the session ends.
|
|
33
|
+
*/
|
|
34
|
+
signInWith(provider: P, options: {
|
|
35
|
+
returnUrl: U;
|
|
36
|
+
redirect?: string;
|
|
37
|
+
invite?: string;
|
|
38
|
+
}): Promise<SocialSignInResult>;
|
|
39
|
+
/** Finishes a web sign-in from the return page's URL. */
|
|
40
|
+
completeSignIn(url?: string): Promise<SocialSignInResult>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface PyloProviderProps {
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
endpoint?: string;
|
|
46
|
+
appId?: string;
|
|
47
|
+
storage?: PyloStorage;
|
|
48
|
+
keyPrefix?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Escape hatch for apps whose auth lives elsewhere. Supplying it disables the
|
|
51
|
+
* session entirely: no login, no logout, no refresh.
|
|
52
|
+
*/
|
|
53
|
+
getToken?: () => Promise<string | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Runs social sign-in in an in-app browser session instead of navigating the
|
|
56
|
+
* page. Resolves with the URL the session ended on, or `null` when dismissed.
|
|
57
|
+
*/
|
|
58
|
+
openAuthSession?: OpenAuthSession;
|
|
59
|
+
handoffCrypto?: HandoffCrypto;
|
|
60
|
+
}
|
|
61
|
+
declare function PyloProvider(props: PyloProviderProps): react_jsx_runtime.JSX.Element;
|
|
62
|
+
declare function usePyloTransport(): Transport;
|
|
63
|
+
declare function usePyloClient<S>(): PyloClient<S>;
|
|
64
|
+
interface PyloAuth<P extends string = SocialProvider, U extends string = string> extends SocialSignIn<P, U> {
|
|
65
|
+
isLoading: boolean;
|
|
66
|
+
isSignedIn: boolean;
|
|
67
|
+
user: PyloUser | undefined;
|
|
68
|
+
login: (email: string, password: string) => Promise<AuthResult>;
|
|
69
|
+
logout: () => Promise<void>;
|
|
70
|
+
getToken: () => Promise<string | null>;
|
|
71
|
+
}
|
|
72
|
+
declare function usePyloAuth(): PyloAuth;
|
|
73
|
+
|
|
74
|
+
export { type OpenAuthSession as O, type PyloStorage as P, type SocialSignIn as S, type PyloAuth as a, PyloProvider as b, type PyloProviderProps as c, type SocialSignInResult as d, createLocalStorageAdapter as e, createMemoryStorage as f, usePyloTransport as g, usePyloAuth as h, usePyloClient as u };
|
package/dist/provider.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import 'react/jsx-runtime';
|
|
|
2
2
|
import 'react';
|
|
3
3
|
import '@pylo/core';
|
|
4
4
|
import '@pylo/auth';
|
|
5
|
-
export { a as PyloAuth, b as PyloProvider, c as PyloProviderProps,
|
|
5
|
+
export { a as PyloAuth, b as PyloProvider, c as PyloProviderProps, h as usePyloAuth, u as usePyloClient, g as usePyloTransport } from './provider-X8jgqOeN.js';
|
|
6
6
|
import './transport-55cSrvQo.js';
|