@estiva-app/identity 0.2.2 → 0.2.3
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 +23 -0
- package/README.md +11 -6
- package/dist/client.d.ts +30 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +77 -15
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +109 -24
- package/src/index.ts +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @estiva-app/identity
|
|
2
2
|
|
|
3
|
+
## 0.2.3 — 2026-10-13
|
|
4
|
+
|
|
5
|
+
**Additive.** A caller that passes `storage` behaves exactly as before, apart
|
|
6
|
+
from renewal now being locked across tabs.
|
|
7
|
+
|
|
8
|
+
- **Renewal is locked across tabs (PEE-3).** A session in `localStorage` is read
|
|
9
|
+
by every tab, and every tab's renewal timer fires for the same expiry; the
|
|
10
|
+
per-page guard from 0.2.0 does not see the other tabs. Estiva ID revokes the
|
|
11
|
+
whole refresh chain when one refresh token is presented twice, so two open
|
|
12
|
+
tabs could sign the person out of everything. Ship and Leaf have shared their
|
|
13
|
+
session since they adopted the package and were exposed to this.
|
|
14
|
+
|
|
15
|
+
`refreshAccessToken` now runs under `lock` (new, optional; the Web Locks API
|
|
16
|
+
by default, unguarded where there is none). A tab that waited for the lock and
|
|
17
|
+
finds a different refresh token stored takes that token instead of calling
|
|
18
|
+
`/token`. A `scheduleRenewal` timer that fires after another tab renewed
|
|
19
|
+
re-arms from the shared token's expiry rather than renewing again.
|
|
20
|
+
|
|
21
|
+
- **`storage` is optional, and defaults to `localStorage`; `pendingStore` then
|
|
22
|
+
defaults to `sessionStorage`.** Every app keeps its session the same way:
|
|
23
|
+
shared by its tabs, the in-flight PKCE values per tab. Peek moves to it with
|
|
24
|
+
PEE-3. Passing `storage` alone still uses it for both.
|
|
25
|
+
|
|
3
26
|
## 0.2.2 — 2026-09-08
|
|
4
27
|
|
|
5
28
|
**Additive.** `token` gains a function form; a string behaves exactly as before,
|
package/README.md
CHANGED
|
@@ -39,15 +39,21 @@ package come out Peek-shaped, which is the whole risk SHA-4 names.
|
|
|
39
39
|
| --- | --- | --- |
|
|
40
40
|
| `clientId` | `estiva-peek` | `estiva-ship` |
|
|
41
41
|
| `redirectUri` | a fixed `/auth/callback` | its current pathname — Ship routes on `location.hash` |
|
|
42
|
-
| `storage` — the session | `sessionStorage`, dies with the tab | `localStorage`, outlives a tab |
|
|
43
|
-
| `pendingStore` — in-flight PKCE credentials | defaults to `storage` | `sessionStorage`, because the flow starts and ends in one tab |
|
|
44
42
|
| `keyPrefix` | `peek.estivaId` | `ship.estiva-id` |
|
|
45
43
|
| `navigate` | the one genuinely untestable act, so it is observable | |
|
|
46
44
|
| `setTimer` / `clearTimer` | the timers `scheduleRenewal` runs on, injected for the same reason | |
|
|
47
45
|
|
|
48
|
-
**
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
**Where the session lives is the same for every app (PEE-3).** `storage` defaults
|
|
47
|
+
to `localStorage` — one session shared by every tab, surviving a tab close — and
|
|
48
|
+
`pendingStore` to `sessionStorage`, because a sign-in starts and ends in one tab.
|
|
49
|
+
Peek kept a per-tab session until PEE-3, and every new tab paid a ~0.5 s silent
|
|
50
|
+
`/authorize` bounce for it. Both stay injectable (a test, a script); a caller
|
|
51
|
+
that passes only `storage` gets it for both, as before.
|
|
52
|
+
|
|
53
|
+
**A shared session needs a cross-tab lock on renewal.** Every tab's timer fires
|
|
54
|
+
for the same expiry, and Estiva ID revokes the whole chain when a refresh token
|
|
55
|
+
is presented twice. `lock` defaults to the Web Locks API; a tab that waited for
|
|
56
|
+
it takes the token another tab just renewed instead of spending the old one.
|
|
51
57
|
|
|
52
58
|
`pendingStore` exists because wiring the second consumer found the package
|
|
53
59
|
assuming one store — it was extracted from Peek, which uses one. That is the "a
|
|
@@ -62,7 +68,6 @@ const client = createEstivaId({
|
|
|
62
68
|
base: ESTIVA_ID_ORIGIN,
|
|
63
69
|
clientId: 'estiva-peek',
|
|
64
70
|
redirectUri: () => `${window.location.origin}/auth/callback`,
|
|
65
|
-
storage: () => (typeof window === 'undefined' ? null : window.sessionStorage),
|
|
66
71
|
keyPrefix: 'peek.estivaId',
|
|
67
72
|
navigate: (url) => window.location.assign(url),
|
|
68
73
|
})
|
package/dist/client.d.ts
CHANGED
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
* `app_credentials` with separate `redirectUris` and separate kind ceilings.
|
|
39
39
|
* - **`redirectUri`** — Peek registers a fixed `/auth/callback`; Ship routes on
|
|
40
40
|
* `location.hash` and registers its current pathname. Neither is more correct.
|
|
41
|
-
* - **`storage`** and **`pendingStore`** — two stores, because
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
41
|
+
* - **`storage`** and **`pendingStore`** — two stores, because the flow needs
|
|
42
|
+
* two. The *session* lives in `localStorage` so every tab shares it and it
|
|
43
|
+
* outlives a tab; the *in-flight PKCE credentials* live in `sessionStorage`
|
|
44
|
+
* because the flow starts and ends in one tab. That is the default since
|
|
45
|
+
* PEE-3, when Peek gave up its per-tab session (a new tab paid a ~0.5 s
|
|
46
|
+
* `/authorize` bounce) and all three apps came to choose the same. Both stay
|
|
47
|
+
* injectable — a test, a script or a future "public computer" mode may differ.
|
|
48
|
+
* A shared session is why renewal takes a cross-tab {@link EstivaIdConfig.lock}.
|
|
49
49
|
* - **`keyPrefix`** — so two apps on one origin cannot read each other's keys.
|
|
50
50
|
* - **`navigate`** — the one genuinely untestable act. Injected so a test can
|
|
51
51
|
* observe where the flow *would* go.
|
|
@@ -74,21 +74,22 @@ export interface EstivaIdConfig {
|
|
|
74
74
|
/**
|
|
75
75
|
* Where the **session** lives — the token and nothing else.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* a tab close. Returns null outside a browser.
|
|
77
|
+
* Defaults to `localStorage`: every tab shares one session and it survives a
|
|
78
|
+
* tab close (PEE-3). Returns null outside a browser.
|
|
80
79
|
*/
|
|
81
|
-
storage
|
|
80
|
+
storage?: () => KeyValueStore | null;
|
|
82
81
|
/**
|
|
83
82
|
* Where an **in-flight sign-in's** single-use credentials live — the verifier,
|
|
84
83
|
* the state, the returnTo, the shell reason and the silent-attempt guard.
|
|
85
84
|
*
|
|
86
|
-
* Defaults to {@link storage}
|
|
85
|
+
* Defaults to `sessionStorage` when {@link storage} is defaulted too, and to
|
|
86
|
+
* {@link storage} when it is given — so a caller passing one store keeps
|
|
87
|
+
* exactly the behaviour it had before the defaults existed.
|
|
87
88
|
*
|
|
88
|
-
* **
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
89
|
+
* **The two are separate because the flow needs it.** The session belongs in
|
|
90
|
+
* `localStorage` so it outlives a tab, while the PKCE credentials are per-tab
|
|
91
|
+
* by nature — the flow starts and ends in one tab, and the guard's whole point
|
|
92
|
+
* is that a genuinely new tab is entitled to a fresh attempt.
|
|
92
93
|
*
|
|
93
94
|
* This parameter was added by wiring the second consumer. The package had
|
|
94
95
|
* assumed one store because it was extracted from Peek, which uses one: the
|
|
@@ -110,6 +111,18 @@ export interface EstivaIdConfig {
|
|
|
110
111
|
*/
|
|
111
112
|
setTimer?: (run: () => void, ms: number) => unknown;
|
|
112
113
|
clearTimer?: (handle: unknown) => void;
|
|
114
|
+
/**
|
|
115
|
+
* Runs `run` while holding the lock `name` against every other tab of this
|
|
116
|
+
* origin (PEE-3). Renewal holds it, because a shared session means every
|
|
117
|
+
* tab's timer fires at the same moment, and Estiva ID revokes the whole chain
|
|
118
|
+
* when one refresh token is presented twice — two tabs renewing together
|
|
119
|
+
* would sign the person out of all of them.
|
|
120
|
+
*
|
|
121
|
+
* Defaults to the Web Locks API (`navigator.locks.request`), and to running
|
|
122
|
+
* `run` unguarded where there is none — a single-page runtime has no other
|
|
123
|
+
* tab to race.
|
|
124
|
+
*/
|
|
125
|
+
lock?: <T>(name: string, run: () => Promise<T>) => Promise<T>;
|
|
113
126
|
}
|
|
114
127
|
export interface StoredToken {
|
|
115
128
|
accessToken: string;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAyB,MAAM,YAAY,CAAA;AAsCxF,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACpE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAyB,MAAM,YAAY,CAAA;AAsCxF,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACpE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAA;AA8BvE,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;IACzC,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAA;IACnD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAWD,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAEzC,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACxC;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AASD,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACpD,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChF,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACrF,kBAAkB,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACrD,eAAe,EAAE,CAAC,QAAQ,CAAC,EAAE,eAAe,KAAK,eAAe,CAAA;IAChE,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;IACrC,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAA;IAChD,UAAU,EAAE,MAAM,OAAO,CAAA;IACzB,kBAAkB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IAC5C,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IACjC,gBAAgB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;IAC/C,eAAe,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;CAC1C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAokBrE"}
|
package/dist/client.js
CHANGED
|
@@ -38,30 +38,50 @@
|
|
|
38
38
|
* `app_credentials` with separate `redirectUris` and separate kind ceilings.
|
|
39
39
|
* - **`redirectUri`** — Peek registers a fixed `/auth/callback`; Ship routes on
|
|
40
40
|
* `location.hash` and registers its current pathname. Neither is more correct.
|
|
41
|
-
* - **`storage`** and **`pendingStore`** — two stores, because
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
41
|
+
* - **`storage`** and **`pendingStore`** — two stores, because the flow needs
|
|
42
|
+
* two. The *session* lives in `localStorage` so every tab shares it and it
|
|
43
|
+
* outlives a tab; the *in-flight PKCE credentials* live in `sessionStorage`
|
|
44
|
+
* because the flow starts and ends in one tab. That is the default since
|
|
45
|
+
* PEE-3, when Peek gave up its per-tab session (a new tab paid a ~0.5 s
|
|
46
|
+
* `/authorize` bounce) and all three apps came to choose the same. Both stay
|
|
47
|
+
* injectable — a test, a script or a future "public computer" mode may differ.
|
|
48
|
+
* A shared session is why renewal takes a cross-tab {@link EstivaIdConfig.lock}.
|
|
49
49
|
* - **`keyPrefix`** — so two apps on one origin cannot read each other's keys.
|
|
50
50
|
* - **`navigate`** — the one genuinely untestable act. Injected so a test can
|
|
51
51
|
* observe where the flow *would* go.
|
|
52
52
|
*/
|
|
53
53
|
import { clearGuard, markGuard } from './shell.js';
|
|
54
|
+
/** A browser store, or null where there is none or reading it throws (a sandboxed frame). */
|
|
55
|
+
function webStore(which) {
|
|
56
|
+
try {
|
|
57
|
+
if (which === 'local')
|
|
58
|
+
return typeof localStorage === 'undefined' ? null : (localStorage ?? null);
|
|
59
|
+
return typeof sessionStorage === 'undefined' ? null : (sessionStorage ?? null);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function webLock(name, run) {
|
|
66
|
+
const locks = typeof navigator === 'undefined' ? undefined : navigator?.locks;
|
|
67
|
+
return locks ? locks.request(name, run) : run();
|
|
68
|
+
}
|
|
54
69
|
/**
|
|
55
70
|
* `setTimeout`'s ceiling. Above 2^31-1 ms the delay overflows to a negative
|
|
56
71
|
* int32 and the timer fires immediately.
|
|
57
72
|
*/
|
|
58
73
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
74
|
+
/** How long renewal keeps the cross-tab lock after writing, for the write to reach the other tabs (PEE-3). */
|
|
75
|
+
const RENEWAL_SETTLE_MS = 250;
|
|
59
76
|
export class SignInError extends Error {
|
|
60
77
|
}
|
|
61
78
|
export function createEstivaId(config) {
|
|
62
79
|
const now = config.now ?? (() => Date.now());
|
|
63
80
|
/** In-flight credentials, which may live somewhere shorter than the session. */
|
|
64
|
-
|
|
81
|
+
/** The session, shared by every tab unless the app says otherwise (PEE-3). */
|
|
82
|
+
const session = config.storage ?? (() => webStore('local'));
|
|
83
|
+
const pending = config.pendingStore ?? config.storage ?? (() => webStore('session'));
|
|
84
|
+
const lock = config.lock ?? webLock;
|
|
65
85
|
const send = () => config.fetch ?? fetch;
|
|
66
86
|
const go = (url) => {
|
|
67
87
|
if (!config.navigate)
|
|
@@ -149,7 +169,7 @@ export function createEstivaId(config) {
|
|
|
149
169
|
};
|
|
150
170
|
};
|
|
151
171
|
const storedToken = () => {
|
|
152
|
-
const raw =
|
|
172
|
+
const raw = session()?.getItem(TOKEN_KEY);
|
|
153
173
|
if (!raw)
|
|
154
174
|
return null;
|
|
155
175
|
try {
|
|
@@ -186,7 +206,7 @@ export function createEstivaId(config) {
|
|
|
186
206
|
* Anything that wants both says so, and only `beginSignOut` does.
|
|
187
207
|
*/
|
|
188
208
|
const clearSession = () => {
|
|
189
|
-
|
|
209
|
+
session()?.removeItem(TOKEN_KEY);
|
|
190
210
|
// The reason lives with the in-flight credentials, which may be a different
|
|
191
211
|
// store — see `pendingStore`. Still cleared here: a reason outliving the
|
|
192
212
|
// session it described is a lie, and that was true before the split.
|
|
@@ -246,7 +266,7 @@ export function createEstivaId(config) {
|
|
|
246
266
|
* never presented twice — a replay is what Estiva ID treats as theft.
|
|
247
267
|
*/
|
|
248
268
|
const performRefresh = async () => {
|
|
249
|
-
const s =
|
|
269
|
+
const s = session();
|
|
250
270
|
const current = storedToken();
|
|
251
271
|
if (!s || !current?.refreshToken)
|
|
252
272
|
return null;
|
|
@@ -268,11 +288,22 @@ export function createEstivaId(config) {
|
|
|
268
288
|
// not sign somebody out.
|
|
269
289
|
return null;
|
|
270
290
|
}
|
|
291
|
+
/*
|
|
292
|
+
The session is shared by every tab (PEE-3), and signing out takes no lock:
|
|
293
|
+
while `/token` was answering, another tab may have signed out — or in.
|
|
294
|
+
Then this answer belongs to a session that is gone, and writing it would
|
|
295
|
+
put a live token back after the sign-out, for its whole lifetime. Only
|
|
296
|
+
the session this renewal spent is replaced or cleared.
|
|
297
|
+
*/
|
|
298
|
+
const stillOurs = () => storedToken()?.refreshToken === current.refreshToken;
|
|
271
299
|
if (!response.ok) {
|
|
272
|
-
|
|
300
|
+
if (stillOurs())
|
|
301
|
+
clearSession();
|
|
273
302
|
return null;
|
|
274
303
|
}
|
|
275
304
|
const body = (await response.json());
|
|
305
|
+
if (!stillOurs())
|
|
306
|
+
return validToken();
|
|
276
307
|
if (!body.access_token) {
|
|
277
308
|
clearSession();
|
|
278
309
|
return null;
|
|
@@ -299,12 +330,35 @@ export function createEstivaId(config) {
|
|
|
299
330
|
*
|
|
300
331
|
* Coalescing rather than caching: the promise is dropped the moment it
|
|
301
332
|
* settles, so the *next* expiry renews again.
|
|
333
|
+
*
|
|
334
|
+
* **And never across tabs either (PEE-3).** The promise guards one page; a
|
|
335
|
+
* session in `localStorage` is shared by every tab, and each tab's timer
|
|
336
|
+
* fires at the same moment, for the same expiry. So the renewal runs under
|
|
337
|
+
* {@link EstivaIdConfig.lock}, and a tab that waited for it compares the
|
|
338
|
+
* stored refresh token with the one it set out to spend: if another tab
|
|
339
|
+
* renewed (or signed out, or in) meanwhile, that tab's answer is taken and
|
|
340
|
+
* `/token` is not called — presenting the spent token would be the replay.
|
|
302
341
|
*/
|
|
303
342
|
let refreshing = null;
|
|
304
343
|
const refreshAccessToken = () => {
|
|
305
344
|
if (refreshing)
|
|
306
345
|
return refreshing;
|
|
307
|
-
|
|
346
|
+
const spending = storedToken()?.refreshToken;
|
|
347
|
+
refreshing = new Promise((answer, fail) => {
|
|
348
|
+
lock(`${config.keyPrefix}.renewal`, async () => {
|
|
349
|
+
if (storedToken()?.refreshToken !== spending)
|
|
350
|
+
return answer(validToken());
|
|
351
|
+
answer(await performRefresh());
|
|
352
|
+
/*
|
|
353
|
+
Held a little longer than the renewal, though the caller already has
|
|
354
|
+
its answer. A browser does not order one tab's `localStorage` write
|
|
355
|
+
with another tab's lock grant — the write reaches the other process's
|
|
356
|
+
copy shortly after — so a tab granted the lock at once could still
|
|
357
|
+
read the spent token and present it again.
|
|
358
|
+
*/
|
|
359
|
+
await new Promise((settle) => setTimeout(settle, RENEWAL_SETTLE_MS));
|
|
360
|
+
}).catch(fail);
|
|
361
|
+
}).finally(() => {
|
|
308
362
|
refreshing = null;
|
|
309
363
|
});
|
|
310
364
|
return refreshing;
|
|
@@ -406,7 +460,7 @@ export function createEstivaId(config) {
|
|
|
406
460
|
*/
|
|
407
461
|
async completeSignIn(search) {
|
|
408
462
|
const s = pending();
|
|
409
|
-
const sessionStore =
|
|
463
|
+
const sessionStore = session();
|
|
410
464
|
if (!s || !sessionStore)
|
|
411
465
|
throw new SignInError('sign-in requires a browser');
|
|
412
466
|
const params = new URLSearchParams(search);
|
|
@@ -516,6 +570,14 @@ export function createEstivaId(config) {
|
|
|
516
570
|
const delay = Math.min(Math.max(0, token.expiresAt - now()), MAX_TIMER_MS);
|
|
517
571
|
handle = setTimer(() => {
|
|
518
572
|
handle = null;
|
|
573
|
+
// Another tab sharing the session renewed first (PEE-3): follow its
|
|
574
|
+
// token rather than spend a grant this tab no longer needs.
|
|
575
|
+
const shared = storedToken();
|
|
576
|
+
if (shared && shared.expiresAt > token.expiresAt) {
|
|
577
|
+
handlers.onRenewed?.(shared);
|
|
578
|
+
arm();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
519
581
|
void refreshAccessToken().then((next) => {
|
|
520
582
|
if (cancelled)
|
|
521
583
|
return;
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
*
|
|
25
25
|
* ## Every difference between the two consumers is a parameter
|
|
26
26
|
*
|
|
27
|
-
* `clientId`, `redirectUri`, `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `
|
|
27
|
+
* `clientId`, `redirectUri`, `keyPrefix` and `navigate` are all injected,
|
|
28
|
+
* because Peek and Ship genuinely differ on every one of them and hardcoding
|
|
29
|
+
* any would have made the package come out Peek-shaped. `storage` and
|
|
30
|
+
* `pendingStore` are injectable too, but since PEE-3 every app takes the same
|
|
31
|
+
* defaults (`localStorage` for the session, `sessionStorage` for a sign-in in
|
|
32
|
+
* flight). See `client.ts` for what each difference actually is.
|
|
31
33
|
*
|
|
32
34
|
* ## The globals prediction this package falsified
|
|
33
35
|
*
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,UAAU,EACV,sBAAsB,EACtB,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
*
|
|
25
25
|
* ## Every difference between the two consumers is a parameter
|
|
26
26
|
*
|
|
27
|
-
* `clientId`, `redirectUri`, `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `
|
|
27
|
+
* `clientId`, `redirectUri`, `keyPrefix` and `navigate` are all injected,
|
|
28
|
+
* because Peek and Ship genuinely differ on every one of them and hardcoding
|
|
29
|
+
* any would have made the package come out Peek-shaped. `storage` and
|
|
30
|
+
* `pendingStore` are injectable too, but since PEE-3 every app takes the same
|
|
31
|
+
* defaults (`localStorage` for the session, `sessionStorage` for a sign-in in
|
|
32
|
+
* flight). See `client.ts` for what each difference actually is.
|
|
31
33
|
*
|
|
32
34
|
* ## The globals prediction this package falsified
|
|
33
35
|
*
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC,OAAO,EAQL,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,UAAU,EACV,sBAAsB,EACtB,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,OAAO,EAOL,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAA;AAEpB,OAAO,EAIL,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estiva-app/identity",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Estiva ID sign-in: authorization code + PKCE, token storage, silent renewal, sign-out, signing via POST /sign, and the auth-shell state machine. Knows nothing about any app's data layer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/client.ts
CHANGED
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
* `app_credentials` with separate `redirectUris` and separate kind ceilings.
|
|
39
39
|
* - **`redirectUri`** — Peek registers a fixed `/auth/callback`; Ship routes on
|
|
40
40
|
* `location.hash` and registers its current pathname. Neither is more correct.
|
|
41
|
-
* - **`storage`** and **`pendingStore`** — two stores, because
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
41
|
+
* - **`storage`** and **`pendingStore`** — two stores, because the flow needs
|
|
42
|
+
* two. The *session* lives in `localStorage` so every tab shares it and it
|
|
43
|
+
* outlives a tab; the *in-flight PKCE credentials* live in `sessionStorage`
|
|
44
|
+
* because the flow starts and ends in one tab. That is the default since
|
|
45
|
+
* PEE-3, when Peek gave up its per-tab session (a new tab paid a ~0.5 s
|
|
46
|
+
* `/authorize` bounce) and all three apps came to choose the same. Both stay
|
|
47
|
+
* injectable — a test, a script or a future "public computer" mode may differ.
|
|
48
|
+
* A shared session is why renewal takes a cross-tab {@link EstivaIdConfig.lock}.
|
|
49
49
|
* - **`keyPrefix`** — so two apps on one origin cannot read each other's keys.
|
|
50
50
|
* - **`navigate`** — the one genuinely untestable act. Injected so a test can
|
|
51
51
|
* observe where the flow *would* go.
|
|
@@ -96,6 +96,32 @@ export type FetchLike = (
|
|
|
96
96
|
|
|
97
97
|
declare const fetch: FetchLike
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The defaults for `storage`, `pendingStore` and `lock`. Read through `typeof`
|
|
101
|
+
* inside functions, so a runtime without them — Node, a worker — gets null or
|
|
102
|
+
* the unguarded path rather than a ReferenceError.
|
|
103
|
+
*/
|
|
104
|
+
declare const localStorage: KeyValueStore | undefined
|
|
105
|
+
declare const sessionStorage: KeyValueStore | undefined
|
|
106
|
+
declare const navigator:
|
|
107
|
+
| { locks?: { request<T>(name: string, run: () => Promise<T>): Promise<T> } }
|
|
108
|
+
| undefined
|
|
109
|
+
|
|
110
|
+
/** A browser store, or null where there is none or reading it throws (a sandboxed frame). */
|
|
111
|
+
function webStore(which: 'local' | 'session'): KeyValueStore | null {
|
|
112
|
+
try {
|
|
113
|
+
if (which === 'local') return typeof localStorage === 'undefined' ? null : (localStorage ?? null)
|
|
114
|
+
return typeof sessionStorage === 'undefined' ? null : (sessionStorage ?? null)
|
|
115
|
+
} catch {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function webLock<T>(name: string, run: () => Promise<T>): Promise<T> {
|
|
121
|
+
const locks = typeof navigator === 'undefined' ? undefined : navigator?.locks
|
|
122
|
+
return locks ? locks.request(name, run) : run()
|
|
123
|
+
}
|
|
124
|
+
|
|
99
125
|
export interface EstivaIdConfig {
|
|
100
126
|
/** Origin of the identity service, e.g. `https://id.estiva.app`. */
|
|
101
127
|
base: string
|
|
@@ -109,21 +135,22 @@ export interface EstivaIdConfig {
|
|
|
109
135
|
/**
|
|
110
136
|
* Where the **session** lives — the token and nothing else.
|
|
111
137
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* a tab close. Returns null outside a browser.
|
|
138
|
+
* Defaults to `localStorage`: every tab shares one session and it survives a
|
|
139
|
+
* tab close (PEE-3). Returns null outside a browser.
|
|
115
140
|
*/
|
|
116
|
-
storage
|
|
141
|
+
storage?: () => KeyValueStore | null
|
|
117
142
|
/**
|
|
118
143
|
* Where an **in-flight sign-in's** single-use credentials live — the verifier,
|
|
119
144
|
* the state, the returnTo, the shell reason and the silent-attempt guard.
|
|
120
145
|
*
|
|
121
|
-
* Defaults to {@link storage}
|
|
146
|
+
* Defaults to `sessionStorage` when {@link storage} is defaulted too, and to
|
|
147
|
+
* {@link storage} when it is given — so a caller passing one store keeps
|
|
148
|
+
* exactly the behaviour it had before the defaults existed.
|
|
122
149
|
*
|
|
123
|
-
* **
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
150
|
+
* **The two are separate because the flow needs it.** The session belongs in
|
|
151
|
+
* `localStorage` so it outlives a tab, while the PKCE credentials are per-tab
|
|
152
|
+
* by nature — the flow starts and ends in one tab, and the guard's whole point
|
|
153
|
+
* is that a genuinely new tab is entitled to a fresh attempt.
|
|
127
154
|
*
|
|
128
155
|
* This parameter was added by wiring the second consumer. The package had
|
|
129
156
|
* assumed one store because it was extracted from Peek, which uses one: the
|
|
@@ -145,6 +172,18 @@ export interface EstivaIdConfig {
|
|
|
145
172
|
*/
|
|
146
173
|
setTimer?: (run: () => void, ms: number) => unknown
|
|
147
174
|
clearTimer?: (handle: unknown) => void
|
|
175
|
+
/**
|
|
176
|
+
* Runs `run` while holding the lock `name` against every other tab of this
|
|
177
|
+
* origin (PEE-3). Renewal holds it, because a shared session means every
|
|
178
|
+
* tab's timer fires at the same moment, and Estiva ID revokes the whole chain
|
|
179
|
+
* when one refresh token is presented twice — two tabs renewing together
|
|
180
|
+
* would sign the person out of all of them.
|
|
181
|
+
*
|
|
182
|
+
* Defaults to the Web Locks API (`navigator.locks.request`), and to running
|
|
183
|
+
* `run` unguarded where there is none — a single-page runtime has no other
|
|
184
|
+
* tab to race.
|
|
185
|
+
*/
|
|
186
|
+
lock?: <T>(name: string, run: () => Promise<T>) => Promise<T>
|
|
148
187
|
}
|
|
149
188
|
|
|
150
189
|
export interface StoredToken {
|
|
@@ -172,6 +211,9 @@ export interface StoredToken {
|
|
|
172
211
|
*/
|
|
173
212
|
const MAX_TIMER_MS = 2_147_483_647
|
|
174
213
|
|
|
214
|
+
/** How long renewal keeps the cross-tab lock after writing, for the write to reach the other tabs (PEE-3). */
|
|
215
|
+
const RENEWAL_SETTLE_MS = 250
|
|
216
|
+
|
|
175
217
|
export class SignInError extends Error {}
|
|
176
218
|
|
|
177
219
|
/** What a scheduled renewal reports back. Both optional; neither is required to be useful. */
|
|
@@ -225,7 +267,10 @@ export interface EstivaIdClient {
|
|
|
225
267
|
export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
226
268
|
const now = config.now ?? (() => Date.now())
|
|
227
269
|
/** In-flight credentials, which may live somewhere shorter than the session. */
|
|
228
|
-
|
|
270
|
+
/** The session, shared by every tab unless the app says otherwise (PEE-3). */
|
|
271
|
+
const session = config.storage ?? (() => webStore('local'))
|
|
272
|
+
const pending = config.pendingStore ?? config.storage ?? (() => webStore('session'))
|
|
273
|
+
const lock = config.lock ?? webLock
|
|
229
274
|
const send = () => config.fetch ?? fetch
|
|
230
275
|
const go = (url: string) => {
|
|
231
276
|
if (!config.navigate) throw new Error('createEstivaId: navigate is required to leave for Estiva ID')
|
|
@@ -316,7 +361,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
316
361
|
}
|
|
317
362
|
|
|
318
363
|
const storedToken = (): StoredToken | null => {
|
|
319
|
-
const raw =
|
|
364
|
+
const raw = session()?.getItem(TOKEN_KEY)
|
|
320
365
|
if (!raw) return null
|
|
321
366
|
try {
|
|
322
367
|
const parsed = JSON.parse(raw) as StoredToken
|
|
@@ -353,7 +398,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
353
398
|
* Anything that wants both says so, and only `beginSignOut` does.
|
|
354
399
|
*/
|
|
355
400
|
const clearSession = (): void => {
|
|
356
|
-
|
|
401
|
+
session()?.removeItem(TOKEN_KEY)
|
|
357
402
|
// The reason lives with the in-flight credentials, which may be a different
|
|
358
403
|
// store — see `pendingStore`. Still cleared here: a reason outliving the
|
|
359
404
|
// session it described is a lie, and that was true before the split.
|
|
@@ -414,7 +459,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
414
459
|
* never presented twice — a replay is what Estiva ID treats as theft.
|
|
415
460
|
*/
|
|
416
461
|
const performRefresh = async (): Promise<StoredToken | null> => {
|
|
417
|
-
const s =
|
|
462
|
+
const s = session()
|
|
418
463
|
const current = storedToken()
|
|
419
464
|
if (!s || !current?.refreshToken) return null
|
|
420
465
|
|
|
@@ -436,12 +481,22 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
436
481
|
return null
|
|
437
482
|
}
|
|
438
483
|
|
|
484
|
+
/*
|
|
485
|
+
The session is shared by every tab (PEE-3), and signing out takes no lock:
|
|
486
|
+
while `/token` was answering, another tab may have signed out — or in.
|
|
487
|
+
Then this answer belongs to a session that is gone, and writing it would
|
|
488
|
+
put a live token back after the sign-out, for its whole lifetime. Only
|
|
489
|
+
the session this renewal spent is replaced or cleared.
|
|
490
|
+
*/
|
|
491
|
+
const stillOurs = () => storedToken()?.refreshToken === current.refreshToken
|
|
492
|
+
|
|
439
493
|
if (!response.ok) {
|
|
440
|
-
clearSession()
|
|
494
|
+
if (stillOurs()) clearSession()
|
|
441
495
|
return null
|
|
442
496
|
}
|
|
443
497
|
|
|
444
498
|
const body = (await response.json()) as TokenResponse
|
|
499
|
+
if (!stillOurs()) return validToken()
|
|
445
500
|
if (!body.access_token) {
|
|
446
501
|
clearSession()
|
|
447
502
|
return null
|
|
@@ -470,12 +525,34 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
470
525
|
*
|
|
471
526
|
* Coalescing rather than caching: the promise is dropped the moment it
|
|
472
527
|
* settles, so the *next* expiry renews again.
|
|
528
|
+
*
|
|
529
|
+
* **And never across tabs either (PEE-3).** The promise guards one page; a
|
|
530
|
+
* session in `localStorage` is shared by every tab, and each tab's timer
|
|
531
|
+
* fires at the same moment, for the same expiry. So the renewal runs under
|
|
532
|
+
* {@link EstivaIdConfig.lock}, and a tab that waited for it compares the
|
|
533
|
+
* stored refresh token with the one it set out to spend: if another tab
|
|
534
|
+
* renewed (or signed out, or in) meanwhile, that tab's answer is taken and
|
|
535
|
+
* `/token` is not called — presenting the spent token would be the replay.
|
|
473
536
|
*/
|
|
474
537
|
let refreshing: Promise<StoredToken | null> | null = null
|
|
475
538
|
|
|
476
539
|
const refreshAccessToken = (): Promise<StoredToken | null> => {
|
|
477
540
|
if (refreshing) return refreshing
|
|
478
|
-
|
|
541
|
+
const spending = storedToken()?.refreshToken
|
|
542
|
+
refreshing = new Promise<StoredToken | null>((answer, fail) => {
|
|
543
|
+
lock(`${config.keyPrefix}.renewal`, async () => {
|
|
544
|
+
if (storedToken()?.refreshToken !== spending) return answer(validToken())
|
|
545
|
+
answer(await performRefresh())
|
|
546
|
+
/*
|
|
547
|
+
Held a little longer than the renewal, though the caller already has
|
|
548
|
+
its answer. A browser does not order one tab's `localStorage` write
|
|
549
|
+
with another tab's lock grant — the write reaches the other process's
|
|
550
|
+
copy shortly after — so a tab granted the lock at once could still
|
|
551
|
+
read the spent token and present it again.
|
|
552
|
+
*/
|
|
553
|
+
await new Promise<void>((settle) => setTimeout(settle, RENEWAL_SETTLE_MS))
|
|
554
|
+
}).catch(fail)
|
|
555
|
+
}).finally(() => {
|
|
479
556
|
refreshing = null
|
|
480
557
|
})
|
|
481
558
|
return refreshing
|
|
@@ -586,7 +663,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
586
663
|
*/
|
|
587
664
|
async completeSignIn(search: string): Promise<{ token: StoredToken; returnTo: string }> {
|
|
588
665
|
const s = pending()
|
|
589
|
-
const sessionStore =
|
|
666
|
+
const sessionStore = session()
|
|
590
667
|
if (!s || !sessionStore) throw new SignInError('sign-in requires a browser')
|
|
591
668
|
|
|
592
669
|
const params = new URLSearchParams(search)
|
|
@@ -707,6 +784,14 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
707
784
|
|
|
708
785
|
handle = setTimer(() => {
|
|
709
786
|
handle = null
|
|
787
|
+
// Another tab sharing the session renewed first (PEE-3): follow its
|
|
788
|
+
// token rather than spend a grant this tab no longer needs.
|
|
789
|
+
const shared = storedToken()
|
|
790
|
+
if (shared && shared.expiresAt > token.expiresAt) {
|
|
791
|
+
handlers.onRenewed?.(shared)
|
|
792
|
+
arm()
|
|
793
|
+
return
|
|
794
|
+
}
|
|
710
795
|
void refreshAccessToken().then((next) => {
|
|
711
796
|
if (cancelled) return
|
|
712
797
|
if (!next) {
|
package/src/index.ts
CHANGED
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
*
|
|
25
25
|
* ## Every difference between the two consumers is a parameter
|
|
26
26
|
*
|
|
27
|
-
* `clientId`, `redirectUri`, `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `
|
|
27
|
+
* `clientId`, `redirectUri`, `keyPrefix` and `navigate` are all injected,
|
|
28
|
+
* because Peek and Ship genuinely differ on every one of them and hardcoding
|
|
29
|
+
* any would have made the package come out Peek-shaped. `storage` and
|
|
30
|
+
* `pendingStore` are injectable too, but since PEE-3 every app takes the same
|
|
31
|
+
* defaults (`localStorage` for the session, `sessionStorage` for a sign-in in
|
|
32
|
+
* flight). See `client.ts` for what each difference actually is.
|
|
31
33
|
*
|
|
32
34
|
* ## The globals prediction this package falsified
|
|
33
35
|
*
|