@passflow/react 0.15.1 → 0.17.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/CHANGELOG.md +39 -0
- package/README.md +94 -12
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +133 -143
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/flow/index.d.cts +1 -1
- package/dist/src/components/flow/index.d.ts +1 -1
- package/dist/src/components/flow/index.d.ts.map +1 -1
- package/dist/src/components/flow/passflow/flow-router.d.cts +39 -0
- package/dist/src/components/flow/passflow/flow-router.d.ts +39 -0
- package/dist/src/components/flow/passflow/flow-router.d.ts.map +1 -0
- package/dist/src/components/flow/passflow/index.d.cts +25 -3
- package/dist/src/components/flow/passflow/index.d.ts +25 -3
- package/dist/src/components/flow/passflow/index.d.ts.map +1 -1
- package/dist/src/components/flow/two-factor-setup-magic-link/index.d.cts +6 -1
- package/dist/src/components/flow/two-factor-setup-magic-link/index.d.ts +6 -1
- package/dist/src/components/flow/two-factor-setup-magic-link/index.d.ts.map +1 -1
- package/dist/src/components/form/forgot-password/forgot-password.d.ts.map +1 -1
- package/dist/src/components/form/invitation-join/index.d.ts.map +1 -1
- package/dist/src/components/form/signin/index.d.ts.map +1 -1
- package/dist/src/components/form/signup/index.d.ts.map +1 -1
- package/dist/src/components/provider/passflow-provider.d.ts.map +1 -1
- package/dist/src/components/provider/use-dispose-on-replace.d.cts +17 -0
- package/dist/src/components/provider/use-dispose-on-replace.d.ts +17 -0
- package/dist/src/components/provider/use-dispose-on-replace.d.ts.map +1 -0
- package/dist/src/context/auth-context.d.cts +5 -4
- package/dist/src/context/auth-context.d.ts +5 -4
- package/dist/src/context/auth-context.d.ts.map +1 -1
- package/dist/src/context/index.d.cts +1 -0
- package/dist/src/context/index.d.ts +1 -0
- package/dist/src/context/index.d.ts.map +1 -1
- package/dist/src/context/search-context.d.cts +20 -0
- package/dist/src/context/search-context.d.ts +20 -0
- package/dist/src/context/search-context.d.ts.map +1 -0
- package/dist/src/hooks/index.d.cts +2 -1
- package/dist/src/hooks/index.d.ts +2 -1
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/use-refresh-status.d.cts +36 -0
- package/dist/src/hooks/use-refresh-status.d.ts +36 -0
- package/dist/src/hooks/use-refresh-status.d.ts.map +1 -0
- package/dist/src/hooks/use-session-expired.d.cts +8 -4
- package/dist/src/hooks/use-session-expired.d.ts +8 -4
- package/dist/src/hooks/use-session-expired.d.ts.map +1 -1
- package/dist/src/utils/url-params.d.cts +2 -4
- package/dist/src/utils/url-params.d.ts +2 -4
- package/dist/src/utils/url-params.d.ts.map +1 -1
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@passflow/react` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.17.0] - 2026-11-03
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Requires `@passflow/core` `^0.21.0`: one token refresh per browser across tabs, with transient failures kept apart from the end of a session. See core's `docs/token-refresh.md`, "Migrating from 0.20.1". What reaches React apps: a network error or 5xx during refresh no longer signs the user out; `SessionExpired` fires only at a real end; `SignOut` fires once per sign-out.
|
|
10
|
+
- `SessionExpiredReason` is core's type, re-exported from the package root as before. It adds `'successor_timeout'` and `'identity_changed'`, so `useSessionExpired` and `AuthProvider`'s `onSessionExpired` can receive them. An exhaustive `switch` over the reason needs the new cases.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `useRefreshStatus()` returns `{ isFailing, since, nextRetryAt }` while token refresh is failing transiently (`RefreshFailing` until `RefreshRecovered`), for a "reconnecting" notice. Call it in a component that lives as long as the app: a component that mounts mid-episode starts idle.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `PassflowProvider` disposes the `Passflow` instances it replaces (its state changes on app ID discovery and app settings) and the one it holds on unmount. From core 0.21 each instance holds refresh timers, a `BroadcastChannel` and Web Locks, so an undisposed one went on acting as another tab.
|
|
19
|
+
|
|
20
|
+
## [0.16.0] - 2026-11-03
|
|
21
|
+
|
|
22
|
+
### Breaking
|
|
23
|
+
|
|
24
|
+
- `react-router` is no longer a peer dependency, and the package does not import it. The only peers are `react` and `react-dom`, `^18 || ^19`. `PassflowFlow` keeps its own small router, so it works the same in an app that routes with React Router, TanStack Router, Wouter, another router, or none. A host that installed `react-router` only for this package can remove it.
|
|
25
|
+
- `PassflowFlow` no longer picks up a host React Router `<Router>`, and the console warning about `routerType` inside one is gone. Inside an app router in browser mode it shares the address bar with it: each side sees the other's navigations, and Back reaches both. Mount it on a catch-all route (`/auth/*` in React Router, `/auth/$` in TanStack Router, `<Route path="/auth" nest>` in Wouter).
|
|
26
|
+
- `pathPrefix` is an absolute path from the root of the URL. When it is not given, the flow takes it from the first URL it sees, so `<PassflowFlow />` on `/auth` or `/auth/signin` uses `/auth`. Before, inside a host router, it was relative to the parent route.
|
|
27
|
+
- `TwoFactorSetupMagicLinkFlow` no longer uses React Router's `useParams`. It reads the token from the URL segment after `/two-factor-setup-magic-link/`, or from a new `token` prop.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `<PassflowFlow />` with no props works: browser history, prefix from the URL, every screen at a fixed path under it (`/signin`, `/signup`, `/verify-challenge-otp`, `/two-factor-setup-magic-link/:token`, …).
|
|
32
|
+
- Full control: pass `location` and `navigate` together and the app's router owns the location, for example a TanStack Router memory history. Their types are exported as `PassflowFlowLocation` and `PassflowFlowNavigate`.
|
|
33
|
+
- `PassflowFlow` serves the 2FA setup magic link (`/two-factor-setup-magic-link/:token`) as one of its screens.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Forms inside `PassflowFlow` read search params from the flow's location. In memory or hash mode, or under `location` + `navigate`, they read the address bar, which never carried them; forgot-password then threw "Invalid default method param".
|
|
38
|
+
|
|
39
|
+
### Internal
|
|
40
|
+
|
|
41
|
+
- Tests run the zero-config flow inside React Router, TanStack Router and Wouter (jsdom), and inside a TanStack Router app in Chromium. `@tanstack/react-router` and `wouter` are dev dependencies; `react-router` stays one, for those tests and the docs.
|
|
42
|
+
- The consumer type check installs no router.
|
|
43
|
+
|
|
5
44
|
## [0.15.1] - 2026-11-02
|
|
6
45
|
|
|
7
46
|
### Fixed
|
package/README.md
CHANGED
|
@@ -46,6 +46,8 @@ This is a SDK for react application.
|
|
|
46
46
|
- [useJoinInvite](#usejoininvite)
|
|
47
47
|
- [useLogout](#uselogout)
|
|
48
48
|
- [usePasswordlessComplete](#usepasswordlesscomplete)
|
|
49
|
+
- [useSessionExpired](#usesessionexpired)
|
|
50
|
+
- [useRefreshStatus](#userefreshstatus)
|
|
49
51
|
|
|
50
52
|
to install just type:
|
|
51
53
|
|
|
@@ -137,17 +139,15 @@ Both scripts automatically start the demo app and Storybook. `pnpm test` uses th
|
|
|
137
139
|
## Installation
|
|
138
140
|
|
|
139
141
|
```bash
|
|
140
|
-
pnpm add @passflow/react
|
|
142
|
+
pnpm add @passflow/react
|
|
141
143
|
```
|
|
142
144
|
|
|
143
|
-
|
|
145
|
+
No router is needed. `PassflowFlow` has its own small router, and works on its own or inside an app routed by React Router, TanStack Router, Wouter or anything else.
|
|
144
146
|
|
|
145
147
|
## Requirements
|
|
146
148
|
|
|
147
|
-
- React 18
|
|
148
|
-
- React Router 7 or 8 (React Router 6 is not supported)
|
|
149
|
+
- React 18 or 19
|
|
149
150
|
- Node `^22.12.0 || ^24.0.0 || >=26.0.0`
|
|
150
|
-
- Your app may route with React Router, Wouter or TanStack Router
|
|
151
151
|
|
|
152
152
|
## Integration
|
|
153
153
|
|
|
@@ -246,21 +246,63 @@ authentication-context claims those tokens carry.
|
|
|
246
246
|
|
|
247
247
|
### PassflowFlow
|
|
248
248
|
|
|
249
|
+
The whole sign-in experience on its own pages: sign-in, sign-up, one-time codes, magic links, password reset, invitations and two-factor. It needs no router and no setup:
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
<PassflowProvider url="https://auth.example.com" appId="your-app-id">
|
|
253
|
+
<PassflowFlow />
|
|
254
|
+
</PassflowProvider>
|
|
255
|
+
```
|
|
256
|
+
|
|
249
257
|
| Prop | Type | Description |
|
|
250
258
|
|------|------|-------------|
|
|
251
259
|
| successAuthRedirect | string | URL to redirect after successful authentication |
|
|
252
|
-
| federatedDisplayMode | "
|
|
253
|
-
| pathPrefix | string |
|
|
260
|
+
| federatedDisplayMode | "modal" \| "redirect" | Federated authentication display mode |
|
|
261
|
+
| pathPrefix | string | Where the flow lives, such as `/auth`. Optional: worked out from the first URL |
|
|
262
|
+
| routerType | "browser" \| "hash" \| "memory" | The history the flow keeps. Default `browser` |
|
|
263
|
+
| location, navigate | see below | Full control by the app's router. Optional, always together |
|
|
264
|
+
|
|
265
|
+
**Paths.** The flow's screens are `<prefix>/signin`, `<prefix>/signup`, `<prefix>/verify-challenge-otp`, `<prefix>/verify-challenge-magic-link`, `<prefix>/forgot-password`, `<prefix>/forgot-password/success`, `<prefix>/password/reset`, `<prefix>/invitation`, `<prefix>/two-factor-verify`, `<prefix>/two-factor-setup` and `<prefix>/two-factor-setup-magic-link/:token`. Without `pathPrefix` the flow takes the prefix from the first URL it sees: `/auth/signin` and `/auth` both give `/auth`, `/` gives none. A path under the prefix that names no screen goes to sign-in.
|
|
254
266
|
|
|
255
|
-
Inside an app
|
|
267
|
+
**Inside an app's router.** Mount the flow on a catch-all route; nothing else is needed. The flow moves the address bar and announces it with `popstate`, the event React Router, TanStack Router and Wouter listen to, so the app's router follows the flow and the flow follows the app, Back and Forward included:
|
|
256
268
|
|
|
257
269
|
```tsx
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
270
|
+
// TanStack Router
|
|
271
|
+
const authRoute = createRoute({ getParentRoute: () => rootRoute, path: '/auth/$', component: () => <PassflowFlow /> });
|
|
272
|
+
|
|
273
|
+
// React Router
|
|
274
|
+
<Route path="/auth/*" element={<PassflowFlow />} />
|
|
275
|
+
|
|
276
|
+
// Wouter
|
|
277
|
+
<Route path="/auth" nest><PassflowFlow /></Route>
|
|
262
278
|
```
|
|
263
279
|
|
|
280
|
+
**`routerType`.** `browser` (default) uses the address bar. `hash` keeps the flow's path after `#`. `memory` leaves the URL alone, for an embedded widget or tests.
|
|
281
|
+
|
|
282
|
+
**Full control.** When the app's router does not use the address bar (it runs in hash or memory mode), hand the flow its location and a way to move it. `routerType` is then not used:
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
// TanStack Router with a memory or hash history
|
|
286
|
+
function AuthPage() {
|
|
287
|
+
const router = useRouter();
|
|
288
|
+
const location = useRouterState({ select: (state) => state.location });
|
|
289
|
+
return (
|
|
290
|
+
<PassflowFlow
|
|
291
|
+
location={{ pathname: location.pathname, search: location.searchStr }}
|
|
292
|
+
navigate={(to, { replace }) => {
|
|
293
|
+
const href = `${to.pathname}${to.search}`;
|
|
294
|
+
if (replace) router.history.replace(href);
|
|
295
|
+
else router.history.push(href);
|
|
296
|
+
}}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`TwoFactorSetupMagicLinkFlow` can also be mounted on its own page. It reads the token from the URL (the segment after `/two-factor-setup-magic-link/`), or takes it as a `token` prop.
|
|
303
|
+
|
|
304
|
+
The sections below wire the app's router into the **standalone form components** (`SignIn`, `SignUp`, …) through `PassflowProvider`'s `navigate` and `router` props. `PassflowFlow` does not need them.
|
|
305
|
+
|
|
264
306
|
### React Router
|
|
265
307
|
|
|
266
308
|
Example of integration with React Router:
|
|
@@ -907,3 +949,43 @@ const { fetch, isLoading, isError, error } = usePasswordlessComplete();
|
|
|
907
949
|
| isLoading | `boolean` | Loading state |
|
|
908
950
|
| isError | `boolean` | Error state |
|
|
909
951
|
| error | `string` | Error message |
|
|
952
|
+
|
|
953
|
+
### useSessionExpired
|
|
954
|
+
Hook for reacting to the end of a session, for example to redirect to your login page.
|
|
955
|
+
|
|
956
|
+
```typescript
|
|
957
|
+
const { isSessionExpired, expiredReason, resetSessionExpired } = useSessionExpired({
|
|
958
|
+
onSessionExpired: (reason) => navigate('/signin'),
|
|
959
|
+
});
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
`SessionExpired` fires only when the session has really ended: the last auth strike (`'refresh_failed'`), no successor token pair in time (`'successor_timeout'`), or another user's tokens in storage (`'identity_changed'`). A transient refresh failure (network error, 5xx) keeps the session and shows up as `RefreshFailing` / `RefreshRecovered` instead; use [useRefreshStatus](#userefreshstatus) for that. The state resets on `SignIn` and `Register`.
|
|
963
|
+
|
|
964
|
+
| Parameter | Type | Description |
|
|
965
|
+
|-----------|------|-------------|
|
|
966
|
+
| onSessionExpired | `(reason: SessionExpiredReason) => void` (optional) | Called when the session ends |
|
|
967
|
+
|
|
968
|
+
**Returns:**
|
|
969
|
+
|
|
970
|
+
| Property | Type | Description |
|
|
971
|
+
|----------|------|-------------|
|
|
972
|
+
| isSessionExpired | `boolean` | True once the session has ended |
|
|
973
|
+
| expiredReason | `SessionExpiredReason \| null` | Why it ended (`SessionExpiredReason` is exported from `@passflow/core`) |
|
|
974
|
+
| resetSessionExpired | `() => void` | Clears the expired state |
|
|
975
|
+
|
|
976
|
+
### useRefreshStatus
|
|
977
|
+
Hook for showing a "connection trouble" notice while token refresh is failing transiently. The session is kept during the episode; show the notice on `RefreshFailing` and hide it on `RefreshRecovered`.
|
|
978
|
+
|
|
979
|
+
```typescript
|
|
980
|
+
const { isFailing, since, nextRetryAt } = useRefreshStatus();
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
Set by `RefreshFailing`. Cleared by `RefreshRecovered`, `SessionExpired`, `SignOut`, `SignIn` and `Register`. The episode is known only from events, so a component that mounts after `RefreshFailing` starts idle: call the hook in a component that lives as long as the app, near `PassflowProvider`.
|
|
984
|
+
|
|
985
|
+
**Returns:**
|
|
986
|
+
|
|
987
|
+
| Property | Type | Description |
|
|
988
|
+
|----------|------|-------------|
|
|
989
|
+
| isFailing | `boolean` | True inside a transient-failure episode |
|
|
990
|
+
| since | `number \| null` | Epoch ms of the episode's first failure |
|
|
991
|
+
| nextRetryAt | `number \| null` | Epoch ms of the pending retry, when known |
|