@pylo/react 0.7.0 → 0.9.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-IKQ52QLH.js → chunk-BPRHGSZF.js} +175 -22
- package/dist/{chunk-DOPZ5GRU.js → chunk-O456XBDT.js} +1 -1
- package/dist/hooks.js +2 -2
- package/dist/index.d.ts +22 -9
- package/dist/index.js +4 -3
- package/dist/provider-4DBl_6p0.d.ts +74 -0
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-SBMMJYJT.js +0 -346
- package/dist/chunk-VUY6OTMP.js +0 -540
package/README.md
CHANGED
|
@@ -1,113 +1,11 @@
|
|
|
1
1
|
# @pylo/react
|
|
2
2
|
|
|
3
|
-
Type-safe Pylo SDK for React single-page apps
|
|
4
|
-
typed client, generated from your own Pylo schema.
|
|
3
|
+
Type-safe Pylo SDK for React single-page apps, such as Vite.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
cookies. For Expo and React Native use [`@pylo/expo`](../expo).
|
|
5
|
+
## Documentation
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
[https://docs.pyloapp.com/getting-started/quickstart-react](https://docs.pyloapp.com/getting-started/quickstart-react)
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
npm install @pylo/react @tanstack/react-query
|
|
13
|
-
```
|
|
9
|
+
## License
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Add a `pylo.config.ts` at your project root, then run the generator. It writes
|
|
18
|
-
`.pylo/index.ts` and registers the schema, so no `declare module` is needed.
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npx pylo generate
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Set up
|
|
25
|
-
|
|
26
|
-
Your app owns the QueryClient. `PyloProvider` goes inside it, because signing
|
|
27
|
-
out clears the query cache.
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
31
|
-
import { PyloProvider } from "@pylo/react";
|
|
32
|
-
|
|
33
|
-
const queryClient = new QueryClient();
|
|
34
|
-
|
|
35
|
-
export function App() {
|
|
36
|
-
return (
|
|
37
|
-
<QueryClientProvider client={queryClient}>
|
|
38
|
-
<PyloProvider appId={import.meta.env.VITE_PYLO_APP_ID}>
|
|
39
|
-
<Routes />
|
|
40
|
-
</PyloProvider>
|
|
41
|
-
</QueryClientProvider>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Sign in
|
|
47
|
-
|
|
48
|
-
```tsx
|
|
49
|
-
import { usePyloAuth } from "@pylo/react";
|
|
50
|
-
|
|
51
|
-
function SignIn() {
|
|
52
|
-
const { isLoading, isSignedIn, user, login, logout } = usePyloAuth();
|
|
53
|
-
|
|
54
|
-
if (isLoading) return <Spinner />;
|
|
55
|
-
if (isSignedIn) return <button onClick={() => logout()}>Sign out {user?.email}</button>;
|
|
56
|
-
|
|
57
|
-
return <button onClick={() => login(email, password)}>Sign in</button>;
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
`isLoading` stays true until stored tokens have been read, so an already
|
|
62
|
-
signed-in user never sees a login screen flash.
|
|
63
|
-
|
|
64
|
-
## Query data
|
|
65
|
-
|
|
66
|
-
```tsx
|
|
67
|
-
import { createPyloHooks } from "@pylo/react";
|
|
68
|
-
import type { PyloSchema } from "@pylo/types";
|
|
69
|
-
|
|
70
|
-
const { usePyloList, usePyloUpsert } = createPyloHooks<PyloSchema>();
|
|
71
|
-
|
|
72
|
-
function Contacts() {
|
|
73
|
-
const { data, isLoading } = usePyloList("contact", {
|
|
74
|
-
select: { id: true, name: true, company: { select: { name: true } } },
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const upsert = usePyloUpsert("contact");
|
|
78
|
-
|
|
79
|
-
return <List rows={data} onRename={(id, name) => upsert.mutate({ id, name })} />;
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
For imperative calls outside a query, `usePyloClient<PyloSchema>()` returns the
|
|
84
|
-
same typed client the server SDK exposes.
|
|
85
|
-
|
|
86
|
-
## Token storage
|
|
87
|
-
|
|
88
|
-
Tokens live in `localStorage`, which any script running on your page can read.
|
|
89
|
-
Refresh tokens rotate on every use, but a strong Content Security Policy is what
|
|
90
|
-
actually protects them. If your app has a backend, keep the tokens there and
|
|
91
|
-
proxy requests through it instead.
|
|
92
|
-
|
|
93
|
-
The token is refreshed before it expires and once more if a request comes back
|
|
94
|
-
unauthorized. Only a rejected refresh token ends the session: a transient server
|
|
95
|
-
error leaves it intact, so a backend deploy does not sign everyone out.
|
|
96
|
-
|
|
97
|
-
## Bringing your own auth
|
|
98
|
-
|
|
99
|
-
Pass `getToken` instead, and the provider skips login, logout and refresh
|
|
100
|
-
entirely.
|
|
101
|
-
|
|
102
|
-
```tsx
|
|
103
|
-
<PyloProvider getToken={() => auth.getAccessToken()}>
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Custom storage
|
|
107
|
-
|
|
108
|
-
`PyloProvider` takes any `PyloStorage`: three async methods for get, set and
|
|
109
|
-
remove.
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
import type { PyloStorage } from "@pylo/react";
|
|
113
|
-
```
|
|
11
|
+
[MIT](./LICENSE)
|
|
@@ -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) {
|
|
@@ -39,19 +41,24 @@ function createSessionStore(options) {
|
|
|
39
41
|
refreshToken = refresh2;
|
|
40
42
|
await options.storage.setItem(AUTH_KEY, auth);
|
|
41
43
|
await options.storage.setItem(REFRESH_KEY, refresh2);
|
|
42
|
-
setState({ status: "
|
|
44
|
+
setState({ status: "loggedIn", token: auth });
|
|
45
|
+
}
|
|
46
|
+
async function startSession(auth, refresh2) {
|
|
47
|
+
var _a2;
|
|
48
|
+
await persist(auth, refresh2);
|
|
49
|
+
(_a2 = options.onLogin) == null ? void 0 : _a2.call(options);
|
|
43
50
|
}
|
|
44
51
|
async function clear() {
|
|
45
52
|
var _a2;
|
|
46
53
|
refreshToken = null;
|
|
47
54
|
await options.storage.removeItem(AUTH_KEY);
|
|
48
55
|
await options.storage.removeItem(REFRESH_KEY);
|
|
49
|
-
setState({ status: "
|
|
50
|
-
(_a2 = options.
|
|
56
|
+
setState({ status: "loggedOut", token: null });
|
|
57
|
+
(_a2 = options.onLogout) == null ? void 0 : _a2.call(options);
|
|
51
58
|
}
|
|
52
59
|
async function runRefresh() {
|
|
53
60
|
await init();
|
|
54
|
-
if (state.status !== "
|
|
61
|
+
if (state.status !== "loggedIn") return null;
|
|
55
62
|
if (!refreshToken) {
|
|
56
63
|
await clear();
|
|
57
64
|
return null;
|
|
@@ -81,11 +88,11 @@ function createSessionStore(options) {
|
|
|
81
88
|
]);
|
|
82
89
|
refreshToken = stored;
|
|
83
90
|
setState(
|
|
84
|
-
auth ? { status: "
|
|
91
|
+
auth ? { status: "loggedIn", token: auth } : { status: "loggedOut", token: null }
|
|
85
92
|
);
|
|
86
93
|
} catch {
|
|
87
94
|
refreshToken = null;
|
|
88
|
-
setState({ status: "
|
|
95
|
+
setState({ status: "loggedOut", token: null });
|
|
89
96
|
}
|
|
90
97
|
})();
|
|
91
98
|
return ready;
|
|
@@ -111,7 +118,7 @@ function createSessionStore(options) {
|
|
|
111
118
|
},
|
|
112
119
|
refresh,
|
|
113
120
|
async login(email, password) {
|
|
114
|
-
var _a2
|
|
121
|
+
var _a2;
|
|
115
122
|
const response = await graphqlRequest(
|
|
116
123
|
options.endpoint,
|
|
117
124
|
LOGIN_MUTATION,
|
|
@@ -139,14 +146,135 @@ function createSessionStore(options) {
|
|
|
139
146
|
};
|
|
140
147
|
}
|
|
141
148
|
const { auth_token, refresh_token } = response.data.login.data;
|
|
142
|
-
await
|
|
143
|
-
(_b = options.onSignIn) == null ? void 0 : _b.call(options);
|
|
149
|
+
await startSession(auth_token, refresh_token);
|
|
144
150
|
return { success: true, authToken: auth_token, refreshToken: refresh_token };
|
|
145
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: "LOGIN_FAILED",
|
|
167
|
+
message: error instanceof Error ? error.message : "Login failed"
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (hasErrors(response) || !response.data) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: {
|
|
175
|
+
code: "LOGIN_FAILED",
|
|
176
|
+
message: (_a2 = extractErrorMessage(response.errors)) != null ? _a2 : "Login failed"
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const { auth_token, refresh_token, redirect } = response.data.redeemLoginHandoff.data;
|
|
181
|
+
await startSession(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
|
+
},
|
|
146
190
|
logout: clear
|
|
147
191
|
};
|
|
148
192
|
}
|
|
149
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 createSocialLogin(o) {
|
|
205
|
+
const verifierKey = `${o.keyPrefix}.handoff_verifier`;
|
|
206
|
+
let completing = null;
|
|
207
|
+
function redeem(parsed, verifier) {
|
|
208
|
+
if (!parsed) return failed("LOGIN_FAILED", "No login response in this URL");
|
|
209
|
+
if ("error" in parsed) return failed("LOGIN_FAILED", parsed.error);
|
|
210
|
+
if (!verifier) {
|
|
211
|
+
return failed("LOGIN_FAILED", "No login 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("LOGIN_FAILED", error instanceof Error ? error.message : "Login failed");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
async loginWith(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
|
+
"LOGIN_FAILED",
|
|
252
|
+
error instanceof Error ? error.message : "Login failed"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (o.openAuthSession) {
|
|
256
|
+
if (finalUrl === null) return failed("CANCELLED", "Login 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
|
+
completeLogin(url) {
|
|
265
|
+
if (o.openAuthSession) {
|
|
266
|
+
return Promise.resolve(
|
|
267
|
+
failed("LOGIN_FAILED", "loginWith already completes the login 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
|
+
|
|
150
278
|
// src/session/storage.ts
|
|
151
279
|
function createMemoryStorage() {
|
|
152
280
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -246,29 +374,45 @@ function usePyloContext() {
|
|
|
246
374
|
}
|
|
247
375
|
return value;
|
|
248
376
|
}
|
|
249
|
-
var
|
|
377
|
+
var LOGGED_OUT = { status: "loggedOut", token: null };
|
|
250
378
|
var NO_OP = () => () => {
|
|
251
379
|
};
|
|
252
380
|
function PyloProvider(props) {
|
|
253
|
-
var _a;
|
|
381
|
+
var _a, _b;
|
|
254
382
|
const queryClient = useQueryClient();
|
|
255
383
|
const endpoint = resolveEndpoint(props.endpoint);
|
|
256
384
|
const storeRef = useRef(null);
|
|
385
|
+
const socialRef = useRef(null);
|
|
386
|
+
const openAuthSessionRef = useRef(props.openAuthSession);
|
|
387
|
+
openAuthSessionRef.current = props.openAuthSession;
|
|
257
388
|
if (storeRef.current === null && !props.getToken) {
|
|
258
389
|
storeRef.current = createSessionStore({
|
|
259
390
|
endpoint,
|
|
260
391
|
storage: (_a = props.storage) != null ? _a : createLocalStorageAdapter(),
|
|
261
392
|
...props.appId !== void 0 ? { appId: props.appId } : {},
|
|
262
393
|
...props.keyPrefix !== void 0 ? { keyPrefix: props.keyPrefix } : {},
|
|
263
|
-
|
|
264
|
-
|
|
394
|
+
onLogout: () => queryClient.clear(),
|
|
395
|
+
onLogin: () => void queryClient.invalidateQueries({ queryKey: ["pylo"] })
|
|
265
396
|
});
|
|
397
|
+
if (props.appId !== void 0) {
|
|
398
|
+
socialRef.current = createSocialLogin({
|
|
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
|
+
}
|
|
266
409
|
}
|
|
267
410
|
const store = storeRef.current;
|
|
411
|
+
const social = socialRef.current;
|
|
268
412
|
const state = useSyncExternalStore(
|
|
269
413
|
store ? store.subscribe : NO_OP,
|
|
270
|
-
store ? store.getState : () =>
|
|
271
|
-
store ? store.getState : () =>
|
|
414
|
+
store ? store.getState : () => LOGGED_OUT,
|
|
415
|
+
store ? store.getState : () => LOGGED_OUT
|
|
272
416
|
);
|
|
273
417
|
useEffect(() => {
|
|
274
418
|
void (store == null ? void 0 : store.init());
|
|
@@ -284,8 +428,8 @@ function PyloProvider(props) {
|
|
|
284
428
|
getToken,
|
|
285
429
|
...store ? { onUnauthorized: () => store.refresh() } : {}
|
|
286
430
|
});
|
|
287
|
-
return { transport, endpoint, state, store, getToken };
|
|
288
|
-
}, [endpoint, state, store, isEscapeHatch]);
|
|
431
|
+
return { transport, endpoint, state, store, social, getToken };
|
|
432
|
+
}, [endpoint, state, store, social, isEscapeHatch]);
|
|
289
433
|
return /* @__PURE__ */ jsx(PyloContext.Provider, { value, children: props.children });
|
|
290
434
|
}
|
|
291
435
|
function usePyloTransport() {
|
|
@@ -305,19 +449,19 @@ function usePyloClient() {
|
|
|
305
449
|
);
|
|
306
450
|
}
|
|
307
451
|
function usePyloAuth() {
|
|
308
|
-
const { state, store, transport, getToken } = usePyloContext();
|
|
309
|
-
const
|
|
452
|
+
const { state, store, social, transport, getToken } = usePyloContext();
|
|
453
|
+
const isLoggedIn = state.status === "loggedIn";
|
|
310
454
|
const me = useQuery({
|
|
311
455
|
queryKey: ["pylo", "me"],
|
|
312
456
|
queryFn: async () => {
|
|
313
457
|
const data = await transport(ME_QUERY, {});
|
|
314
458
|
return data.me.current_user.data;
|
|
315
459
|
},
|
|
316
|
-
enabled:
|
|
460
|
+
enabled: isLoggedIn
|
|
317
461
|
});
|
|
318
462
|
return {
|
|
319
|
-
isLoading: state.status === "loading" ||
|
|
320
|
-
|
|
463
|
+
isLoading: state.status === "loading" || isLoggedIn && me.isLoading,
|
|
464
|
+
isLoggedIn,
|
|
321
465
|
user: me.data,
|
|
322
466
|
login: async (email, password) => {
|
|
323
467
|
if (!store) {
|
|
@@ -331,8 +475,17 @@ function usePyloAuth() {
|
|
|
331
475
|
}
|
|
332
476
|
await store.logout();
|
|
333
477
|
},
|
|
478
|
+
loginWith: async (provider, options) => requireSocial("loginWith").loginWith(provider, options),
|
|
479
|
+
completeLogin: async (url) => requireSocial("completeLogin").completeLogin(url),
|
|
334
480
|
getToken
|
|
335
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
|
+
}
|
|
336
489
|
}
|
|
337
490
|
|
|
338
491
|
export {
|
package/dist/hooks.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
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-4DBl_6p0.js';
|
|
6
|
+
export { O as OpenAuthSession, b as PyloProvider, c as PyloProviderProps, S as SocialLogin, d as SocialLoginResult, e as createLocalStorageAdapter, f as createMemoryStorage, u as usePyloClient, g as usePyloTransport } from './provider-4DBl_6p0.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';
|
|
10
10
|
import 'react';
|
|
11
11
|
import '@tanstack/react-query';
|
|
12
12
|
|
|
13
|
-
type SessionStatus = "loading" | "
|
|
13
|
+
type SessionStatus = "loading" | "loggedIn" | "loggedOut";
|
|
14
14
|
interface SessionState {
|
|
15
15
|
status: SessionStatus;
|
|
16
16
|
token: string | null;
|
|
@@ -21,9 +21,9 @@ interface SessionStoreOptions {
|
|
|
21
21
|
appId?: string;
|
|
22
22
|
/** Namespaces the storage keys, for apps holding more than one session. */
|
|
23
23
|
keyPrefix?: string;
|
|
24
|
-
|
|
25
|
-
/** Called after a successful login, not after a refresh. */
|
|
26
|
-
|
|
24
|
+
onLogout?: () => void;
|
|
25
|
+
/** Called after a successful login or handoff, not after a refresh. */
|
|
26
|
+
onLogin?: () => void;
|
|
27
27
|
}
|
|
28
28
|
interface SessionStore {
|
|
29
29
|
getState(): SessionState;
|
|
@@ -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 login 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 `loginWith` 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 `loginWith` 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-O456XBDT.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-BPRHGSZF.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 SocialLoginResult = AuthResult & {
|
|
26
|
+
redirect?: string;
|
|
27
|
+
};
|
|
28
|
+
type OpenAuthSession = (url: string, returnUrl: string) => Promise<string | null>;
|
|
29
|
+
interface SocialLogin<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 `completeLogin`. With `openAuthSession` it resolves once the session ends.
|
|
33
|
+
*/
|
|
34
|
+
loginWith(provider: P, options: {
|
|
35
|
+
returnUrl: U;
|
|
36
|
+
redirect?: string;
|
|
37
|
+
invite?: string;
|
|
38
|
+
}): Promise<SocialLoginResult>;
|
|
39
|
+
/** Finishes a web login from the return page's URL. */
|
|
40
|
+
completeLogin(url?: string): Promise<SocialLoginResult>;
|
|
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 login 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 SocialLogin<P, U> {
|
|
65
|
+
isLoading: boolean;
|
|
66
|
+
isLoggedIn: 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 SocialLogin as S, type PyloAuth as a, PyloProvider as b, type PyloProviderProps as c, type SocialLoginResult 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-4DBl_6p0.js';
|
|
6
6
|
import './transport-55cSrvQo.js';
|
package/dist/provider.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylo/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Type-safe React SDK for Pylo — hooks, session provider, client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"LICENSE"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@pylo/auth": "0.0
|
|
40
|
-
"@pylo/core": "0.
|
|
39
|
+
"@pylo/auth": "0.2.0",
|
|
40
|
+
"@pylo/core": "0.8.1"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@tanstack/react-query": ">=5.0.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
],
|
|
59
59
|
"author": "Okeano GmbH",
|
|
60
60
|
"license": "MIT",
|
|
61
|
-
"homepage": "https://
|
|
61
|
+
"homepage": "https://docs.pyloapp.com/getting-started/quickstart-react",
|
|
62
62
|
"repository": {
|
|
63
63
|
"type": "git",
|
|
64
64
|
"url": "git+https://github.com/Okeano-GmbH/pylo-sdks.git",
|