@braidlabs/gateway 0.1.1
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 +464 -0
- package/package.json +44 -0
- package/src/appd.d.ts +130 -0
- package/src/appd.js +93 -0
- package/src/appd.js.map +1 -0
- package/src/breaker.d.ts +51 -0
- package/src/breaker.js +96 -0
- package/src/breaker.js.map +1 -0
- package/src/discovery.d.ts +91 -0
- package/src/discovery.js +127 -0
- package/src/discovery.js.map +1 -0
- package/src/gateway.d.ts +241 -0
- package/src/gateway.js +735 -0
- package/src/gateway.js.map +1 -0
- package/src/index.d.ts +36 -0
- package/src/index.js +27 -0
- package/src/index.js.map +1 -0
- package/src/node.d.ts +35 -0
- package/src/node.js +252 -0
- package/src/node.js.map +1 -0
- package/src/protocol.d.ts +79 -0
- package/src/protocol.js +89 -0
- package/src/protocol.js.map +1 -0
- package/src/registry.d.ts +184 -0
- package/src/registry.js +187 -0
- package/src/registry.js.map +1 -0
- package/src/rewriter/html-rewrite-stream.d.ts +70 -0
- package/src/rewriter/html-rewrite-stream.js +483 -0
- package/src/rewriter/html-rewrite-stream.js.map +1 -0
- package/src/rewriter/transforms.d.ts +107 -0
- package/src/rewriter/transforms.js +312 -0
- package/src/rewriter/transforms.js.map +1 -0
- package/src/single-flight.d.ts +53 -0
- package/src/single-flight.js +97 -0
- package/src/single-flight.js.map +1 -0
- package/src/telemetry.d.ts +115 -0
- package/src/telemetry.js +177 -0
- package/src/telemetry.js.map +1 -0
package/src/appd.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { BRAID_FRAGMENT_PREFIX } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* Projects one manifest into an AppD record.
|
|
4
|
+
*
|
|
5
|
+
* `details.url` is the interesting decision. AppD asks where a web app lives, and a fragment does
|
|
6
|
+
* not live anywhere on its own — it appears inside a host page. So:
|
|
7
|
+
*
|
|
8
|
+
* - if the fragment declares `pierce`, the first pattern names a page it actually appears on, and
|
|
9
|
+
* that page *is* the answer: an operator following the link sees the app;
|
|
10
|
+
* - otherwise the fragment's mount is used, which is honest but is a namespace path rather than a
|
|
11
|
+
* page, and `hostManifests.braid.standalonePage` says so.
|
|
12
|
+
*
|
|
13
|
+
* Either way the Braid-aware path is `hostManifests`, because "open this URL" is the wrong verb.
|
|
14
|
+
*/
|
|
15
|
+
export function toAppdApplication(manifest, origin) {
|
|
16
|
+
const mount = `${BRAID_FRAGMENT_PREFIX}${encodeURIComponent(manifest.id)}/`;
|
|
17
|
+
const page = firstPiercePage(manifest.pierce);
|
|
18
|
+
const application = {
|
|
19
|
+
appId: manifest.id,
|
|
20
|
+
name: manifest.id,
|
|
21
|
+
type: 'web',
|
|
22
|
+
details: { url: new URL(page ?? mount, origin).href },
|
|
23
|
+
title: manifest.title ?? manifest.id,
|
|
24
|
+
hostManifests: {
|
|
25
|
+
braid: { fragmentId: manifest.id, mount, adapter: manifest.adapter, standalonePage: page !== null },
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
if (manifest.description)
|
|
29
|
+
application.description = manifest.description;
|
|
30
|
+
if (manifest.tags?.length)
|
|
31
|
+
application.categories = [...manifest.tags];
|
|
32
|
+
const appd = manifest.appd;
|
|
33
|
+
if (appd?.publisher)
|
|
34
|
+
application.publisher = appd.publisher;
|
|
35
|
+
if (appd?.contactEmail)
|
|
36
|
+
application.contactEmail = appd.contactEmail;
|
|
37
|
+
if (appd?.supportEmail)
|
|
38
|
+
application.supportEmail = appd.supportEmail;
|
|
39
|
+
if (appd?.moreInfo)
|
|
40
|
+
application.moreInfo = appd.moreInfo;
|
|
41
|
+
if (appd?.version)
|
|
42
|
+
application.version = appd.version;
|
|
43
|
+
if (appd?.tooltip)
|
|
44
|
+
application.tooltip = appd.tooltip;
|
|
45
|
+
if (appd?.icons?.length)
|
|
46
|
+
application.icons = appd.icons.map((icon) => ({ ...icon }));
|
|
47
|
+
const interop = toInterop(manifest.fdc3);
|
|
48
|
+
if (interop)
|
|
49
|
+
application.interop = interop;
|
|
50
|
+
return application;
|
|
51
|
+
}
|
|
52
|
+
function toInterop(fdc3) {
|
|
53
|
+
if (!fdc3)
|
|
54
|
+
return undefined;
|
|
55
|
+
const listensFor = Object.entries(fdc3.listensFor ?? {}).reduce((accumulator, [intent, declaration]) => {
|
|
56
|
+
// `name` is required on an AppD intent record and duplicates the key. Filling it from the key
|
|
57
|
+
// rather than requiring it in the manifest keeps one source of truth for the intent's name.
|
|
58
|
+
accumulator[intent] = {
|
|
59
|
+
name: intent,
|
|
60
|
+
contexts: declaration.contexts ?? [],
|
|
61
|
+
...(declaration.displayName ? { displayName: declaration.displayName } : {}),
|
|
62
|
+
...(declaration.resultType ? { resultType: declaration.resultType } : {}),
|
|
63
|
+
};
|
|
64
|
+
return accumulator;
|
|
65
|
+
}, {});
|
|
66
|
+
const intents = {
|
|
67
|
+
...(Object.keys(listensFor ?? {}).length > 0 ? { listensFor } : {}),
|
|
68
|
+
...(fdc3.raises && Object.keys(fdc3.raises).length > 0 ? { raises: { ...fdc3.raises } } : {}),
|
|
69
|
+
};
|
|
70
|
+
const interop = {
|
|
71
|
+
...(Object.keys(intents).length > 0 ? { intents } : {}),
|
|
72
|
+
...(fdc3.userChannels ? { userChannels: { ...fdc3.userChannels } } : {}),
|
|
73
|
+
};
|
|
74
|
+
return Object.keys(interop).length > 0 ? interop : undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The first pierce pattern reduced to a concrete page path.
|
|
78
|
+
*
|
|
79
|
+
* A wildcard suffix is dropped rather than filled — `/billing/*` becomes `/billing`, which is a
|
|
80
|
+
* page an operator can open. A pattern with a named parameter (`/orders/:id`) names no particular
|
|
81
|
+
* page, so it yields nothing rather than a link to `/orders/undefined`.
|
|
82
|
+
*/
|
|
83
|
+
function firstPiercePage(pierce) {
|
|
84
|
+
for (const pattern of pierce ?? []) {
|
|
85
|
+
if (pattern.includes(':'))
|
|
86
|
+
continue;
|
|
87
|
+
const trimmed = pattern.replace(/\/?\*+$/, '');
|
|
88
|
+
if (trimmed)
|
|
89
|
+
return trimmed;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=appd.js.map
|
package/src/appd.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appd.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/appd.ts"],"names":[],"mappings":"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"}
|
package/src/breaker.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A per-fragment circuit breaker.
|
|
3
|
+
*
|
|
4
|
+
* **The problem it solves is compounding, not failure.** A fragment endpoint that is down already
|
|
5
|
+
* costs its full `timeoutMs` — but it costs that on *every* request, because nothing remembers the
|
|
6
|
+
* last one. Four fragments at a 3s budget means a page held hostage to the worst of them, over and
|
|
7
|
+
* over, with the shell's own response waiting behind it. The fallback machinery already handles a
|
|
8
|
+
* fragment that fails; what it cannot do is stop paying to rediscover that it fails.
|
|
9
|
+
*
|
|
10
|
+
* So the breaker's job is to convert a slow, repeated failure into a fast, cheap one. It is a
|
|
11
|
+
* latency control first and an availability control second.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately per fragment id, never global: fragments are independently deployed, and one team's
|
|
14
|
+
* bad release must not open a circuit in front of another team's healthy app.
|
|
15
|
+
*/
|
|
16
|
+
export interface BreakerOptions {
|
|
17
|
+
/** Consecutive failures before the circuit opens. */
|
|
18
|
+
failureThreshold: number;
|
|
19
|
+
/** How long it stays open before a single probe is allowed through, in ms. */
|
|
20
|
+
resetTimeoutMs: number;
|
|
21
|
+
}
|
|
22
|
+
export declare const DEFAULT_BREAKER: BreakerOptions;
|
|
23
|
+
export type BreakerState = 'closed' | 'open' | 'half-open';
|
|
24
|
+
export interface BreakerTransition {
|
|
25
|
+
fragmentId: string;
|
|
26
|
+
from: BreakerState;
|
|
27
|
+
to: BreakerState;
|
|
28
|
+
/** Consecutive failures at the moment of the transition. */
|
|
29
|
+
failures: number;
|
|
30
|
+
}
|
|
31
|
+
export interface Breaker {
|
|
32
|
+
/**
|
|
33
|
+
* Whether a request may proceed.
|
|
34
|
+
*
|
|
35
|
+
* Calling this *claims* the half-open probe when it returns true in that state, so a caller
|
|
36
|
+
* that asks must go on to report the outcome — otherwise recovery stalls behind a probe that
|
|
37
|
+
* never resolves.
|
|
38
|
+
*/
|
|
39
|
+
allows(fragmentId: string): boolean;
|
|
40
|
+
succeeded(fragmentId: string): void;
|
|
41
|
+
failed(fragmentId: string): void;
|
|
42
|
+
stateOf(fragmentId: string): BreakerState;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a breaker.
|
|
46
|
+
*
|
|
47
|
+
* `onTransition` fires only on genuine state changes, never per request — a hook that fired on
|
|
48
|
+
* every call would be a metrics firehose describing nothing, and the interesting events here are
|
|
49
|
+
* exactly the four edges between states.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createBreaker(options?: Partial<BreakerOptions>, onTransition?: (transition: BreakerTransition) => void, now?: () => number): Breaker;
|
package/src/breaker.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A per-fragment circuit breaker.
|
|
3
|
+
*
|
|
4
|
+
* **The problem it solves is compounding, not failure.** A fragment endpoint that is down already
|
|
5
|
+
* costs its full `timeoutMs` — but it costs that on *every* request, because nothing remembers the
|
|
6
|
+
* last one. Four fragments at a 3s budget means a page held hostage to the worst of them, over and
|
|
7
|
+
* over, with the shell's own response waiting behind it. The fallback machinery already handles a
|
|
8
|
+
* fragment that fails; what it cannot do is stop paying to rediscover that it fails.
|
|
9
|
+
*
|
|
10
|
+
* So the breaker's job is to convert a slow, repeated failure into a fast, cheap one. It is a
|
|
11
|
+
* latency control first and an availability control second.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately per fragment id, never global: fragments are independently deployed, and one team's
|
|
14
|
+
* bad release must not open a circuit in front of another team's healthy app.
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_BREAKER = {
|
|
17
|
+
// Three, not one: a single failure is noise — a deploy rolling, a pod cycling, a dropped
|
|
18
|
+
// connection. Opening on the first one would make the breaker itself the outage.
|
|
19
|
+
failureThreshold: 3,
|
|
20
|
+
resetTimeoutMs: 10_000,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Creates a breaker.
|
|
24
|
+
*
|
|
25
|
+
* `onTransition` fires only on genuine state changes, never per request — a hook that fired on
|
|
26
|
+
* every call would be a metrics firehose describing nothing, and the interesting events here are
|
|
27
|
+
* exactly the four edges between states.
|
|
28
|
+
*/
|
|
29
|
+
export function createBreaker(options = {}, onTransition, now = Date.now) {
|
|
30
|
+
const { failureThreshold, resetTimeoutMs } = { ...DEFAULT_BREAKER, ...options };
|
|
31
|
+
const circuits = new Map();
|
|
32
|
+
const circuitFor = (fragmentId) => {
|
|
33
|
+
let circuit = circuits.get(fragmentId);
|
|
34
|
+
if (!circuit) {
|
|
35
|
+
circuit = { state: 'closed', failures: 0, openedAt: 0, probing: false };
|
|
36
|
+
circuits.set(fragmentId, circuit);
|
|
37
|
+
}
|
|
38
|
+
return circuit;
|
|
39
|
+
};
|
|
40
|
+
const transition = (fragmentId, circuit, to) => {
|
|
41
|
+
if (circuit.state === to)
|
|
42
|
+
return;
|
|
43
|
+
const from = circuit.state;
|
|
44
|
+
circuit.state = to;
|
|
45
|
+
onTransition?.({ fragmentId, from, to, failures: circuit.failures });
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
allows(fragmentId) {
|
|
49
|
+
const circuit = circuitFor(fragmentId);
|
|
50
|
+
if (circuit.state === 'closed')
|
|
51
|
+
return true;
|
|
52
|
+
if (circuit.state === 'open') {
|
|
53
|
+
if (now() - circuit.openedAt < resetTimeoutMs)
|
|
54
|
+
return false;
|
|
55
|
+
// The cooldown has elapsed: promote to half-open and let exactly this caller probe.
|
|
56
|
+
transition(fragmentId, circuit, 'half-open');
|
|
57
|
+
circuit.probing = true;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// half-open: one probe at a time. Everything else is still shed, because a recovering
|
|
61
|
+
// endpoint hit with the full load that took it down is an endpoint that goes down again.
|
|
62
|
+
if (circuit.probing)
|
|
63
|
+
return false;
|
|
64
|
+
circuit.probing = true;
|
|
65
|
+
return true;
|
|
66
|
+
},
|
|
67
|
+
succeeded(fragmentId) {
|
|
68
|
+
const circuit = circuitFor(fragmentId);
|
|
69
|
+
circuit.failures = 0;
|
|
70
|
+
circuit.probing = false;
|
|
71
|
+
transition(fragmentId, circuit, 'closed');
|
|
72
|
+
},
|
|
73
|
+
failed(fragmentId) {
|
|
74
|
+
const circuit = circuitFor(fragmentId);
|
|
75
|
+
circuit.failures += 1;
|
|
76
|
+
circuit.probing = false;
|
|
77
|
+
// A failed probe re-opens immediately and restarts the cooldown, rather than counting
|
|
78
|
+
// toward the threshold again — the endpoint has already proved it is still broken.
|
|
79
|
+
if (circuit.state === 'half-open' || circuit.failures >= failureThreshold) {
|
|
80
|
+
circuit.openedAt = now();
|
|
81
|
+
transition(fragmentId, circuit, 'open');
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
stateOf(fragmentId) {
|
|
85
|
+
const circuit = circuits.get(fragmentId);
|
|
86
|
+
if (!circuit)
|
|
87
|
+
return 'closed';
|
|
88
|
+
// Report the state a caller would actually get, so an operator reading this does not see
|
|
89
|
+
// `open` for a circuit whose cooldown lapsed minutes ago and which is really ready to probe.
|
|
90
|
+
if (circuit.state === 'open' && now() - circuit.openedAt >= resetTimeoutMs)
|
|
91
|
+
return 'half-open';
|
|
92
|
+
return circuit.state;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=breaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"breaker.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Principal, Registry } from './registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* The discovery endpoint: a probeable, paginated listing of the fragments this gateway serves.
|
|
4
|
+
*
|
|
5
|
+
* It answers "what can I compose here?" for shells that build their UI from the registry rather
|
|
6
|
+
* than hard-coding slot names — a launcher, an admin console, a dashboard of available apps.
|
|
7
|
+
*
|
|
8
|
+
* It is **off by default**. A registry lists internal service topology, so publishing it is a
|
|
9
|
+
* decision, not a default.
|
|
10
|
+
*/
|
|
11
|
+
export interface DiscoveryOptions {
|
|
12
|
+
/** Where to serve the listing. Defaults to `/__braid/registry`. */
|
|
13
|
+
path?: string;
|
|
14
|
+
/** Items per page when the caller doesn't ask. Defaults to 100. */
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
/** Hard ceiling on `pageSize`, whatever the caller asks for. Defaults to 100. */
|
|
17
|
+
maxPageSize?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Include each fragment's `endpoint` in the listing. Defaults to false, because endpoints are
|
|
20
|
+
* usually internal origins and a public listing of them is a map of your private network.
|
|
21
|
+
* Forced on in development mode.
|
|
22
|
+
*/
|
|
23
|
+
includeEndpoints?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Also serve the registry in FDC3 **App Directory** shape, under `<path>/appd/v2/apps`.
|
|
26
|
+
*
|
|
27
|
+
* Off by default. It is a projection over the same manifests and the same `access.list` rules —
|
|
28
|
+
* not a second directory — so turning it on adds a shape, never a source of truth.
|
|
29
|
+
*/
|
|
30
|
+
appd?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface DiscoveryEntry {
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
tags?: string[];
|
|
37
|
+
adapter: string;
|
|
38
|
+
contractVersion?: string;
|
|
39
|
+
/** Where this fragment is addressable — what a `<fragment-slot name>` resolves to. */
|
|
40
|
+
mount: string;
|
|
41
|
+
/** Page URL patterns this fragment is server-rendered into, if any. */
|
|
42
|
+
pierce?: string[];
|
|
43
|
+
/**
|
|
44
|
+
* Whether *this caller* may actually load the fragment.
|
|
45
|
+
*
|
|
46
|
+
* Listing and loading are separate rules, so a launcher can legitimately show an app the user
|
|
47
|
+
* cannot open yet — this flag is what lets it render that state instead of a broken tile.
|
|
48
|
+
*/
|
|
49
|
+
loadable: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the fragment renders the page's route (a screen) or sits at a fixed path (a widget).
|
|
52
|
+
*
|
|
53
|
+
* Published because it changes how a host embeds it: a widget needs {@link DiscoveryEntry.src},
|
|
54
|
+
* a screen does not. Without this a consumer has to guess, and guessing wrong is the "widget
|
|
55
|
+
* renders an empty shell on every page" failure.
|
|
56
|
+
*/
|
|
57
|
+
bound: boolean;
|
|
58
|
+
/** Where an unbound fragment's content lives, as a path on its own endpoint. */
|
|
59
|
+
src?: string;
|
|
60
|
+
/** Present only when `includeEndpoints` is on (or in development). */
|
|
61
|
+
endpoint?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface DiscoveryPage {
|
|
64
|
+
items: DiscoveryEntry[];
|
|
65
|
+
page: number;
|
|
66
|
+
pageSize: number;
|
|
67
|
+
/** Total *visible* to this caller, not total registered. */
|
|
68
|
+
total: number;
|
|
69
|
+
totalPages: number;
|
|
70
|
+
hasMore: boolean;
|
|
71
|
+
protocolVersion: string;
|
|
72
|
+
/** True when filtering was skipped because the gateway is in development mode. */
|
|
73
|
+
unfiltered?: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface DiscoveryHandler {
|
|
76
|
+
/** The listing path itself. */
|
|
77
|
+
path: string;
|
|
78
|
+
/** Whether this handler answers for a pathname — the listing, or its App Directory projection. */
|
|
79
|
+
owns(pathname: string): boolean;
|
|
80
|
+
handle(request: Request, url: URL): Promise<Response>;
|
|
81
|
+
}
|
|
82
|
+
export declare const DEFAULT_DISCOVERY_PATH = "/__braid/registry";
|
|
83
|
+
export declare const DEFAULT_DISCOVERY_PAGE_SIZE = 100;
|
|
84
|
+
/**
|
|
85
|
+
* Builds the discovery handler, or null when discovery is not enabled.
|
|
86
|
+
*
|
|
87
|
+
* In development every fragment is listed with its endpoint regardless of `visibility`, because
|
|
88
|
+
* a registry you cannot see is hard to debug — and the gateway says so loudly at startup, since
|
|
89
|
+
* that is precisely the configuration you must not ship.
|
|
90
|
+
*/
|
|
91
|
+
export declare function createDiscoveryHandler(registry: Registry, options: DiscoveryOptions | undefined, mode: 'production' | 'development', resolvePrincipal: (request: Request) => Promise<Principal | undefined>): DiscoveryHandler | null;
|
package/src/discovery.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { BRAID_FRAGMENT_PREFIX, BRAID_PROTOCOL_VERSION } from './protocol.js';
|
|
2
|
+
import { toAppdApplication } from './appd.js';
|
|
3
|
+
import { canFetch, canList } from './registry.js';
|
|
4
|
+
export const DEFAULT_DISCOVERY_PATH = '/__braid/registry';
|
|
5
|
+
export const DEFAULT_DISCOVERY_PAGE_SIZE = 100;
|
|
6
|
+
/**
|
|
7
|
+
* Builds the discovery handler, or null when discovery is not enabled.
|
|
8
|
+
*
|
|
9
|
+
* In development every fragment is listed with its endpoint regardless of `visibility`, because
|
|
10
|
+
* a registry you cannot see is hard to debug — and the gateway says so loudly at startup, since
|
|
11
|
+
* that is precisely the configuration you must not ship.
|
|
12
|
+
*/
|
|
13
|
+
export function createDiscoveryHandler(registry, options, mode, resolvePrincipal) {
|
|
14
|
+
if (!options)
|
|
15
|
+
return null;
|
|
16
|
+
const path = options.path ?? DEFAULT_DISCOVERY_PATH;
|
|
17
|
+
const maxPageSize = Math.max(1, options.maxPageSize ?? DEFAULT_DISCOVERY_PAGE_SIZE);
|
|
18
|
+
const defaultPageSize = Math.min(Math.max(1, options.pageSize ?? DEFAULT_DISCOVERY_PAGE_SIZE), maxPageSize);
|
|
19
|
+
const isDevelopment = mode === 'development';
|
|
20
|
+
const includeEndpoints = isDevelopment || (options.includeEndpoints ?? false);
|
|
21
|
+
if (isDevelopment) {
|
|
22
|
+
console.warn(`braid-gateway: the discovery endpoint at "${path}" is running in development mode — it lists every ` +
|
|
23
|
+
`fragment with its endpoint, ignoring each manifest's "access" rules. Do not ship this configuration.`);
|
|
24
|
+
}
|
|
25
|
+
const appdEnabled = options.appd ?? false;
|
|
26
|
+
const appdPath = `${path}/appd/v2/apps`;
|
|
27
|
+
return {
|
|
28
|
+
path,
|
|
29
|
+
/** True for the discovery path and, when enabled, its App Directory projection. */
|
|
30
|
+
owns(pathname) {
|
|
31
|
+
return pathname === path || (appdEnabled && (pathname === appdPath || pathname.startsWith(`${appdPath}/`)));
|
|
32
|
+
},
|
|
33
|
+
async handle(request, url) {
|
|
34
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
35
|
+
return json({ error: 'method not allowed' }, 405, { Allow: 'GET, HEAD' });
|
|
36
|
+
}
|
|
37
|
+
const principal = isDevelopment ? undefined : await resolvePrincipal(request);
|
|
38
|
+
const all = await registry.listFragments();
|
|
39
|
+
const visible = isDevelopment ? all : all.filter((manifest) => canList(manifest, principal));
|
|
40
|
+
// The App Directory is the same list, in AppD's shape — same manifests, same access rules,
|
|
41
|
+
// so a caller can never see a resolver through AppD that discovery would have hidden.
|
|
42
|
+
if (appdEnabled && url.pathname.startsWith(appdPath)) {
|
|
43
|
+
const requested = url.pathname.slice(appdPath.length).replace(/^\//, '');
|
|
44
|
+
if (!requested) {
|
|
45
|
+
const body = {
|
|
46
|
+
applications: visible.map((manifest) => toAppdApplication(manifest, url.origin)),
|
|
47
|
+
message: 'OK',
|
|
48
|
+
};
|
|
49
|
+
return json(body, 200, { 'Cache-Control': 'no-store', Vary: 'cookie, authorization' });
|
|
50
|
+
}
|
|
51
|
+
const appId = decodeURIComponent(requested);
|
|
52
|
+
const match = visible.find((manifest) => manifest.id === appId);
|
|
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' });
|
|
60
|
+
}
|
|
61
|
+
const pageSize = clampPageSize(url.searchParams.get('pageSize'), defaultPageSize, maxPageSize);
|
|
62
|
+
const totalPages = Math.max(1, Math.ceil(visible.length / pageSize));
|
|
63
|
+
const page = clampPage(url.searchParams.get('page'), totalPages);
|
|
64
|
+
const start = (page - 1) * pageSize;
|
|
65
|
+
const items = visible
|
|
66
|
+
.slice(start, start + pageSize)
|
|
67
|
+
.map((manifest) => toEntry(manifest, includeEndpoints, isDevelopment || canFetch(manifest, principal)));
|
|
68
|
+
const body = {
|
|
69
|
+
items,
|
|
70
|
+
page,
|
|
71
|
+
pageSize,
|
|
72
|
+
total: visible.length,
|
|
73
|
+
totalPages,
|
|
74
|
+
hasMore: start + items.length < visible.length,
|
|
75
|
+
protocolVersion: BRAID_PROTOCOL_VERSION,
|
|
76
|
+
...(isDevelopment ? { unfiltered: true } : {}),
|
|
77
|
+
};
|
|
78
|
+
// the listing depends on who asked, so it must never land in a shared cache
|
|
79
|
+
return json(body, 200, {
|
|
80
|
+
'Cache-Control': 'no-store',
|
|
81
|
+
Vary: 'cookie, authorization',
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function toEntry(manifest, includeEndpoints, loadable) {
|
|
87
|
+
const entry = {
|
|
88
|
+
id: manifest.id,
|
|
89
|
+
title: manifest.title ?? manifest.id,
|
|
90
|
+
adapter: manifest.adapter,
|
|
91
|
+
mount: `${BRAID_FRAGMENT_PREFIX}${encodeURIComponent(manifest.id)}/`,
|
|
92
|
+
loadable,
|
|
93
|
+
bound: manifest.bound,
|
|
94
|
+
};
|
|
95
|
+
if (manifest.description)
|
|
96
|
+
entry.description = manifest.description;
|
|
97
|
+
if (manifest.tags?.length)
|
|
98
|
+
entry.tags = [...manifest.tags];
|
|
99
|
+
if (manifest.contractVersion)
|
|
100
|
+
entry.contractVersion = manifest.contractVersion;
|
|
101
|
+
if (manifest.pierce?.length)
|
|
102
|
+
entry.pierce = [...manifest.pierce];
|
|
103
|
+
if (manifest.src)
|
|
104
|
+
entry.src = manifest.src;
|
|
105
|
+
if (includeEndpoints && typeof manifest.endpoint === 'string')
|
|
106
|
+
entry.endpoint = manifest.endpoint;
|
|
107
|
+
return entry;
|
|
108
|
+
}
|
|
109
|
+
function clampPageSize(requested, fallback, max) {
|
|
110
|
+
const parsed = Number.parseInt(requested ?? '', 10);
|
|
111
|
+
if (!Number.isFinite(parsed) || parsed < 1)
|
|
112
|
+
return fallback;
|
|
113
|
+
return Math.min(parsed, max);
|
|
114
|
+
}
|
|
115
|
+
function clampPage(requested, totalPages) {
|
|
116
|
+
const parsed = Number.parseInt(requested ?? '', 10);
|
|
117
|
+
if (!Number.isFinite(parsed) || parsed < 1)
|
|
118
|
+
return 1;
|
|
119
|
+
return Math.min(parsed, totalPages);
|
|
120
|
+
}
|
|
121
|
+
function json(body, status, headers = {}) {
|
|
122
|
+
return new Response(JSON.stringify(body), {
|
|
123
|
+
status,
|
|
124
|
+
headers: { 'Content-Type': 'application/json;charset=UTF-8', ...headers },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/discovery.ts"],"names":[],"mappings":"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"}
|