@opendatalabs/personal-server-ts-server 1.33.7 → 1.34.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/dist/__fixtures__/bound-record-store.d.ts +19 -0
- package/dist/__fixtures__/bound-record-store.d.ts.map +1 -0
- package/dist/__fixtures__/bound-record-store.js +60 -0
- package/dist/__fixtures__/bound-record-store.js.map +1 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.d.ts +41 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.d.ts.map +1 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.js +34 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.js.map +1 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.d.ts +112 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.d.ts.map +1 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.js +226 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.js.map +1 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-types.d.ts +135 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-types.d.ts.map +1 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-types.js +21 -0
- package/dist/__fixtures__/cg-pdpp-client/pdpp-types.js.map +1 -0
- package/dist/api-auth.d.ts +18 -2
- package/dist/api-auth.d.ts.map +1 -1
- package/dist/api-auth.js +114 -22
- package/dist/api-auth.js.map +1 -1
- package/dist/app.d.ts +60 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +139 -1
- package/dist/app.js.map +1 -1
- package/dist/bootstrap.d.ts +10 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +358 -7
- package/dist/bootstrap.js.map +1 -1
- package/dist/enclave-main.d.ts +4 -0
- package/dist/enclave-main.d.ts.map +1 -1
- package/dist/enclave-main.js +152 -1
- package/dist/enclave-main.js.map +1 -1
- package/dist/middleware/body-limit.d.ts +1 -1
- package/dist/middleware/body-limit.d.ts.map +1 -1
- package/dist/middleware/body-limit.js +8 -3
- package/dist/middleware/body-limit.js.map +1 -1
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/cors.js +1 -0
- package/dist/middleware/cors.js.map +1 -1
- package/dist/package.json +9 -1
- package/dist/pdpp/bootstrap.d.ts +72 -0
- package/dist/pdpp/bootstrap.d.ts.map +1 -0
- package/dist/pdpp/bootstrap.js +270 -0
- package/dist/pdpp/bootstrap.js.map +1 -0
- package/dist/pdpp/chain-enforcement.d.ts +95 -0
- package/dist/pdpp/chain-enforcement.d.ts.map +1 -0
- package/dist/pdpp/chain-enforcement.js +145 -0
- package/dist/pdpp/chain-enforcement.js.map +1 -0
- package/dist/pdpp/client-document-fetch.d.ts +63 -0
- package/dist/pdpp/client-document-fetch.d.ts.map +1 -0
- package/dist/pdpp/client-document-fetch.js +254 -0
- package/dist/pdpp/client-document-fetch.js.map +1 -0
- package/dist/pdpp/declaration-registry.d.ts +83 -0
- package/dist/pdpp/declaration-registry.d.ts.map +1 -0
- package/dist/pdpp/declaration-registry.js +251 -0
- package/dist/pdpp/declaration-registry.js.map +1 -0
- package/dist/pdpp/deployment.d.ts +70 -0
- package/dist/pdpp/deployment.d.ts.map +1 -0
- package/dist/pdpp/deployment.js +104 -0
- package/dist/pdpp/deployment.js.map +1 -0
- package/dist/pdpp/record-schema.d.ts +2 -0
- package/dist/pdpp/record-schema.d.ts.map +1 -0
- package/dist/pdpp/record-schema.js +2 -0
- package/dist/pdpp/record-schema.js.map +1 -0
- package/dist/pdpp/records-bootstrap.d.ts +111 -0
- package/dist/pdpp/records-bootstrap.d.ts.map +1 -0
- package/dist/pdpp/records-bootstrap.js +353 -0
- package/dist/pdpp/records-bootstrap.js.map +1 -0
- package/dist/pdpp/source-id-compat.d.ts +5 -0
- package/dist/pdpp/source-id-compat.d.ts.map +1 -0
- package/dist/pdpp/source-id-compat.js +16 -0
- package/dist/pdpp/source-id-compat.js.map +1 -0
- package/dist/pdpp/test-connections.d.ts +6 -0
- package/dist/pdpp/test-connections.d.ts.map +1 -0
- package/dist/pdpp/test-connections.js +37 -0
- package/dist/pdpp/test-connections.js.map +1 -0
- package/dist/routes/auth-device.d.ts.map +1 -1
- package/dist/routes/auth-device.js +3 -0
- package/dist/routes/auth-device.js.map +1 -1
- package/dist/routes/chunked-scope-import.d.ts +41 -0
- package/dist/routes/chunked-scope-import.d.ts.map +1 -0
- package/dist/routes/chunked-scope-import.js +901 -0
- package/dist/routes/chunked-scope-import.js.map +1 -0
- package/dist/routes/data.d.ts +23 -1
- package/dist/routes/data.d.ts.map +1 -1
- package/dist/routes/data.js +112 -1
- package/dist/routes/data.js.map +1 -1
- package/dist/routes/grants.d.ts.map +1 -1
- package/dist/routes/grants.js +2 -0
- package/dist/routes/grants.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +21 -12
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/pdpp-auth.d.ts +157 -0
- package/dist/routes/pdpp-auth.d.ts.map +1 -0
- package/dist/routes/pdpp-auth.js +953 -0
- package/dist/routes/pdpp-auth.js.map +1 -0
- package/dist/routes/pdpp-blobs.d.ts +26 -0
- package/dist/routes/pdpp-blobs.d.ts.map +1 -0
- package/dist/routes/pdpp-blobs.js +179 -0
- package/dist/routes/pdpp-blobs.js.map +1 -0
- package/dist/routes/pdpp-connections.d.ts +28 -0
- package/dist/routes/pdpp-connections.d.ts.map +1 -0
- package/dist/routes/pdpp-connections.js +144 -0
- package/dist/routes/pdpp-connections.js.map +1 -0
- package/dist/routes/pdpp-declarations.d.ts +58 -0
- package/dist/routes/pdpp-declarations.d.ts.map +1 -0
- package/dist/routes/pdpp-declarations.js +94 -0
- package/dist/routes/pdpp-declarations.js.map +1 -0
- package/dist/routes/pdpp-instance-bindings.d.ts +26 -0
- package/dist/routes/pdpp-instance-bindings.d.ts.map +1 -0
- package/dist/routes/pdpp-instance-bindings.js +124 -0
- package/dist/routes/pdpp-instance-bindings.js.map +1 -0
- package/dist/routes/pdpp-records.d.ts +158 -0
- package/dist/routes/pdpp-records.d.ts.map +1 -0
- package/dist/routes/pdpp-records.js +1537 -0
- package/dist/routes/pdpp-records.js.map +1 -0
- package/dist/routes/pdpp-well-known.d.ts +13 -0
- package/dist/routes/pdpp-well-known.d.ts.map +1 -0
- package/dist/routes/pdpp-well-known.js +20 -0
- package/dist/routes/pdpp-well-known.js.map +1 -0
- package/dist/storage/declaration-witness.d.ts +56 -0
- package/dist/storage/declaration-witness.d.ts.map +1 -0
- package/dist/storage/declaration-witness.js +273 -0
- package/dist/storage/declaration-witness.js.map +1 -0
- package/dist/storage/hierarchy.d.ts +25 -1
- package/dist/storage/hierarchy.d.ts.map +1 -1
- package/dist/storage/hierarchy.js +210 -16
- package/dist/storage/hierarchy.js.map +1 -1
- package/dist/storage/import-operations.d.ts +11 -0
- package/dist/storage/import-operations.d.ts.map +1 -0
- package/dist/storage/import-operations.js +261 -0
- package/dist/storage/import-operations.js.map +1 -0
- package/dist/storage/index-manager.d.ts +24 -2
- package/dist/storage/index-manager.d.ts.map +1 -1
- package/dist/storage/index-manager.js +279 -30
- package/dist/storage/index-manager.js.map +1 -1
- package/dist/storage/index-schema.d.ts +1 -1
- package/dist/storage/index-schema.d.ts.map +1 -1
- package/dist/storage/index-schema.js +189 -8
- package/dist/storage/index-schema.js.map +1 -1
- package/dist/storage/legacy-scope-authority.d.ts +14 -0
- package/dist/storage/legacy-scope-authority.d.ts.map +1 -0
- package/dist/storage/legacy-scope-authority.js +298 -0
- package/dist/storage/legacy-scope-authority.js.map +1 -0
- package/dist/storage/node-data-storage.d.ts +6 -0
- package/dist/storage/node-data-storage.d.ts.map +1 -1
- package/dist/storage/node-data-storage.js +161 -2
- package/dist/storage/node-data-storage.js.map +1 -1
- package/dist/storage/pdpp-grant-binding-sqlite-store.d.ts +17 -0
- package/dist/storage/pdpp-grant-binding-sqlite-store.d.ts.map +1 -0
- package/dist/storage/pdpp-grant-binding-sqlite-store.js +159 -0
- package/dist/storage/pdpp-grant-binding-sqlite-store.js.map +1 -0
- package/dist/storage/pdpp-records-sqlite-store.d.ts +112 -0
- package/dist/storage/pdpp-records-sqlite-store.d.ts.map +1 -0
- package/dist/storage/pdpp-records-sqlite-store.js +1440 -0
- package/dist/storage/pdpp-records-sqlite-store.js.map +1 -0
- package/dist/storage/pdpp-stream-completion.d.ts +5 -0
- package/dist/storage/pdpp-stream-completion.d.ts.map +1 -0
- package/dist/storage/pdpp-stream-completion.js +10 -0
- package/dist/storage/pdpp-stream-completion.js.map +1 -0
- package/dist/storage/scope-deletion-history.d.ts +8 -0
- package/dist/storage/scope-deletion-history.d.ts.map +1 -0
- package/dist/storage/scope-deletion-history.js +40 -0
- package/dist/storage/scope-deletion-history.js.map +1 -0
- package/dist/ui/ps-lite-debug.js +3697 -876
- package/package.json +12 -4
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded production fetcher for a URL-hosted client identity document
|
|
3
|
+
* (Core §6). Implements `ClientDocumentFetcher` from
|
|
4
|
+
* `@opendatalabs/personal-server-ts-core/pdpp`.
|
|
5
|
+
*
|
|
6
|
+
* Invariants:
|
|
7
|
+
* - HTTPS only.
|
|
8
|
+
* - Every address actually dialed is checked: a hostname's DNS answers via
|
|
9
|
+
* a custom `lookup` on the `undici` dispatcher (defends DNS rebinding —
|
|
10
|
+
* `net`/`tls`/`undici` dial exactly the address `lookup` returns, so
|
|
11
|
+
* there is no separate resolution to race), and a literal-IP URL
|
|
12
|
+
* hostname separately, since that never reaches `dns.lookup` at all.
|
|
13
|
+
* - No redirect is followed; a redirect or an oversized/rejected body is
|
|
14
|
+
* drained (`discardBody`), never left dangling on the socket.
|
|
15
|
+
* - One `AbortSignal`/timeout spans connect + headers + body + cleanup.
|
|
16
|
+
* - The body is read incrementally and dropped past the byte cap.
|
|
17
|
+
* - No credentials are forwarded.
|
|
18
|
+
*/
|
|
19
|
+
import dns from "node:dns";
|
|
20
|
+
import net from "node:net";
|
|
21
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
22
|
+
export const FETCH_TIMEOUT_MS = 5_000;
|
|
23
|
+
export const MAX_RESPONSE_BYTES = 64 * 1024;
|
|
24
|
+
/** Thrown by the connect-time guard; surfaces as a `fetch_failed` result. */
|
|
25
|
+
export class BlockedAddressError extends Error {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Non-public address space, checked against the literal address a DNS
|
|
29
|
+
* answer resolved to — not the hostname string. `net.BlockList` normalizes
|
|
30
|
+
* both IPv4-mapped IPv6 forms (`::ffff:a.b.c.d` and `::ffff:7f00:1`) and the
|
|
31
|
+
* fully-expanded form (`0:0:0:0:0:0:0:1`) against the same IPv4/IPv6 rules,
|
|
32
|
+
* so listing each CIDR once covers all three.
|
|
33
|
+
*/
|
|
34
|
+
const blockedAddresses = new net.BlockList();
|
|
35
|
+
for (const cidr of [
|
|
36
|
+
"0.0.0.0/8", // "this network" / unspecified-as-source
|
|
37
|
+
"10.0.0.0/8", // RFC1918 private
|
|
38
|
+
"100.64.0.0/10", // shared address space / CGNAT (RFC6598)
|
|
39
|
+
"127.0.0.0/8", // loopback
|
|
40
|
+
"169.254.0.0/16", // link-local, incl. cloud metadata
|
|
41
|
+
"172.16.0.0/12", // RFC1918 private
|
|
42
|
+
"192.0.0.0/24", // IETF protocol assignments
|
|
43
|
+
"192.0.2.0/24", // documentation (TEST-NET-1)
|
|
44
|
+
"192.168.0.0/16", // RFC1918 private
|
|
45
|
+
"198.18.0.0/15", // benchmarking
|
|
46
|
+
"198.51.100.0/24", // documentation (TEST-NET-2)
|
|
47
|
+
"203.0.113.0/24", // documentation (TEST-NET-3)
|
|
48
|
+
"224.0.0.0/4", // multicast
|
|
49
|
+
"240.0.0.0/4", // reserved
|
|
50
|
+
"255.255.255.255/32", // limited broadcast
|
|
51
|
+
]) {
|
|
52
|
+
const [address, prefix] = cidr.split("/");
|
|
53
|
+
blockedAddresses.addSubnet(address, Number(prefix), "ipv4");
|
|
54
|
+
}
|
|
55
|
+
for (const cidr of [
|
|
56
|
+
"::/128", // unspecified
|
|
57
|
+
"::1/128", // loopback
|
|
58
|
+
"fc00::/7", // unique local (ULA)
|
|
59
|
+
"fe80::/10", // link-local
|
|
60
|
+
"ff00::/8", // multicast
|
|
61
|
+
]) {
|
|
62
|
+
const [address, prefix] = cidr.split("/");
|
|
63
|
+
blockedAddresses.addSubnet(address, Number(prefix), "ipv6");
|
|
64
|
+
}
|
|
65
|
+
export function isBlockedAddress(address) {
|
|
66
|
+
const version = net.isIP(address);
|
|
67
|
+
if (version === 0)
|
|
68
|
+
return true; // not a literal address — refuse, don't guess
|
|
69
|
+
return blockedAddresses.check(address, version === 4 ? "ipv4" : "ipv6");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A URL hostname that is itself a literal IP (e.g. `https://127.0.0.1/`,
|
|
73
|
+
* `https://[::1]/`) never reaches `dns.lookup` — `net`/`tls`/`undici` dial it
|
|
74
|
+
* directly, so the guarded `lookup` in {@link buildGuardedLookup} never runs.
|
|
75
|
+
* Must be checked separately, before the dial, against the same predicate.
|
|
76
|
+
*/
|
|
77
|
+
function literalHostAddress(hostname) {
|
|
78
|
+
const unbracketed = hostname.startsWith("[") && hostname.endsWith("]")
|
|
79
|
+
? hostname.slice(1, -1)
|
|
80
|
+
: hostname;
|
|
81
|
+
return net.isIP(unbracketed) === 0 ? null : unbracketed;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build a `lookup`-compatible function (the shape `node:net`/`node:tls` and
|
|
85
|
+
* `undici`'s connector accept) that refuses to resolve to a blocked address.
|
|
86
|
+
* Delegates the actual resolution to `node:dns`, always requesting every
|
|
87
|
+
* answer so a rebinding attempt cannot hide a blocked address behind a
|
|
88
|
+
* permitted one that happens to sort first.
|
|
89
|
+
*
|
|
90
|
+
* `isBlocked` defaults to {@link isBlockedAddress}; overriding it is a
|
|
91
|
+
* test-only seam (see `__testing`) so tests can run a real loopback server
|
|
92
|
+
* without disabling the guard's own logic, which is what the override would
|
|
93
|
+
* do if it replaced `lookup` entirely instead of only the address predicate.
|
|
94
|
+
*/
|
|
95
|
+
function buildGuardedLookup(isBlocked = isBlockedAddress) {
|
|
96
|
+
return (hostname, options, callback) => {
|
|
97
|
+
// Always ask `dns.lookup` for every answer so a blocked address cannot
|
|
98
|
+
// hide behind a permitted one that happens to sort first, regardless of
|
|
99
|
+
// whether the caller itself wanted `all`.
|
|
100
|
+
dns.lookup(hostname, { ...options, all: true }, (err, addresses) => {
|
|
101
|
+
if (err)
|
|
102
|
+
return callback(err, "", undefined);
|
|
103
|
+
const list = addresses;
|
|
104
|
+
if (list.length === 0) {
|
|
105
|
+
return callback(new BlockedAddressError(`${hostname} resolved to no addresses`), "", undefined);
|
|
106
|
+
}
|
|
107
|
+
const blocked = list.find((a) => isBlocked(a.address));
|
|
108
|
+
if (blocked) {
|
|
109
|
+
return callback(new BlockedAddressError(`${hostname} resolved to a blocked address: ${blocked.address}`), "", undefined);
|
|
110
|
+
}
|
|
111
|
+
// Reply in the shape the caller actually requested: the array form
|
|
112
|
+
// when `options.all` was set (as undici's connector does), otherwise
|
|
113
|
+
// the single-address form `net`/`tls` expect.
|
|
114
|
+
if (options.all) {
|
|
115
|
+
return callback(null, list, undefined);
|
|
116
|
+
}
|
|
117
|
+
callback(null, list[0].address, list[0].family);
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Build a dispatcher that only ever connects to an address that passes the
|
|
123
|
+
* guard, bundled with the exact predicate it was built with so a caller
|
|
124
|
+
* can't pass a mismatched one to the pre-dial literal-IP check in
|
|
125
|
+
* {@link createBoundedFetcher}. `tlsOpts` is exposed solely so tests can
|
|
126
|
+
* trust a local self-signed certificate; `isBlocked` lets tests run against
|
|
127
|
+
* loopback without disabling the guard mechanism itself. Production code
|
|
128
|
+
* must call {@link boundedClientDocumentFetcher}, which passes neither.
|
|
129
|
+
*/
|
|
130
|
+
function buildGuardedAgent(tlsOpts) {
|
|
131
|
+
const { isBlocked = isBlockedAddress, ...rest } = tlsOpts ?? {};
|
|
132
|
+
return {
|
|
133
|
+
dispatcher: new Agent({
|
|
134
|
+
connect: { lookup: buildGuardedLookup(isBlocked), ...rest },
|
|
135
|
+
}),
|
|
136
|
+
isBlocked,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** One dispatcher, reused across calls; the guard is stateless per lookup. */
|
|
140
|
+
const guarded = buildGuardedAgent();
|
|
141
|
+
/**
|
|
142
|
+
* Read a response body, aborting the moment it exceeds `limit` bytes.
|
|
143
|
+
* Mirrors `readCapped` in core's `declaration.ts` — same bound, same
|
|
144
|
+
* reasoning: `.text()` would buffer the whole body before the cap could act.
|
|
145
|
+
*/
|
|
146
|
+
async function readCapped(response, limit) {
|
|
147
|
+
const stream = response.body;
|
|
148
|
+
if (!stream) {
|
|
149
|
+
const buffered = await response.text();
|
|
150
|
+
return Buffer.byteLength(buffered, "utf8") > limit ? null : buffered;
|
|
151
|
+
}
|
|
152
|
+
const reader = stream.getReader();
|
|
153
|
+
const chunks = [];
|
|
154
|
+
let total = 0;
|
|
155
|
+
try {
|
|
156
|
+
for (;;) {
|
|
157
|
+
const { done, value } = await reader.read();
|
|
158
|
+
if (done)
|
|
159
|
+
break;
|
|
160
|
+
if (!value)
|
|
161
|
+
continue;
|
|
162
|
+
total += value.byteLength;
|
|
163
|
+
if (total > limit) {
|
|
164
|
+
await reader.cancel();
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
chunks.push(value);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
reader.releaseLock();
|
|
172
|
+
}
|
|
173
|
+
return Buffer.concat(chunks.map((c) => Buffer.from(c))).toString("utf8");
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build a bounded `ClientDocumentFetcher`. HTTPS-only, no redirects, one
|
|
177
|
+
* connect+headers+body timeout, a streamed byte cap, and address validation
|
|
178
|
+
* on every DNS answer *and* on a literal-IP hostname (which never reaches
|
|
179
|
+
* `dns.lookup`, so the dispatcher's guarded `lookup` alone would miss it).
|
|
180
|
+
*
|
|
181
|
+
* Takes {@link buildGuardedAgent}'s return value as a whole (not just its
|
|
182
|
+
* `dispatcher`) so the pre-dial literal-IP check always uses the exact same
|
|
183
|
+
* predicate the dispatcher's `lookup` guard was built with — there is no
|
|
184
|
+
* call shape that lets the two diverge.
|
|
185
|
+
*/
|
|
186
|
+
function createBoundedFetcher({ dispatcher, isBlocked, }) {
|
|
187
|
+
return async (url) => {
|
|
188
|
+
const parsed = new URL(url);
|
|
189
|
+
if (parsed.protocol !== "https:") {
|
|
190
|
+
throw new Error(`client_id document fetch requires https, got ${parsed.protocol}`);
|
|
191
|
+
}
|
|
192
|
+
const literal = literalHostAddress(parsed.hostname);
|
|
193
|
+
if (literal && isBlocked(literal)) {
|
|
194
|
+
throw new BlockedAddressError(`client_id document fetch refuses a blocked literal address: ${literal}`);
|
|
195
|
+
}
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
198
|
+
try {
|
|
199
|
+
const response = await undiciFetch(parsed.toString(), {
|
|
200
|
+
redirect: "manual",
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
dispatcher,
|
|
203
|
+
// No forwarded credentials: no cookies, no stored authorization.
|
|
204
|
+
});
|
|
205
|
+
if (response.status >= 300 && response.status < 400) {
|
|
206
|
+
await discardBody(response);
|
|
207
|
+
throw new Error(`client_id document fetch refuses to follow a redirect (status ${response.status})`);
|
|
208
|
+
}
|
|
209
|
+
const declaredLength = response.headers.get("content-length");
|
|
210
|
+
if (declaredLength && Number(declaredLength) > MAX_RESPONSE_BYTES) {
|
|
211
|
+
await discardBody(response);
|
|
212
|
+
return { status: response.status, body: "", finalUrl: url };
|
|
213
|
+
}
|
|
214
|
+
const body = await readCapped(response, MAX_RESPONSE_BYTES);
|
|
215
|
+
if (body === null) {
|
|
216
|
+
// Oversized: reported as a non-200 empty body so the resolver's
|
|
217
|
+
// generic "unexpected status" handling covers it without a new code
|
|
218
|
+
// path duplicating the size check `resolveUrlHostedClientIdentity`
|
|
219
|
+
// already performs on the string it receives.
|
|
220
|
+
return { status: 0, body: "", finalUrl: url };
|
|
221
|
+
}
|
|
222
|
+
return { status: response.status, body, finalUrl: parsed.toString() };
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
// Cleared last: the deadline must still apply while a rejected
|
|
226
|
+
// response's body is drained/destroyed above, not just during the
|
|
227
|
+
// initial connect+headers await.
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Drop a response body we're not going to read, rather than leaving it
|
|
234
|
+
* unconsumed. Called before `finally` clears the timer, so a hostile server
|
|
235
|
+
* stalling this drain still hits the request's own deadline instead of
|
|
236
|
+
* hanging past it.
|
|
237
|
+
*/
|
|
238
|
+
async function discardBody(response) {
|
|
239
|
+
const stream = response.body;
|
|
240
|
+
if (!stream)
|
|
241
|
+
return;
|
|
242
|
+
try {
|
|
243
|
+
await stream.cancel();
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// A destroyed/errored stream may reject cancel(); the socket is already
|
|
247
|
+
// gone either way.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** The production fetcher: no test-only overrides reach this path. */
|
|
251
|
+
export const boundedClientDocumentFetcher = createBoundedFetcher(guarded);
|
|
252
|
+
/** Test-only seam: same bounds, with a dispatcher that trusts a test CA. */
|
|
253
|
+
export const __testing = { createBoundedFetcher, buildGuardedAgent };
|
|
254
|
+
//# sourceMappingURL=client-document-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-document-fetch.js","sourceRoot":"","sources":["../../src/pdpp/client-document-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A declaration registry that can change while the server is running.
|
|
3
|
+
*
|
|
4
|
+
* `buildDeclarationRegistry` (deployment.ts) parses `config.pdpp
|
|
5
|
+
* .declarationPaths` once at construction and pins the survivors. That is the
|
|
6
|
+
* right shape for a deployment whose declarations are config, and it stays
|
|
7
|
+
* exactly as it was. What it cannot do is accept a candidate: offering one
|
|
8
|
+
* means editing config and restarting, so a caller cannot ask "would you
|
|
9
|
+
* accept this?" and get an answer in the same server lifetime.
|
|
10
|
+
*
|
|
11
|
+
* This is that acceptance surface's storage. Two properties it must have that
|
|
12
|
+
* the boot-time registry does not:
|
|
13
|
+
*
|
|
14
|
+
* - **Mutable in place.** The AS's `resolveDeclaration` lookup must see an
|
|
15
|
+
* accepted declaration immediately, or an acceptance is indistinguishable
|
|
16
|
+
* from a refusal until the next restart.
|
|
17
|
+
* - **Durable.** An operator who submitted a declaration must not silently
|
|
18
|
+
* lose it on restart, which would make acceptance look transactional and
|
|
19
|
+
* behave like a cache.
|
|
20
|
+
*
|
|
21
|
+
* ## Why this is a separate table from `pdpp_declarations`
|
|
22
|
+
*
|
|
23
|
+
* The auth store's `pdpp_declarations` retains a snapshot per
|
|
24
|
+
* `(source_id, version)` and is deliberately immutable — it is the evidence an
|
|
25
|
+
* ISSUED GRANT points at, and rewriting it would change what an owner
|
|
26
|
+
* consented to after the fact. This table answers a different question:
|
|
27
|
+
* *which* version is currently authoritative for a source. Those must not
|
|
28
|
+
* share a row, because accepting a new version has to move the current
|
|
29
|
+
* pointer while leaving every retained revision a live grant depends on
|
|
30
|
+
* exactly where it was.
|
|
31
|
+
*/
|
|
32
|
+
import type { DeclarationFailure, DeclarationSnapshot } from "@opendatalabs/personal-server-ts-core/pdpp";
|
|
33
|
+
import type { Logger } from "pino";
|
|
34
|
+
export interface MutableDeclarationRegistry {
|
|
35
|
+
/** The live lookup the AS consumes. Null when nothing is retained. */
|
|
36
|
+
resolve(sourceId: string): DeclarationSnapshot | null;
|
|
37
|
+
/** The exact retained bytes for a source, for digest verification. */
|
|
38
|
+
documentFor(sourceId: string): string | null;
|
|
39
|
+
/** Every currently-authoritative declaration. */
|
|
40
|
+
list(): DeclarationSnapshot[];
|
|
41
|
+
/**
|
|
42
|
+
* Validate a candidate and, on success, make it authoritative for its
|
|
43
|
+
* source. A refusal changes nothing — in particular it must not displace an
|
|
44
|
+
* already-retained declaration, or one malformed submission would disarm a
|
|
45
|
+
* working grant surface.
|
|
46
|
+
*/
|
|
47
|
+
submit(document: string): DeclarationSubmissionResult;
|
|
48
|
+
close(): void;
|
|
49
|
+
}
|
|
50
|
+
export type DeclarationSubmissionResult = {
|
|
51
|
+
ok: true;
|
|
52
|
+
snapshot: DeclarationSnapshot;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
failure: DeclarationFailure;
|
|
56
|
+
};
|
|
57
|
+
export interface OpenDeclarationRegistryOptions {
|
|
58
|
+
/** SQLite file. Use `:memory:` for a registry that does not outlive it. */
|
|
59
|
+
path: string;
|
|
60
|
+
/**
|
|
61
|
+
* The connector inventory this PS serves. A SUBMITTED declaration naming a
|
|
62
|
+
* source this server holds no data for is refused: a grant over data that
|
|
63
|
+
* cannot exist here is at best useless, and at worst a consent screen
|
|
64
|
+
* showing an owner something they are not sharing. Seeds are not gated;
|
|
65
|
+
* see `seed`.
|
|
66
|
+
*/
|
|
67
|
+
supportedConnectors: string[];
|
|
68
|
+
logger: Logger;
|
|
69
|
+
/**
|
|
70
|
+
* Declarations from `config.pdpp.declarationPaths`, seeded on open.
|
|
71
|
+
* Configuration is the operator's admission decision, so seeds skip the
|
|
72
|
+
* connector gate: a fresh server with no legacy data must still mount the
|
|
73
|
+
* sources it was configured for. Submitted declarations win over seeds for
|
|
74
|
+
* the same source: an operator's explicit later decision is the more
|
|
75
|
+
* recent one.
|
|
76
|
+
*/
|
|
77
|
+
seed?: {
|
|
78
|
+
sourceId: string;
|
|
79
|
+
document: string;
|
|
80
|
+
}[];
|
|
81
|
+
}
|
|
82
|
+
export declare function openDeclarationRegistry(options: OpenDeclarationRegistryOptions): MutableDeclarationRegistry;
|
|
83
|
+
//# sourceMappingURL=declaration-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declaration-registry.d.ts","sourceRoot":"","sources":["../../src/pdpp/declaration-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAyBnC,MAAM,WAAW,0BAA0B;IACzC,sEAAsE;IACtE,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAAC;IACtD,sEAAsE;IACtE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7C,iDAAiD;IACjD,IAAI,IAAI,mBAAmB,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,2BAA2B,CAAC;IACtD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,MAAM,2BAA2B,GACnC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,mBAAmB,CAAA;CAAE,GAC3C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAE/C,MAAM,WAAW,8BAA8B;IAC7C,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,8BAA8B,GACtC,0BAA0B,CAkK5B"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A declaration registry that can change while the server is running.
|
|
3
|
+
*
|
|
4
|
+
* `buildDeclarationRegistry` (deployment.ts) parses `config.pdpp
|
|
5
|
+
* .declarationPaths` once at construction and pins the survivors. That is the
|
|
6
|
+
* right shape for a deployment whose declarations are config, and it stays
|
|
7
|
+
* exactly as it was. What it cannot do is accept a candidate: offering one
|
|
8
|
+
* means editing config and restarting, so a caller cannot ask "would you
|
|
9
|
+
* accept this?" and get an answer in the same server lifetime.
|
|
10
|
+
*
|
|
11
|
+
* This is that acceptance surface's storage. Two properties it must have that
|
|
12
|
+
* the boot-time registry does not:
|
|
13
|
+
*
|
|
14
|
+
* - **Mutable in place.** The AS's `resolveDeclaration` lookup must see an
|
|
15
|
+
* accepted declaration immediately, or an acceptance is indistinguishable
|
|
16
|
+
* from a refusal until the next restart.
|
|
17
|
+
* - **Durable.** An operator who submitted a declaration must not silently
|
|
18
|
+
* lose it on restart, which would make acceptance look transactional and
|
|
19
|
+
* behave like a cache.
|
|
20
|
+
*
|
|
21
|
+
* ## Why this is a separate table from `pdpp_declarations`
|
|
22
|
+
*
|
|
23
|
+
* The auth store's `pdpp_declarations` retains a snapshot per
|
|
24
|
+
* `(source_id, version)` and is deliberately immutable — it is the evidence an
|
|
25
|
+
* ISSUED GRANT points at, and rewriting it would change what an owner
|
|
26
|
+
* consented to after the fact. This table answers a different question:
|
|
27
|
+
* *which* version is currently authoritative for a source. Those must not
|
|
28
|
+
* share a row, because accepting a new version has to move the current
|
|
29
|
+
* pointer while leaving every retained revision a live grant depends on
|
|
30
|
+
* exactly where it was.
|
|
31
|
+
*/
|
|
32
|
+
import Database from "better-sqlite3";
|
|
33
|
+
import { isDeepStrictEqual } from "node:util";
|
|
34
|
+
import { parseDeclaration } from "@opendatalabs/personal-server-ts-core/pdpp";
|
|
35
|
+
const SCHEMA_SQL = `
|
|
36
|
+
CREATE TABLE IF NOT EXISTS pdpp_current_declarations (
|
|
37
|
+
source_id TEXT NOT NULL,
|
|
38
|
+
version TEXT NOT NULL,
|
|
39
|
+
digest TEXT NOT NULL,
|
|
40
|
+
snapshot_json TEXT NOT NULL,
|
|
41
|
+
-- The exact submitted bytes. snapshot_json is the parsed projection, so
|
|
42
|
+
-- re-digesting it does NOT reproduce digest; a producer's claimed digest
|
|
43
|
+
-- can only honestly be checked against what was actually submitted.
|
|
44
|
+
document TEXT NOT NULL,
|
|
45
|
+
accepted_at TEXT NOT NULL,
|
|
46
|
+
is_current INTEGER NOT NULL DEFAULT 1,
|
|
47
|
+
-- 'configured' (seeded from config at boot) or 'submitted' (runtime
|
|
48
|
+
-- route). Boot config follows the current configured declaration, but
|
|
49
|
+
-- never displaces a submission.
|
|
50
|
+
origin TEXT NOT NULL DEFAULT 'configured',
|
|
51
|
+
PRIMARY KEY (source_id, version)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE INDEX IF NOT EXISTS pdpp_current_declarations_current_idx
|
|
55
|
+
ON pdpp_current_declarations (source_id, is_current);
|
|
56
|
+
`;
|
|
57
|
+
export function openDeclarationRegistry(options) {
|
|
58
|
+
const db = new Database(options.path);
|
|
59
|
+
db.pragma("journal_mode = WAL");
|
|
60
|
+
migrateSchema(db);
|
|
61
|
+
const supported = new Set(options.supportedConnectors);
|
|
62
|
+
function accept(document, admission) {
|
|
63
|
+
// The submitted document names its own source, so there is no separately
|
|
64
|
+
// supplied id to cross-check it against. Reading the claim first and
|
|
65
|
+
// parsing against it means `source_id_mismatch` stays reachable for a
|
|
66
|
+
// document whose nested `source.id` disagrees with its normalized form,
|
|
67
|
+
// rather than being unreachable here by construction.
|
|
68
|
+
const claimed = claimedSourceId(document);
|
|
69
|
+
if (claimed === null) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
failure: {
|
|
73
|
+
code: "invalid_document",
|
|
74
|
+
message: "declaration is not valid JSON, or declares no source id",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const parsed = parseDeclaration(document, claimed);
|
|
79
|
+
if (!parsed.ok)
|
|
80
|
+
return parsed;
|
|
81
|
+
const connector = connectorNameFor(parsed.snapshot.source_id);
|
|
82
|
+
if (admission === "submitted" && !supported.has(connector)) {
|
|
83
|
+
// `untrusted_source` rather than `invalid_document`: the document may
|
|
84
|
+
// be perfectly well formed. What is refused is its authority here.
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
failure: {
|
|
88
|
+
code: "untrusted_source",
|
|
89
|
+
message: `this server does not serve the '${connector}' connector, so it does not accept a declaration for it`,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const existing = db
|
|
94
|
+
.prepare(`SELECT snapshot_json, is_current FROM pdpp_current_declarations
|
|
95
|
+
WHERE source_id = ? AND version = ?`)
|
|
96
|
+
.get(parsed.snapshot.source_id, parsed.snapshot.version);
|
|
97
|
+
if (existing) {
|
|
98
|
+
const retained = JSON.parse(existing.snapshot_json);
|
|
99
|
+
if (!sameDeclarationContent(retained, parsed.snapshot)) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
failure: {
|
|
103
|
+
code: "declaration_equivocation",
|
|
104
|
+
message: "different content was already accepted under this (source.id, declaration_version) key",
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Configuration names the version that is current now, so a
|
|
109
|
+
// configured downgrade re-points to the retained earlier revision.
|
|
110
|
+
if (admission === "configured" && !existing.is_current) {
|
|
111
|
+
db.transaction(() => {
|
|
112
|
+
db.prepare("UPDATE pdpp_current_declarations SET is_current = 0 WHERE source_id = ?").run(parsed.snapshot.source_id);
|
|
113
|
+
db.prepare(`UPDATE pdpp_current_declarations SET is_current = 1
|
|
114
|
+
WHERE source_id = ? AND version = ?`).run(parsed.snapshot.source_id, parsed.snapshot.version);
|
|
115
|
+
})();
|
|
116
|
+
}
|
|
117
|
+
return { ok: true, snapshot: retained };
|
|
118
|
+
}
|
|
119
|
+
db.transaction(() => {
|
|
120
|
+
db.prepare("UPDATE pdpp_current_declarations SET is_current = 0 WHERE source_id = ?").run(parsed.snapshot.source_id);
|
|
121
|
+
db.prepare(`INSERT INTO pdpp_current_declarations
|
|
122
|
+
(source_id, version, digest, snapshot_json, document, accepted_at, is_current, origin)
|
|
123
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?)`).run(parsed.snapshot.source_id, parsed.snapshot.version, parsed.snapshot.digest, JSON.stringify(parsed.snapshot), document, new Date().toISOString(), admission);
|
|
124
|
+
})();
|
|
125
|
+
return { ok: true, snapshot: parsed.snapshot };
|
|
126
|
+
}
|
|
127
|
+
// Seeds go through the same `accept` as a submission — one acceptance path,
|
|
128
|
+
// so a configured declaration and a submitted one cannot diverge on what
|
|
129
|
+
// counts as valid. A seed for a source whose current declaration was
|
|
130
|
+
// submitted is skipped: the operator's later explicit decision wins over
|
|
131
|
+
// boot config. Otherwise the seed becomes current, so an installer that
|
|
132
|
+
// upgrades a declaration and restarts the server is followed.
|
|
133
|
+
for (const seed of options.seed ?? []) {
|
|
134
|
+
const submitted = db
|
|
135
|
+
.prepare(`SELECT 1 FROM pdpp_current_declarations
|
|
136
|
+
WHERE source_id = ? AND is_current = 1 AND origin = 'submitted'`)
|
|
137
|
+
.get(seed.sourceId);
|
|
138
|
+
if (submitted)
|
|
139
|
+
continue;
|
|
140
|
+
const result = accept(seed.document, "configured");
|
|
141
|
+
if (!result.ok) {
|
|
142
|
+
options.logger.warn({ sourceId: seed.sourceId, reason: result.failure.message }, "PDPP declaration rejected: not retained");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
resolve(sourceId) {
|
|
147
|
+
const row = db
|
|
148
|
+
.prepare(`SELECT snapshot_json FROM pdpp_current_declarations
|
|
149
|
+
WHERE source_id = ? AND is_current = 1`)
|
|
150
|
+
.get(sourceId);
|
|
151
|
+
return row
|
|
152
|
+
? JSON.parse(row.snapshot_json)
|
|
153
|
+
: null;
|
|
154
|
+
},
|
|
155
|
+
documentFor(sourceId) {
|
|
156
|
+
const row = db
|
|
157
|
+
.prepare(`SELECT document FROM pdpp_current_declarations
|
|
158
|
+
WHERE source_id = ? AND is_current = 1`)
|
|
159
|
+
.get(sourceId);
|
|
160
|
+
return row?.document ?? null;
|
|
161
|
+
},
|
|
162
|
+
list() {
|
|
163
|
+
const rows = db
|
|
164
|
+
.prepare(`SELECT snapshot_json FROM pdpp_current_declarations
|
|
165
|
+
WHERE is_current = 1 ORDER BY source_id`)
|
|
166
|
+
.all();
|
|
167
|
+
return rows.map((r) => JSON.parse(r.snapshot_json));
|
|
168
|
+
},
|
|
169
|
+
submit: (document) => accept(document, "submitted"),
|
|
170
|
+
close() {
|
|
171
|
+
db.close();
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function migrateSchema(db) {
|
|
176
|
+
const columns = db
|
|
177
|
+
.prepare("PRAGMA table_info(pdpp_current_declarations)")
|
|
178
|
+
.all();
|
|
179
|
+
if (columns.length === 0) {
|
|
180
|
+
db.exec(SCHEMA_SQL);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (columns.some((column) => column.name === "is_current")) {
|
|
184
|
+
if (!columns.some((column) => column.name === "origin")) {
|
|
185
|
+
// Rows written before `origin` existed cannot be told apart. Treating
|
|
186
|
+
// them as configured lets boot config move past them; a submission
|
|
187
|
+
// that should outrank config must be submitted again.
|
|
188
|
+
db.exec("ALTER TABLE pdpp_current_declarations ADD COLUMN origin TEXT NOT NULL DEFAULT 'configured'");
|
|
189
|
+
}
|
|
190
|
+
db.exec(SCHEMA_SQL);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
db.transaction(() => {
|
|
194
|
+
db.exec("ALTER TABLE pdpp_current_declarations RENAME TO pdpp_current_declarations_legacy");
|
|
195
|
+
db.exec(SCHEMA_SQL);
|
|
196
|
+
db.exec(`INSERT INTO pdpp_current_declarations
|
|
197
|
+
(source_id, version, digest, snapshot_json, document, accepted_at, is_current)
|
|
198
|
+
SELECT source_id, version, digest, snapshot_json, document, accepted_at, 1
|
|
199
|
+
FROM pdpp_current_declarations_legacy`);
|
|
200
|
+
db.exec("DROP TABLE pdpp_current_declarations_legacy");
|
|
201
|
+
})();
|
|
202
|
+
}
|
|
203
|
+
function sameDeclarationContent(a, b) {
|
|
204
|
+
const { digest: _aDigest, ...aContent } = a;
|
|
205
|
+
const { digest: _bDigest, ...bContent } = b;
|
|
206
|
+
return isDeepStrictEqual(aContent, bContent);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The source id a document claims, read without validating anything else.
|
|
210
|
+
*
|
|
211
|
+
* Accepts both the normative nested shape (`source: { id }`) and the internal
|
|
212
|
+
* flat one (`source_id`), because `parseDeclaration` normalizes between them
|
|
213
|
+
* and this has to agree with whichever it will read.
|
|
214
|
+
*/
|
|
215
|
+
function claimedSourceId(document) {
|
|
216
|
+
let doc;
|
|
217
|
+
try {
|
|
218
|
+
doc = JSON.parse(document);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (typeof doc !== "object" || doc === null)
|
|
224
|
+
return null;
|
|
225
|
+
const record = doc;
|
|
226
|
+
const nested = record.source?.id;
|
|
227
|
+
if (typeof nested === "string" && nested.length > 0)
|
|
228
|
+
return nested;
|
|
229
|
+
if (typeof record.source_id === "string" && record.source_id.length > 0) {
|
|
230
|
+
return record.source_id;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The connector a source id belongs to — the last path segment of the URI
|
|
236
|
+
* (`https://registry.pdpp.dev/connectors/spotify` → `spotify`). Falls back to
|
|
237
|
+
* the whole id for an unparseable value, so an odd source fails the supported
|
|
238
|
+
* check rather than silently matching a connector it is not.
|
|
239
|
+
*/
|
|
240
|
+
function connectorNameFor(sourceId) {
|
|
241
|
+
try {
|
|
242
|
+
const segments = new URL(sourceId).pathname
|
|
243
|
+
.split("/")
|
|
244
|
+
.filter((s) => s.length > 0);
|
|
245
|
+
return segments[segments.length - 1] ?? sourceId;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return sourceId;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=declaration-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declaration-registry.js","sourceRoot":"","sources":["../../src/pdpp/declaration-registry.ts"],"names":[],"mappings":"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"}
|