@abide/abide 0.121.0 → 0.122.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ABIDE.md +4 -0
- package/CHANGELOG.md +16 -0
- package/docs/REGISTRY.md +5 -5
- package/docs/RULEBOOK.md +15 -3
- package/docs/abide.md +22 -22
- package/package.json +1 -1
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/answerFailure.ts +16 -0
- package/src/server/answerRequest.ts +16 -21
- package/src/server/answerRpc.ts +4 -3
- package/src/server/claudeCode.ts +1 -1
- package/src/server/createApp.ts +5 -1
- package/src/server/csp.ts +2 -2
- package/src/server/hooks.ts +39 -8
- package/src/{server → shared}/BRIDGE_SCHEME.ts +3 -1
- package/src/shared/Middleware.ts +24 -4
- package/src/shared/attributeText.ts +2 -2
- package/src/shared/csp.ts +2 -2
- package/src/shared/rpc.ts +2 -2
- package/src/shared/safeUrl.ts +8 -1
package/ABIDE.md
CHANGED
|
@@ -57,6 +57,10 @@ affected code saying what it did, and tell the user.
|
|
|
57
57
|
scripts, the CLI and MCP alike.
|
|
58
58
|
- A `.abide` cannot export. A `<script server>` in one holds that file's private handlers
|
|
59
59
|
(`const save = POST(…)`), callable by that file alone, and its hooks (`onStart`, `middleware`, …).
|
|
60
|
+
- A rung shared between handlers or layouts is typed `Middleware` and nothing else:
|
|
61
|
+
`export const signedIn: Middleware = async (next) => (await principal.authenticated) ? next() : refuse(401)`
|
|
62
|
+
fits a handler's `middleware: […]` and a layout's `middleware(…)` alike (RULEBOOK 37.14). No
|
|
63
|
+
`<Ctx, Result>` of its own, and no `as` on a `refuse`.
|
|
60
64
|
- A directory holds addresses, tests, benches, `.d.ts` declarations and two fenced directories
|
|
61
65
|
alone: `lib/` for components and modules, and `public/` for files served as they are. Anything
|
|
62
66
|
else beside the pages is a build error.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @abide/abide
|
|
2
2
|
|
|
3
|
+
## 0.122.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a2144de: `Middleware` is one type for a rung shared between handlers and layouts: `const signedIn: Middleware = async (next) => …` fits a handler's `middleware: […]` and a layout's `middleware(…)` alike, with no `<Ctx, Result>` of its own. `Middleware<Response>` is a rung that answers with a `Response` of its own, and `Middleware<never, { args: () => unknown }>` one that reads a handler's args. A `refuse(…)` a layout's rung returns is now answered in its lane — the nearest error page over a page, Problem Details over a handler, and by what the request accepts before routing — and the rung outside it is handed that `Response` (RULEBOOK 37.14, 37.15). BREAKING: `Middleware<Ctx, Result>` written with a context type first no longer reads that way; annotate `next` instead, or use the forms above.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 44e9f1d: ABIDE.md names the one-word Middleware form for a shared rung (RULEBOOK 37.14) ([`0699004`](https://github.com/briancray/abide/commit/0699004266bf8174263d1820cee2e9946eeee8a3))
|
|
12
|
+
|
|
13
|
+
## 0.121.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 7d6571d: a pairing link written into an href survives the URL scheme check — abide-bridge: joins the safe set, and the render warning names the set it checked against (RULEBOOK 32.20, 44.40) ([`aaba479`](https://github.com/briancray/abide/commit/aaba479a86b318d2441e920b6f7cfd124cb6c75e))
|
|
18
|
+
|
|
3
19
|
## 0.121.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/docs/REGISTRY.md
CHANGED
|
@@ -218,7 +218,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
218
218
|
| `Value` | `unknown` | What the addressed `Reactive` holds. | 16.3, 16.30 |
|
|
219
219
|
| `Produced` | `unknown` | What it yields, per the unit rule. | 1.2, 16.3 |
|
|
220
220
|
| `Args` | `Record<string, JsonValue> \| undefined` | The arguments, in the body on a mutation and in the URL on a `GET` or `DELETE`. | 11.24, 16.18, 19.15 |
|
|
221
|
-
| `Middleware` |
|
|
221
|
+
| `Middleware` | `Middleware<Answer = never, Extra extends object = object> = <Given extends { request: Request } & Extra, Result>(next: (ctx?: Given) => Promise<Result>, ctx: Given) => Result \| Answer \| Failed<string, unknown> \| Promise<Result \| Answer \| Failed<string, unknown>>` | One rung, written once for every lane whose context carries `Extra`. | 16.14, 16.15, 16.16, 16.21, 37.14, 37.15 |
|
|
222
222
|
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: Reactive<Value, Value, Failures, Produced> \| Keyed<Value, Args, Value, Failures, Produced>, options?: RpcOptions<Args, RungFailures>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | Declares a read any surface may call. | 16.1, 16.2, 16.5, 16.6, 16.30, 16.31, 16.32, 16.68, 16.76, 16.34, 16.35, 16.36, 16.37, 16.39, 16.44, 16.45, 16.46, 16.47, 16.75, 16.50, 16.51, 16.52, 16.64, 18.1, 18.3, 18.6, 18.8, 18.10, 18.12, 18.21, 18.24, 18.27 |
|
|
223
223
|
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: (args?: Args) => Value, options?: RpcOptions<Args, RungFailures> & ReactiveOptions<Value, Value, Failures, Args>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | The plain-function overload, whose `ReactiveOptions` (`ttl`, `global`, `debounce` and the rest) build the value it becomes beside its address. | 16.1, 16.6, 16.7 |
|
|
224
224
|
| `GET` | `<Value, Failures, RungFailures>(handler: Value \| PromiseLike<Value>, options?: RpcOptions<undefined, RungFailures> & ReactiveOptions<Value, Value, Failures>) => Rpc<Value, undefined, Failures \| RungFailures>` | The value overload, for what the handler owns outright, whose `ReactiveOptions` build the value it becomes. | 16.1, 16.6, 16.7 |
|
|
@@ -240,7 +240,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
240
240
|
| Name | Signature | Meaning | Rules |
|
|
241
241
|
| --- | --- | --- | --- |
|
|
242
242
|
| `description` | `string` | The human description carried onto every generated surface. | 16.28 |
|
|
243
|
-
| `middleware` | `
|
|
243
|
+
| `middleware` | `((next: (ctx?: { request: Request; args: () => Args }) => Promise<unknown>, ctx: { request: Request; args: () => Args }) => unknown)[]` | The rungs run per call. | 16.11, 16.12, 16.16, 16.17, 16.18, 16.19, 16.20 |
|
|
244
244
|
| `timeout` | `number` | How long the call may go without progress. | 16.22, 16.58, 16.59, 16.60, 16.61, 16.77 |
|
|
245
245
|
| `clients` | `Clients` | Which surfaces carry this handler. | 16.37, 20.3 |
|
|
246
246
|
| `crossOrigin` | `boolean \| string[]` | Whether other origins may call it, or follow it over the mux. | 16.24, 18.2, 21.19 |
|
|
@@ -351,7 +351,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
351
351
|
| --- | --- | --- | --- |
|
|
352
352
|
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | 44.5, 44.13, 44.17, 44.18, 44.19, 44.24, 44.25, 44.27, 44.30, 44.33 |
|
|
353
353
|
| `ClaudeCodeConfig` | `{ model?: string; fallbackModel?: string; systemPrompt?: string; appendSystemPrompt?: string; effort?: 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'; maxBudgetUsd?: number; betas?: string[]; disallowedTools?: string[]; tools?: string[] }` | The Claude Code options a site may set for a turn, and the built-in tools it asks the visitor for. | 44.16, 44.29 |
|
|
354
|
-
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | 44.14, 44.32, 44.33, 44.40, 44.41, 44.42, 44.43 |
|
|
354
|
+
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | 44.14, 44.32, 44.33, 44.40, 44.41, 44.42, 44.43, 44.44 |
|
|
355
355
|
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | 44.20, 44.22, 44.32, 44.33, 44.34, 44.40 |
|
|
356
356
|
| `BridgeStatus` | `{ paired: boolean; command: string \| undefined; link: string \| undefined }` | Whether a bridge is paired, and the command and the link that pair one where none is. | 44.20, 44.40 |
|
|
357
357
|
| `/__abide/bridge` | `GET`, `POST` | Where a bridge pairs, and is relayed its turns. | 36.7, 44.14, 44.15, 44.19, 44.23, 44.26, 16.57, 16.78 |
|
|
@@ -499,7 +499,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
499
499
|
|
|
500
500
|
| Name | Signature | Meaning | Rules |
|
|
501
501
|
| --- | --- | --- | --- |
|
|
502
|
-
| `csp` | `(sources?: Record<string, string[]>) =>
|
|
502
|
+
| `csp` | `(sources?: Record<string, string[]>) => (next: (ctx?: { request: Request }) => Promise<Response>, ctx: { request: Request }) => Promise<Response>` | The rung that sets `content-security-policy`. | 29.1, 29.2, 29.5, 29.6, 29.7, 29.8 |
|
|
503
503
|
| `csp.nonce` | `() => string` | This response's nonce, for the app's own inline script. | 29.3, 29.4, 29.9, 29.11 |
|
|
504
504
|
|
|
505
505
|
## `url`
|
|
@@ -702,7 +702,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
702
702
|
|
|
703
703
|
| Name | Signature | Meaning | Rules |
|
|
704
704
|
| --- | --- | --- | --- |
|
|
705
|
-
| `middleware` | `(...rungs:
|
|
705
|
+
| `middleware` | `(...rungs: ((next: (ctx?: { request: Request }) => Promise<Response>, ctx: { request: Request }) => Response \| Failed<string, unknown> \| Promise<Response \| Failed<string, unknown>>)[]) => () => void` | The per-request lane at the calling file's route. | 18.26, 37.2, 37.3, 37.13, 37.14, 37.15 |
|
|
706
706
|
| `onStart` | `(fn: (start: () => Promise<void>) => void \| Promise<void>) => () => void` | Wraps the boot. | 37.4, 37.6, 37.13 |
|
|
707
707
|
| `onStop` | `(fn: (stop: () => Promise<void>) => void \| Promise<void>) => () => void` | Mirrors it for teardown. | 37.4, 37.6, 37.13 |
|
|
708
708
|
| `onError` | `(fn: (error: unknown) => unknown) => () => void` | Runs on an unexpected error at the calling file's route. | 37.5, 37.8, 37.13 |
|
package/docs/RULEBOOK.md
CHANGED
|
@@ -1959,8 +1959,8 @@ body.
|
|
|
1959
1959
|
MUST NOT be built as a string.
|
|
1960
1960
|
|
|
1961
1961
|
32.20 An interpolation in a URL-typed attribute MUST be scheme-checked, and a scheme outside
|
|
1962
|
-
`http:`, `https:`, `mailto:`, `tel:`, `blob:`, `data:` or a relative
|
|
1963
|
-
`abide:render`, naming the attribute and not the value.
|
|
1962
|
+
`http:`, `https:`, `mailto:`, `tel:`, `blob:`, `data:`, `abide-bridge:` (44.40) or a relative
|
|
1963
|
+
reference MUST be dropped and warned on `abide:render`, naming the attribute and not the value.
|
|
1964
1964
|
|
|
1965
1965
|
32.21 A control block MUST bind each reactive read and probe call in its subject once for the body.
|
|
1966
1966
|
|
|
@@ -2454,6 +2454,13 @@ through `App.stop`, and the process MUST then exit with 0.
|
|
|
2454
2454
|
37.13 A lifecycle hook called in a module of the app's own that is neither a `.abide` nor a handler
|
|
2455
2455
|
file MUST be a build error naming the module and the hook.
|
|
2456
2456
|
|
|
2457
|
+
37.14 A `Failed` a `middleware` rung returns MUST be answered in the lane the rung is filed over: by
|
|
2458
|
+
the nearest `error.abide` per 35.2 over a page, as 15.21 states over a handler, and as 15.22 paints
|
|
2459
|
+
a 405 for a rung filed at the root route.
|
|
2460
|
+
|
|
2461
|
+
37.15 A `middleware` rung outside one that returned a `Failed` MUST be handed 37.14's answer from
|
|
2462
|
+
`next`, and MUST NOT be handed the `Failed`.
|
|
2463
|
+
|
|
2457
2464
|
# 38. Commands
|
|
2458
2465
|
|
|
2459
2466
|
## The commands
|
|
@@ -3409,12 +3416,17 @@ a link in the `abide-bridge` scheme naming the same site, code, bridge release a
|
|
|
3409
3416
|
whose release is a version alone, and MUST run the release it names.
|
|
3410
3417
|
|
|
3411
3418
|
44.42 A bridge opening an `abide-bridge` link MUST ask the visitor before it pairs, every time, naming
|
|
3412
|
-
the site, the built-in tools and the bridge release the link asks for
|
|
3419
|
+
the site, the built-in tools and the bridge release the link asks for, and the options and the names
|
|
3420
|
+
of the variables `abide-bridge install` kept (44.44).
|
|
3413
3421
|
|
|
3414
3422
|
44.43 `abide-bridge install` MUST write the app that opens links under the visitor's own Application
|
|
3415
3423
|
Support and register it there, and `abide-bridge uninstall` MUST unregister it and remove all
|
|
3416
3424
|
install wrote.
|
|
3417
3425
|
|
|
3426
|
+
44.44 `abide-bridge install` MUST keep the Claude Code options after its `--` and each `--env
|
|
3427
|
+
NAME=VALUE` it is given, and a bridge opening an `abide-bridge` link MUST run Claude Code with those
|
|
3428
|
+
options ahead of the link's built-in tools and with those variables set, and a link MUST NOT set either.
|
|
3429
|
+
|
|
3418
3430
|
# 45. Offline
|
|
3419
3431
|
|
|
3420
3432
|
45.1 Where `ABIDE_OFFLINE` is on, a page `abide build` built MUST register the worker
|
package/docs/abide.md
CHANGED
|
@@ -4227,24 +4227,28 @@ async function exportAll() {
|
|
|
4227
4227
|
```
|
|
4228
4228
|
|
|
4229
4229
|
```ts src/account/exportContacts.handler.ts
|
|
4230
|
-
import { principal, refuse } from '@abide/abide'
|
|
4231
4230
|
import { POST } from '@abide/abide/server'
|
|
4232
4231
|
|
|
4233
4232
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
4233
|
+
import { adminsOnly } from './lib/adminsOnly.ts'
|
|
4234
4234
|
|
|
4235
4235
|
export default POST(async () => CONTACTS, {
|
|
4236
4236
|
description: 'Every contact, for an admin to export.',
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
async (next, ctx) => {
|
|
4240
|
-
const { role } = await principal.resolved
|
|
4241
|
-
if (role !== 'admin') return refuse(403, 'Admins only.')
|
|
4242
|
-
return next(ctx)
|
|
4243
|
-
},
|
|
4244
|
-
],
|
|
4237
|
+
// Runs before the handler, on every call, in process or not.
|
|
4238
|
+
middleware: [adminsOnly],
|
|
4245
4239
|
})
|
|
4246
4240
|
```
|
|
4247
4241
|
|
|
4242
|
+
```ts src/account/lib/adminsOnly.ts
|
|
4243
|
+
import { type Middleware, principal, refuse } from '@abide/abide'
|
|
4244
|
+
|
|
4245
|
+
// One rung for a handler's `middleware` and a layout's alike.
|
|
4246
|
+
export const adminsOnly: Middleware = async (next) => {
|
|
4247
|
+
const { role } = await principal.resolved
|
|
4248
|
+
return role === 'admin' ? next() : refuse(403, 'Admins only.')
|
|
4249
|
+
}
|
|
4250
|
+
```
|
|
4251
|
+
|
|
4248
4252
|
```ts src/account/lib/ExportContacts.test.ts
|
|
4249
4253
|
import { expect, test } from 'bun:test'
|
|
4250
4254
|
|
|
@@ -4278,22 +4282,18 @@ test('sales and a signed-out caller are refused 403', async () => {
|
|
|
4278
4282
|
})
|
|
4279
4283
|
```
|
|
4280
4284
|
|
|
4281
|
-
A rung is a `Middleware`: it is handed `next` and the call's context, and either returns `next(
|
|
4285
|
+
A rung is a `Middleware`: it is handed `next` and the call's context, and either returns `next()` or answers in the handler's place. Returning `refuse(403, …)` refuses, and neither the handler nor any rung after it runs. A throw escapes every rung rather than unwinding through them.
|
|
4282
4286
|
|
|
4283
4287
|
The rung sits on the handler, so it runs on every call: from a browser, from another handler, and from a page rendering on the server. It reads arguments through `ctx.args()`, which parses the body on its first call. So a rung that never asks refuses a signed-out caller before their body is parsed or checked.
|
|
4284
4288
|
|
|
4285
|
-
To guard a whole section, call `middleware(…)` in its `layout.abide`, such as `src/account/admin/layout.abide`. That rung runs over every page and handler below it, and it has the request and nothing else:
|
|
4289
|
+
To guard a whole section, call `middleware(…)` in its `layout.abide`, such as `src/account/admin/layout.abide`. That rung runs over every page and handler below it, and it has the request and nothing else. The same `adminsOnly` fits there, and its refusal is painted by the section's `error.abide` over a page and answered as Problem Details over a handler:
|
|
4286
4290
|
|
|
4287
4291
|
```abide abide
|
|
4288
4292
|
<script server>
|
|
4289
|
-
import { principal } from '@abide/abide'
|
|
4290
4293
|
import { middleware } from '@abide/abide/server'
|
|
4294
|
+
import { adminsOnly } from '#src/account/lib/adminsOnly.ts'
|
|
4291
4295
|
|
|
4292
|
-
middleware(
|
|
4293
|
-
const { role } = await principal.resolved
|
|
4294
|
-
if (role !== 'admin') return new Response('Admins only.', { status: 403 })
|
|
4295
|
-
return next()
|
|
4296
|
-
})
|
|
4296
|
+
middleware(adminsOnly)
|
|
4297
4297
|
</script>
|
|
4298
4298
|
|
|
4299
4299
|
<slot/>
|
|
@@ -7776,7 +7776,7 @@ Reactive primitives: what is written in place rather than imported.
|
|
|
7776
7776
|
|
|
7777
7777
|
| Name | Signature | Meaning | Rules |
|
|
7778
7778
|
| --- | --- | --- | --- |
|
|
7779
|
-
| `Middleware` |
|
|
7779
|
+
| `Middleware` | `Middleware<Answer = never, Extra extends object = object> = <Given extends { request: Request } & Extra, Result>(next: (ctx?: Given) => Promise<Result>, ctx: Given) => Result \| Answer \| Failed<string, unknown> \| Promise<Result \| Answer \| Failed<string, unknown>>` | One rung, written once for every lane whose context carries `Extra`. | [16.14](/rulebook#16.14), [16.15](/rulebook#16.15), [16.16](/rulebook#16.16), [16.21](/rulebook#16.21), [37.14](/rulebook#37.14), [37.15](/rulebook#37.15) |
|
|
7780
7780
|
|
|
7781
7781
|
## Taught in
|
|
7782
7782
|
|
|
@@ -7851,7 +7851,7 @@ Reactive primitives: what is written in place rather than imported.
|
|
|
7851
7851
|
| Name | Signature | Meaning | Rules |
|
|
7852
7852
|
| --- | --- | --- | --- |
|
|
7853
7853
|
| `description` | `string` | The human description carried onto every generated surface. | [16.28](/rulebook#16.28) |
|
|
7854
|
-
| `middleware` | `
|
|
7854
|
+
| `middleware` | `((next: (ctx?: { request: Request; args: () => Args }) => Promise<unknown>, ctx: { request: Request; args: () => Args }) => unknown)[]` | The rungs run per call. | [16.11](/rulebook#16.11), [16.12](/rulebook#16.12), [16.16](/rulebook#16.16), [16.17](/rulebook#16.17), [16.18](/rulebook#16.18), [16.19](/rulebook#16.19), [16.20](/rulebook#16.20) |
|
|
7855
7855
|
| `timeout` | `number` | How long the call may go without progress. | [16.22](/rulebook#16.22), [16.58](/rulebook#16.58), [16.59](/rulebook#16.59), [16.60](/rulebook#16.60), [16.61](/rulebook#16.61), [16.77](/rulebook#16.77) |
|
|
7856
7856
|
| `clients` | `Clients` | Which surfaces carry this handler. | [16.37](/rulebook#16.37), [20.3](/rulebook#20.3) |
|
|
7857
7857
|
| `crossOrigin` | `boolean \| string[]` | Whether other origins may call it, or follow it over the mux. | [16.24](/rulebook#16.24), [18.2](/rulebook#18.2), [21.19](/rulebook#21.19) |
|
|
@@ -8419,7 +8419,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8419
8419
|
| Name | Signature | Meaning | Rules |
|
|
8420
8420
|
| --- | --- | --- | --- |
|
|
8421
8421
|
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | [44.5](/rulebook#44.5), [44.13](/rulebook#44.13), [44.17](/rulebook#44.17), [44.18](/rulebook#44.18), [44.19](/rulebook#44.19), [44.24](/rulebook#44.24), [44.25](/rulebook#44.25), [44.27](/rulebook#44.27), [44.30](/rulebook#44.30), [44.33](/rulebook#44.33) |
|
|
8422
|
-
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | [44.14](/rulebook#44.14), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.40](/rulebook#44.40), [44.41](/rulebook#44.41), [44.42](/rulebook#44.42), [44.43](/rulebook#44.43) |
|
|
8422
|
+
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | [44.14](/rulebook#44.14), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.40](/rulebook#44.40), [44.41](/rulebook#44.41), [44.42](/rulebook#44.42), [44.43](/rulebook#44.43), [44.44](/rulebook#44.44) |
|
|
8423
8423
|
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | [44.20](/rulebook#44.20), [44.22](/rulebook#44.22), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.34](/rulebook#44.34), [44.40](/rulebook#44.40) |
|
|
8424
8424
|
| `/__abide/bridge` | `GET`, `POST` | Where a bridge pairs, and is relayed its turns. | [36.7](/rulebook#36.7), [44.14](/rulebook#44.14), [44.15](/rulebook#44.15), [44.19](/rulebook#44.19), [44.23](/rulebook#44.23), [44.26](/rulebook#44.26), [16.57](/rulebook#16.57), [16.78](/rulebook#16.78) |
|
|
8425
8425
|
|
|
@@ -8977,7 +8977,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8977
8977
|
|
|
8978
8978
|
| Name | Signature | Meaning | Rules |
|
|
8979
8979
|
| --- | --- | --- | --- |
|
|
8980
|
-
| `csp` | `(sources?: Record<string, string[]>) =>
|
|
8980
|
+
| `csp` | `(sources?: Record<string, string[]>) => (next: (ctx?: { request: Request }) => Promise<Response>, ctx: { request: Request }) => Promise<Response>` | The rung that sets `content-security-policy`. | [29.1](/rulebook#29.1), [29.2](/rulebook#29.2), [29.5](/rulebook#29.5), [29.6](/rulebook#29.6), [29.7](/rulebook#29.7), [29.8](/rulebook#29.8) |
|
|
8981
8981
|
| `csp.nonce` | `() => string` | This response's nonce, for the app's own inline script. | [29.3](/rulebook#29.3), [29.4](/rulebook#29.4), [29.9](/rulebook#29.9), [29.11](/rulebook#29.11) |
|
|
8982
8982
|
|
|
8983
8983
|
## Taught in
|
|
@@ -9511,7 +9511,7 @@ Configuration: what is written in place rather than imported.
|
|
|
9511
9511
|
|
|
9512
9512
|
| Name | Signature | Meaning | Rules |
|
|
9513
9513
|
| --- | --- | --- | --- |
|
|
9514
|
-
| `middleware` | `(...rungs:
|
|
9514
|
+
| `middleware` | `(...rungs: ((next: (ctx?: { request: Request }) => Promise<Response>, ctx: { request: Request }) => Response \| Failed<string, unknown> \| Promise<Response \| Failed<string, unknown>>)[]) => () => void` | The per-request lane at the calling file's route. | [18.26](/rulebook#18.26), [37.2](/rulebook#37.2), [37.3](/rulebook#37.3), [37.13](/rulebook#37.13), [37.14](/rulebook#37.14), [37.15](/rulebook#37.15) |
|
|
9515
9515
|
|
|
9516
9516
|
## Taught in
|
|
9517
9517
|
|
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// The `abide-bridge` release a pairing command names, so a visitor's `bunx` runs the relay this
|
|
2
2
|
// server speaks. `packages/abide/tests/claudeCode.test.ts` holds it to the bridge package's own.
|
|
3
|
-
export const BRIDGE_VERSION = '0.
|
|
3
|
+
export const BRIDGE_VERSION = '0.5.0'
|
|
@@ -10,6 +10,7 @@ import { coversRoute, parseRoutePattern } from '#shared/routePattern.ts'
|
|
|
10
10
|
|
|
11
11
|
import type { AppSources } from './answerRequest.ts'
|
|
12
12
|
import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
|
|
13
|
+
import { prefers } from './prefers.ts'
|
|
13
14
|
import { boundPage, renderDocument } from './render.ts'
|
|
14
15
|
import { beginRender, currentRequestScope } from './requestScope.ts'
|
|
15
16
|
|
|
@@ -100,3 +101,18 @@ export async function answerFailure(
|
|
|
100
101
|
headers.set('content-type', 'text/plain; charset=utf-8')
|
|
101
102
|
return new Response(failure.message, { status, headers })
|
|
102
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A failure at an address whose lane only the request can say: a page's, painted, where it prefers
|
|
107
|
+
* a document, and a handler's otherwise. 15.22.
|
|
108
|
+
*/
|
|
109
|
+
export function laneFailure(
|
|
110
|
+
app: ErrorPages,
|
|
111
|
+
pathname: string,
|
|
112
|
+
failure: Failed<string, unknown>,
|
|
113
|
+
request: Request,
|
|
114
|
+
): Response | Promise<Response> {
|
|
115
|
+
return prefers(request, 'text/html', 'application/json')
|
|
116
|
+
? answerFailure(app, pathname, failure)
|
|
117
|
+
: answered(failure)
|
|
118
|
+
}
|
|
@@ -22,7 +22,7 @@ import { DEBUG_META } from '#shared/DEBUG_META.ts'
|
|
|
22
22
|
import { decodedOrKept } from '#shared/decodedOrKept.ts'
|
|
23
23
|
import { ERROR_META } from '#shared/ERROR_META.ts'
|
|
24
24
|
import { escapeAttribute } from '#shared/escapeText.ts'
|
|
25
|
-
import {
|
|
25
|
+
import { isFailed } from '#shared/Failed.ts'
|
|
26
26
|
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
27
27
|
import { health } from '#shared/health.ts'
|
|
28
28
|
import { KEEP_HEADER } from '#shared/KEEP_HEADER.ts'
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
import type { Method, Rpc } from '#shared/rpc.ts'
|
|
46
46
|
import { rpcRequest } from '#shared/rpcRequest.ts'
|
|
47
47
|
|
|
48
|
-
import {
|
|
48
|
+
import { answerFailure, laneFailure } from './answerFailure.ts'
|
|
49
49
|
import {
|
|
50
50
|
answerRpc,
|
|
51
51
|
type Mounted,
|
|
@@ -64,12 +64,11 @@ import { settingOn } from './config.ts'
|
|
|
64
64
|
import { noteCallInRender } from './deferredBuilds.ts'
|
|
65
65
|
import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
|
|
66
66
|
import { answerMcp, openApi } from './generatedSurfaces.ts'
|
|
67
|
-
import { type
|
|
67
|
+
import { failureOf, type LaneRung, rungsOver } from './hooks.ts'
|
|
68
68
|
import { chunksOf, type Manifest } from './manifestOf.ts'
|
|
69
69
|
import { muxAdmission } from './muxUpgrade.ts'
|
|
70
70
|
import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
|
|
71
71
|
import { plainAnswer } from './plainAnswer.ts'
|
|
72
|
-
import { prefers } from './prefers.ts'
|
|
73
72
|
import { PRINCIPAL_MOUNT } from './principal.ts'
|
|
74
73
|
import {
|
|
75
74
|
boundPage,
|
|
@@ -625,7 +624,18 @@ export async function appSources(parts: SourceParts): Promise<AppSources> {
|
|
|
625
624
|
if (row.page !== undefined)
|
|
626
625
|
row.answers.set(
|
|
627
626
|
row.page,
|
|
628
|
-
answerInside(
|
|
627
|
+
answerInside(
|
|
628
|
+
sources,
|
|
629
|
+
row.page,
|
|
630
|
+
// 37.14 — a page's lane paints a refusal as its error page does (35.2).
|
|
631
|
+
rungsOver(row.pattern, (failure, request) =>
|
|
632
|
+
answerFailure(
|
|
633
|
+
sources,
|
|
634
|
+
new URL(request.url).pathname,
|
|
635
|
+
failure,
|
|
636
|
+
),
|
|
637
|
+
),
|
|
638
|
+
),
|
|
629
639
|
)
|
|
630
640
|
for (const handler of row.handlers.values())
|
|
631
641
|
row.answers.set(
|
|
@@ -640,7 +650,7 @@ export async function appSources(parts: SourceParts): Promise<AppSources> {
|
|
|
640
650
|
function answerInside(
|
|
641
651
|
app: AppSources,
|
|
642
652
|
found: Ranked | Mounted,
|
|
643
|
-
rungs:
|
|
653
|
+
rungs: LaneRung[],
|
|
644
654
|
): (ctx: { request: Request }) => Promise<Response> {
|
|
645
655
|
return compose(rungs, ({ request }) => answerMatched(app, request, found))
|
|
646
656
|
}
|
|
@@ -774,21 +784,6 @@ async function methodRefused(
|
|
|
774
784
|
return answer
|
|
775
785
|
}
|
|
776
786
|
|
|
777
|
-
/**
|
|
778
|
-
* A failure at an address whose lane only the request can say: a page's, painted, where it prefers
|
|
779
|
-
* a document, and a handler's otherwise. 15.22.
|
|
780
|
-
*/
|
|
781
|
-
function laneFailure(
|
|
782
|
-
app: AppSources,
|
|
783
|
-
pathname: string,
|
|
784
|
-
failure: Failed<string, unknown>,
|
|
785
|
-
request: Request,
|
|
786
|
-
): Response | Promise<Response> {
|
|
787
|
-
return prefers(request, 'text/html', 'application/json')
|
|
788
|
-
? answerFailure(app, pathname, failure)
|
|
789
|
-
: answered(failure)
|
|
790
|
-
}
|
|
791
|
-
|
|
792
787
|
/**
|
|
793
788
|
* A public file's answer, conditional and range-aware off the etag read at boot.
|
|
794
789
|
*
|
package/src/server/answerRpc.ts
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
crossingOrigin,
|
|
45
45
|
crossingRefusal,
|
|
46
46
|
} from './crossingOrigin.ts'
|
|
47
|
-
import { type
|
|
47
|
+
import { failureOf, type LaneRung, rungsOver } from './hooks.ts'
|
|
48
48
|
import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
|
|
49
49
|
import { prefers } from './prefers.ts'
|
|
50
50
|
import {
|
|
@@ -120,7 +120,7 @@ export type Mounted = Mount & {
|
|
|
120
120
|
* The rungs filed over `scope`, read once at mount (16.14) — what an http call and a socket
|
|
121
121
|
* subscription to it both pass through (37.2, 18.26).
|
|
122
122
|
*/
|
|
123
|
-
rungs:
|
|
123
|
+
rungs: LaneRung[]
|
|
124
124
|
handler: Rpc<unknown, unknown> & Gated<unknown> & HeldToAnnotation
|
|
125
125
|
/**
|
|
126
126
|
* The args' JSON Schema, probed once at mount.
|
|
@@ -203,7 +203,8 @@ export async function mountHandlers(
|
|
|
203
203
|
...mount,
|
|
204
204
|
// 37.1, 16.72 — the route its file's hooks are filed at, which the build read off
|
|
205
205
|
// where the file answers (23.19's mount for a package's), never its place on disk.
|
|
206
|
-
|
|
206
|
+
// 37.14 — a handler's lane answers a refusal as Problem Details (15.1).
|
|
207
|
+
rungs: rungsOver(mount.scope, answered),
|
|
207
208
|
handler,
|
|
208
209
|
argsDocument: declared ?? (mount.args as Document | undefined),
|
|
209
210
|
})
|
package/src/server/claudeCode.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// directory, settings — is not an option here at all, and its built-in `tools` are a request the visitor
|
|
9
9
|
// grants or does not: the bridge decides (44.15, 44.16, 44.29).
|
|
10
10
|
|
|
11
|
+
import { BRIDGE_SCHEME } from '#shared/BRIDGE_SCHEME.ts'
|
|
11
12
|
import { mounted } from '#shared/mount.ts'
|
|
12
13
|
import { refuse } from '#shared/refuse.ts'
|
|
13
14
|
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
forwardOf,
|
|
20
21
|
type Provider,
|
|
21
22
|
} from './assistant.ts'
|
|
22
|
-
import { BRIDGE_SCHEME } from './BRIDGE_SCHEME.ts'
|
|
23
23
|
import { BRIDGE_VERSION } from './BRIDGE_VERSION.ts'
|
|
24
24
|
import {
|
|
25
25
|
followSignIn,
|
package/src/server/createApp.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { PRINCIPAL_HEADER } from '#shared/PRINCIPAL_HEADER.ts'
|
|
|
17
17
|
import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
|
|
18
18
|
|
|
19
19
|
import { servedTable } from './abideModules.ts'
|
|
20
|
+
import { laneFailure } from './answerFailure.ts'
|
|
20
21
|
import { answerRequest, type AppSources } from './answerRequest.ts'
|
|
21
22
|
import type { Mount } from './answerRpc.ts'
|
|
22
23
|
import { Mux, type MuxConnection } from './answerSocket.ts'
|
|
@@ -278,7 +279,10 @@ export async function bootEntered(
|
|
|
278
279
|
// 16.14, 37.3 — THE APP LANE'S ONION, composed once over the whole request path: the root
|
|
279
280
|
// route's rungs, the ones below it running after routing (37.2, `answerRequest`). 21.20 — the
|
|
280
281
|
// center takes its own time back out of what `fetch` counts for the whole onion.
|
|
281
|
-
|
|
282
|
+
// 37.14 — before routing, the lane is the request's to say (15.22).
|
|
283
|
+
const rungs = rootRungs((failure, request) =>
|
|
284
|
+
laneFailure(booted, new URL(request.url).pathname, failure, request),
|
|
285
|
+
)
|
|
282
286
|
const answer = compose(rungs, async ({ request }) => {
|
|
283
287
|
const began = performance.now()
|
|
284
288
|
const answered = await answerRequest(booted, request)
|
package/src/server/csp.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// THE NONCE IS MINTED BY WHICHEVER ASKS FIRST — a page reading `csp.nonce` while it renders, or this
|
|
6
6
|
// rung writing a document's header — so the two agree, and an answer that is not a document mints none.
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type { Rung } from '#shared/Middleware.ts'
|
|
9
9
|
|
|
10
10
|
import { randomToken } from './randomToken.ts'
|
|
11
11
|
import { EARLY_CALL_HASH } from './render.ts'
|
|
@@ -64,7 +64,7 @@ function policy(
|
|
|
64
64
|
|
|
65
65
|
export function cspRung(
|
|
66
66
|
sources: Record<string, string[]> | undefined,
|
|
67
|
-
):
|
|
67
|
+
): Rung<{ request: Request }, Response> {
|
|
68
68
|
const written = policy(sources)
|
|
69
69
|
return async (next) => {
|
|
70
70
|
const answer = await next()
|
package/src/server/hooks.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { type Failed, isFailed } from '#shared/Failed.ts'
|
|
15
15
|
import { namedLogger } from '#shared/log.ts'
|
|
16
|
-
import type {
|
|
16
|
+
import type { Rung } from '#shared/Middleware.ts'
|
|
17
17
|
import { aborted, refuse } from '#shared/refuse.ts'
|
|
18
18
|
import {
|
|
19
19
|
coversRoute,
|
|
@@ -26,7 +26,25 @@ import { config } from './config.ts'
|
|
|
26
26
|
import type { LauncherOptions } from './launcherOptions.ts'
|
|
27
27
|
import { currentRequestScope } from './requestScope.ts'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
type AppContext = { request: Request }
|
|
30
|
+
|
|
31
|
+
/** A rung `middleware` files: what `next` answered passed on, or a refusal in its place (37.14). */
|
|
32
|
+
export type AppRung = (
|
|
33
|
+
next: (ctx?: AppContext) => Promise<Response>,
|
|
34
|
+
ctx: AppContext,
|
|
35
|
+
) =>
|
|
36
|
+
| Response
|
|
37
|
+
| Failed<string, unknown>
|
|
38
|
+
| Promise<Response | Failed<string, unknown>>
|
|
39
|
+
|
|
40
|
+
/** An app rung as its lane composes it, a refusal it returned already answered (37.14). */
|
|
41
|
+
export type LaneRung = Rung<AppContext, Response>
|
|
42
|
+
|
|
43
|
+
/** How a lane answers a refusal one of its rungs returned, in that lane's representation (37.14). */
|
|
44
|
+
export type RefusalAnswer = (
|
|
45
|
+
failure: Failed<string, unknown>,
|
|
46
|
+
request: Request,
|
|
47
|
+
) => Response | Promise<Response>
|
|
30
48
|
|
|
31
49
|
export type Env = Record<string, string | undefined>
|
|
32
50
|
export type ConfigDefaults = (env: Env) => unknown
|
|
@@ -106,11 +124,24 @@ export function inOrder<T>(list: readonly Filed<T>[]): T[] {
|
|
|
106
124
|
return items
|
|
107
125
|
}
|
|
108
126
|
|
|
127
|
+
/**
|
|
128
|
+
* `rung` with a refusal it returns answered by `answer`.
|
|
129
|
+
*
|
|
130
|
+
* 37.14, 37.15 — PER LINK, not at the onion's edge: a rung outside this one is handed the answer
|
|
131
|
+
* from `next`, so one that reads a `Response` (29.2's) reads one whichever rung inside it refused.
|
|
132
|
+
*/
|
|
133
|
+
function answering(rung: AppRung, answer: RefusalAnswer): LaneRung {
|
|
134
|
+
return async (next, ctx) => {
|
|
135
|
+
const result = await rung(next, ctx)
|
|
136
|
+
return isFailed(result) ? answer(result, ctx.request) : result
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
109
140
|
/** The rungs filed at the root route, which run before routing. 37.3. */
|
|
110
|
-
export function rootRungs():
|
|
111
|
-
const rungs:
|
|
141
|
+
export function rootRungs(answer: RefusalAnswer): LaneRung[] {
|
|
142
|
+
const rungs: LaneRung[] = []
|
|
112
143
|
for (const entry of HOOKS.rungs)
|
|
113
|
-
if (entry.route === ROOT) rungs.push(entry.item)
|
|
144
|
+
if (entry.route === ROOT) rungs.push(answering(entry.item, answer))
|
|
114
145
|
return rungs
|
|
115
146
|
}
|
|
116
147
|
|
|
@@ -120,12 +151,12 @@ export function rootRungs(): AppRung[] {
|
|
|
120
151
|
* 37.2 — every rung filed at `route` or at a route that is its front, the root's aside: those ran
|
|
121
152
|
* before routing (37.3).
|
|
122
153
|
*/
|
|
123
|
-
export function rungsOver(route: string):
|
|
154
|
+
export function rungsOver(route: string, answer: RefusalAnswer): LaneRung[] {
|
|
124
155
|
const segments = parseRoutePattern(route)
|
|
125
|
-
const rungs:
|
|
156
|
+
const rungs: LaneRung[] = []
|
|
126
157
|
for (const entry of HOOKS.rungs)
|
|
127
158
|
if (entry.route !== ROOT && coversRoute(entry.segments, segments))
|
|
128
|
-
rungs.push(entry.item)
|
|
159
|
+
rungs.push(answering(entry.item, answer))
|
|
129
160
|
return rungs
|
|
130
161
|
}
|
|
131
162
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
// 44.40 — the scheme a pairing link is written in, which the handler `abide-bridge install` registers
|
|
2
|
-
// opens. The bridge spells it out as `PAIRING_SCHEME`, and a test holds the two equal.
|
|
2
|
+
// opens. The bridge spells it out as `PAIRING_SCHEME`, and a test holds the two equal. A leaf in
|
|
3
|
+
// #shared because both seams read it: the server mints the link, and 32.20's `safeUrl` lets a page
|
|
4
|
+
// write it into an `href`.
|
|
3
5
|
export const BRIDGE_SCHEME = 'abide-bridge'
|
package/src/shared/Middleware.ts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
// ONE RUNG OF AN ONION, and the onion. The app lane's `middleware` (37.3) and a handler's rungs
|
|
2
2
|
// (16.11) are the same shape over a different `Ctx`, so they compose through the one function.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import type { Failed } from './Failed.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A rung written once for every lane whose context carries `Extra`: generic over the context it is
|
|
8
|
+
* handed and over what `next` answers, so it passes `next()`'s answer on, refuses in its place, or
|
|
9
|
+
* answers with an `Answer` of its own.
|
|
10
|
+
*/
|
|
11
|
+
export type Middleware<Answer = never, Extra extends object = object> = <
|
|
12
|
+
Given extends { request: Request } & Extra,
|
|
13
|
+
Result,
|
|
14
|
+
>(
|
|
15
|
+
next: (ctx?: Given) => Promise<Result>,
|
|
16
|
+
ctx: Given,
|
|
17
|
+
) =>
|
|
18
|
+
| Result
|
|
19
|
+
| Answer
|
|
20
|
+
| Failed<string, unknown>
|
|
21
|
+
| Promise<Result | Answer | Failed<string, unknown>>
|
|
22
|
+
|
|
23
|
+
/** A rung as one lane composes it, over that lane's context and what its center answers. */
|
|
24
|
+
export type Rung<Ctx, Result> = (
|
|
5
25
|
next: (ctx?: Ctx) => Promise<Result>,
|
|
6
26
|
ctx: Ctx,
|
|
7
27
|
) => Result | Promise<Result>
|
|
@@ -17,17 +37,17 @@ export type Middleware<Ctx, Result> = (
|
|
|
17
37
|
* `next()` with no argument hands the inner link the ctx this rung was given.
|
|
18
38
|
*/
|
|
19
39
|
export function compose<Ctx, Result>(
|
|
20
|
-
rungs: readonly
|
|
40
|
+
rungs: readonly Rung<Ctx, Result>[],
|
|
21
41
|
terminal: (ctx: Ctx) => Promise<Result>,
|
|
22
42
|
): (ctx: Ctx) => Promise<Result> {
|
|
23
43
|
let link = terminal
|
|
24
44
|
for (let at = rungs.length - 1; at >= 0; at -= 1)
|
|
25
|
-
link = linkOf(rungs[at] as
|
|
45
|
+
link = linkOf(rungs[at] as Rung<Ctx, Result>, link)
|
|
26
46
|
return link
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
function linkOf<Ctx, Result>(
|
|
30
|
-
rung:
|
|
50
|
+
rung: Rung<Ctx, Result>,
|
|
31
51
|
inner: (ctx: Ctx) => Promise<Result>,
|
|
32
52
|
): (ctx: Ctx) => Promise<Result> {
|
|
33
53
|
return async (ctx) => rung((given) => inner(given ?? ctx), ctx)
|
|
@@ -10,7 +10,7 @@ import { BOOLEAN_ATTRIBUTES } from './BOOLEAN_ATTRIBUTES.ts'
|
|
|
10
10
|
import { escapeAttribute } from './escapeText.ts'
|
|
11
11
|
import { RENDER_LOG } from './log.ts'
|
|
12
12
|
import { textOf } from './Raw.ts'
|
|
13
|
-
import { safeUrl } from './safeUrl.ts'
|
|
13
|
+
import { SAFE_SCHEMES, safeUrl } from './safeUrl.ts'
|
|
14
14
|
import { URL_ATTRIBUTES } from './URL_ATTRIBUTES.ts'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -31,7 +31,7 @@ export function attributeText(name: string, value: unknown): string | null {
|
|
|
31
31
|
const safe = safeUrl(text)
|
|
32
32
|
if (safe !== text)
|
|
33
33
|
RENDER_LOG.warning(
|
|
34
|
-
`abide: \`${name}\` was handed a URL whose scheme is not
|
|
34
|
+
`abide: \`${name}\` was handed a URL whose scheme is not one of ${[...SAFE_SCHEMES].join(' ')}, and is written empty. RULEBOOK 32.20.`,
|
|
35
35
|
)
|
|
36
36
|
return safe
|
|
37
37
|
}
|
package/src/shared/csp.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// mint one for, so `csp.nonce` answers the one its document was served with, which is what a
|
|
4
4
|
// binding that wrote it on the server hydrates to.
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { Rung as LaneRung } from './Middleware.ts'
|
|
7
7
|
|
|
8
|
-
type Rung =
|
|
8
|
+
type Rung = LaneRung<{ request: Request }, Response>
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The server's content-security-policy half, as slots `#server/installServer.ts` fills from
|
package/src/shared/rpc.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { GLOBAL_GETS } from './GLOBAL_GETS.ts'
|
|
|
19
19
|
import { HandlerRoute } from './HandlerRoute.ts'
|
|
20
20
|
import { IN_PROCESS_ORIGIN } from './IN_PROCESS_ORIGIN.ts'
|
|
21
21
|
import type { Issues } from './Issues.ts'
|
|
22
|
-
import { compose, type
|
|
22
|
+
import { compose, type Rung } from './Middleware.ts'
|
|
23
23
|
import { outbox, type QueuedCall, sendingFor } from './outbox.ts'
|
|
24
24
|
import { principalRotated } from './principalRotated.ts'
|
|
25
25
|
import {
|
|
@@ -192,7 +192,7 @@ const SENT: unique symbol = Symbol('abide.sent')
|
|
|
192
192
|
|
|
193
193
|
export type RpcOptions<Args = unknown> = {
|
|
194
194
|
description?: string
|
|
195
|
-
middleware?:
|
|
195
|
+
middleware?: Rung<RpcContext<Args>, unknown>[]
|
|
196
196
|
timeout?: number
|
|
197
197
|
clients?: Clients
|
|
198
198
|
crossOrigin?: boolean | string[]
|
package/src/shared/safeUrl.ts
CHANGED
|
@@ -6,15 +6,22 @@
|
|
|
6
6
|
//
|
|
7
7
|
// The warning on `abide:render` the clause also asks for is `attributeText`'s, which knows the
|
|
8
8
|
// attribute's name.
|
|
9
|
+
//
|
|
10
|
+
// `abide-bridge:` IS IN THE SET because abide mints it: a pairing's `link` (44.40) is written into an
|
|
11
|
+
// `href` or the visitor cannot open it, and the docs' own pairing page does exactly that. It is the
|
|
12
|
+
// one scheme, not custom schemes generally — what it opens asks the visitor before it does anything,
|
|
13
|
+
// every time, naming the site it pairs with (44.42), so a link from anywhere can do no more than ask.
|
|
14
|
+
import { BRIDGE_SCHEME } from './BRIDGE_SCHEME.ts'
|
|
9
15
|
import { textOf } from './Raw.ts'
|
|
10
16
|
|
|
11
|
-
const SAFE_SCHEMES = new Set([
|
|
17
|
+
export const SAFE_SCHEMES = new Set([
|
|
12
18
|
'http:',
|
|
13
19
|
'https:',
|
|
14
20
|
'mailto:',
|
|
15
21
|
'tel:',
|
|
16
22
|
'blob:',
|
|
17
23
|
'data:',
|
|
24
|
+
`${BRIDGE_SCHEME}:`,
|
|
18
25
|
])
|
|
19
26
|
|
|
20
27
|
/** A URL attribute's text, or `''` where its scheme is outside the safe set. */
|