@owlmeans/api 0.1.18-rc.12 → 0.1.18-rc.14
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/api/SKILL.md +47 -9
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +21 -2
- package/build/service.js.map +1 -1
- package/build/utils/handler.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/service.ts +26 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ HTTP API client service for OwlMeans client contexts — carries entrypoint call
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
bun add @owlmeans/api
|
|
15
|
+
bun add @owlmeans/api@^0.1.18-rc.12
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -65,7 +65,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
65
65
|
your project's skill store (`.agents/skills/`):
|
|
66
66
|
|
|
67
67
|
```sh
|
|
68
|
-
npx @owlmeans/agent-skills
|
|
68
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.13
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
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/api",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.14",
|
|
5
|
+
"generatedAt": "2026-09-10T18:54:42.510Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: api
|
|
3
|
-
description: How to use @owlmeans/api — the axios-based HTTP client service that carries entrypoint calls between services. Auto-invoked when importing the API client or when ctx.entrypoint(...).call() under the hood is involved.
|
|
3
|
+
description: How to use @owlmeans/api — the axios-based HTTP client service that carries entrypoint calls between services, its status-to-outcome mapping, typed transport errors and per-request timeout/abort. Auto-invoked when importing the API client, or when ctx.entrypoint(...).call() under the hood is involved.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,14 +8,14 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/api
|
|
9
9
|
|
|
10
10
|
**Layer:** Core
|
|
11
|
-
**Install:** `"@owlmeans/api": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/api": "^0.1.18-rc.14"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
## Key Exports
|
|
14
14
|
|
|
15
15
|
| Export | Description |
|
|
16
16
|
|--------|-------------|
|
|
17
17
|
| `createApiService(alias?)` | Factory for the HTTP client service (axios) |
|
|
18
|
-
| `appendApiClient(ctx, alias?)` | Register it and make it the context's default `webService` |
|
|
18
|
+
| `appendApiClient(ctx, alias?)` | Register it and make it the context's default `webService` when none is set |
|
|
19
19
|
| `ApiClient` | Service interface — a single `handler(req, reply)` |
|
|
20
20
|
| `ApiError`, `ApiClientError`, `ServerCrashedError`, `ServerAuthError` | Typed transport errors |
|
|
21
21
|
| Constants | Status codes (`OK`, `CREATED`, `ACCEPTED`, `FINISHED`, `UNAUTHORIZED_ERROR`, `FORBIDDEN_ERROR`, `SERVER_ERROR`), `DEFAULT_ALIAS` (`web-client`) |
|
|
@@ -32,10 +32,44 @@ host, port, base, protocol and whether the hop is TLS — and `makeSecurityHelpe
|
|
|
32
32
|
that. A per-request `host`, `base` or `unsecure` still overrides it. `:params` come from
|
|
33
33
|
`request.params` and a missing one is a `SyntaxError`, not a literal `:id` in the URL.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
A request whose `canceled` flag is already set is dropped without a round trip.
|
|
36
|
+
|
|
37
|
+
## Per-request controls
|
|
38
|
+
|
|
39
|
+
These live on the request the caller passes and are forwarded to the transport:
|
|
40
|
+
|
|
41
|
+
- `timeout` — milliseconds for the single round trip; omitted or `0` means no timeout, so a stuck
|
|
42
|
+
peer hangs forever unless a caller bounds it.
|
|
43
|
+
- `signal` — an `AbortSignal`, which aborts the request in flight.
|
|
44
|
+
- `headers` — a `content-type` of `application/x-www-form-urlencoded` makes the body serialize
|
|
45
|
+
through `qs` instead of JSON. A string body on a `POST` with no `content-type` gets
|
|
46
|
+
`application/json` and is sent verbatim rather than re-quoted.
|
|
47
|
+
|
|
48
|
+
## Reading the answer
|
|
49
|
+
|
|
50
|
+
`validateStatus` is `() => true`, so **no status throws**: every answer the peer sends, 4xx and 5xx
|
|
51
|
+
included, comes back and is mapped here.
|
|
52
|
+
|
|
53
|
+
| Status | Result |
|
|
54
|
+
|--------|--------|
|
|
55
|
+
| 200 / 202 | Resolves with the body and an `Ok` / `Accepted` outcome |
|
|
56
|
+
| 201 / 204 | Resolves with the body, or with the response headers when the body is empty |
|
|
57
|
+
| any other | Rejects |
|
|
58
|
+
|
|
59
|
+
A rejection prefers the server's own error: a text body is rehydrated into the original
|
|
60
|
+
`ResilientError`, so a `handleError` reply on the far side arrives as the same class it was thrown
|
|
61
|
+
as. Failing that it becomes `ServerCrashedError` (500), `ServerAuthError` (401) or `ApiClientError`
|
|
62
|
+
(403 and the rest).
|
|
63
|
+
|
|
64
|
+
**A failure with no answer at all is a different family.** Suppressing status errors does not wrap
|
|
65
|
+
the call: an expired `timeout` (`ECONNABORTED`), an aborted `signal` (`CanceledError` /
|
|
66
|
+
`ERR_CANCELED`), a refused connection or a DNS failure rejects with the raw axios error, which is
|
|
67
|
+
none of the typed errors above. A caller that bounds a call has to catch that too — testing for
|
|
68
|
+
`ApiClientError` alone lets a timeout through as an unhandled rejection.
|
|
69
|
+
|
|
70
|
+
An `auth-token-refresh` response header is consumed here: when the context has an auth service, the
|
|
71
|
+
rotated token is handed to it, which is what keeps a long session alive without the caller doing
|
|
72
|
+
anything.
|
|
39
73
|
|
|
40
74
|
Building a URL without making the call is a different question — that is `entrypointUrl` from
|
|
41
75
|
`@owlmeans/client-entrypoint/utils`, or `ep.url(req, { absolute })`.
|
|
@@ -49,10 +83,14 @@ import { appendApiClient } from '@owlmeans/api'
|
|
|
49
83
|
appendApiClient(context)
|
|
50
84
|
```
|
|
51
85
|
|
|
86
|
+
`appendApiClient` only claims `cfg.webService` when it is unset, so an app that has already named a
|
|
87
|
+
transport keeps it.
|
|
88
|
+
|
|
52
89
|
## Depends On
|
|
53
90
|
|
|
54
91
|
- `@owlmeans/context` — service registration
|
|
55
92
|
- `@owlmeans/entrypoint` — the entrypoint being addressed, `@owlmeans/client-route` — `extractParams`
|
|
56
|
-
- `@owlmeans/config` — `makeSecurityHelper`
|
|
57
|
-
- `@owlmeans/
|
|
93
|
+
- `@owlmeans/config` — `makeSecurityHelper`, `@owlmeans/client-config` — the config shape
|
|
94
|
+
- `@owlmeans/auth-common` — the token-refresh header and the auth service it updates
|
|
95
|
+
- `@owlmeans/error`, `@owlmeans/route`
|
|
58
96
|
- `axios`, `qs` (runtime)
|
package/build/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAa;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAa;IAE5C,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAmB;IAElD,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF;AAED,qBAAa,kBAAmB,SAAQ,cAAc;IACpD,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF;AAED,qBAAa,eAAgB,SAAQ,cAAc;IACjD,OAAuB,QAAQ,SAAyC;IAExE,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":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,OAAO,QAAS,SAAQ,cAAc;IACnC,MAAM,CAAU,QAAQ,GAAG,UAAU,CAAA;IAE5C,YAAY,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,OAAO,QAAS,SAAQ,cAAc;IACnC,MAAM,CAAU,QAAQ,GAAG,UAAU,CAAA;IAE5C,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,OAAO,EAAE,CAAC,CAAA;IAC5C,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,QAAQ;IACnC,MAAM,CAAU,QAAQ,GAAG,gBAAgB,CAAA;IAElD,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAA;IACrC,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IAC7C,MAAM,CAAU,QAAQ,GAAG,GAAG,cAAc,CAAC,QAAQ,eAAe,CAAA;IAE3E,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,WAAW,OAAO,EAAE,CAAC,CAAA;QAC3B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAA;IACzC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,cAAc;IAC1C,MAAM,CAAU,QAAQ,GAAG,GAAG,cAAc,CAAC,QAAQ,YAAY,CAAA;IAExE,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;CACF;AAED,cAAc,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;AAC3C,cAAc,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA;AACjD,cAAc,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAA;AACrD,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA"}
|
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAM3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAM3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAa3D,KAAK,MAAM,GAAG,iBAAiB,CAAA;AAE/B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;CACnE;AAED,eAAO,MAAM,gBAAgB,WAAW,MAAM,KAAmB,SA+FhE,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,UAAS,MAAM,KAAmB,CAU/G,CAAA"}
|
package/build/service.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { assertContext, createService } from '@owlmeans/context';
|
|
2
2
|
import { extractParams } from '@owlmeans/client-route';
|
|
3
3
|
import axios from 'axios';
|
|
4
|
-
import { DEFAULT_ALIAS } from './consts.js';
|
|
4
|
+
import { DEFAULT_ALIAS, UNAUTHORIZED_ERROR } from './consts.js';
|
|
5
5
|
import { processResponse } from './utils/handler.js';
|
|
6
6
|
import qs from 'qs';
|
|
7
7
|
import { makeSecurityHelper } from '@owlmeans/config';
|
|
8
8
|
import { RouteMethod } from '@owlmeans/route';
|
|
9
|
-
import { DEF_AUTH_SRV, TOKEN_UPDATE } from '@owlmeans/auth-common';
|
|
9
|
+
import { AUTH_HEADER, DEF_AUTH_SRV, TOKEN_UPDATE } from '@owlmeans/auth-common';
|
|
10
|
+
/** Whether a request carried an authentication header of its own. */
|
|
11
|
+
const presented = (headers) => headers != null && Object.entries(headers).some(([key, value]) => key.toLowerCase() === AUTH_HEADER && value != null && value !== '');
|
|
10
12
|
export const createApiService = (alias = DEFAULT_ALIAS) => {
|
|
11
13
|
const location = `api.service:${alias}`;
|
|
12
14
|
const client = createService(alias, {
|
|
@@ -60,6 +62,23 @@ export const createApiService = (alias = DEFAULT_ALIAS) => {
|
|
|
60
62
|
const update = headers.get(TOKEN_UPDATE);
|
|
61
63
|
await auth.update(update == null || update === '' ? undefined : update);
|
|
62
64
|
}
|
|
65
|
+
else if (response.status === UNAUTHORIZED_ERROR
|
|
66
|
+
&& presented(request.headers)
|
|
67
|
+
&& context.hasService(DEF_AUTH_SRV)) {
|
|
68
|
+
/**
|
|
69
|
+
* A session the server has refused is not a session, and keeping it is worse than having
|
|
70
|
+
* none: the application goes on rendering its signed-in tree over a credential that fails
|
|
71
|
+
* every call it makes, and nothing on the client ever asks again — `authenticated()` reads
|
|
72
|
+
* storage and decodes an envelope, it does not consult the server. What that leaves is an
|
|
73
|
+
* app that looks signed in, works at nothing, and offers "Log out" where the control that
|
|
74
|
+
* would fix it should be.
|
|
75
|
+
*
|
|
76
|
+
* Narrow on purpose. Only a request that actually PRESENTED this context's bearer counts,
|
|
77
|
+
* so a 401 from an authentication attempt — bad code, wrong password, an expired challenge
|
|
78
|
+
* — never clears the session of whoever is already signed in.
|
|
79
|
+
*/
|
|
80
|
+
await context.service(DEF_AUTH_SRV).update(undefined);
|
|
81
|
+
}
|
|
63
82
|
processResponse(response, reply);
|
|
64
83
|
return [reply.error ?? reply.value, reply.outcome];
|
|
65
84
|
}
|
package/build/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAEtD,OAAO,KAAuB,MAAM,OAAO,CAAA;AAG3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/utils/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAK1C,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/utils/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAK1C,eAAO,MAAM,eAAe,aAAc,aAAa,SAAS,gBAAgB,CAAC,GAAG,CAAC,SAyCpF,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/api",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
25
|
-
"@owlmeans/client-config": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/client-route": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/config": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/error": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/route": "^0.1.18-rc.
|
|
24
|
+
"@owlmeans/auth-common": "^0.1.18-rc.14",
|
|
25
|
+
"@owlmeans/client-config": "^0.1.18-rc.13",
|
|
26
|
+
"@owlmeans/client-route": "^0.1.18-rc.14",
|
|
27
|
+
"@owlmeans/config": "^0.1.18-rc.13",
|
|
28
|
+
"@owlmeans/context": "^0.1.18-rc.9",
|
|
29
|
+
"@owlmeans/error": "^0.1.18-rc.9",
|
|
30
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.12",
|
|
31
|
+
"@owlmeans/route": "^0.1.18-rc.10",
|
|
32
32
|
"axios": "^1.7.7",
|
|
33
33
|
"qs": "^6.13.0"
|
|
34
34
|
},
|
package/src/service.ts
CHANGED
|
@@ -5,15 +5,21 @@ import type { ApiClient } from './types.js'
|
|
|
5
5
|
import axios, { AxiosHeaders } from 'axios'
|
|
6
6
|
import type { AxiosRequestTransformer } from 'axios'
|
|
7
7
|
import type { CommonEntrypoint } from '@owlmeans/entrypoint'
|
|
8
|
-
import { DEFAULT_ALIAS } from './consts.js'
|
|
8
|
+
import { DEFAULT_ALIAS, UNAUTHORIZED_ERROR } from './consts.js'
|
|
9
9
|
import { processResponse } from './utils/handler.js'
|
|
10
10
|
import { BasicClientConfig } from '@owlmeans/client-config'
|
|
11
11
|
import qs from 'qs'
|
|
12
12
|
import { makeSecurityHelper } from '@owlmeans/config'
|
|
13
13
|
import { RouteMethod } from '@owlmeans/route'
|
|
14
|
-
import { DEF_AUTH_SRV, TOKEN_UPDATE } from '@owlmeans/auth-common'
|
|
14
|
+
import { AUTH_HEADER, DEF_AUTH_SRV, TOKEN_UPDATE } from '@owlmeans/auth-common'
|
|
15
15
|
import type { AuthService } from '@owlmeans/auth-common'
|
|
16
16
|
|
|
17
|
+
/** Whether a request carried an authentication header of its own. */
|
|
18
|
+
const presented = (headers?: Record<string, unknown>): boolean =>
|
|
19
|
+
headers != null && Object.entries(headers).some(
|
|
20
|
+
([key, value]) => key.toLowerCase() === AUTH_HEADER && value != null && value !== ''
|
|
21
|
+
)
|
|
22
|
+
|
|
17
23
|
type Config = BasicClientConfig
|
|
18
24
|
|
|
19
25
|
interface Context<C extends Config = Config> extends BasicContext<C> {
|
|
@@ -85,6 +91,24 @@ export const createApiService = (alias: string = DEFAULT_ALIAS): ApiClient => {
|
|
|
85
91
|
const auth = context.service<AuthService>(DEF_AUTH_SRV)
|
|
86
92
|
const update = headers.get(TOKEN_UPDATE) as string
|
|
87
93
|
await auth.update(update == null || update === '' ? undefined : update)
|
|
94
|
+
} else if (
|
|
95
|
+
response.status === UNAUTHORIZED_ERROR
|
|
96
|
+
&& presented(request.headers)
|
|
97
|
+
&& context.hasService(DEF_AUTH_SRV)
|
|
98
|
+
) {
|
|
99
|
+
/**
|
|
100
|
+
* A session the server has refused is not a session, and keeping it is worse than having
|
|
101
|
+
* none: the application goes on rendering its signed-in tree over a credential that fails
|
|
102
|
+
* every call it makes, and nothing on the client ever asks again — `authenticated()` reads
|
|
103
|
+
* storage and decodes an envelope, it does not consult the server. What that leaves is an
|
|
104
|
+
* app that looks signed in, works at nothing, and offers "Log out" where the control that
|
|
105
|
+
* would fix it should be.
|
|
106
|
+
*
|
|
107
|
+
* Narrow on purpose. Only a request that actually PRESENTED this context's bearer counts,
|
|
108
|
+
* so a 401 from an authentication attempt — bad code, wrong password, an expired challenge
|
|
109
|
+
* — never clears the session of whoever is already signed in.
|
|
110
|
+
*/
|
|
111
|
+
await context.service<AuthService>(DEF_AUTH_SRV).update(undefined)
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
processResponse(response, reply)
|