@owlmeans/web-client 0.1.18-rc.23 → 0.1.18-rc.25
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 +87 -16
- package/build/components/dispatcher/component.d.ts.map +1 -1
- package/build/components/dispatcher/component.js +8 -2
- package/build/components/dispatcher/component.js.map +1 -1
- package/build/components/dispatcher/parametrise.d.ts.map +1 -1
- package/build/context.d.ts.map +1 -1
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js.map +1 -1
- package/build/helper.d.ts +19 -0
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +34 -0
- package/build/helper.js.map +1 -1
- package/build/login/pump.d.ts.map +1 -1
- package/build/login/screen.d.ts +16 -3
- package/build/login/screen.d.ts.map +1 -1
- package/build/login/screen.js +27 -21
- package/build/login/screen.js.map +1 -1
- package/build/login/service.d.ts.map +1 -1
- package/build/main.d.ts.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js.map +1 -1
- package/package.json +17 -17
- package/src/components/dispatcher/component.tsx +9 -2
- package/src/helper.ts +38 -0
- package/src/login/screen.tsx +28 -25
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ React web application bootstrap — context factory, entrypoint/routing utilitie
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
bun add @owlmeans/web-client
|
|
18
|
+
bun add @owlmeans/web-client@^0.1.18-rc.23
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
@@ -88,7 +88,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
88
88
|
your project's skill store (`.agents/skills/`):
|
|
89
89
|
|
|
90
90
|
```sh
|
|
91
|
-
npx @owlmeans/agent-skills
|
|
91
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.13
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
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.25",
|
|
5
|
+
"generatedAt": "2026-09-10T18:54:42.497Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: web-client
|
|
3
|
-
description: How to use @owlmeans/web-client — browser entry point
|
|
3
|
+
description: How to use @owlmeans/web-client — the browser entry point — makeContext(), renderApp(), elevate() to attach React components to entrypoint declarations, context.registerEntrypoints(), context.serviceRoute(), the dispatcher and surrogate login screens, and useAuthenticated(). Auto-invoked when working with a web app entry point, attaching React components to entrypoints, or wiring browser login.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,22 +8,45 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/web-client
|
|
9
9
|
|
|
10
10
|
**Layer:** Web (React)
|
|
11
|
-
**Install:** `"@owlmeans/web-client": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/web-client": "^0.1.18-rc.25"` in `dependencies`
|
|
12
|
+
|
|
13
|
+
The browser layer over `@owlmeans/client`. It builds the context, mounts the app, and brings
|
|
14
|
+
working authentication with nothing registered by hand.
|
|
15
|
+
|
|
16
|
+
**What is here and what is not.** `@owlmeans/client` owns the React primitives and does not
|
|
17
|
+
re-export from here; this package owns the browser wiring and re-exports only the few authoring
|
|
18
|
+
helpers below. In particular `useNavigate`, `useEntrypoint`, `useStoreModel`, `useStoreList`,
|
|
19
|
+
`useValue` and `useToggle` are imported from `@owlmeans/client` even in a browser-only app.
|
|
12
20
|
|
|
13
21
|
## Key Exports
|
|
14
22
|
|
|
15
23
|
| Export | Description |
|
|
16
24
|
|--------|-------------|
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
|
|
|
25
|
+
| `makeContext(cfg)` | Build the browser context: the client context plus the primary host, the auth service, web db, the auth client resource, the router and login |
|
|
26
|
+
| `AppConfig` / `AppContext` | The config and context types — `ClientContext` plus `auth()` and `login()` |
|
|
27
|
+
| `renderApp(context, opts?, children?)` | Mount the app. `children` render inside the provider and before the router — the global overlay slot |
|
|
28
|
+
| `render(node, opts?)` / `RenderOptions` | The bare mount: `{ domId, onReady, hydrate, debug }`, default root id `root` |
|
|
29
|
+
| `WebApp` | The mounted tree — the i18n provider around `App` from `@owlmeans/client` |
|
|
30
|
+
| `useContext()` | The context, typed as `AppContext` |
|
|
31
|
+
| `useAuthenticated()` | Whether this browsing context holds a session — it reports, it does not guard |
|
|
32
|
+
| `entrypoints` | The auth entrypoint list, with the dispatcher and the login-surrogate screens already elevated. Spread it into the app's own list |
|
|
33
|
+
| `Dispatcher` / `parametriseDispatcher(defaults, Com?)` | The post-login landing screen, and a wrapper that pre-fills its props |
|
|
34
|
+
| `Uploader` / `ImageUploader` / `UploaderProps` / `UploaderRootProps` | Dropzone-based file input; props are `react-dropzone`'s `DropzoneOptions` plus `Root`/`rootProps`. The image variant only DEFAULTS `maxSize` to 5 MB — pass `maxSize` to override it |
|
|
35
|
+
| `UploaderError` / `FileUploadingError` | Upload failures, on top of `ComponentError` |
|
|
36
|
+
| `appendWebLogin(ctx)` | Register the login host plus the redirect and surrogate-window plugins |
|
|
37
|
+
| `makeRedirectLoginPlugin()` / `makeSurrogateLoginPlugin()` / `awaitSurrogate(win, type, onMessage)` | The two browser login plugins and the window pump they share |
|
|
38
|
+
| `SurrogateScreen` / `LoginSurrogateView` / `SurrogateStage` / `SurrogateViewProps` | What the surrogate login window renders |
|
|
39
|
+
| `makeAuthWebService(alias?)` / `appendWebAuthService(ctx, alias?)` | The auth service whose `update(undefined)` leaves for the dispatcher. The append form also registers `authMiddleware` and the logout middleware |
|
|
40
|
+
| `extractPrimaryHost(ctx)` | Fill `cfg.primaryHost` / `primaryPort` from `window.location` |
|
|
41
|
+
| `DEFAULT_ROOT` (`root`), `REDIRECT_LOGIN`, `SURROGATE_LOGIN`, `SURROGATE_LOGIN_PRIORITY` (100) | Constants |
|
|
42
|
+
|
|
43
|
+
### Re-exported for authoring
|
|
44
|
+
|
|
45
|
+
`handler` (from `@owlmeans/client`); `entrypoint`, `elevate`, `provideRequest`, `stab` and the
|
|
46
|
+
`ClientEntrypoint` type (from `@owlmeans/client-entrypoint`); `route`, `frontend` (from
|
|
47
|
+
`@owlmeans/route`); `config` (from `@owlmeans/client-context`); `service` (from `@owlmeans/config`);
|
|
48
|
+
`AppType`, `HOME`, `ROOT`, `BASE`, `GUEST` (from `@owlmeans/context`). Import them from here so an
|
|
49
|
+
app's entrypoint file needs one import.
|
|
27
50
|
|
|
28
51
|
## Usage
|
|
29
52
|
|
|
@@ -43,6 +66,12 @@ context.serviceRoute(MANAGER_API, true)
|
|
|
43
66
|
renderApp<Config, Context>(context)
|
|
44
67
|
```
|
|
45
68
|
|
|
69
|
+
`serviceRoute(alias, makeDefault?)` marks a service's routing root; an alias `cfg.services` does not
|
|
70
|
+
carry throws a `SyntaxError` naming everything that is registered. Anything passed as `renderApp`'s
|
|
71
|
+
third argument mounts once, above routing, and survives navigation — which is where a consent
|
|
72
|
+
dialog or a toast surface belongs, since the same component inside a route is torn down and rebuilt
|
|
73
|
+
on every navigation and a dialog would close itself.
|
|
74
|
+
|
|
46
75
|
### Entrypoint elevation with React components
|
|
47
76
|
```typescript
|
|
48
77
|
import { elevate, route, frontend, handler, entrypoint } from '@owlmeans/web-client'
|
|
@@ -75,6 +104,12 @@ const href = await context.entrypoint<ClientEntrypoint<Project>>(alias)
|
|
|
75
104
|
The auth header is attached automatically for guarded entrypoints. An entrypoint that renders a
|
|
76
105
|
screen answers `url()` and throws from `call()`/`invoke()` — a screen is navigated to, not called.
|
|
77
106
|
|
|
107
|
+
A guarded call whose session the server rejects logs the user out rather than surfacing the error
|
|
108
|
+
raw. `appendWebAuthService` registers a logout middleware for this — internal to the package, not
|
|
109
|
+
an import — which wraps `invoke` on every guarded backend entrypoint (that covers `call`, since
|
|
110
|
+
`call` reads `invoke` at the moment it runs), and an `AuthUnknown` ending in `:invalid` clears the
|
|
111
|
+
token, which through the web auth service leaves for the dispatcher.
|
|
112
|
+
|
|
78
113
|
### Login comes with the context
|
|
79
114
|
|
|
80
115
|
`makeContext` calls `appendWebLogin`, which registers the login host (`@owlmeans/client-auth/login`)
|
|
@@ -89,9 +124,33 @@ import { useLogin, useLogout } from '@owlmeans/client-auth/login'
|
|
|
89
124
|
const [, onLogIn] = useLogin() // one control, one handler, no environment checks
|
|
90
125
|
```
|
|
91
126
|
|
|
127
|
+
The dispatcher and the surrogate screen are elevated on the auth entrypoint list this package
|
|
128
|
+
exports, so an app that spreads `entrypoints` into its own list gets both without an edit. The
|
|
129
|
+
surrogate screen renders `LoginSurrogateView` with plain elements and inline styles on purpose: it
|
|
130
|
+
runs in a popup opened by apps with entirely different stylesheets, none of which is loaded there.
|
|
131
|
+
|
|
92
132
|
An app that needs a different mechanic registers its own plugin at a higher priority after building
|
|
93
133
|
the base context (`context.login().registerPlugin(...)`); it never replaces these. The contract, the
|
|
94
|
-
cascade and the invariants are the
|
|
134
|
+
cascade and the invariants are the [[login-plugins]] skill.
|
|
135
|
+
|
|
136
|
+
### Reading auth state
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { useAuthenticated } from '@owlmeans/web-client'
|
|
140
|
+
|
|
141
|
+
const authenticated = useAuthenticated() // false until the check settles
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`useAuthenticated()` asks `context.auth().authenticated()` — the one channel the dispatcher and the
|
|
145
|
+
login screen already read, answering from the auth service's token or rehydrating it from the auth
|
|
146
|
+
resource. It is what a header uses to choose between the "Log in" and "Log out" control; nothing
|
|
147
|
+
about it navigates, and it does not re-check, because both ways out of its answer replace the
|
|
148
|
+
document.
|
|
149
|
+
|
|
150
|
+
**It reports; it does not guard.** The guard is `useSelfAuth` from `@owlmeans/client-auth`, which
|
|
151
|
+
sends an anonymous visitor to the dispatcher and defaults to doing so — the wrong behaviour for a
|
|
152
|
+
component that only wants to know which control to draw. An application declares neither hook
|
|
153
|
+
itself.
|
|
95
154
|
|
|
96
155
|
### Navigation
|
|
97
156
|
```typescript
|
|
@@ -110,9 +169,21 @@ which does it for you.
|
|
|
110
169
|
|
|
111
170
|
## Depends On
|
|
112
171
|
|
|
113
|
-
- `@owlmeans/client`, `@owlmeans/web-router` (default OwlMeans browser routing), `@owlmeans/web-
|
|
114
|
-
|
|
115
|
-
- `@owlmeans/
|
|
172
|
+
- `@owlmeans/client`, `@owlmeans/web-router` (default OwlMeans browser routing), `@owlmeans/web-db`
|
|
173
|
+
(the IndexedDB store behind the client resources), `@owlmeans/client-i18n`
|
|
174
|
+
- `@owlmeans/client-auth` — `AUTH_RESOURCE`, the dispatcher HOC, and the `./login` host the browser
|
|
175
|
+
plugins register on; `@owlmeans/auth`, `@owlmeans/auth-common`
|
|
176
|
+
- `@owlmeans/client-context`, `@owlmeans/client-entrypoint`, `@owlmeans/client-resource`,
|
|
177
|
+
`@owlmeans/client-route`, `@owlmeans/route`, `@owlmeans/config`, `@owlmeans/context`,
|
|
178
|
+
`@owlmeans/error`, `@owlmeans/i18n`, `react-dropzone`
|
|
179
|
+
- `@owlmeans/web-panel` and `@owlmeans/mui-panel` build **on** this package, not the other way
|
|
180
|
+
round: a UI-family app imports the panel package and gets this one with it
|
|
116
181
|
- `react`, `react-dom` (peer). It does not depend on `react-router` — opt into it with
|
|
117
182
|
`@owlmeans/web-router-react-router` (`appendReactRouter`). Routing needs no explicit provider:
|
|
118
183
|
`PanelApp`/`App` compile through `context.router()`, so nothing passes a `provide` prop.
|
|
184
|
+
|
|
185
|
+
## Related
|
|
186
|
+
|
|
187
|
+
- [[client]] — the React primitives and the hooks this does not re-export
|
|
188
|
+
- [[login-plugins]] — the plugin cascade the two browser plugins take part in
|
|
189
|
+
- [[web-router]] — the routing this context registers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../../src/components/dispatcher/component.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../../src/components/dispatcher/component.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,6FAsGrB,CAAA"}
|
|
@@ -3,7 +3,7 @@ import { DispatcherHOC } from '@owlmeans/client-auth';
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
4
|
import { useI18nLib } from '@owlmeans/client-i18n';
|
|
5
5
|
import { AUTH_QUERY } from '@owlmeans/auth';
|
|
6
|
-
import { FallbackLoginScreen, LoginIntent, LoginOutcome, ResumeAction, resumeAction, } from '@owlmeans/client-auth/login';
|
|
6
|
+
import { FallbackLoginScreen, LoginIntent, LoginOutcome, ResumeAction, resumeAction, LOGIN_FRESH_QUERY, } from '@owlmeans/client-auth/login';
|
|
7
7
|
import { LoginSurrogateView, SurrogateStage } from '../../login/view.js';
|
|
8
8
|
import { useContext } from '../../context.js';
|
|
9
9
|
export const Dispatcher = DispatcherHOC(({ provideToken, navigate }) => {
|
|
@@ -33,7 +33,13 @@ export const Dispatcher = DispatcherHOC(({ provideToken, navigate }) => {
|
|
|
33
33
|
provideToken({ token }, params);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
// `fresh` says this window was opened to PRODUCE a session, so one it merely finds on this
|
|
37
|
+
// origin is not the answer — see LOGIN_FRESH_QUERY. Kept identical to the relying party's own
|
|
38
|
+
// dispatcher: the last time these two held their own copy of a decision they drifted.
|
|
39
|
+
const found = query.has(LOGIN_FRESH_QUERY)
|
|
40
|
+
? Promise.resolve(null)
|
|
41
|
+
: context.auth().authenticated();
|
|
42
|
+
void found.then(async (authzToken) => {
|
|
37
43
|
if (authzToken == null || authzToken === '') {
|
|
38
44
|
setChoose(true);
|
|
39
45
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../../src/components/dispatcher/component.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EACL,mBAAmB,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../../src/components/dispatcher/component.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parametrise.d.ts","sourceRoot":"","sources":["../../../src/components/dispatcher/parametrise.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAI5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE9C,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"parametrise.d.ts","sourceRoot":"","sources":["../../../src/components/dispatcher/parametrise.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAI5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE9C,eAAO,MAAM,qBAAqB,QAAS,OAAO,CAAC,iBAAiB,CAAC,QAAQ,eAAe,CAAC,eAAe,CAAC,aAE5F,iBAAiB,gCAiBjC,CAAA"}
|
package/build/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAUlD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAUlD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,OAC7F,CAAC,KACL,CAWF,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,QACnF,CAAA"}
|
package/build/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,qBAAa,aAAc,SAAQ,cAAc;IAC/C,OAAuB,QAAQ,EAAE,MAAM,CAAuC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,qBAAa,aAAc,SAAQ,cAAc;IAC/C,OAAuB,QAAQ,EAAE,MAAM,CAAuC;IAE9E,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,EAAE,MAAM,CAA2C;IAE3E,QAAQ,EAAE,KAAK,GAAG,SAAS,CAAA;IAElC,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF"}
|
package/build/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,MAAM,OAAO,aAAc,SAAQ,cAAc;IACxC,MAAM,CAAU,QAAQ,GAAW,GAAG,cAAc,CAAC,QAAQ,UAAU,CAAA;IAE9E,YAAY,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,MAAM,OAAO,aAAc,SAAQ,cAAc;IACxC,MAAM,CAAU,QAAQ,GAAW,GAAG,cAAc,CAAC,QAAQ,UAAU,CAAA;IAE9E,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,YAAY,OAAO,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAA;IACpC,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,aAAa;IAC5C,MAAM,CAAU,QAAQ,GAAW,GAAG,aAAa,CAAC,QAAQ,eAAe,CAAA;IAE3E,QAAQ,CAAmB;IAElC,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,kBAAkB,OAAO,EAAE,CAAC,CAAA;QAClC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAA;IACzC,CAAC;CACF;AAED,cAAc,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAA;AAChD,cAAc,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAA"}
|
package/build/helper.d.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
1
|
import type { AppConfig, AppContext } from './types.js';
|
|
2
2
|
export declare const extractPrimaryHost: <C extends AppConfig = AppConfig, T extends AppContext<C> = AppContext<C>>(context: T) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Whether this browsing context currently holds a session — the flag a screen renders against
|
|
5
|
+
* when it offers "Log in" or "Log out", a profile menu, or an authenticated-only panel.
|
|
6
|
+
*
|
|
7
|
+
* It reads the one channel the rest of the package reads, `context.auth().authenticated()`, which
|
|
8
|
+
* answers from the auth service's token or, in a cold document, rehydrates it from the auth
|
|
9
|
+
* resource. Nothing here touches storage or decodes a token: a second way to ask the same question
|
|
10
|
+
* is a second thing to keep in step with logout.
|
|
11
|
+
*
|
|
12
|
+
* The answer is `false` until that promise settles and is not re-checked afterwards, because both
|
|
13
|
+
* transitions out of the current answer replace the document — signing in navigates through the
|
|
14
|
+
* dispatcher, and `auth().update(undefined)` assigns `document.location.href`. There is no in-page
|
|
15
|
+
* change to subscribe to.
|
|
16
|
+
*
|
|
17
|
+
* This reports; it does not guard. `useSelfAuth` from `@owlmeans/client-auth` is the guard — it
|
|
18
|
+
* sends an anonymous visitor to the dispatcher, which is the wrong behaviour for a component that
|
|
19
|
+
* only wants to know which control to draw.
|
|
20
|
+
*/
|
|
21
|
+
export declare const useAuthenticated: () => boolean;
|
|
3
22
|
//# sourceMappingURL=helper.d.ts.map
|
package/build/helper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvD,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,SAKtH,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gBAAgB,QAAO,OAgBnC,CAAA"}
|
package/build/helper.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useContext } from '@owlmeans/client';
|
|
1
3
|
export const extractPrimaryHost = (context) => {
|
|
2
4
|
if (typeof window !== 'undefined') {
|
|
3
5
|
context.cfg.primaryHost = window.location.hostname;
|
|
4
6
|
context.cfg.primaryPort = window.location.port != null && window.location.port != '' ? parseInt(window.location.port) : undefined;
|
|
5
7
|
}
|
|
6
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Whether this browsing context currently holds a session — the flag a screen renders against
|
|
11
|
+
* when it offers "Log in" or "Log out", a profile menu, or an authenticated-only panel.
|
|
12
|
+
*
|
|
13
|
+
* It reads the one channel the rest of the package reads, `context.auth().authenticated()`, which
|
|
14
|
+
* answers from the auth service's token or, in a cold document, rehydrates it from the auth
|
|
15
|
+
* resource. Nothing here touches storage or decodes a token: a second way to ask the same question
|
|
16
|
+
* is a second thing to keep in step with logout.
|
|
17
|
+
*
|
|
18
|
+
* The answer is `false` until that promise settles and is not re-checked afterwards, because both
|
|
19
|
+
* transitions out of the current answer replace the document — signing in navigates through the
|
|
20
|
+
* dispatcher, and `auth().update(undefined)` assigns `document.location.href`. There is no in-page
|
|
21
|
+
* change to subscribe to.
|
|
22
|
+
*
|
|
23
|
+
* This reports; it does not guard. `useSelfAuth` from `@owlmeans/client-auth` is the guard — it
|
|
24
|
+
* sends an anonymous visitor to the dispatcher, which is the wrong behaviour for a component that
|
|
25
|
+
* only wants to know which control to draw.
|
|
26
|
+
*/
|
|
27
|
+
export const useAuthenticated = () => {
|
|
28
|
+
const context = useContext();
|
|
29
|
+
const [authenticated, setAuthenticated] = useState(false);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
let mounted = true;
|
|
32
|
+
void context.auth().authenticated().then(token => {
|
|
33
|
+
if (mounted) {
|
|
34
|
+
setAuthenticated(token != null && token !== '');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return () => { mounted = false; };
|
|
38
|
+
}, []);
|
|
39
|
+
return authenticated;
|
|
40
|
+
};
|
|
7
41
|
//# sourceMappingURL=helper.js.map
|
package/build/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAG7C,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAA2E,OAAU,EAAE,EAAE;IACzH,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAA;QAClD,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACnI,CAAC;AACH,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAY,EAAE;IAC5C,MAAM,OAAO,GAAG,UAAU,EAAyB,CAAA;IACnD,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEzD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,GAAG,IAAI,CAAA;QAClB,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;YAC/C,IAAI,OAAO,EAAE,CAAC;gBACZ,gBAAgB,CAAC,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC,CAAA;YACjD,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,OAAO,GAAG,EAAE,GAAG,OAAO,GAAG,KAAK,CAAA,CAAC,CAAC,CAAA;IAClC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,aAAa,CAAA;AACtB,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pump.d.ts","sourceRoot":"","sources":["../../src/login/pump.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAEhF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"pump.d.ts","sourceRoot":"","sources":["../../src/login/pump.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAEhF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,cACd,MAAM,eACJ,MAAM,aACR,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,OAAO,CAAC,YAAY,CAAC,KAC1F,OAAO,CAAC,YAAY,CAiCrB,CAAA"}
|
package/build/login/screen.d.ts
CHANGED
|
@@ -7,9 +7,22 @@ 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
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* It does not run the authorization machine itself either: it forwards to the dispatcher (`next`),
|
|
11
|
+
* which owns that flow.
|
|
12
|
+
*
|
|
13
|
+
* It used to short-circuit that when a session was already present on this origin — hand it back,
|
|
14
|
+
* no provider round trip. That is wrong, and it was the whole of a reported defect. This window is
|
|
15
|
+
* first-party on the application's origin, so what it finds there is whatever the person's own
|
|
16
|
+
* tabs left behind, and a token being present says nothing about the record behind it still
|
|
17
|
+
* existing: `authenticated()` reads storage and decodes an envelope, and no client asks the server.
|
|
18
|
+
* A session that had been revoked, replaced or expired was therefore handed back to a window that
|
|
19
|
+
* had asked for a LOGIN, adopted, and used to fail every call — and the next press of the same
|
|
20
|
+
* button found the same dead token and did it again. Nothing recovered but clearing site data.
|
|
21
|
+
*
|
|
22
|
+
* So an explicit login PRODUCES a session. `fresh` says so to the dispatcher, which skips its own
|
|
23
|
+
* resume for the same reason. The cost is one provider round trip that the provider's own session
|
|
24
|
+
* normally answers without asking the person anything; the gain is that what comes back has just
|
|
25
|
+
* been vouched for, and that signing in as somebody else is possible at all.
|
|
13
26
|
*/
|
|
14
27
|
export declare const SurrogateScreen: FC;
|
|
15
28
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,eAAe,EAAE,EAoF7B,CAAA"}
|
package/build/login/screen.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { useContext } from '@owlmeans/client';
|
|
4
4
|
import { useI18nLib } from '@owlmeans/client-i18n';
|
|
5
|
-
import { LoginIntent, LoginOutcome, LOGIN_INTENT_QUERY, LOGIN_METHOD_QUERY, LOGIN_NEXT_QUERY, } from '@owlmeans/client-auth/login';
|
|
5
|
+
import { LoginIntent, LoginOutcome, LOGIN_FRESH_QUERY, LOGIN_INTENT_QUERY, LOGIN_METHOD_QUERY, LOGIN_NEXT_QUERY, } from '@owlmeans/client-auth/login';
|
|
6
6
|
import { LoginSurrogateView, SurrogateStage } from './view.js';
|
|
7
7
|
/**
|
|
8
8
|
* The login window an embedded application opens one level up.
|
|
@@ -12,9 +12,22 @@ import { LoginSurrogateView, SurrogateStage } from './view.js';
|
|
|
12
12
|
* application, with its navigation, inside itself. This screen has no continuation at all — it
|
|
13
13
|
* either hands something back and closes, or it says what it is waiting for.
|
|
14
14
|
*
|
|
15
|
-
* It
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* It does not run the authorization machine itself either: it forwards to the dispatcher (`next`),
|
|
16
|
+
* which owns that flow.
|
|
17
|
+
*
|
|
18
|
+
* It used to short-circuit that when a session was already present on this origin — hand it back,
|
|
19
|
+
* no provider round trip. That is wrong, and it was the whole of a reported defect. This window is
|
|
20
|
+
* first-party on the application's origin, so what it finds there is whatever the person's own
|
|
21
|
+
* tabs left behind, and a token being present says nothing about the record behind it still
|
|
22
|
+
* existing: `authenticated()` reads storage and decodes an envelope, and no client asks the server.
|
|
23
|
+
* A session that had been revoked, replaced or expired was therefore handed back to a window that
|
|
24
|
+
* had asked for a LOGIN, adopted, and used to fail every call — and the next press of the same
|
|
25
|
+
* button found the same dead token and did it again. Nothing recovered but clearing site data.
|
|
26
|
+
*
|
|
27
|
+
* So an explicit login PRODUCES a session. `fresh` says so to the dispatcher, which skips its own
|
|
28
|
+
* resume for the same reason. The cost is one provider round trip that the provider's own session
|
|
29
|
+
* normally answers without asking the person anything; the gain is that what comes back has just
|
|
30
|
+
* been vouched for, and that signing in as somebody else is possible at all.
|
|
18
31
|
*/
|
|
19
32
|
export const SurrogateScreen = () => {
|
|
20
33
|
const context = useContext();
|
|
@@ -51,7 +64,6 @@ export const SurrogateScreen = () => {
|
|
|
51
64
|
return;
|
|
52
65
|
}
|
|
53
66
|
const run = async () => {
|
|
54
|
-
const token = await context.auth().authenticated();
|
|
55
67
|
if (intent === LoginIntent.Logout) {
|
|
56
68
|
// Revoked FIRST, before anything can be awaited on a channel that may never answer: a
|
|
57
69
|
// window the user closes half a second later must still have ended the session it opened
|
|
@@ -63,28 +75,22 @@ export const SurrogateScreen = () => {
|
|
|
63
75
|
: SurrogateStage.Failed);
|
|
64
76
|
return;
|
|
65
77
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
setStage(outcome === LoginOutcome.Handled ? SurrogateStage.Handing
|
|
71
|
-
: outcome === LoginOutcome.Orphaned ? SurrogateStage.Orphaned
|
|
72
|
-
: SurrogateStage.Failed);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// Nothing here yet — the dispatcher owns the authorization round trip, and the provider's
|
|
76
|
-
// callback lands there rather than on this route. The method the user already chose travels
|
|
77
|
-
// with it, so the dispatcher does not ask a second time in a window with no one to ask.
|
|
78
|
+
// The dispatcher owns the authorization round trip, and the provider's callback lands there
|
|
79
|
+
// rather than on this route. Two things travel with it: the method the user already chose,
|
|
80
|
+
// so the dispatcher does not ask a second time in a window with no one to ask, and `fresh`,
|
|
81
|
+
// which tells it that a session it happens to find here is not the answer to this request.
|
|
78
82
|
const next = query.get(LOGIN_NEXT_QUERY);
|
|
79
83
|
if (next == null || next === '') {
|
|
80
84
|
setStage(SurrogateStage.Gesture);
|
|
81
85
|
return;
|
|
82
86
|
}
|
|
83
87
|
const method = query.get(LOGIN_METHOD_QUERY);
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
const params = new URLSearchParams();
|
|
89
|
+
if (method != null && method !== '') {
|
|
90
|
+
params.set(LOGIN_METHOD_QUERY, method);
|
|
91
|
+
}
|
|
92
|
+
params.set(LOGIN_FRESH_QUERY, '1');
|
|
93
|
+
window.location.href = `${next}${next.includes('?') ? '&' : '?'}${params.toString()}`;
|
|
88
94
|
};
|
|
89
95
|
void run().catch((e) => {
|
|
90
96
|
console.error(e);
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/login/screen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/login/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIlE;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/login/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIlE;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,KAAG,CAMzF,CAAA"}
|
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,
|
|
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,SAkB3D,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/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAI3E,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAI3E,eAAO,MAAM,kBAAkB,WAAW,MAAM,KAAmB,WAclE,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,OAChF,CAAC,UAAS,MAAM,KACpB,CAAC,GAAG,iBAaN,CAAA"}
|
package/build/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAElD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAElD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAK,GAAW,aAAa,EAAe,EAAE;IAC/E,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IAEtC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAC7B,OAAO,CAAC,MAAM,GAAG,KAAK,EAAC,KAAK,EAAC,EAAE;QAC7B,MAAM,MAAM,CAAC,KAAK,CAAC,CAAA;QACnB,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,UAAU,CAA2B,UAAU,CAAC,CAAA;YACnF,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAA;YAC9B,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAA;QAC9B,CAAC;IACH,CAAC,CAAA;IAED,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,GAAM,EAAE,KAAK,GAAW,aAAa,EACd,EAAE;IACzB,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IAEzC,MAAM,OAAO,GAAG,GAA4B,CAAA;IAE5C,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAEhC,OAAO,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA;IAC1C,OAAO,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAA;IAE5C,OAAO,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAE/C,OAAO,OAAO,CAAA;AAChB,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.25",
|
|
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.
|
|
34
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
35
|
-
"@owlmeans/error": "^0.1.18-rc.
|
|
36
|
-
"@owlmeans/i18n": "^0.1.18-rc.
|
|
37
|
-
"@owlmeans/route": "^0.1.18-rc.
|
|
38
|
-
"@owlmeans/web-db": "^0.1.18-rc.
|
|
39
|
-
"@owlmeans/web-router": "^0.1.18-rc.
|
|
24
|
+
"@owlmeans/auth": "^0.1.18-rc.10",
|
|
25
|
+
"@owlmeans/auth-common": "^0.1.18-rc.14",
|
|
26
|
+
"@owlmeans/client": "^0.1.18-rc.17",
|
|
27
|
+
"@owlmeans/client-auth": "^0.1.18-rc.21",
|
|
28
|
+
"@owlmeans/client-context": "^0.1.18-rc.14",
|
|
29
|
+
"@owlmeans/client-i18n": "^0.1.18-rc.16",
|
|
30
|
+
"@owlmeans/client-entrypoint": "^0.1.18-rc.14",
|
|
31
|
+
"@owlmeans/client-resource": "^0.1.18-rc.14",
|
|
32
|
+
"@owlmeans/client-route": "^0.1.18-rc.14",
|
|
33
|
+
"@owlmeans/config": "^0.1.18-rc.13",
|
|
34
|
+
"@owlmeans/context": "^0.1.18-rc.9",
|
|
35
|
+
"@owlmeans/error": "^0.1.18-rc.9",
|
|
36
|
+
"@owlmeans/i18n": "^0.1.18-rc.9",
|
|
37
|
+
"@owlmeans/route": "^0.1.18-rc.10",
|
|
38
|
+
"@owlmeans/web-db": "^0.1.18-rc.15",
|
|
39
|
+
"@owlmeans/web-router": "^0.1.18-rc.15",
|
|
40
40
|
"react-dropzone": "^14.3.5"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'
|
|
|
3
3
|
import { useI18nLib } from '@owlmeans/client-i18n'
|
|
4
4
|
import { AUTH_QUERY } from '@owlmeans/auth'
|
|
5
5
|
import {
|
|
6
|
-
FallbackLoginScreen, LoginIntent, LoginOutcome, ResumeAction, resumeAction,
|
|
6
|
+
FallbackLoginScreen, LoginIntent, LoginOutcome, ResumeAction, resumeAction, LOGIN_FRESH_QUERY,
|
|
7
7
|
} from '@owlmeans/client-auth/login'
|
|
8
8
|
import { LoginSurrogateView, SurrogateStage } from '../../login/view.js'
|
|
9
9
|
import { useContext } from '../../context.js'
|
|
@@ -42,7 +42,14 @@ export const Dispatcher = DispatcherHOC(({ provideToken, navigate }) => {
|
|
|
42
42
|
return
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
// `fresh` says this window was opened to PRODUCE a session, so one it merely finds on this
|
|
46
|
+
// origin is not the answer — see LOGIN_FRESH_QUERY. Kept identical to the relying party's own
|
|
47
|
+
// dispatcher: the last time these two held their own copy of a decision they drifted.
|
|
48
|
+
const found = query.has(LOGIN_FRESH_QUERY)
|
|
49
|
+
? Promise.resolve(null)
|
|
50
|
+
: context.auth().authenticated()
|
|
51
|
+
|
|
52
|
+
void found.then(async authzToken => {
|
|
46
53
|
if (authzToken == null || authzToken === '') {
|
|
47
54
|
setChoose(true)
|
|
48
55
|
|
package/src/helper.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { useContext } from '@owlmeans/client'
|
|
1
3
|
import type { AppConfig, AppContext } from './types.js'
|
|
2
4
|
|
|
3
5
|
export const extractPrimaryHost = <C extends AppConfig = AppConfig, T extends AppContext<C> = AppContext<C>>(context: T) => {
|
|
@@ -6,3 +8,39 @@ export const extractPrimaryHost = <C extends AppConfig = AppConfig, T extends Ap
|
|
|
6
8
|
context.cfg.primaryPort = window.location.port != null && window.location.port != '' ? parseInt(window.location.port) : undefined
|
|
7
9
|
}
|
|
8
10
|
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether this browsing context currently holds a session — the flag a screen renders against
|
|
14
|
+
* when it offers "Log in" or "Log out", a profile menu, or an authenticated-only panel.
|
|
15
|
+
*
|
|
16
|
+
* It reads the one channel the rest of the package reads, `context.auth().authenticated()`, which
|
|
17
|
+
* answers from the auth service's token or, in a cold document, rehydrates it from the auth
|
|
18
|
+
* resource. Nothing here touches storage or decodes a token: a second way to ask the same question
|
|
19
|
+
* is a second thing to keep in step with logout.
|
|
20
|
+
*
|
|
21
|
+
* The answer is `false` until that promise settles and is not re-checked afterwards, because both
|
|
22
|
+
* transitions out of the current answer replace the document — signing in navigates through the
|
|
23
|
+
* dispatcher, and `auth().update(undefined)` assigns `document.location.href`. There is no in-page
|
|
24
|
+
* change to subscribe to.
|
|
25
|
+
*
|
|
26
|
+
* This reports; it does not guard. `useSelfAuth` from `@owlmeans/client-auth` is the guard — it
|
|
27
|
+
* sends an anonymous visitor to the dispatcher, which is the wrong behaviour for a component that
|
|
28
|
+
* only wants to know which control to draw.
|
|
29
|
+
*/
|
|
30
|
+
export const useAuthenticated = (): boolean => {
|
|
31
|
+
const context = useContext<AppConfig, AppContext>()
|
|
32
|
+
const [authenticated, setAuthenticated] = useState(false)
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
let mounted = true
|
|
36
|
+
void context.auth().authenticated().then(token => {
|
|
37
|
+
if (mounted) {
|
|
38
|
+
setAuthenticated(token != null && token !== '')
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return () => { mounted = false }
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
return authenticated
|
|
46
|
+
}
|
package/src/login/screen.tsx
CHANGED
|
@@ -3,7 +3,8 @@ import type { FC } from 'react'
|
|
|
3
3
|
import { useContext } from '@owlmeans/client'
|
|
4
4
|
import { useI18nLib } from '@owlmeans/client-i18n'
|
|
5
5
|
import {
|
|
6
|
-
LoginIntent, LoginOutcome, LOGIN_INTENT_QUERY, LOGIN_METHOD_QUERY,
|
|
6
|
+
LoginIntent, LoginOutcome, LOGIN_FRESH_QUERY, LOGIN_INTENT_QUERY, LOGIN_METHOD_QUERY,
|
|
7
|
+
LOGIN_NEXT_QUERY,
|
|
7
8
|
} from '@owlmeans/client-auth/login'
|
|
8
9
|
import type { LoginService } from '@owlmeans/client-auth/login'
|
|
9
10
|
import type { AppContext } from '../types.js'
|
|
@@ -17,9 +18,22 @@ 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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* It does not run the authorization machine itself either: it forwards to the dispatcher (`next`),
|
|
22
|
+
* which owns that flow.
|
|
23
|
+
*
|
|
24
|
+
* It used to short-circuit that when a session was already present on this origin — hand it back,
|
|
25
|
+
* no provider round trip. That is wrong, and it was the whole of a reported defect. This window is
|
|
26
|
+
* first-party on the application's origin, so what it finds there is whatever the person's own
|
|
27
|
+
* tabs left behind, and a token being present says nothing about the record behind it still
|
|
28
|
+
* existing: `authenticated()` reads storage and decodes an envelope, and no client asks the server.
|
|
29
|
+
* A session that had been revoked, replaced or expired was therefore handed back to a window that
|
|
30
|
+
* had asked for a LOGIN, adopted, and used to fail every call — and the next press of the same
|
|
31
|
+
* button found the same dead token and did it again. Nothing recovered but clearing site data.
|
|
32
|
+
*
|
|
33
|
+
* So an explicit login PRODUCES a session. `fresh` says so to the dispatcher, which skips its own
|
|
34
|
+
* resume for the same reason. The cost is one provider round trip that the provider's own session
|
|
35
|
+
* normally answers without asking the person anything; the gain is that what comes back has just
|
|
36
|
+
* been vouched for, and that signing in as somebody else is possible at all.
|
|
23
37
|
*/
|
|
24
38
|
export const SurrogateScreen: FC = () => {
|
|
25
39
|
const context = useContext() as unknown as AppContext
|
|
@@ -62,8 +76,6 @@ export const SurrogateScreen: FC = () => {
|
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
const run = async (): Promise<void> => {
|
|
65
|
-
const token = await context.auth().authenticated()
|
|
66
|
-
|
|
67
79
|
if (intent === LoginIntent.Logout) {
|
|
68
80
|
// Revoked FIRST, before anything can be awaited on a channel that may never answer: a
|
|
69
81
|
// window the user closes half a second later must still have ended the session it opened
|
|
@@ -78,31 +90,22 @@ export const SurrogateScreen: FC = () => {
|
|
|
78
90
|
return
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
setStage(
|
|
86
|
-
outcome === LoginOutcome.Handled ? SurrogateStage.Handing
|
|
87
|
-
: outcome === LoginOutcome.Orphaned ? SurrogateStage.Orphaned
|
|
88
|
-
: SurrogateStage.Failed
|
|
89
|
-
)
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Nothing here yet — the dispatcher owns the authorization round trip, and the provider's
|
|
94
|
-
// callback lands there rather than on this route. The method the user already chose travels
|
|
95
|
-
// with it, so the dispatcher does not ask a second time in a window with no one to ask.
|
|
93
|
+
// The dispatcher owns the authorization round trip, and the provider's callback lands there
|
|
94
|
+
// rather than on this route. Two things travel with it: the method the user already chose,
|
|
95
|
+
// so the dispatcher does not ask a second time in a window with no one to ask, and `fresh`,
|
|
96
|
+
// which tells it that a session it happens to find here is not the answer to this request.
|
|
96
97
|
const next = query.get(LOGIN_NEXT_QUERY)
|
|
97
98
|
if (next == null || next === '') {
|
|
98
99
|
setStage(SurrogateStage.Gesture)
|
|
99
100
|
return
|
|
100
101
|
}
|
|
101
102
|
const method = query.get(LOGIN_METHOD_QUERY)
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
const params = new URLSearchParams()
|
|
104
|
+
if (method != null && method !== '') {
|
|
105
|
+
params.set(LOGIN_METHOD_QUERY, method)
|
|
106
|
+
}
|
|
107
|
+
params.set(LOGIN_FRESH_QUERY, '1')
|
|
108
|
+
window.location.href = `${next}${next.includes('?') ? '&' : '?'}${params.toString()}`
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
void run().catch((e: Error) => {
|