@braidlabs/gateway 0.3.0 → 0.5.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/README.md +25 -6
- package/package.json +1 -1
- package/src/appd.d.ts +26 -8
- package/src/appd.js +56 -9
- package/src/appd.js.map +1 -1
- package/src/breaker.d.ts +2 -0
- package/src/breaker.js +3 -0
- package/src/breaker.js.map +1 -1
- package/src/discovery.js +19 -17
- package/src/discovery.js.map +1 -1
- package/src/gateway.d.ts +84 -8
- package/src/gateway.js +178 -14
- package/src/gateway.js.map +1 -1
- package/src/index.d.ts +5 -5
- package/src/index.js +1 -1
- package/src/index.js.map +1 -1
- package/src/node.js +11 -1
- package/src/node.js.map +1 -1
- package/src/protocol.d.ts +11 -0
- package/src/protocol.js +11 -0
- package/src/protocol.js.map +1 -1
- package/src/registry.d.ts +25 -0
- package/src/registry.js +89 -26
- package/src/registry.js.map +1 -1
- package/src/rewriter/transforms.d.ts +16 -1
- package/src/rewriter/transforms.js +42 -2
- package/src/rewriter/transforms.js.map +1 -1
- package/src/single-flight.js +5 -16
- package/src/single-flight.js.map +1 -1
- package/src/telemetry.d.ts +16 -1
- package/src/telemetry.js.map +1 -1
package/README.md
CHANGED
|
@@ -45,8 +45,9 @@ to load, and the element to mount:
|
|
|
45
45
|
createGateway({ registry, discovery: { appd: true } });
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Serves the same registry
|
|
49
|
-
`/appd/v2/apps/{appId}`
|
|
48
|
+
Serves the same registry as an FDC3 2.2 App Directory: `/__braid/registry/appd/v2/apps` lists
|
|
49
|
+
`{ applications, message }`, and `/appd/v2/apps/{appId}` returns the bare `Application` record.
|
|
50
|
+
It is a **projection, not a second directory** — same manifests, same
|
|
50
51
|
`access.list` rules — so a caller can never see through AppD a resolver that discovery would have
|
|
51
52
|
hidden. `findIntent` becomes a registry query, and a user only sees resolvers they may use.
|
|
52
53
|
|
|
@@ -69,11 +70,17 @@ Two mapping decisions worth knowing:
|
|
|
69
70
|
A fragment is *mounted into a page*, not opened as a window, so a Braid-aware agent reads
|
|
70
71
|
`hostManifests.braid` and mounts a `<fragment-slot>`; one that does not falls back to the URL.
|
|
71
72
|
|
|
72
|
-
An app the caller may not list 404s
|
|
73
|
-
let an unauthorized caller enumerate the registry
|
|
73
|
+
An app the caller may not list 404s (with AppD's `{ code, message }` error body) exactly as an
|
|
74
|
+
unregistered one does — distinguishing them would let an unauthorized caller enumerate the registry
|
|
75
|
+
one id at a time.
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
Records are validated in the test suite against FDC3 2.2's own App Directory schema. Relative icon
|
|
78
|
+
`src` values are resolved against the origin, since AppD requires absolute URLs, and a link that is
|
|
79
|
+
not http(s) is dropped rather than passed on. Optional members
|
|
80
|
+
Braid has no data for (`lang`, `screenshots`, `localizedVersions`, `interop.appChannels`) are not
|
|
81
|
+
produced. To serve the same directory from the governed registry instead of a gateway, see
|
|
82
|
+
`createRegistryApi({ appd })` in `@braidlabs/registry`; to give fragments `window.fdc3`, see
|
|
83
|
+
`@braidlabs/fdc3`.
|
|
77
84
|
|
|
78
85
|
A relative `entry` is re-rooted into the fragment's namespace, so the module and its imports are
|
|
79
86
|
fetched through the gateway rather than from the host's root. Such a fragment serves no document,
|
|
@@ -166,6 +173,13 @@ as a declarative shadow root, so the browser parses it into exactly the shape th
|
|
|
166
173
|
would have built; the slot then adopts it instead of fetching. Shells with no matching slot get
|
|
167
174
|
one created before `</body>`.
|
|
168
175
|
|
|
176
|
+
A shell can instead mark one `<fragment-slot outlet>`. It takes the page's routed (bound) fragment,
|
|
177
|
+
the first in registration order that no earlier named slot claimed, and is named after it. A shell
|
|
178
|
+
that composes many routes then needs one slot rather than one per fragment id. Unbound widgets
|
|
179
|
+
never fill an outlet. A named slot after the outlet is too late for a fragment the outlet already
|
|
180
|
+
took, so the gateway unnames it (`data-braid-in-outlet`) and warns, rather than let the client boot
|
|
181
|
+
a second copy. The outlet is filled on full page loads only.
|
|
182
|
+
|
|
169
183
|
Fragment HTML is transformed on the way through: the doctype is stripped, `<html>/<head>/<body>`
|
|
170
184
|
become `braid-html/braid-head/braid-body` (start *and* end tags), scripts are neutralized to
|
|
171
185
|
`type="inert"`, and script preload links become `rel="inert-*"`. Fragment scripts are therefore
|
|
@@ -547,6 +561,11 @@ user against the shell. A fragment that needs to know who is asking should be gi
|
|
|
547
561
|
scoped to it: a per-request assertion via `additionalHeaders`, or a token exchanged for the
|
|
548
562
|
fragment's own audience. `forwardCredentials: true` restores the old behaviour, and is only
|
|
549
563
|
appropriate when every endpoint in the registry sits inside the same trust boundary as the shell.
|
|
564
|
+
When only some do, pass a function instead, `forwardCredentials: (fragment) => …`, and the gateway
|
|
565
|
+
decides per fragment. A function that throws forwards nothing, and websocket upgrades follow the
|
|
566
|
+
same decision. `proxy-authorization` is stripped with them, and `credentialHeaders` adds any
|
|
567
|
+
identity header an auth proxy in front of the shell injects (`x-auth-request-access-token`,
|
|
568
|
+
`cf-access-jwt-assertion`, …).
|
|
550
569
|
|
|
551
570
|
Not yet implemented from the architecture's security section: allow-listed manifest origins and
|
|
552
571
|
signed manifests. Until those land, treat the registry as trusted configuration.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braidlabs/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Braid gateway — fetch-native origin-front middleware that routes fragment traffic by exact id under the reserved /__braid/frag/ namespace, loads fragment manifests, and passes everything else through to your existing app.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"micro-frontends",
|
package/src/appd.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ import type { ResolvedFragmentManifest } from './registry.js';
|
|
|
11
11
|
* The payoff for FDC3 is direct: `findIntent` becomes a registry query, and because discovery
|
|
12
12
|
* filters by the caller's principal, a user only ever sees resolvers they are permitted to use.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Records are checked against FDC3 2.2's own AppD schema in `appd.spec.ts` (vendored under
|
|
15
|
+
* `tools/fdc3/`). Optional members Braid has no data for — `lang`, `screenshots`,
|
|
16
|
+
* `localizedVersions`, `interop.appChannels` — are not produced.
|
|
17
17
|
*/
|
|
18
18
|
/** The `fdc3` block a manifest may carry. A superset of what AppD needs; the rest is for the runtime. */
|
|
19
19
|
export interface FragmentFdc3Metadata {
|
|
@@ -51,14 +51,14 @@ export interface FragmentAppdMetadata {
|
|
|
51
51
|
type?: string;
|
|
52
52
|
}[];
|
|
53
53
|
}
|
|
54
|
+
/** An FDC3 AppD v2 `Application` record — the members Braid fills. */
|
|
54
55
|
export interface AppdApplication {
|
|
55
56
|
appId: string;
|
|
56
|
-
|
|
57
|
+
title: string;
|
|
57
58
|
type: 'web';
|
|
58
59
|
details: {
|
|
59
60
|
url: string;
|
|
60
61
|
};
|
|
61
|
-
title?: string;
|
|
62
62
|
description?: string;
|
|
63
63
|
tooltip?: string;
|
|
64
64
|
version?: string;
|
|
@@ -75,7 +75,6 @@ export interface AppdApplication {
|
|
|
75
75
|
interop?: {
|
|
76
76
|
intents?: {
|
|
77
77
|
listensFor?: Record<string, {
|
|
78
|
-
name: string;
|
|
79
78
|
displayName?: string;
|
|
80
79
|
contexts: string[];
|
|
81
80
|
resultType?: string;
|
|
@@ -110,10 +109,29 @@ export interface AppdListResponse {
|
|
|
110
109
|
applications: AppdApplication[];
|
|
111
110
|
message: string;
|
|
112
111
|
}
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
/** AppD's error body, `ErrorDTO`. */
|
|
113
|
+
export interface AppdError {
|
|
114
|
+
code: number;
|
|
115
115
|
message: string;
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* The App Directory's two reads over a set of manifests the caller may already see: the listing,
|
|
119
|
+
* or one application by `appId`. `GET /v2/apps/{appId}` answers with the bare `Application`, not
|
|
120
|
+
* a wrapper. Headers are the caller's — what may be cached differs between the gateway, whose
|
|
121
|
+
* listing depends on who asked, and the registry.
|
|
122
|
+
*/
|
|
123
|
+
export declare function appdResponse(options: {
|
|
124
|
+
manifests: readonly ResolvedFragmentManifest[];
|
|
125
|
+
origin: string;
|
|
126
|
+
/** Present for `/v2/apps/{appId}`, absent for the listing. */
|
|
127
|
+
appId?: string;
|
|
128
|
+
}): {
|
|
129
|
+
status: 200;
|
|
130
|
+
body: AppdListResponse | AppdApplication;
|
|
131
|
+
} | {
|
|
132
|
+
status: 404;
|
|
133
|
+
body: AppdError;
|
|
134
|
+
};
|
|
117
135
|
/**
|
|
118
136
|
* Projects one manifest into an AppD record.
|
|
119
137
|
*
|
package/src/appd.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { BRAID_FRAGMENT_PREFIX } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* The App Directory's two reads over a set of manifests the caller may already see: the listing,
|
|
4
|
+
* or one application by `appId`. `GET /v2/apps/{appId}` answers with the bare `Application`, not
|
|
5
|
+
* a wrapper. Headers are the caller's — what may be cached differs between the gateway, whose
|
|
6
|
+
* listing depends on who asked, and the registry.
|
|
7
|
+
*/
|
|
8
|
+
export function appdResponse(options) {
|
|
9
|
+
const { manifests, origin, appId } = options;
|
|
10
|
+
if (appId === undefined) {
|
|
11
|
+
// one manifest the projection cannot read is left out, not allowed to fail everyone's listing
|
|
12
|
+
const applications = manifests.flatMap((manifest) => {
|
|
13
|
+
try {
|
|
14
|
+
return [toAppdApplication(manifest, origin)];
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return { status: 200, body: { applications, message: 'OK' } };
|
|
21
|
+
}
|
|
22
|
+
const match = manifests.find((manifest) => manifest.id === appId);
|
|
23
|
+
// the same 404 for "not visible to you" as for "not registered" — distinguishing them would let
|
|
24
|
+
// an unauthorized caller enumerate the registry one id at a time
|
|
25
|
+
if (!match)
|
|
26
|
+
return { status: 404, body: { code: 404, message: `No application with appId "${appId}"` } };
|
|
27
|
+
return { status: 200, body: toAppdApplication(match, origin) };
|
|
28
|
+
}
|
|
2
29
|
/**
|
|
3
30
|
* Projects one manifest into an AppD record.
|
|
4
31
|
*
|
|
@@ -15,12 +42,12 @@ import { BRAID_FRAGMENT_PREFIX } from './protocol.js';
|
|
|
15
42
|
export function toAppdApplication(manifest, origin) {
|
|
16
43
|
const mount = `${BRAID_FRAGMENT_PREFIX}${encodeURIComponent(manifest.id)}/`;
|
|
17
44
|
const page = firstPiercePage(manifest.pierce);
|
|
45
|
+
// `name` is deprecated in AppD v2 in favour of `appId` and `title`, so it is not set.
|
|
18
46
|
const application = {
|
|
19
47
|
appId: manifest.id,
|
|
20
|
-
|
|
48
|
+
title: manifest.title ?? manifest.id,
|
|
21
49
|
type: 'web',
|
|
22
50
|
details: { url: new URL(page ?? mount, origin).href },
|
|
23
|
-
title: manifest.title ?? manifest.id,
|
|
24
51
|
hostManifests: {
|
|
25
52
|
braid: { fragmentId: manifest.id, mount, adapter: manifest.adapter, standalonePage: page !== null },
|
|
26
53
|
},
|
|
@@ -36,14 +63,20 @@ export function toAppdApplication(manifest, origin) {
|
|
|
36
63
|
application.contactEmail = appd.contactEmail;
|
|
37
64
|
if (appd?.supportEmail)
|
|
38
65
|
application.supportEmail = appd.supportEmail;
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
const moreInfo = appd?.moreInfo ? webUrl(appd.moreInfo, origin) : null;
|
|
67
|
+
if (moreInfo)
|
|
68
|
+
application.moreInfo = moreInfo;
|
|
41
69
|
if (appd?.version)
|
|
42
70
|
application.version = appd.version;
|
|
43
71
|
if (appd?.tooltip)
|
|
44
72
|
application.tooltip = appd.tooltip;
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
// AppD requires an absolute icon url; a manifest may name one relative to the shell
|
|
74
|
+
const icons = (appd?.icons ?? []).flatMap((icon) => {
|
|
75
|
+
const src = webUrl(icon.src, origin);
|
|
76
|
+
return src ? [{ ...icon, src }] : [];
|
|
77
|
+
});
|
|
78
|
+
if (icons.length)
|
|
79
|
+
application.icons = icons;
|
|
47
80
|
const interop = toInterop(manifest.fdc3);
|
|
48
81
|
if (interop)
|
|
49
82
|
application.interop = interop;
|
|
@@ -53,10 +86,8 @@ function toInterop(fdc3) {
|
|
|
53
86
|
if (!fdc3)
|
|
54
87
|
return undefined;
|
|
55
88
|
const listensFor = Object.entries(fdc3.listensFor ?? {}).reduce((accumulator, [intent, declaration]) => {
|
|
56
|
-
//
|
|
57
|
-
// rather than requiring it in the manifest keeps one source of truth for the intent's name.
|
|
89
|
+
// the key is the intent's name; AppD v2's intent record has no `name` member of its own
|
|
58
90
|
accumulator[intent] = {
|
|
59
|
-
name: intent,
|
|
60
91
|
contexts: declaration.contexts ?? [],
|
|
61
92
|
...(declaration.displayName ? { displayName: declaration.displayName } : {}),
|
|
62
93
|
...(declaration.resultType ? { resultType: declaration.resultType } : {}),
|
|
@@ -73,6 +104,22 @@ function toInterop(fdc3) {
|
|
|
73
104
|
};
|
|
74
105
|
return Object.keys(interop).length > 0 ? interop : undefined;
|
|
75
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* A manifest-supplied link, absolute on `origin`, or null when it is not an http(s) URL. Dropping
|
|
109
|
+
* a bad one keeps one manifest's typo from failing the whole listing, and keeps `javascript:` out
|
|
110
|
+
* of every launcher that renders the directory.
|
|
111
|
+
*/
|
|
112
|
+
function webUrl(value, origin) {
|
|
113
|
+
if (typeof value !== 'string')
|
|
114
|
+
return null;
|
|
115
|
+
try {
|
|
116
|
+
const url = new URL(value, origin);
|
|
117
|
+
return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : null;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
76
123
|
/**
|
|
77
124
|
* The first pierce pattern reduced to a concrete page path.
|
|
78
125
|
*
|
package/src/appd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appd.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/appd.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"appd.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/appd.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAmGtD;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAK5B;IACC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAE7C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,8FAA8F;QAC9F,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAClD,IAAI,CAAC;gBACH,OAAO,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;YAC/C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;IAChE,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;IAClE,gGAAgG;IAChG,iEAAiE;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,8BAA8B,KAAK,GAAG,EAAE,EAAE,CAAC;IACzG,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAkC,EAAE,MAAc;IAClF,MAAM,KAAK,GAAG,GAAG,qBAAqB,GAAG,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC;IAC5E,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE9C,sFAAsF;IACtF,MAAM,WAAW,GAAoB;QACnC,KAAK,EAAE,QAAQ,CAAC,EAAE;QAClB,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,EAAE;QACpC,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE;QACrD,aAAa,EAAE;YACb,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,KAAK,IAAI,EAAE;SACpG;KACF,CAAC;IAEF,IAAI,QAAQ,CAAC,WAAW;QAAE,WAAW,CAAC,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC;IACzE,IAAI,QAAQ,CAAC,IAAI,EAAE,MAAM;QAAE,WAAW,CAAC,UAAU,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEvE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,IAAI,IAAI,EAAE,SAAS;QAAE,WAAW,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAC5D,IAAI,IAAI,EAAE,YAAY;QAAE,WAAW,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACrE,IAAI,IAAI,EAAE,YAAY;QAAE,WAAW,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACrE,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvE,IAAI,QAAQ;QAAE,WAAW,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC9C,IAAI,IAAI,EAAE,OAAO;QAAE,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IACtD,IAAI,IAAI,EAAE,OAAO;QAAE,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IACtD,oFAAoF;IACpF,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACjD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,CAAC,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM;QAAE,WAAW,CAAC,KAAK,GAAG,KAAK,CAAC;IAE5C,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,OAAO;QAAE,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC;IAE3C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,SAAS,CAAC,IAAsC;IACvD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAE7D,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,EAAE;QACvC,wFAAwF;QACxF,WAAY,CAAC,MAAM,CAAC,GAAG;YACrB,QAAQ,EAAE,WAAW,CAAC,QAAQ,IAAI,EAAE;YACpC,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1E,CAAC;QACF,OAAO,WAAW,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG;QACd,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9F,CAAC;IAEF,MAAM,OAAO,GAAG;QACd,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzE,CAAC;IAEF,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CAAC,KAAc,EAAE,MAAc;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACnC,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,MAA4B;IACnD,KAAK,MAAM,OAAO,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QACpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/src/breaker.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export interface Breaker {
|
|
|
40
40
|
succeeded(fragmentId: string): void;
|
|
41
41
|
failed(fragmentId: string): void;
|
|
42
42
|
stateOf(fragmentId: string): BreakerState;
|
|
43
|
+
/** Drops a fragment's circuit — after a registry refresh removed it or changed its endpoint. */
|
|
44
|
+
forget(fragmentId: string): void;
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
45
47
|
* Creates a breaker.
|
package/src/breaker.js
CHANGED
|
@@ -64,6 +64,9 @@ export function createBreaker(options = {}, onTransition, now = Date.now) {
|
|
|
64
64
|
circuit.probing = true;
|
|
65
65
|
return true;
|
|
66
66
|
},
|
|
67
|
+
forget(fragmentId) {
|
|
68
|
+
circuits.delete(fragmentId);
|
|
69
|
+
},
|
|
67
70
|
succeeded(fragmentId) {
|
|
68
71
|
const circuit = circuitFor(fragmentId);
|
|
69
72
|
circuit.failures = 0;
|
package/src/breaker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breaker.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH,MAAM,CAAC,MAAM,eAAe,GAAmB;IAC7C,yFAAyF;IACzF,iFAAiF;IACjF,gBAAgB,EAAE,CAAC;IACnB,cAAc,EAAE,MAAM;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"breaker.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/breaker.ts"],"names":[],"mappings":"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"}
|
package/src/discovery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BRAID_FRAGMENT_PREFIX, BRAID_PROTOCOL_VERSION } from './protocol.js';
|
|
2
|
-
import {
|
|
2
|
+
import { appdResponse } from './appd.js';
|
|
3
3
|
import { canFetch, canList } from './registry.js';
|
|
4
4
|
export const DEFAULT_DISCOVERY_PATH = '/__braid/registry';
|
|
5
5
|
export const DEFAULT_DISCOVERY_PAGE_SIZE = 100;
|
|
@@ -41,22 +41,15 @@ export function createDiscoveryHandler(registry, options, mode, resolvePrincipal
|
|
|
41
41
|
// so a caller can never see a resolver through AppD that discovery would have hidden.
|
|
42
42
|
if (appdEnabled && url.pathname.startsWith(appdPath)) {
|
|
43
43
|
const requested = url.pathname.slice(appdPath.length).replace(/^\//, '');
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!match) {
|
|
54
|
-
// 404 for "not visible to you" as well as "not registered" — distinguishing them would
|
|
55
|
-
// let an unauthorized caller enumerate the registry one id at a time.
|
|
56
|
-
return json({ message: `No application with appId "${appId}"` }, 404, { 'Cache-Control': 'no-store' });
|
|
57
|
-
}
|
|
58
|
-
const body = { application: toAppdApplication(match, url.origin), message: 'OK' };
|
|
59
|
-
return json(body, 200, { 'Cache-Control': 'no-store', Vary: 'cookie, authorization' });
|
|
44
|
+
const { status, body } = appdResponse({
|
|
45
|
+
manifests: visible,
|
|
46
|
+
origin: url.origin,
|
|
47
|
+
...(requested ? { appId: safeDecode(requested) } : {}),
|
|
48
|
+
});
|
|
49
|
+
return json(body, status, {
|
|
50
|
+
'Cache-Control': 'no-store',
|
|
51
|
+
...(status === 200 ? { Vary: 'cookie, authorization' } : {}),
|
|
52
|
+
});
|
|
60
53
|
}
|
|
61
54
|
const pageSize = clampPageSize(url.searchParams.get('pageSize'), defaultPageSize, maxPageSize);
|
|
62
55
|
const totalPages = Math.max(1, Math.ceil(visible.length / pageSize));
|
|
@@ -118,6 +111,15 @@ function clampPage(requested, totalPages) {
|
|
|
118
111
|
return 1;
|
|
119
112
|
return Math.min(parsed, totalPages);
|
|
120
113
|
}
|
|
114
|
+
/** A malformed escape names no application, rather than throwing. */
|
|
115
|
+
function safeDecode(segment) {
|
|
116
|
+
try {
|
|
117
|
+
return decodeURIComponent(segment);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return segment;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
121
123
|
function json(body, status, headers = {}) {
|
|
122
124
|
return new Response(JSON.stringify(body), {
|
|
123
125
|
status,
|
package/src/discovery.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/discovery.ts"],"names":[],"mappings":"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"}
|
package/src/gateway.d.ts
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
-
import { Principal, RegistrySource } from './registry.js';
|
|
1
|
+
import { Principal, RegistryRefresh, RegistrySource, FragmentManifest } from './registry.js';
|
|
2
2
|
import { DiscoveryOptions } from './discovery.js';
|
|
3
3
|
import { type BreakerOptions } from './breaker.js';
|
|
4
4
|
import { type TelemetryOptions } from './telemetry.js';
|
|
5
5
|
export interface GatewayOptions {
|
|
6
6
|
/** The fragment registry: inline manifests, a JSON URL, or an async loader. */
|
|
7
7
|
registry: RegistrySource;
|
|
8
|
+
/**
|
|
9
|
+
* Reload the registry source every this many milliseconds, and serve what it returns from then
|
|
10
|
+
* on — a re-pin goes live without a restart.
|
|
11
|
+
*
|
|
12
|
+
* Off by default, which keeps the snapshot model's "a re-pin is a deploy". With a source that
|
|
13
|
+
* follows a moving pointer (`snapshotRegistry` without `pinned`, a URL), this is hot re-pin; with
|
|
14
|
+
* a pinned source it is a cheap no-op. Each reload is all-or-nothing, and one that fails leaves
|
|
15
|
+
* the current registry serving and is reported as a `registry` telemetry event.
|
|
16
|
+
*
|
|
17
|
+
* Instances poll independently, so a fleet converges within one interval rather than at one
|
|
18
|
+
* instant. Not allowed with inline manifests, which cannot change.
|
|
19
|
+
*/
|
|
20
|
+
registryRefreshMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Supplies the page itself for a document request, in place of the shell app behind the gateway:
|
|
23
|
+
* return HTML, or null to let `next` (the app) answer as usual.
|
|
24
|
+
*
|
|
25
|
+
* Fragments are pierced into what this returns exactly as into an app's response, and a page it
|
|
26
|
+
* answers is served even when no fragment pierces it. This is how a shell defined as data — see
|
|
27
|
+
* `shellDocuments` in `@braidlabs/registry` — is served with no app and no rebuild. Consulted only
|
|
28
|
+
* for document requests (GET, `Sec-Fetch-Dest: document` or an HTML `Accept`).
|
|
29
|
+
*/
|
|
30
|
+
documents?: (request: Request, context: DocumentsContext) => Response | null | Promise<Response | null>;
|
|
8
31
|
/** 'development' enables verbose error bodies; defaults to 'development'. */
|
|
9
32
|
mode?: 'production' | 'development';
|
|
10
33
|
/**
|
|
@@ -28,8 +51,31 @@ export interface GatewayOptions {
|
|
|
28
51
|
* assertion minted per request via {@link GatewayOptions.additionalHeaders}, or a token
|
|
29
52
|
* exchanged for the fragment's own audience. Turn this on only when every endpoint in the
|
|
30
53
|
* registry is inside the same trust boundary as the shell.
|
|
54
|
+
*
|
|
55
|
+
* Or decide per fragment, when only some of them are — the usual case once a gateway composes
|
|
56
|
+
* other teams' gateways:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* forwardCredentials: (fragment) => typeof fragment.endpoint === 'string' &&
|
|
60
|
+
* fragment.endpoint.startsWith('https://orders.internal/'),
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* The function is the host's, never the manifest's: a fragment that could declare its own
|
|
64
|
+
* entitlement to the user's cookies would not be behind a boundary at all. A function that
|
|
65
|
+
* throws forwards nothing. The same decision applies to websocket upgrades.
|
|
31
66
|
*/
|
|
32
|
-
forwardCredentials?: boolean;
|
|
67
|
+
forwardCredentials?: boolean | ((fragment: FragmentManifest) => boolean);
|
|
68
|
+
/**
|
|
69
|
+
* More request headers that carry the caller's identity, stripped alongside `cookie`,
|
|
70
|
+
* `authorization` and `proxy-authorization` whenever {@link GatewayOptions.forwardCredentials}
|
|
71
|
+
* does not allow a fragment.
|
|
72
|
+
*
|
|
73
|
+
* An auth proxy in front of the shell often adds its own: `x-auth-request-access-token`
|
|
74
|
+
* (oauth2-proxy), `cf-access-jwt-assertion` (Cloudflare Access), `x-amzn-oidc-data` (an ALB).
|
|
75
|
+
* The gateway cannot know them, and each one is the user's identity handed to every fragment
|
|
76
|
+
* endpoint unless it is listed here.
|
|
77
|
+
*/
|
|
78
|
+
credentialHeaders?: readonly string[];
|
|
33
79
|
/**
|
|
34
80
|
* Publishes a paginated listing of the fragments this gateway serves, for shells that build
|
|
35
81
|
* their UI from the registry instead of hard-coding slot names.
|
|
@@ -206,6 +252,25 @@ export interface RoutingEvent {
|
|
|
206
252
|
fragmentIds: string[];
|
|
207
253
|
at: number;
|
|
208
254
|
}
|
|
255
|
+
/** Where a fragment websocket upgrade goes, and what may travel with it. See `resolveUpgrade`. */
|
|
256
|
+
export interface UpgradeTarget {
|
|
257
|
+
fragmentId: string;
|
|
258
|
+
target: URL;
|
|
259
|
+
forwardCredentials: boolean;
|
|
260
|
+
/** Headers that carry the caller's identity: stripped unless `forwardCredentials`. */
|
|
261
|
+
credentialHeaders: readonly string[];
|
|
262
|
+
/** The `x-braid-via` value for the upstream request, this gateway's hop included. */
|
|
263
|
+
via: string;
|
|
264
|
+
}
|
|
265
|
+
/** What the gateway tells a `documents` hook about the registry it is serving right now. */
|
|
266
|
+
export interface DocumentsContext {
|
|
267
|
+
/**
|
|
268
|
+
* The manifests the gateway is serving — exactly the array its registry source last returned and
|
|
269
|
+
* it accepted. A hook that renders from the same snapshot (`shellDocuments`) keys on this, so a
|
|
270
|
+
* page's shell can never come from a snapshot the gateway refused to load.
|
|
271
|
+
*/
|
|
272
|
+
manifests: readonly FragmentManifest[];
|
|
273
|
+
}
|
|
209
274
|
export interface BraidGateway {
|
|
210
275
|
/**
|
|
211
276
|
* Handles a request if it belongs to Braid: a fragment-namespace request, or a document
|
|
@@ -223,13 +288,24 @@ export interface BraidGateway {
|
|
|
223
288
|
* addressed through the fragment namespace, so the gateway has to say where they go — the
|
|
224
289
|
* socket plumbing itself belongs to the platform binding.
|
|
225
290
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
291
|
+
* A binding other than `toNodeUpgradeHandler` **must** remove `credentialHeaders` from the
|
|
292
|
+
* upstream request when `forwardCredentials` is false, and set `x-braid-via` to `via` — the
|
|
293
|
+
* socket otherwise carries the user's identity past the boundary every fetch respects.
|
|
294
|
+
*
|
|
295
|
+
* @returns the endpoint URL to dial, whether the caller's credentials may go with it (the same
|
|
296
|
+
* {@link GatewayOptions.forwardCredentials} rule as a fetch) and which headers carry
|
|
297
|
+
* them, and the hop header to send — or null when the request is not a fragment
|
|
298
|
+
* upgrade, the fragment is unknown, the caller may not load it, or it has already
|
|
299
|
+
* crossed too many gateways.
|
|
300
|
+
*/
|
|
301
|
+
resolveUpgrade(request: Request): Promise<UpgradeTarget | null>;
|
|
302
|
+
/**
|
|
303
|
+
* Reloads the registry source now and swaps in what it returns. Resolves once the new registry
|
|
304
|
+
* is serving; rejects, leaving the current one serving, when the source fails.
|
|
228
305
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
} | null>;
|
|
306
|
+
refreshRegistry(): Promise<RegistryRefresh>;
|
|
307
|
+
/** Stops the `registryRefreshMs` timer. Nothing else holds resources between requests. */
|
|
308
|
+
close(): void;
|
|
233
309
|
}
|
|
234
310
|
export declare function createGateway(options: GatewayOptions): BraidGateway;
|
|
235
311
|
/**
|