@argentic/chest-sdk 0.3.0 → 0.4.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 +208 -43
- package/client/index.ts +4 -3
- package/client/src/api.ts +18 -0
- package/client/src/chest.ts +30 -6
- package/client/src/events.ts +12 -104
- package/client/src/files.ts +9 -9
- package/client/src/members.ts +18 -13
- package/client/src/schedules.ts +87 -0
- package/client/src/signed.ts +166 -0
- package/client/src/testing.ts +132 -54
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/src/api.d.ts +2 -0
- package/dist/src/api.d.ts.map +1 -1
- package/dist/src/api.js +18 -0
- package/dist/src/api.js.map +1 -1
- package/dist/src/chest.d.ts +5 -0
- package/dist/src/chest.d.ts.map +1 -1
- package/dist/src/chest.js +13 -1
- package/dist/src/chest.js.map +1 -1
- package/dist/src/events.d.ts +3 -6
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +8 -102
- package/dist/src/events.js.map +1 -1
- package/dist/src/files.d.ts +1 -0
- package/dist/src/files.d.ts.map +1 -1
- package/dist/src/files.js +6 -7
- package/dist/src/files.js.map +1 -1
- package/dist/src/members.d.ts +1 -1
- package/dist/src/members.d.ts.map +1 -1
- package/dist/src/members.js +6 -5
- package/dist/src/members.js.map +1 -1
- package/dist/src/schedules.d.ts +15 -0
- package/dist/src/schedules.d.ts.map +1 -0
- package/dist/src/schedules.js +45 -0
- package/dist/src/schedules.js.map +1 -0
- package/dist/src/signed.d.ts +29 -0
- package/dist/src/signed.d.ts.map +1 -0
- package/dist/src/signed.js +139 -0
- package/dist/src/signed.js.map +1 -0
- package/dist/src/testing.d.ts +15 -5
- package/dist/src/testing.d.ts.map +1 -1
- package/dist/src/testing.js +124 -52
- package/dist/src/testing.js.map +1 -1
- package/package.json +14 -3
package/client/src/testing.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash,
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
3
3
|
import type { AddressInfo } from "node:net";
|
|
4
4
|
import type { Alias, Provider } from "./ai.js";
|
|
@@ -6,21 +6,25 @@ import type { AiUnavailableReason } from "./errors.js";
|
|
|
6
6
|
import type { ChestEvent } from "./events.js";
|
|
7
7
|
import { groupIdPattern, memberIdPattern, type Member } from "./member.js";
|
|
8
8
|
import { forget } from "./members.js";
|
|
9
|
+
import { fakeOrigins } from "./api.js";
|
|
10
|
+
import { eventChannel, scheduleChannel, sign, signClaims, type Channel } from "./signed.js";
|
|
9
11
|
|
|
10
12
|
// For a tool's own tests, never imported by its production code: a member's
|
|
11
13
|
// assertion signed as the Chest signs it, and a Chest's API in the test's
|
|
12
|
-
// process that answers members, groups, files (stat, move, links
|
|
13
|
-
// it
|
|
14
|
+
// process that answers members, groups, files (stat, move, links and
|
|
15
|
+
// uploads, which it serves and takes itself as the team host would, on its
|
|
16
|
+
// own origin), badges, notifications, AI (chat,
|
|
14
17
|
// streamed or not, embeddings, models, usage: deterministic answers, no
|
|
15
18
|
// provider) and the acknowledgment of an erasure with the Chest's bounds,
|
|
16
|
-
// quotas and errors; and that delivers an event
|
|
17
|
-
// Chest signs
|
|
19
|
+
// quotas and errors; and that delivers an event or a run of a schedule to
|
|
20
|
+
// the tool, signed as the Chest signs them.
|
|
18
21
|
//
|
|
19
22
|
// import { fakeChest, withMember } from "@argentic/chest-sdk/testing";
|
|
20
23
|
// const chest = await fakeChest({ members: [camille], capabilities: ["members", "files", "notifications"] });
|
|
21
24
|
// const response = await app(withMember(new Request("http://tool/chest"), camille));
|
|
22
25
|
// assert.equal(chest.notifications[0]?.member, camille.id);
|
|
23
26
|
// assert.equal(await chest.emit({ type: "access.revoked", data: { id: camille.id } }, request => app(request)), 204);
|
|
27
|
+
// assert.equal(await chest.run("morning", request => app(request)), 204);
|
|
24
28
|
// await chest.close();
|
|
25
29
|
|
|
26
30
|
// A group as a fake Chest keeps it: its identifier, its name, and the
|
|
@@ -28,6 +32,10 @@ import { forget } from "./members.js";
|
|
|
28
32
|
export type FakeGroup = { id: string; name: string; members: string[] };
|
|
29
33
|
// A file as a fake Chest keeps it.
|
|
30
34
|
export type FakeFile = { data: Uint8Array; type: string; updated: string };
|
|
35
|
+
// Someone the tool had who no longer has it: a member of the Chest without
|
|
36
|
+
// access to it, a member who left ("former", by default), or one whose data
|
|
37
|
+
// was erased (their name gone).
|
|
38
|
+
export type FakeFormer = { id: string; name?: string; status?: "no_access" | "former" | "erased" };
|
|
31
39
|
// A notification as a fake Chest keeps it: the member it went to, its text
|
|
32
40
|
// cleaned as the Chest cleans it, its path (/chest when not said) and its
|
|
33
41
|
// key.
|
|
@@ -51,22 +59,24 @@ export type FakeAi = { models?: FakeAiModel[]; reply?: (request: Record<string,
|
|
|
51
59
|
// sent (null for a GET).
|
|
52
60
|
export type FakeAiCall = { path: string; body: unknown };
|
|
53
61
|
|
|
54
|
-
// What a fake Chest is given: the members who have the tool, those
|
|
55
|
-
//
|
|
62
|
+
// What a fake Chest is given: the members who have the tool, those it had
|
|
63
|
+
// who no longer have it (FakeFormer), its groups, the
|
|
56
64
|
// capabilities its version holds (a capability left out answers 403;
|
|
57
65
|
// members, files, notifications and ai by default, members.email to read the
|
|
58
66
|
// addresses), the events it receives (["member.*"] by default, [] to answer
|
|
59
67
|
// an acknowledgment 403), the files it keeps, its AI, and what the Chest is
|
|
60
|
-
// (the chest module: "Test organization", UTC and
|
|
68
|
+
// (the chest module: "Test organization", UTC, English and euros by default;
|
|
69
|
+
// the tool at https://<tool>-chest.chest.test, its public part at
|
|
70
|
+
// https://<tool>.chest.test).
|
|
61
71
|
export type FakeChestOptions = {
|
|
62
72
|
members?: Member[];
|
|
63
|
-
former?:
|
|
73
|
+
former?: FakeFormer[];
|
|
64
74
|
groups?: FakeGroup[];
|
|
65
75
|
capabilities?: string[];
|
|
66
76
|
receives?: string[];
|
|
67
77
|
files?: Record<string, { data: Uint8Array | string; type?: string }>;
|
|
68
78
|
ai?: FakeAi;
|
|
69
|
-
chest?: { organization?: string; timeZone?: string; language?: string };
|
|
79
|
+
chest?: { organization?: string; timeZone?: string; language?: string; currency?: string; teamUrl?: string; publicUrl?: string | null };
|
|
70
80
|
};
|
|
71
81
|
|
|
72
82
|
// An event for emit: its type and data; its id (a new evt_… by default) and
|
|
@@ -74,13 +84,20 @@ export type FakeChestOptions = {
|
|
|
74
84
|
// twice.
|
|
75
85
|
export type FakeEvent = { [K in ChestEvent["type"]]: { type: K; data: Extract<ChestEvent, { type: K }>["data"]; id?: string; occurredAt?: string } }[ChestEvent["type"]];
|
|
76
86
|
|
|
77
|
-
// A
|
|
78
|
-
//
|
|
87
|
+
// A run for run: its id (a new run_… by default: name it to deliver the same
|
|
88
|
+
// run twice), the time it stands for (now by default) and its attempt (1 by
|
|
89
|
+
// default).
|
|
90
|
+
export type FakeRun = { id?: string; scheduledAt?: string; attempt?: number };
|
|
91
|
+
|
|
92
|
+
// A fake Chest in the test's process: its address — its API, and the origin
|
|
93
|
+
// where it serves the links and takes the uploads it signs —, the token and
|
|
94
|
+
// the tool it set in the environment, what it keeps (members, groups and files a test
|
|
79
95
|
// changes or reads; the notifications the tool sent, in the order sent, a
|
|
80
96
|
// replaced one last; each member's badge; the erasures the tool
|
|
81
97
|
// acknowledged; its calls to AI, in order), emit, which delivers an event to the tool — POST
|
|
82
98
|
// /chest-events of its address, or a handler of Web Requests — and says the
|
|
83
|
-
// status it answered,
|
|
99
|
+
// status it answered, run, which delivers a run of a schedule the same way
|
|
100
|
+
// on /chest-schedules, and close, which stops it and restores the
|
|
84
101
|
// environment. Its members are those who have the tool: the others are
|
|
85
102
|
// skipped.
|
|
86
103
|
export type FakeChest = {
|
|
@@ -95,11 +112,10 @@ export type FakeChest = {
|
|
|
95
112
|
acknowledged: string[];
|
|
96
113
|
ai: FakeAiCall[];
|
|
97
114
|
emit(event: FakeEvent, to: string | ((request: Request) => Response | Promise<Response>)): Promise<number>;
|
|
115
|
+
run(name: string, to: string | ((request: Request) => Response | Promise<Response>), run?: FakeRun): Promise<number>;
|
|
98
116
|
close(): Promise<void>;
|
|
99
117
|
};
|
|
100
118
|
|
|
101
|
-
const encode = (value: unknown): string => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
102
|
-
|
|
103
119
|
// What an assertion is signed with and says besides the member: the token
|
|
104
120
|
// (CHEST_TOKEN by default) and the tool (CHEST_TOOL by default) it is for,
|
|
105
121
|
// and when it is issued (now by default). The member is signed as given: a
|
|
@@ -116,27 +132,16 @@ export function signAssertion(member: Member, options: AssertionOptions = {}): s
|
|
|
116
132
|
if (!token || !tool) throw new Error("signAssertion needs a token and a tool: start a fakeChest, or name them");
|
|
117
133
|
if (!memberIdPattern.test(member.id) || !member.groups.every(g => groupIdPattern.test(g))) throw new Error("signAssertion needs identifiers of the Chest's shape (mbr_…, grp_…)");
|
|
118
134
|
const iat = Math.floor((options.now ?? new Date()).getTime() / 1000);
|
|
119
|
-
|
|
135
|
+
// Under the label of the assertion's shape, as the Chest signs it and
|
|
136
|
+
// member() reads it.
|
|
137
|
+
return signClaims("Chest-Member v2", {
|
|
120
138
|
iss: `https://${tool}-chest.chest.test`, aud: tool, iat, exp: iat + 60, sub: member.id,
|
|
121
139
|
given_name: member.firstName, family_name: member.lastName, name: member.name, picture: member.photo ?? "", role: member.role ?? "",
|
|
122
140
|
admin: member.isAdmin, builder: member.isBuilder, groups: member.groups, time_zone: member.timeZone, ...(member.email === undefined ? {} : { email: member.email }),
|
|
123
141
|
language: member.language,
|
|
124
|
-
});
|
|
125
|
-
// The key as the Chest derives it, and member() reads it: HMAC-SHA256 of
|
|
126
|
-
// the label of the assertion's shape under the text of the token.
|
|
127
|
-
const key = createHmac("sha256", Buffer.from(token, "utf8")).update("Chest-Member v2").digest();
|
|
128
|
-
return body + "." + createHmac("sha256", key).update(body).digest("base64url");
|
|
142
|
+
}, token);
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
// signEvent is the Chest-Event value the Chest would send with that body:
|
|
132
|
-
// HS256 under the key the token derives for events, for the tool, naming the
|
|
133
|
-
// event and the digest of the body, valid 60 seconds.
|
|
134
|
-
function signEvent(id: string, body: string, options: { token: string; tool: string }): string {
|
|
135
|
-
const iat = Math.floor(Date.now() / 1000);
|
|
136
|
-
const signed = encode({ alg: "HS256", typ: "JWT" }) + "." + encode({ aud: options.tool, iat, exp: iat + 60, jti: id, digest: createHash("sha256").update(body).digest("base64url") });
|
|
137
|
-
const key = createHmac("sha256", Buffer.from(options.token, "utf8")).update("Chest-Event v1").digest();
|
|
138
|
-
return signed + "." + createHmac("sha256", key).update(signed).digest("base64url");
|
|
139
|
-
}
|
|
140
145
|
|
|
141
146
|
// withMember is the request carrying that member's assertion, signed with
|
|
142
147
|
// the options of signAssertion: a new Web Request, or the same Node request
|
|
@@ -156,6 +161,19 @@ export function withMember<R extends Request | IncomingMessage>(request: R, memb
|
|
|
156
161
|
const maxLimit = 500, defaultLimit = 100, maxLookup = 200, callsPerMinute = 600;
|
|
157
162
|
const maxObject = 32 << 20, maxObjects = 10000, maxTotal = 1 << 30;
|
|
158
163
|
const namePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}(\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}){0,7}$/u;
|
|
164
|
+
// Its links and uploads: their paths on the team host, their lives in
|
|
165
|
+
// seconds, the images it makes thumbnails of, the first bytes an upload of
|
|
166
|
+
// a type must start with, the endings of the names it chooses (chest/toolfiles).
|
|
167
|
+
const linkPath = "/_chest/files/", uploadPath = "/_chest/files/upload/", linkLife = 900, uploadLife = 900;
|
|
168
|
+
const mediaPattern = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,62}\/(\*|[a-z0-9][a-z0-9!#$&^_.+-]{0,62})$/u;
|
|
169
|
+
const thumbnailed = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
170
|
+
const starts = (...heads: string[]) => (data: Buffer): boolean => heads.some(head => data.subarray(0, head.length).equals(Buffer.from(head, "latin1")));
|
|
171
|
+
const signatures: Record<string, (data: Buffer) => boolean> = {
|
|
172
|
+
"image/jpeg": starts("\xff\xd8\xff"), "image/png": starts("\x89PNG\r\n\x1a\n"), "image/gif": starts("GIF87a", "GIF89a"),
|
|
173
|
+
"image/webp": data => data.subarray(0, 4).toString("latin1") === "RIFF" && data.subarray(8, 12).toString("latin1") === "WEBP", "application/pdf": starts("%PDF-"),
|
|
174
|
+
};
|
|
175
|
+
const extensions: Record<string, string> = { "image/jpeg": ".jpg", "image/png": ".png", "image/gif": ".gif", "image/webp": ".webp", "image/avif": ".avif", "image/heic": ".heic", "application/pdf": ".pdf", "text/plain": ".txt", "text/csv": ".csv", "application/json": ".json", "application/zip": ".zip", "video/mp4": ".mp4", "audio/mpeg": ".mp3" };
|
|
176
|
+
const newToken = (): string => randomBytes(18).toString("base64url") + "." + randomBytes(12).toString("base64url");
|
|
159
177
|
// Its notifications: recipients and badges a call, text, quotas.
|
|
160
178
|
const maxRecipients = 500, maxTitle = 80, maxText = 280, maxPath = 512, maxCount = 9999;
|
|
161
179
|
const recipientsPerHour = 1000, itemsPerDay = 100, badgesPerMinute = 600;
|
|
@@ -216,12 +234,12 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
216
234
|
const receives = options.receives ?? ["member.*"];
|
|
217
235
|
// The erasures the tool was told of, by emit: those it may acknowledge.
|
|
218
236
|
const erasures = new Set<string>();
|
|
219
|
-
const chest: FakeChest = { api: "", token, tool, members: [...(options.members ?? [])], groups: [...(options.groups ?? [])], files, notifications: [], badges: new Map(), acknowledged: [], ai: [], emit: async () => 0, close: async () => {} };
|
|
237
|
+
const chest: FakeChest = { api: "", token, tool, members: [...(options.members ?? [])], groups: [...(options.groups ?? [])], files, notifications: [], badges: new Map(), acknowledged: [], ai: [], emit: async () => 0, run: async () => 0, close: async () => {} };
|
|
220
238
|
const former = [...(options.former ?? [])];
|
|
221
239
|
let window = 0, calls = 0;
|
|
222
240
|
const shown = (m: Member) => ({ id: m.id, first_name: m.firstName, last_name: m.lastName, name: m.name, photo: m.photo, role: m.role, admin: m.isAdmin, builder: m.isBuilder, groups: m.groups, language: m.language, time_zone: m.timeZone, ...(email && m.email !== undefined ? { email: m.email } : {}) });
|
|
223
241
|
const key = (m: Member) => fold(m.name) + "\u0000" + m.id;
|
|
224
|
-
const described = (name: string, f: FakeFile) => ({ name, type: f.type, size: f.data.byteLength, updated: f.updated });
|
|
242
|
+
const described = (name: string, f: FakeFile) => ({ name, type: f.type, size: f.data.byteLength, sha256: createHash("sha256").update(f.data).digest("hex"), updated: f.updated });
|
|
225
243
|
|
|
226
244
|
async function members(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {
|
|
227
245
|
if (!capabilities.has("members")) return send(response, 403, { error: "capability_not_granted" });
|
|
@@ -252,7 +270,8 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
252
270
|
for (const id of new Set(ids as string[])) {
|
|
253
271
|
const m = chest.members.find(x => x.id === id), f = former.find(x => x.id === id);
|
|
254
272
|
if (m) answer.members.push(shown(m));
|
|
255
|
-
else if (f?.erased) answer.former.push({ id, status: "erased" });
|
|
273
|
+
else if (f?.status === "erased") answer.former.push({ id, status: "erased" });
|
|
274
|
+
else if (f?.status === "no_access") answer.former.push({ id, name: f.name ?? "Test member", status: "no_access" });
|
|
256
275
|
else if (f) answer.former.push({ id, ...(f.name ? { name: f.name } : {}), status: "former" });
|
|
257
276
|
else answer.unknown.push(id);
|
|
258
277
|
}
|
|
@@ -275,23 +294,30 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
275
294
|
const names = [...files.keys()].filter(n => n.startsWith(prefix) && n > after).sort();
|
|
276
295
|
return send(response, 200, { files: names.slice(0, 1000).map(n => described(n, files.get(n)!)), next: names.length > 1000 ? names[999] : null });
|
|
277
296
|
}
|
|
278
|
-
if (request.method === "POST" && url.pathname === "/files/url") {
|
|
279
|
-
const raw = await body(request, 4096);
|
|
280
|
-
let name: unknown;
|
|
281
|
-
try { name = (JSON.parse(raw?.toString() ?? "") as { name?: unknown }).name; } catch { name = undefined; }
|
|
282
|
-
if (typeof name !== "string") return send(response, 400, { error: "invalid_body" });
|
|
283
|
-
if (!files.has(name)) return send(response, 404, { error: "not_found" });
|
|
284
|
-
return send(response, 200, { url: `https://${tool}-chest.chest.test/_chest/files/${Buffer.from(name).toString("base64url")}.fake`, expires_in: 900 });
|
|
285
|
-
}
|
|
286
|
-
if (request.method === "POST" && (url.pathname === "/files/move" || url.pathname === "/files/upload-url")) {
|
|
297
|
+
if (request.method === "POST" && (url.pathname === "/files/url" || url.pathname === "/files/move" || url.pathname === "/files/upload-url")) {
|
|
287
298
|
const raw = await body(request, 4096);
|
|
288
299
|
let command: Record<string, unknown> = {};
|
|
289
300
|
try { command = JSON.parse(raw?.toString() ?? "") as Record<string, unknown>; } catch { command = {}; }
|
|
301
|
+
if (url.pathname === "/files/url") {
|
|
302
|
+
const { name, thumbnail, download } = command;
|
|
303
|
+
if (typeof name !== "string" || !(thumbnail === undefined || thumbnail === 256 || thumbnail === 1024) || !(download === undefined || typeof download === "boolean")) return send(response, 400, { error: "invalid_body" });
|
|
304
|
+
const object = files.get(name);
|
|
305
|
+
if (!object) return send(response, 404, { error: "not_found" });
|
|
306
|
+
if (thumbnail !== undefined && !thumbnailed.includes(object.type)) return send(response, 400, { error: "no_thumbnail" });
|
|
307
|
+
const token = newToken();
|
|
308
|
+
links.set(token, { name, object, until: Date.now() + linkLife * 1000, download: download === true });
|
|
309
|
+
return send(response, 200, { url: `${chest.api}${linkPath}${token}`, expires_in: linkLife });
|
|
310
|
+
}
|
|
290
311
|
if (url.pathname === "/files/upload-url") {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (typeof
|
|
294
|
-
|
|
312
|
+
const { name, max_size: maxSize, types, expires_in: expiresIn } = command;
|
|
313
|
+
const folder = typeof name === "string" && name.endsWith("/");
|
|
314
|
+
if (typeof name !== "string" || !namePattern.test(folder ? name.slice(0, -1) : name)) return send(response, 400, { error: "invalid_name" });
|
|
315
|
+
if (!(types === undefined || (Array.isArray(types) && types.length <= 8 && types.every(t => typeof t === "string" && mediaPattern.test(t))))) return send(response, 400, { error: "invalid_type" });
|
|
316
|
+
if (!(maxSize === undefined || (typeof maxSize === "number" && Number.isSafeInteger(maxSize) && maxSize > 0)) || !(expiresIn === undefined || (typeof expiresIn === "number" && Number.isInteger(expiresIn) && expiresIn > 0 && expiresIn <= uploadLife))) return send(response, 400, { error: "invalid_body" });
|
|
317
|
+
if ((maxSize ?? 0) > maxObject) return send(response, 413, { error: "too_large" });
|
|
318
|
+
const token = newToken(), life = (expiresIn as number | undefined) ?? uploadLife;
|
|
319
|
+
uploads.set(token, { name, maxSize: (maxSize as number | undefined) ?? maxObject, types: (types as string[] | undefined) ?? [], until: Date.now() + life * 1000 });
|
|
320
|
+
return send(response, 200, { url: `${chest.api}${uploadPath}${token}`, method: "PUT", expires_in: life });
|
|
295
321
|
}
|
|
296
322
|
const from = command["from"], to = command["to"];
|
|
297
323
|
if (typeof from !== "string" || typeof to !== "string" || !namePattern.test(from) || !namePattern.test(to)) return send(response, 400, { error: "invalid_name" });
|
|
@@ -323,6 +349,39 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
323
349
|
send(response, 404, { error: "not_found" });
|
|
324
350
|
}
|
|
325
351
|
|
|
352
|
+
// The team host's part of the files, on the fake's own origin: a link it
|
|
353
|
+
// signed opens the content it was signed for, for its life, as long as
|
|
354
|
+
// the file is that content — a thumbnail is the image itself, a fake does
|
|
355
|
+
// not reduce it —; an upload token takes one file, once, within its life,
|
|
356
|
+
// of the types it names and up to its size, whose first bytes are those of
|
|
357
|
+
// its type, named by the Chest in a folder. Nobody's session is checked:
|
|
358
|
+
// the Chest's own front does that.
|
|
359
|
+
const links = new Map<string, { name: string; object: FakeFile; until: number; download: boolean }>();
|
|
360
|
+
const uploads = new Map<string, { name: string; maxSize: number; types: string[]; until: number }>();
|
|
361
|
+
async function front(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {
|
|
362
|
+
if (request.method === "GET" && url.pathname.startsWith(linkPath) && !url.pathname.startsWith(uploadPath)) {
|
|
363
|
+
const link = links.get(url.pathname.slice(linkPath.length));
|
|
364
|
+
if (!link || link.until < Date.now() || files.get(link.name) !== link.object) return send(response, 404, { error: "not_found" });
|
|
365
|
+
const headers: Record<string, string> = { "Content-Type": link.object.type, "Content-Length": String(link.object.data.byteLength), "X-Content-Type-Options": "nosniff" };
|
|
366
|
+
if (link.download) headers["Content-Disposition"] = `attachment; filename="${link.name.split("/").pop()}"`;
|
|
367
|
+
return void response.writeHead(200, headers).end(link.object.data);
|
|
368
|
+
}
|
|
369
|
+
if (request.method !== "PUT" || !url.pathname.startsWith(uploadPath)) return send(response, 404, { error: "not_found" });
|
|
370
|
+
const token = url.pathname.slice(uploadPath.length), grant = uploads.get(token);
|
|
371
|
+
uploads.delete(token);
|
|
372
|
+
if (!grant || grant.until < Date.now()) return send(response, 403, { error: "invalid_token" });
|
|
373
|
+
const type = (request.headers["content-type"] ?? "").split(";")[0]!.trim().toLowerCase();
|
|
374
|
+
if (!mediaPattern.test(type) || type.endsWith("/*") || (grant.types.length > 0 && !grant.types.some(t => t === type || (t.endsWith("/*") && type.startsWith(t.slice(0, -1)))))) return send(response, 415, { error: "type_refused" });
|
|
375
|
+
const data = await body(request, grant.maxSize);
|
|
376
|
+
if (data === null) return send(response, 413, { error: "too_large" });
|
|
377
|
+
if (signatures[type] && !signatures[type]!(data)) return send(response, 400, { error: "type_mismatch" });
|
|
378
|
+
const name = grant.name.endsWith("/") ? grant.name + randomBytes(10).toString("hex") + (extensions[type] ?? "") : grant.name;
|
|
379
|
+
const total = [...files.entries()].reduce((sum, [n, f]) => n === name ? sum : sum + f.data.byteLength, 0);
|
|
380
|
+
if (total + data.length > maxTotal || (!files.has(name) && files.size >= maxObjects)) return send(response, 429, { error: "quota_exceeded" });
|
|
381
|
+
files.set(name, { data: new Uint8Array(data), type, updated: new Date().toISOString() });
|
|
382
|
+
send(response, 201, { name, type, size: data.length });
|
|
383
|
+
}
|
|
384
|
+
|
|
326
385
|
// The windows of the notification quotas, each from the first call it
|
|
327
386
|
// counts: the tool's recipients this hour, each member's items this day,
|
|
328
387
|
// the tool's badge writes this minute.
|
|
@@ -529,7 +588,8 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
529
588
|
|
|
530
589
|
const server = createServer((request, response) => {
|
|
531
590
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
532
|
-
const route = url.pathname.startsWith("/
|
|
591
|
+
const route = url.pathname.startsWith("/_chest/files/") ? front
|
|
592
|
+
: url.pathname.startsWith("/erasures/") ? erasuresRoute
|
|
533
593
|
: url.pathname.startsWith("/ai/") ? aiRoute
|
|
534
594
|
: url.pathname === "/members" || url.pathname.startsWith("/members/") || url.pathname === "/groups" ? members
|
|
535
595
|
: url.pathname === "/files" || url.pathname.startsWith("/files/") ? filesRoute
|
|
@@ -538,20 +598,38 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
538
598
|
route(request, response, url).catch(() => { if (!response.headersSent) send(response, 503, { error: "unavailable" }); else response.destroy(); });
|
|
539
599
|
});
|
|
540
600
|
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve));
|
|
541
|
-
const saved = Object.fromEntries(["CHEST_API", "CHEST_TOKEN", "CHEST_TOOL", "CHEST_ORGANIZATION", "CHEST_TIME_ZONE", "CHEST_LANGUAGE"].map(name => [name, process.env[name]]));
|
|
601
|
+
const saved = Object.fromEntries(["CHEST_API", "CHEST_TOKEN", "CHEST_TOOL", "CHEST_ORGANIZATION", "CHEST_TIME_ZONE", "CHEST_LANGUAGE", "CHEST_CURRENCY", "CHEST_TEAM_URL", "CHEST_PUBLIC_URL"].map(name => [name, process.env[name]]));
|
|
542
602
|
chest.api = "http://127.0.0.1:" + (server.address() as AddressInfo).port;
|
|
543
|
-
|
|
603
|
+
// Its links are taken by the files module of this process, for as long as
|
|
604
|
+
// it runs (api.ts, fakeOrigins).
|
|
605
|
+
fakeOrigins.add(chest.api);
|
|
606
|
+
const publicUrl = options.chest?.publicUrl === undefined ? `https://${tool}.chest.test` : options.chest.publicUrl;
|
|
607
|
+
Object.assign(process.env, { CHEST_API: chest.api, CHEST_TOKEN: token, CHEST_TOOL: tool, CHEST_ORGANIZATION: options.chest?.organization ?? "Test organization", CHEST_TIME_ZONE: options.chest?.timeZone ?? "UTC", CHEST_LANGUAGE: options.chest?.language ?? "en", CHEST_CURRENCY: options.chest?.currency ?? "EUR", CHEST_TEAM_URL: options.chest?.teamUrl ?? `https://${tool}-chest.chest.test` });
|
|
608
|
+
if (publicUrl === null) delete process.env["CHEST_PUBLIC_URL"];
|
|
609
|
+
else process.env["CHEST_PUBLIC_URL"] = publicUrl;
|
|
544
610
|
forget();
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const request = new Request(typeof to === "string" ? to.replace(/\/$/u, "") + "/chest-events" : "http://tool.test/chest-events", { method: "POST", headers: { "Content-Type": "application/json", "Chest-Event": signEvent(id, body, { token, tool }) }, body });
|
|
611
|
+
// post delivers what the Chest posts to the tool on a channel — at its
|
|
612
|
+
// address, or to a handler — and says the status it answered.
|
|
613
|
+
const post = async (path: string, channel: Channel, id: string, body: string, to: string | ((request: Request) => Response | Promise<Response>)): Promise<number> => {
|
|
614
|
+
const request = new Request((typeof to === "string" ? to.replace(/\/$/u, "") : "http://tool.test") + path, { method: "POST", headers: { "Content-Type": "application/json", [channel.header]: sign(channel, id, body, { token, tool }) }, body });
|
|
550
615
|
const answer = typeof to === "string" ? await fetch(request, { redirect: "manual" }) : await to(request);
|
|
551
616
|
await answer.body?.cancel();
|
|
552
617
|
return answer.status;
|
|
553
618
|
};
|
|
619
|
+
const newId = (prefix: string): string => prefix + Array.from(randomBytes(26), b => "abcdefghijklmnopqrstuvwxyz234567"[b & 31]).join("");
|
|
620
|
+
chest.emit = async (event, to) => {
|
|
621
|
+
const id = event.id ?? newId("evt_");
|
|
622
|
+
const body = JSON.stringify({ id, type: event.type, occurredAt: event.occurredAt ?? new Date().toISOString(), data: event.data });
|
|
623
|
+
if (event.type === "member.erased") erasures.add(event.data.erasure);
|
|
624
|
+
return post("/chest-events", eventChannel, id, body, to);
|
|
625
|
+
};
|
|
626
|
+
chest.run = async (name, to, run = {}) => {
|
|
627
|
+
const id = run.id ?? newId("run_");
|
|
628
|
+
const body = JSON.stringify({ id, name, scheduledAt: run.scheduledAt ?? new Date().toISOString(), attempt: run.attempt ?? 1 });
|
|
629
|
+
return post("/chest-schedules", scheduleChannel, id, body, to);
|
|
630
|
+
};
|
|
554
631
|
chest.close = async () => {
|
|
632
|
+
fakeOrigins.delete(chest.api);
|
|
555
633
|
server.closeAllConnections();
|
|
556
634
|
await new Promise<void>(resolve => server.close(() => resolve()));
|
|
557
635
|
for (const [name, value] of Object.entries(saved)) {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export * as files from "./src/files.js";
|
|
|
6
6
|
export * as members from "./src/members.js";
|
|
7
7
|
export * as notifications from "./src/notifications.js";
|
|
8
8
|
export * as events from "./src/events.js";
|
|
9
|
+
export * as schedules from "./src/schedules.js";
|
|
9
10
|
export * as ai from "./src/ai.js";
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAOA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAOA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The package root: every published module a tool's code uses (tool contract
|
|
2
|
-
//
|
|
3
|
-
// /database, /files, /members, /notifications, /events, /ai, /errors), which
|
|
4
|
-
// pulls in nothing else. The files, members, notifications, events and ai
|
|
2
|
+
// 0.4). Each one is also its own subpath (@argentic/chest-sdk/member, /chest,
|
|
3
|
+
// /database, /files, /members, /notifications, /events, /schedules, /ai, /errors), which
|
|
4
|
+
// pulls in nothing else. The files, members, notifications, events, schedules and ai
|
|
5
5
|
// APIs are namespaces here, as their names (get, list, stat, move, notify,
|
|
6
6
|
// verify, chat…) are too plain to stand alone. @argentic/chest-sdk/testing is for a tool's tests only, and
|
|
7
7
|
// is not here.
|
|
@@ -13,5 +13,6 @@ export * as files from "./src/files.js";
|
|
|
13
13
|
export * as members from "./src/members.js";
|
|
14
14
|
export * as notifications from "./src/notifications.js";
|
|
15
15
|
export * as events from "./src/events.js";
|
|
16
|
+
export * as schedules from "./src/schedules.js";
|
|
16
17
|
export * as ai from "./src/ai.js";
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,yFAAyF;AACzF,qFAAqF;AACrF,2EAA2E;AAC3E,2GAA2G;AAC3G,eAAe;AACf,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/src/api.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ChestError } from "./errors.js";
|
|
2
|
+
export declare const fakeOrigins: Set<string>;
|
|
3
|
+
export declare function chestLink(url: unknown, path: string): string | undefined;
|
|
2
4
|
export declare function ask(capability: string, method: string, path: string, init?: {
|
|
3
5
|
body?: Uint8Array<ArrayBuffer> | string;
|
|
4
6
|
type?: string;
|
package/dist/src/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,UAAU,EAAqD,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,UAAU,EAAqD,MAAM,aAAa,CAAC;AAiBlH,eAAO,MAAM,WAAW,aAAoB,CAAC;AAK7C,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKxE;AAcD,wBAAsB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAW7M;AAID,wBAAsB,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAqBjF;AAGD,wBAAsB,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/D;AAGD,wBAAsB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAazF"}
|
package/dist/src/api.js
CHANGED
|
@@ -6,6 +6,24 @@ import { CapabilityNotGranted, ChestError, QuotaExceeded, RateLimited, TooLarge,
|
|
|
6
6
|
// identity. Not a published module.
|
|
7
7
|
const maxAnswer = 4 << 20;
|
|
8
8
|
const deadline = 120000;
|
|
9
|
+
// fakeOrigins are the origins of the fake Chests a test started in this
|
|
10
|
+
// process (testing.ts, fakeChest), each http://127.0.0.1:<port>: links and
|
|
11
|
+
// uploads a fake gives on them are taken as the Chest's https ones are. Only
|
|
12
|
+
// the testing module adds to it — the one module no production code
|
|
13
|
+
// imports —, and only for as long as its fake runs; an origin is never read
|
|
14
|
+
// from the environment, so nothing set around a tool widens what it accepts.
|
|
15
|
+
export const fakeOrigins = new Set();
|
|
16
|
+
// chestLink reads a link to the team host the Chest answered, at path (its
|
|
17
|
+
// links, its uploads): the token it carries, or undefined for an address
|
|
18
|
+
// that is not one — https, or the origin of a fake Chest of this process.
|
|
19
|
+
export function chestLink(url, path) {
|
|
20
|
+
if (typeof url !== "string")
|
|
21
|
+
return undefined;
|
|
22
|
+
const found = /^(https:\/\/[A-Za-z0-9.-]{1,253}(?::[0-9]{1,5})?|http:\/\/127\.0\.0\.1:[0-9]{1,5})(\/_chest\/[a-z/]+\/)([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/u.exec(url);
|
|
23
|
+
if (!found || found[2] !== path || (found[1].startsWith("http:") && !fakeOrigins.has(found[1])))
|
|
24
|
+
return undefined;
|
|
25
|
+
return found[3];
|
|
26
|
+
}
|
|
9
27
|
// base is the Chest's API as the launcher gives it; without, the version holds
|
|
10
28
|
// none of the capabilities that use it.
|
|
11
29
|
function base(capability) {
|
package/dist/src/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/src/chest.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export type Chest = {
|
|
|
4
4
|
};
|
|
5
5
|
readonly timeZone: string;
|
|
6
6
|
readonly language: string;
|
|
7
|
+
readonly currency: string;
|
|
8
|
+
readonly tool: {
|
|
9
|
+
readonly teamUrl: string;
|
|
10
|
+
readonly publicUrl: string | null;
|
|
11
|
+
};
|
|
7
12
|
today(at?: Date | number): string;
|
|
8
13
|
};
|
|
9
14
|
export declare const chest: Chest;
|
package/dist/src/chest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chest.d.ts","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chest.d.ts","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"AAoCA,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,YAAY,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC/E,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;CACnC,CAAC;AAqCF,eAAO,MAAM,KAAK,EAAE,KAuBnB,CAAC"}
|
package/dist/src/chest.js
CHANGED
|
@@ -2,8 +2,12 @@ import { ChestError } from "./errors.js";
|
|
|
2
2
|
import { languagePattern, timeZonePattern } from "./member.js";
|
|
3
3
|
// The shapes the Chest gives: the organization's (2 to 80 characters,
|
|
4
4
|
// counted as code points, without control characters), a zone's
|
|
5
|
-
// (timeZonePattern, and one this runtime knows), a language's
|
|
5
|
+
// (timeZonePattern, and one this runtime knows), a language's, a currency's,
|
|
6
|
+
// an origin's.
|
|
6
7
|
const organizationPattern = /^[^\u0000-\u001f\u007f-\u009f]{2,80}$/u;
|
|
8
|
+
// An ISO 4217 code; an https origin without a path, as the Chest gives them.
|
|
9
|
+
const currencyPattern = /^[A-Z]{3}$/u;
|
|
10
|
+
const originPattern = /^https:\/\/[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?(:[0-9]{1,5})?$/u;
|
|
7
11
|
function read(name, valid) {
|
|
8
12
|
const value = process.env[name];
|
|
9
13
|
if (typeof value !== "string" || !valid(value))
|
|
@@ -39,6 +43,14 @@ export const chest = {
|
|
|
39
43
|
get language() {
|
|
40
44
|
return read("CHEST_LANGUAGE", value => languagePattern.test(value));
|
|
41
45
|
},
|
|
46
|
+
get currency() {
|
|
47
|
+
return read("CHEST_CURRENCY", value => currencyPattern.test(value));
|
|
48
|
+
},
|
|
49
|
+
get tool() {
|
|
50
|
+
const teamUrl = read("CHEST_TEAM_URL", value => originPattern.test(value));
|
|
51
|
+
const publicUrl = process.env["CHEST_PUBLIC_URL"] === undefined ? null : read("CHEST_PUBLIC_URL", value => originPattern.test(value));
|
|
52
|
+
return { teamUrl, publicUrl };
|
|
53
|
+
},
|
|
42
54
|
today(at = Date.now()) {
|
|
43
55
|
const instant = typeof at === "number" ? new Date(at) : at;
|
|
44
56
|
if (Number.isNaN(instant.getTime()))
|
package/dist/src/chest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chest.js","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"chest.js","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"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"}
|
package/dist/src/events.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
|
-
|
|
2
|
+
import { type Seen } from "./signed.js";
|
|
3
|
+
export type MemberChange = "name" | "photo" | "role" | "groups" | "email" | "language" | "timeZone";
|
|
3
4
|
export type MemberUpdated = {
|
|
4
5
|
id: string;
|
|
5
6
|
type: "member.updated";
|
|
@@ -42,11 +43,7 @@ export type Handlers = {
|
|
|
42
43
|
type: K;
|
|
43
44
|
}>) => void | Promise<void>;
|
|
44
45
|
};
|
|
45
|
-
export type Seen
|
|
46
|
-
has(id: string): boolean | Promise<boolean>;
|
|
47
|
-
add(id: string): void | Promise<void>;
|
|
48
|
-
};
|
|
49
|
-
export declare function memorySeen(limit?: number): Seen;
|
|
46
|
+
export { memorySeen, type Seen } from "./signed.js";
|
|
50
47
|
export declare const erasureIdPattern: RegExp;
|
|
51
48
|
export declare function verify(request: IncomingMessage | Request): Promise<ChestEvent | null>;
|
|
52
49
|
export declare function handle(request: IncomingMessage | Request, handlers: Handlers, options?: {
|
package/dist/src/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../client/src/events.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../client/src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAKjD,OAAO,EAA6D,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AA0BnG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpG,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,YAAY,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAEtI,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE7G,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAG7G,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE9I,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,aAAa,GAAG,aAAa,GAAG,YAAY,CAAC;AACtF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;AAGhD,MAAM,MAAM,QAAQ,GAAG;KAAG,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAAE,CAAC;AAIrH,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAGpD,eAAO,MAAM,gBAAgB,QAAwB,CAAC;AAsCtD,wBAAsB,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAG3F;AAWD,wBAAsB,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAanI;AAQD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASvE"}
|