@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.
Files changed (47) hide show
  1. package/README.md +208 -43
  2. package/client/index.ts +4 -3
  3. package/client/src/api.ts +18 -0
  4. package/client/src/chest.ts +30 -6
  5. package/client/src/events.ts +12 -104
  6. package/client/src/files.ts +9 -9
  7. package/client/src/members.ts +18 -13
  8. package/client/src/schedules.ts +87 -0
  9. package/client/src/signed.ts +166 -0
  10. package/client/src/testing.ts +132 -54
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +4 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/src/api.d.ts +2 -0
  16. package/dist/src/api.d.ts.map +1 -1
  17. package/dist/src/api.js +18 -0
  18. package/dist/src/api.js.map +1 -1
  19. package/dist/src/chest.d.ts +5 -0
  20. package/dist/src/chest.d.ts.map +1 -1
  21. package/dist/src/chest.js +13 -1
  22. package/dist/src/chest.js.map +1 -1
  23. package/dist/src/events.d.ts +3 -6
  24. package/dist/src/events.d.ts.map +1 -1
  25. package/dist/src/events.js +8 -102
  26. package/dist/src/events.js.map +1 -1
  27. package/dist/src/files.d.ts +1 -0
  28. package/dist/src/files.d.ts.map +1 -1
  29. package/dist/src/files.js +6 -7
  30. package/dist/src/files.js.map +1 -1
  31. package/dist/src/members.d.ts +1 -1
  32. package/dist/src/members.d.ts.map +1 -1
  33. package/dist/src/members.js +6 -5
  34. package/dist/src/members.js.map +1 -1
  35. package/dist/src/schedules.d.ts +15 -0
  36. package/dist/src/schedules.d.ts.map +1 -0
  37. package/dist/src/schedules.js +45 -0
  38. package/dist/src/schedules.js.map +1 -0
  39. package/dist/src/signed.d.ts +29 -0
  40. package/dist/src/signed.d.ts.map +1 -0
  41. package/dist/src/signed.js +139 -0
  42. package/dist/src/signed.js.map +1 -0
  43. package/dist/src/testing.d.ts +15 -5
  44. package/dist/src/testing.d.ts.map +1 -1
  45. package/dist/src/testing.js +124 -52
  46. package/dist/src/testing.js.map +1 -1
  47. package/package.json +14 -3
@@ -1,4 +1,4 @@
1
- import { createHash, createHmac, randomBytes } from "node:crypto";
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; an upload
13
- // it authorises but does not receive), badges, notifications, AI (chat,
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 to the tool, signed as the
17
- // Chest signs it.
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 who left
55
- // it (erased: their data was erased, the name gone), its groups, the
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 English by default).
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?: { id: string; name?: string; erased?: boolean }[];
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 fake Chest in the test's process: its address, the token and the tool it
78
- // set in the environment, what it keeps (members, groups and files a test
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, and close, which stops it and restores the
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
- const body = encode({ alg: "HS256", typ: "JWT" }) + "." + encode({
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
- // The upload itself goes from a member's browser to the team host,
292
- // which a fake Chest does not play: it only authorises it.
293
- if (typeof command["name"] !== "string") return send(response, 400, { error: "invalid_body" });
294
- return send(response, 200, { url: `https://${tool}-chest.chest.test/_chest/files/upload/${Buffer.from(command["name"]).toString("base64url")}.fake`, method: "PUT", expires_in: typeof command["expires_in"] === "number" ? command["expires_in"] : 900 });
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("/erasures/") ? erasuresRoute
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
- 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" });
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
- chest.emit = async (event, to) => {
546
- const id = event.id ?? "evt_" + Array.from(randomBytes(26), b => "abcdefghijklmnopqrstuvwxyz234567"[b & 31]).join("");
547
- const body = JSON.stringify({ id, type: event.type, occurredAt: event.occurredAt ?? new Date().toISOString(), data: event.data });
548
- if (event.type === "member.erased") erasures.add(event.data.erasure);
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
@@ -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
- // v2). Each one is also its own subpath (@argentic/chest-sdk/member, /chest,
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,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,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,EAAE,MAAM,aAAa,CAAC"}
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;
@@ -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;AAuBlH,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"}
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) {
@@ -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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"}
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"chest.d.ts","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"AA0BA,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,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;CACnC,CAAC;AAiCF,eAAO,MAAM,KAAK,EAAE,KAenB,CAAC"}
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()))
@@ -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;AAgC/D,sEAAsE;AACtE,gEAAgE;AAChE,+DAA+D;AAC/D,MAAM,mBAAmB,GAAG,wCAAwC,CAAC;AAErE,SAAS,IAAI,CAAC,IAAY,EAAE,KAAiC;IAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,GAAG,EAAE,2BAA2B,IAAI,wBAAwB,CAAC,CAAC;IACnJ,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,IAAI,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC;AAE9D,+DAA+D;AAC/D,SAAS,MAAM,CAAC,EAAQ,EAAE,IAAY;IACpC,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxL,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,2EAA2E;AAC3E,sBAAsB;AACtB,MAAM,CAAC,MAAM,KAAK,GAAU;IAC1B,IAAI,YAAY;QACd,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;IACxF,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,KAAK,CAAC,EAAE,GAAkB,IAAI,CAAC,GAAG,EAAE;QAClC,MAAM,OAAO,GAAG,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,4BAA4B,CAAC,CAAC;QACxF,OAAO,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACjC,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"chest.js","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage } from "node:http";
2
- export type MemberChange = "name" | "photo" | "role" | "groups" | "email";
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?: {
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../client/src/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AA4BjD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE1E,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;AAWrH,MAAM,MAAM,IAAI,GAAG;IAAE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAI1G,wBAAgB,UAAU,CAAC,KAAK,SAAQ,GAAG,IAAI,CAU9C;AAcD,eAAO,MAAM,gBAAgB,QAAwB,CAAC;AAoGtD,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"}
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"}