@owlmeans/web-client 0.1.18-rc.50 → 0.1.18-rc.52
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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-client/SKILL.md +45 -3
- package/build/exports.d.ts +2 -1
- package/build/exports.d.ts.map +1 -1
- package/build/exports.js +1 -1
- package/build/exports.js.map +1 -1
- package/build/login/screen.d.ts +27 -3
- package/build/login/screen.d.ts.map +1 -1
- package/build/login/screen.js +50 -10
- package/build/login/screen.js.map +1 -1
- package/build/main.d.ts.map +1 -1
- package/build/main.js +9 -1
- package/build/main.js.map +1 -1
- package/package.json +15 -15
- package/src/exports.ts +7 -1
- package/src/login/screen.tsx +54 -10
- package/src/main.tsx +8 -1
- package/tests/surrogate.spec.ts +19 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ is the legacy MUI layer — start nothing new there. React Native apps use the `
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
bun add @owlmeans/web-client@^0.1.18-rc.
|
|
14
|
+
bun add @owlmeans/web-client@^0.1.18-rc.52
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Concepts
|
|
@@ -289,7 +289,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
289
289
|
your project's skill store (`.agents/skills/`):
|
|
290
290
|
|
|
291
291
|
```sh
|
|
292
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
292
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.39
|
|
293
293
|
```
|
|
294
294
|
|
|
295
295
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/web-client",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.52",
|
|
5
|
+
"generatedAt": "2026-09-25T12:22:06.199Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: web-client
|
|
3
|
-
description: Bind OwlMeans shared entrypoint protocols in a browser application. Use when registering client routes, attaching React screens, calling API protocols, or configuring the browser context.
|
|
3
|
+
description: Bind OwlMeans shared entrypoint protocols in a browser application. Use when registering client routes, attaching React screens (including lazily-loaded ones), calling API protocols, or configuring the browser context.
|
|
4
4
|
---
|
|
5
5
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
6
6
|
|
|
7
7
|
# Browser protocol entrypoints
|
|
8
8
|
|
|
9
|
-
**Install:** `bun add @owlmeans/web-client@^0.1.18-rc.
|
|
9
|
+
**Install:** `bun add @owlmeans/web-client@^0.1.18-rc.52`
|
|
10
10
|
|
|
11
11
|
Shared protocol declarations are immutable. Bind a complete protocol tree for callable API routes,
|
|
12
12
|
then bind frontend declarations to screens.
|
|
@@ -31,11 +31,53 @@ const project = await context.entrypoint(projectProtocols.get).call({
|
|
|
31
31
|
})
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## Lazily-loaded screens
|
|
35
|
+
|
|
36
|
+
`lazyHandler` and `lazyComponent` (from `@owlmeans/client`) are re-exported here and by
|
|
37
|
+
`@owlmeans/web-panel`, next to `handler`; so are, here only, the chunk-failure tools
|
|
38
|
+
`retryImport`, `isChunkLoadError`, `reloadOnce` and `recoverFromChunkError` (types
|
|
39
|
+
`LazyErrorRenderer`, `RetryImportOptions`). A `lazyHandler(...)` result binds exactly like
|
|
40
|
+
`handler(Component)`; declare it at module scope, where the bindings live — never inside a render.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { bindScreen, lazyHandler } from '@owlmeans/web-client'
|
|
44
|
+
|
|
45
|
+
const reportsScreen = lazyHandler(() => import('./screens/reports.js'), 'ReportsScreen', {
|
|
46
|
+
fallback: <Spinner />,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export const clientBindings = [
|
|
50
|
+
bindScreen(webProtocols.reports, reportsScreen),
|
|
51
|
+
]
|
|
52
|
+
// reportsScreen.preload() on hover/focus of a link renders the screen without its fallback.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The fallback renders inside the screen's own `Suspense` boundary, so the layout around it stays
|
|
56
|
+
mounted while the chunk loads, and the screen's own error boundary keeps a failed chunk from
|
|
57
|
+
unmounting it: a fetch failure is retried, then `error` renders — or, without one, the guarded
|
|
58
|
+
reload starts. Rules and options: the `client` skill, Code-splitting and Chunk failures.
|
|
59
|
+
|
|
60
|
+
## Rendering after an async boot
|
|
61
|
+
|
|
62
|
+
If your app awaits something (e.g. `prepareI18n`) before calling `render()`, this is handled
|
|
63
|
+
correctly — the render helper checks `document.readyState` rather than unconditionally waiting for
|
|
64
|
+
an event that may already have fired. It waits for `DOMContentLoaded` only while the document is
|
|
65
|
+
still `loading` and mounts at once otherwise; `renderApp` and `@owlmeans/web-panel`'s `render` both
|
|
66
|
+
go through it.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { prepareI18n } from '@owlmeans/client-i18n'
|
|
70
|
+
|
|
71
|
+
await prepareI18n(context.cfg)
|
|
72
|
+
renderApp(context) // or @owlmeans/web-panel's render(context)
|
|
73
|
+
```
|
|
74
|
+
|
|
34
75
|
## Rules
|
|
35
76
|
|
|
36
77
|
- Declare paths, guards, gates and contracts once in the shared protocol tree.
|
|
37
78
|
- Bind every API declaration the browser calls, including route parents.
|
|
38
|
-
- Use `bindScreen(protocol, handler(Component))` only for frontend route
|
|
79
|
+
- Use `bindScreen(protocol, handler(Component))` — or `lazyHandler(...)` — only for frontend route
|
|
80
|
+
declarations.
|
|
39
81
|
- Do not replace request/response typing at a call site; update the shared contract instead.
|
|
40
82
|
- Keep framework entrypoints and application entrypoints in one registered array, without mutating a
|
|
41
83
|
shared declaration collection.
|
package/build/exports.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { handler } from '@owlmeans/client';
|
|
1
|
+
export { handler, lazyHandler, lazyComponent, retryImport, isChunkLoadError, reloadOnce, recoverFromChunkError, chunkUrlOf, cacheBustedUrl, } from '@owlmeans/client';
|
|
2
|
+
export type { LazyComponent, LazyComponentOptions, LazyErrorRenderer, LazyHandler, RetryImportOptions } from '@owlmeans/client';
|
|
2
3
|
export { config } from '@owlmeans/client-context';
|
|
3
4
|
export { service } from '@owlmeans/config';
|
|
4
5
|
export { AppType, HOME, ROOT, BASE, GUEST } from '@owlmeans/context';
|
package/build/exports.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,qBAAqB,EACrG,UAAU,EAAE,cAAc,GAC3B,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,aAAa,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,WAAW,EAAE,kBAAkB,EACxF,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAC7E,YAAY,EAAE,WAAW,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAA"}
|
package/build/exports.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { handler } from '@owlmeans/client';
|
|
1
|
+
export { handler, lazyHandler, lazyComponent, retryImport, isChunkLoadError, reloadOnce, recoverFromChunkError, chunkUrlOf, cacheBustedUrl, } from '@owlmeans/client';
|
|
2
2
|
export { config } from '@owlmeans/client-context';
|
|
3
3
|
export { service } from '@owlmeans/config';
|
|
4
4
|
export { AppType, HOME, ROOT, BASE, GUEST } from '@owlmeans/context';
|
package/build/exports.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,qBAAqB,EACrG,UAAU,EAAE,cAAc,GAC3B,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA"}
|
package/build/login/screen.d.ts
CHANGED
|
@@ -7,9 +7,33 @@ import type { FC } from 'react';
|
|
|
7
7
|
* application, with its navigation, inside itself. This screen has no continuation at all — it
|
|
8
8
|
* either hands something back and closes, or it says what it is waiting for.
|
|
9
9
|
*
|
|
10
|
-
* It also never runs the authorization machine
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* It also never runs the authorization machine itself: it forwards to the dispatcher (`next`),
|
|
11
|
+
* which owns that flow, and hands back what the dispatcher issued — see {@link surrogateLoginStep}
|
|
12
|
+
* for why a session already stored in this window is not simply handed back.
|
|
13
13
|
*/
|
|
14
|
+
/** What a surrogate window opened to SIGN IN does with the session it may already hold. */
|
|
15
|
+
export declare enum SurrogateLoginStep {
|
|
16
|
+
/** Drop the stored session and authenticate afresh through the dispatcher. */
|
|
17
|
+
Forget = "forget",
|
|
18
|
+
/** Hand the stored session to the opener — there is no dispatcher to authenticate through. */
|
|
19
|
+
Resume = "resume",
|
|
20
|
+
/** Nothing stored: authenticate through the dispatcher. */
|
|
21
|
+
Authenticate = "authenticate"
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A sign-in request never reuses the session this window happens to hold.
|
|
25
|
+
*
|
|
26
|
+
* The opener asks for a sign-in only when it has no session its server accepts — and the window it
|
|
27
|
+
* opens does not share its storage: an embedded application's storage is partitioned by the top
|
|
28
|
+
* level site, the popup's is first-party. So the popup can hold a session the server has already
|
|
29
|
+
* REFUSED (its record gone after a restart, revoked, fenced). Handing that back closes the popup
|
|
30
|
+
* at once — a blink — and the opener fails its next request exactly as before, forever, because
|
|
31
|
+
* nothing in the opener can reach the popup's storage to clear it. A fresh round trip costs only
|
|
32
|
+
* redirects while the provider still has a session, and always yields a session the server holds.
|
|
33
|
+
*
|
|
34
|
+
* Only without a dispatcher address is the stored session handed back as it is: there is nothing
|
|
35
|
+
* to authenticate through, and the opener's own 401 handling drops it if it is refused.
|
|
36
|
+
*/
|
|
37
|
+
export declare const surrogateLoginStep: (token: string | null | undefined, next: string | null) => SurrogateLoginStep;
|
|
14
38
|
export declare const SurrogateScreen: FC;
|
|
15
39
|
//# sourceMappingURL=screen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/login/screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/login/screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAW/B;;;;;;;;;;;GAWG;AACH,2FAA2F;AAC3F,oBAAY,kBAAkB;IAC5B,8EAA8E;IAC9E,MAAM,WAAW;IACjB,8FAA8F;IAC9F,MAAM,WAAW;IACjB,2DAA2D;IAC3D,YAAY,iBAAiB;CAC9B;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,UAAW,MAAM,GAAG,IAAI,GAAG,SAAS,QAAQ,MAAM,GAAG,IAAI,KAAG,kBAO1F,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,EAyG7B,CAAA"}
|
package/build/login/screen.js
CHANGED
|
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|
|
3
3
|
import { useContext } from '@owlmeans/client';
|
|
4
4
|
import { useI18nLib } from '@owlmeans/client-i18n';
|
|
5
5
|
import { LoginIntent, LoginOutcome, LOGIN_INTENT_QUERY, LOGIN_METHOD_QUERY, LOGIN_NEXT_QUERY, } from '@owlmeans/client-auth/login';
|
|
6
|
+
import { USER_ID } from '@owlmeans/client-auth';
|
|
6
7
|
import { LoginSurrogateView, SurrogateStage } from './view.js';
|
|
7
8
|
/**
|
|
8
9
|
* The login window an embedded application opens one level up.
|
|
@@ -12,10 +13,41 @@ import { LoginSurrogateView, SurrogateStage } from './view.js';
|
|
|
12
13
|
* application, with its navigation, inside itself. This screen has no continuation at all — it
|
|
13
14
|
* either hands something back and closes, or it says what it is waiting for.
|
|
14
15
|
*
|
|
15
|
-
* It also never runs the authorization machine
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* It also never runs the authorization machine itself: it forwards to the dispatcher (`next`),
|
|
17
|
+
* which owns that flow, and hands back what the dispatcher issued — see {@link surrogateLoginStep}
|
|
18
|
+
* for why a session already stored in this window is not simply handed back.
|
|
18
19
|
*/
|
|
20
|
+
/** What a surrogate window opened to SIGN IN does with the session it may already hold. */
|
|
21
|
+
export var SurrogateLoginStep;
|
|
22
|
+
(function (SurrogateLoginStep) {
|
|
23
|
+
/** Drop the stored session and authenticate afresh through the dispatcher. */
|
|
24
|
+
SurrogateLoginStep["Forget"] = "forget";
|
|
25
|
+
/** Hand the stored session to the opener — there is no dispatcher to authenticate through. */
|
|
26
|
+
SurrogateLoginStep["Resume"] = "resume";
|
|
27
|
+
/** Nothing stored: authenticate through the dispatcher. */
|
|
28
|
+
SurrogateLoginStep["Authenticate"] = "authenticate";
|
|
29
|
+
})(SurrogateLoginStep || (SurrogateLoginStep = {}));
|
|
30
|
+
/**
|
|
31
|
+
* A sign-in request never reuses the session this window happens to hold.
|
|
32
|
+
*
|
|
33
|
+
* The opener asks for a sign-in only when it has no session its server accepts — and the window it
|
|
34
|
+
* opens does not share its storage: an embedded application's storage is partitioned by the top
|
|
35
|
+
* level site, the popup's is first-party. So the popup can hold a session the server has already
|
|
36
|
+
* REFUSED (its record gone after a restart, revoked, fenced). Handing that back closes the popup
|
|
37
|
+
* at once — a blink — and the opener fails its next request exactly as before, forever, because
|
|
38
|
+
* nothing in the opener can reach the popup's storage to clear it. A fresh round trip costs only
|
|
39
|
+
* redirects while the provider still has a session, and always yields a session the server holds.
|
|
40
|
+
*
|
|
41
|
+
* Only without a dispatcher address is the stored session handed back as it is: there is nothing
|
|
42
|
+
* to authenticate through, and the opener's own 401 handling drops it if it is refused.
|
|
43
|
+
*/
|
|
44
|
+
export const surrogateLoginStep = (token, next) => {
|
|
45
|
+
const stored = token != null && token !== '';
|
|
46
|
+
if (!stored) {
|
|
47
|
+
return SurrogateLoginStep.Authenticate;
|
|
48
|
+
}
|
|
49
|
+
return next != null && next !== '' ? SurrogateLoginStep.Forget : SurrogateLoginStep.Resume;
|
|
50
|
+
};
|
|
19
51
|
export const SurrogateScreen = () => {
|
|
20
52
|
const context = useContext();
|
|
21
53
|
const [query] = context.router().useSearchParams();
|
|
@@ -63,19 +95,27 @@ export const SurrogateScreen = () => {
|
|
|
63
95
|
: SurrogateStage.Failed);
|
|
64
96
|
return;
|
|
65
97
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
const next = query.get(LOGIN_NEXT_QUERY);
|
|
99
|
+
const step = surrogateLoginStep(token, next);
|
|
100
|
+
if (step === SurrogateLoginStep.Forget) {
|
|
101
|
+
// Dropped from this window's storage only — the web auth service's own clearing path
|
|
102
|
+
// navigates to the dispatcher, and `next` below is exactly where this window goes anyway.
|
|
103
|
+
const auth = context.auth();
|
|
104
|
+
await auth.store().delete(USER_ID);
|
|
105
|
+
auth.token = undefined;
|
|
106
|
+
auth.auth = undefined;
|
|
107
|
+
}
|
|
108
|
+
if (step === SurrogateLoginStep.Resume && token != null) {
|
|
109
|
+
// Signed in here and nowhere to authenticate afresh: hand what there is to the opener.
|
|
69
110
|
const outcome = await login.resume(token);
|
|
70
111
|
setStage(outcome === LoginOutcome.Handled ? SurrogateStage.Handing
|
|
71
112
|
: outcome === LoginOutcome.Orphaned ? SurrogateStage.Orphaned
|
|
72
113
|
: SurrogateStage.Failed);
|
|
73
114
|
return;
|
|
74
115
|
}
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
const next = query.get(LOGIN_NEXT_QUERY);
|
|
116
|
+
// The dispatcher owns the authorization round trip, and the provider's callback lands there
|
|
117
|
+
// rather than on this route. The method the user already chose travels with it, so the
|
|
118
|
+
// dispatcher does not ask a second time in a window with no one to ask.
|
|
79
119
|
if (next == null || next === '') {
|
|
80
120
|
setStage(SurrogateStage.Gesture);
|
|
81
121
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/login/screen.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EACL,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,GACpF,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/login/screen.tsx"],"names":[],"mappings":"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"}
|
package/build/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAGtE,eAAO,MAAM,MAAM,SAAU,SAAS,SAAS,aAAa,
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAGtE,eAAO,MAAM,MAAM,SAAU,SAAS,SAAS,aAAa,SAyB3D,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,WAC3D,CAAC,SAAS,aAAa,aAAa,SAAS,SACQ,CAAA"}
|
package/build/main.js
CHANGED
|
@@ -19,7 +19,15 @@ export const render = (node, opts) => {
|
|
|
19
19
|
createRoot(root).render(node);
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
-
|
|
22
|
+
// Wait for the DOM only while it is still loading: a caller that awaits something first
|
|
23
|
+
// (`await prepareI18n(cfg); render(...)`) can run after `DOMContentLoaded` has already fired,
|
|
24
|
+
// and a listener added then never runs — the app would never mount.
|
|
25
|
+
if ((opts?.onReady ?? true) && document.readyState === 'loading') {
|
|
26
|
+
window.addEventListener('DOMContentLoaded', _callback, { once: true });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
_callback();
|
|
30
|
+
}
|
|
23
31
|
};
|
|
24
32
|
/**
|
|
25
33
|
* Mount the application.
|
package/build/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAE9C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAe,EAAE,IAAoB,EAAE,EAAE;IAC9D,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,MAAM,GAAG,GAAG,IAAI,EAAE,KAAK,IAAI,YAAY,CAAA;QACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;QACzC,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAA;QAC7C,CAAC;QACD,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,EAAE,CAAC,CAAA;QAC3D,CAAC;QACD,IAAI,IAAI,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;YAC3B,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACzB,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC,CAAA;IAED,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAE9C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAe,EAAE,IAAoB,EAAE,EAAE;IAC9D,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,MAAM,GAAG,GAAG,IAAI,EAAE,KAAK,IAAI,YAAY,CAAA;QACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;QACzC,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAA;QAC7C,CAAC;QACD,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,EAAE,CAAC,CAAA;QAC3D,CAAC;QACD,IAAI,IAAI,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;YAC3B,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACzB,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC,CAAA;IAED,wFAAwF;IACxF,8FAA8F;IAC9F,oEAAoE;IACpE,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACjE,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IACxE,CAAC;SAAM,CAAC;QACN,SAAS,EAAE,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,OAAU,EAAE,IAAoB,EAAE,QAAoB,EACtD,EAAE,CAAC,MAAM,CAAC,KAAC,MAAM,IAAC,OAAO,EAAE,OAAO,YAAG,QAAQ,GAAU,EAAE,IAAI,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-client",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.52",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -21,22 +21,22 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
25
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/client": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/client-auth": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/client-context": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/client-i18n": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/client-entrypoint": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/client-resource": "^0.1.18-rc.
|
|
32
|
-
"@owlmeans/client-route": "^0.1.18-rc.
|
|
33
|
-
"@owlmeans/config": "^0.1.18-rc.
|
|
24
|
+
"@owlmeans/auth": "^0.1.18-rc.36",
|
|
25
|
+
"@owlmeans/auth-common": "^0.1.18-rc.38",
|
|
26
|
+
"@owlmeans/client": "^0.1.18-rc.42",
|
|
27
|
+
"@owlmeans/client-auth": "^0.1.18-rc.49",
|
|
28
|
+
"@owlmeans/client-context": "^0.1.18-rc.39",
|
|
29
|
+
"@owlmeans/client-i18n": "^0.1.18-rc.41",
|
|
30
|
+
"@owlmeans/client-entrypoint": "^0.1.18-rc.38",
|
|
31
|
+
"@owlmeans/client-resource": "^0.1.18-rc.38",
|
|
32
|
+
"@owlmeans/client-route": "^0.1.18-rc.39",
|
|
33
|
+
"@owlmeans/config": "^0.1.18-rc.38",
|
|
34
34
|
"@owlmeans/context": "^0.1.18-rc.32",
|
|
35
|
-
"@owlmeans/error": "^0.1.18-rc.
|
|
36
|
-
"@owlmeans/i18n": "^0.1.18-rc.
|
|
35
|
+
"@owlmeans/error": "^0.1.18-rc.36",
|
|
36
|
+
"@owlmeans/i18n": "^0.1.18-rc.37",
|
|
37
37
|
"@owlmeans/route": "^0.1.18-rc.33",
|
|
38
|
-
"@owlmeans/web-db": "^0.1.18-rc.
|
|
39
|
-
"@owlmeans/web-router": "^0.1.18-rc.
|
|
38
|
+
"@owlmeans/web-db": "^0.1.18-rc.39",
|
|
39
|
+
"@owlmeans/web-router": "^0.1.18-rc.40",
|
|
40
40
|
"react-dropzone": "^14.3.5"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
package/src/exports.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
handler, lazyHandler, lazyComponent, retryImport, isChunkLoadError, reloadOnce, recoverFromChunkError,
|
|
4
|
+
chunkUrlOf, cacheBustedUrl,
|
|
5
|
+
} from '@owlmeans/client'
|
|
6
|
+
export type {
|
|
7
|
+
LazyComponent, LazyComponentOptions, LazyErrorRenderer, LazyHandler, RetryImportOptions
|
|
8
|
+
} from '@owlmeans/client'
|
|
3
9
|
export { config } from '@owlmeans/client-context'
|
|
4
10
|
export { service } from '@owlmeans/config'
|
|
5
11
|
export { AppType, HOME, ROOT, BASE, GUEST } from '@owlmeans/context'
|
package/src/login/screen.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
LoginIntent, LoginOutcome, LOGIN_INTENT_QUERY, LOGIN_METHOD_QUERY, LOGIN_NEXT_QUERY,
|
|
7
7
|
} from '@owlmeans/client-auth/login'
|
|
8
8
|
import type { LoginService } from '@owlmeans/client-auth/login'
|
|
9
|
+
import { USER_ID } from '@owlmeans/client-auth'
|
|
9
10
|
import type { AppContext } from '../types.js'
|
|
10
11
|
import { LoginSurrogateView, SurrogateStage } from './view.js'
|
|
11
12
|
|
|
@@ -17,10 +18,43 @@ import { LoginSurrogateView, SurrogateStage } from './view.js'
|
|
|
17
18
|
* application, with its navigation, inside itself. This screen has no continuation at all — it
|
|
18
19
|
* either hands something back and closes, or it says what it is waiting for.
|
|
19
20
|
*
|
|
20
|
-
* It also never runs the authorization machine
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* It also never runs the authorization machine itself: it forwards to the dispatcher (`next`),
|
|
22
|
+
* which owns that flow, and hands back what the dispatcher issued — see {@link surrogateLoginStep}
|
|
23
|
+
* for why a session already stored in this window is not simply handed back.
|
|
23
24
|
*/
|
|
25
|
+
/** What a surrogate window opened to SIGN IN does with the session it may already hold. */
|
|
26
|
+
export enum SurrogateLoginStep {
|
|
27
|
+
/** Drop the stored session and authenticate afresh through the dispatcher. */
|
|
28
|
+
Forget = 'forget',
|
|
29
|
+
/** Hand the stored session to the opener — there is no dispatcher to authenticate through. */
|
|
30
|
+
Resume = 'resume',
|
|
31
|
+
/** Nothing stored: authenticate through the dispatcher. */
|
|
32
|
+
Authenticate = 'authenticate',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A sign-in request never reuses the session this window happens to hold.
|
|
37
|
+
*
|
|
38
|
+
* The opener asks for a sign-in only when it has no session its server accepts — and the window it
|
|
39
|
+
* opens does not share its storage: an embedded application's storage is partitioned by the top
|
|
40
|
+
* level site, the popup's is first-party. So the popup can hold a session the server has already
|
|
41
|
+
* REFUSED (its record gone after a restart, revoked, fenced). Handing that back closes the popup
|
|
42
|
+
* at once — a blink — and the opener fails its next request exactly as before, forever, because
|
|
43
|
+
* nothing in the opener can reach the popup's storage to clear it. A fresh round trip costs only
|
|
44
|
+
* redirects while the provider still has a session, and always yields a session the server holds.
|
|
45
|
+
*
|
|
46
|
+
* Only without a dispatcher address is the stored session handed back as it is: there is nothing
|
|
47
|
+
* to authenticate through, and the opener's own 401 handling drops it if it is refused.
|
|
48
|
+
*/
|
|
49
|
+
export const surrogateLoginStep = (token: string | null | undefined, next: string | null): SurrogateLoginStep => {
|
|
50
|
+
const stored = token != null && token !== ''
|
|
51
|
+
if (!stored) {
|
|
52
|
+
return SurrogateLoginStep.Authenticate
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return next != null && next !== '' ? SurrogateLoginStep.Forget : SurrogateLoginStep.Resume
|
|
56
|
+
}
|
|
57
|
+
|
|
24
58
|
export const SurrogateScreen: FC = () => {
|
|
25
59
|
const context = useContext() as unknown as AppContext
|
|
26
60
|
const [query] = context.router().useSearchParams()
|
|
@@ -78,9 +112,20 @@ export const SurrogateScreen: FC = () => {
|
|
|
78
112
|
return
|
|
79
113
|
}
|
|
80
114
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
115
|
+
const next = query.get(LOGIN_NEXT_QUERY)
|
|
116
|
+
const step = surrogateLoginStep(token, next)
|
|
117
|
+
|
|
118
|
+
if (step === SurrogateLoginStep.Forget) {
|
|
119
|
+
// Dropped from this window's storage only — the web auth service's own clearing path
|
|
120
|
+
// navigates to the dispatcher, and `next` below is exactly where this window goes anyway.
|
|
121
|
+
const auth = context.auth()
|
|
122
|
+
await auth.store().delete(USER_ID)
|
|
123
|
+
auth.token = undefined
|
|
124
|
+
auth.auth = undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (step === SurrogateLoginStep.Resume && token != null) {
|
|
128
|
+
// Signed in here and nowhere to authenticate afresh: hand what there is to the opener.
|
|
84
129
|
const outcome = await login.resume(token)
|
|
85
130
|
setStage(
|
|
86
131
|
outcome === LoginOutcome.Handled ? SurrogateStage.Handing
|
|
@@ -90,10 +135,9 @@ export const SurrogateScreen: FC = () => {
|
|
|
90
135
|
return
|
|
91
136
|
}
|
|
92
137
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
const next = query.get(LOGIN_NEXT_QUERY)
|
|
138
|
+
// The dispatcher owns the authorization round trip, and the provider's callback lands there
|
|
139
|
+
// rather than on this route. The method the user already chose travels with it, so the
|
|
140
|
+
// dispatcher does not ask a second time in a window with no one to ask.
|
|
97
141
|
if (next == null || next === '') {
|
|
98
142
|
setStage(SurrogateStage.Gesture)
|
|
99
143
|
return
|
package/src/main.tsx
CHANGED
|
@@ -22,7 +22,14 @@ export const render = (node: ReactNode, opts?: RenderOptions) => {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// Wait for the DOM only while it is still loading: a caller that awaits something first
|
|
26
|
+
// (`await prepareI18n(cfg); render(...)`) can run after `DOMContentLoaded` has already fired,
|
|
27
|
+
// and a listener added then never runs — the app would never mount.
|
|
28
|
+
if ((opts?.onReady ?? true) && document.readyState === 'loading') {
|
|
29
|
+
window.addEventListener('DOMContentLoaded', _callback, { once: true })
|
|
30
|
+
} else {
|
|
31
|
+
_callback()
|
|
32
|
+
}
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
/**
|
package/tests/surrogate.spec.ts
CHANGED
|
@@ -50,3 +50,22 @@ describe('centeredPopupFeatures', () => {
|
|
|
50
50
|
expect(centeredPopupFeatures(520, 760)).toBe('popup=yes,width=520,height=760,left=0,top=0')
|
|
51
51
|
})
|
|
52
52
|
})
|
|
53
|
+
|
|
54
|
+
describe('surrogateLoginStep', () => {
|
|
55
|
+
test('a stored session is forgotten when there is a dispatcher to authenticate through', async () => {
|
|
56
|
+
const { surrogateLoginStep, SurrogateLoginStep } = await import('../src/login/screen.js')
|
|
57
|
+
expect(surrogateLoginStep('OIDC-WRAPPED-TOKEN abc', '/dispatcher?x=1')).toBe(SurrogateLoginStep.Forget)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('a stored session is handed back only when there is no dispatcher address', async () => {
|
|
61
|
+
const { surrogateLoginStep, SurrogateLoginStep } = await import('../src/login/screen.js')
|
|
62
|
+
expect(surrogateLoginStep('OIDC-WRAPPED-TOKEN abc', null)).toBe(SurrogateLoginStep.Resume)
|
|
63
|
+
expect(surrogateLoginStep('OIDC-WRAPPED-TOKEN abc', '')).toBe(SurrogateLoginStep.Resume)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('nothing stored authenticates, with or without a dispatcher address', async () => {
|
|
67
|
+
const { surrogateLoginStep, SurrogateLoginStep } = await import('../src/login/screen.js')
|
|
68
|
+
expect(surrogateLoginStep(null, '/dispatcher')).toBe(SurrogateLoginStep.Authenticate)
|
|
69
|
+
expect(surrogateLoginStep('', null)).toBe(SurrogateLoginStep.Authenticate)
|
|
70
|
+
})
|
|
71
|
+
})
|