@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 CHANGED
@@ -1,113 +1,11 @@
1
1
  # @pylo/react
2
2
 
3
- Type-safe Pylo SDK for React single-page apps. Hooks, a session provider, and a
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
- For Next.js use [`@pylo/nextjs`](../nextjs), which keeps tokens in httpOnly
7
- cookies. For Expo and React Native use [`@pylo/expo`](../expo).
5
+ ## Documentation
8
6
 
9
- ## Install
7
+ [https://docs.pyloapp.com/getting-started/quickstart-react](https://docs.pyloapp.com/getting-started/quickstart-react)
10
8
 
11
- ```bash
12
- npm install @pylo/react @tanstack/react-query
13
- ```
9
+ ## License
14
10
 
15
- ## Generate types
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: "signedIn", token: auth });
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: "signedOut", token: null });
50
- (_a2 = options.onSignOut) == null ? void 0 : _a2.call(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 !== "signedIn") return null;
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: "signedIn", token: auth } : { status: "signedOut", token: null }
91
+ auth ? { status: "loggedIn", token: auth } : { status: "loggedOut", token: null }
85
92
  );
86
93
  } catch {
87
94
  refreshToken = null;
88
- setState({ status: "signedOut", token: null });
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, _b;
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 persist(auth_token, refresh_token);
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 SIGNED_OUT = { status: "signedOut", token: null };
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
- onSignOut: () => queryClient.clear(),
264
- onSignIn: () => void queryClient.invalidateQueries({ queryKey: ["pylo"] })
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 : () => SIGNED_OUT,
271
- store ? store.getState : () => SIGNED_OUT
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 isSignedIn = state.status === "signedIn";
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: isSignedIn
460
+ enabled: isLoggedIn
317
461
  });
318
462
  return {
319
- isLoading: state.status === "loading" || isSignedIn && me.isLoading,
320
- isSignedIn,
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 {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  usePyloTransport
3
- } from "./chunk-IKQ52QLH.js";
3
+ } from "./chunk-BPRHGSZF.js";
4
4
 
5
5
  // src/hooks.ts
6
6
  import { useCallback, useRef, useState } from "react";
package/dist/hooks.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import {
3
3
  createPyloHooks
4
- } from "./chunk-DOPZ5GRU.js";
5
- import "./chunk-IKQ52QLH.js";
4
+ } from "./chunk-O456XBDT.js";
5
+ import "./chunk-BPRHGSZF.js";
6
6
  export {
7
7
  createPyloHooks
8
8
  };
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 { P as PyloStorage } from './provider-DTTtfhib.js';
4
- export { a as PyloAuth, b as PyloProvider, c as PyloProviderProps, d as createLocalStorageAdapter, e as createMemoryStorage, u as usePyloAuth, f as usePyloClient, g as usePyloTransport } from './provider-DTTtfhib.js';
5
- import { AuthResult } from '@pylo/auth';
6
- export { AuthResult, PyloUser } from '@pylo/auth';
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" | "signedIn" | "signedOut";
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
- onSignOut?: () => void;
25
- /** Called after a successful login, not after a refresh. */
26
- onSignIn?: () => void;
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-DOPZ5GRU.js";
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-IKQ52QLH.js";
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 };
@@ -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, u as usePyloAuth, f as usePyloClient, g as usePyloTransport } from './provider-DTTtfhib.js';
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
@@ -3,7 +3,7 @@ import {
3
3
  usePyloAuth,
4
4
  usePyloClient,
5
5
  usePyloTransport
6
- } from "./chunk-IKQ52QLH.js";
6
+ } from "./chunk-BPRHGSZF.js";
7
7
  export {
8
8
  PyloProvider,
9
9
  usePyloAuth,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylo/react",
3
- "version": "0.7.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.6",
40
- "@pylo/core": "0.7.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://github.com/Okeano-GmbH/pylo-sdks/tree/main/react#readme",
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",