@vercel/board 0.29.0 → 0.31.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 +8 -17
- package/dist/{a2-HUbQJ0RD.mjs → a2-DngWY2Eb.mjs} +741 -415
- package/dist/access-CY5xVMIK.mjs +242 -0
- package/dist/{access-CV96SYgr.d.mts → access-DPcrKIH_.d.mts} +6 -1
- package/dist/access.d.mts +4 -4
- package/dist/access.mjs +5 -4
- package/dist/{actors-DcJhFbwJ.mjs → actors-Bm-ojWWT.mjs} +2 -2
- package/dist/{auth-JO22iIwr.mjs → auth-CyO2aw5n.mjs} +126 -236
- package/dist/{auth-CU56Q2Xh.d.mts → auth-DLYSt_-A.d.mts} +32 -4
- package/dist/{backend-CPC_vyOo.d.mts → backend-Bob-5CM7.d.mts} +93 -42
- package/dist/backend-a2/blob-vercel.d.mts +1 -1
- package/dist/backend-a2/contract.d.mts +38 -98
- package/dist/backend-a2/contract.mjs +1 -1
- package/dist/backend-a2/scheduler-local.d.mts +1 -1
- package/dist/backend-a2/scheduler-local.mjs +1 -1
- package/dist/backend-a2/scheduler-qstash.d.mts +3 -3
- package/dist/backend-a2/scheduler-qstash.mjs +8 -8
- package/dist/backend-a2/scheduler-vercel.d.mts +3 -3
- package/dist/backend-a2/scheduler-vercel.mjs +6 -6
- package/dist/backend-a2/store-memory.d.mts +2 -2
- package/dist/backend-a2/store-memory.mjs +3 -3
- package/dist/backend-a2/store-postgres.d.mts +1 -1
- package/dist/backend-a2/store-postgres.mjs +34 -15
- package/dist/backend-a2/store-redis-http.d.mts +1 -1
- package/dist/backend-a2/store-redis-http.mjs +6 -6
- package/dist/backend-a2/store-redis.d.mts +2 -2
- package/dist/backend-a2/store-redis.mjs +5 -5
- package/dist/backend-a2/store-sqlite.d.mts +1 -1
- package/dist/backend-a2/store-sqlite.mjs +3 -3
- package/dist/backend-a2.d.mts +143 -242
- package/dist/backend-a2.mjs +2 -2
- package/dist/{backend-http-CqfKhrnn.d.mts → backend-http-BRrRbjRx.d.mts} +59 -45
- package/dist/{backend-http-CzIJMm58.mjs → backend-http-CvUKOn-m.mjs} +11 -10
- package/dist/{backend-http-Bmp0IgD3.mjs → backend-http-D8mUzuKQ.mjs} +2 -2
- package/dist/backend-http.d.mts +1 -1
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.d.mts +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-DQc_TaHp.mjs → backend-vercel-Ba8JDtqW.mjs} +3 -3
- package/dist/{backend-vercel-4tvcPKqX.d.mts → backend-vercel-BaSEUdwg.d.mts} +62 -47
- package/dist/{backend-vercel-BNauAs7w.mjs → backend-vercel-CmY9yZ9m.mjs} +279 -42
- package/dist/backend-vercel.d.mts +1 -1
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.d.mts +1 -1
- package/dist/backend-vercel.node.mjs +1 -1
- package/dist/{blob-DmiQjrYy.d.mts → blob-Bx8QPWEd.d.mts} +2 -2
- package/dist/{blob-DdbpJqN1.mjs → blob-WbB2DJKn.mjs} +30 -11
- package/dist/{chat-provider-BWHbChcQ.d.mts → chat-provider-BChYrrA7.d.mts} +32 -28
- package/dist/{chat-provider-COtsU5jB.mjs → chat-provider-DkFp2hrh.mjs} +105 -60
- package/dist/cli-commands.d.mts +1 -1
- package/dist/cli-commands.mjs +1 -1
- package/dist/cli-embedded.d.mts +2 -2
- package/dist/cli-embedded.mjs +1 -1
- package/dist/cli.mjs +4 -4
- package/dist/{client-BqAjk0Q4.d.mts → client-COfYrgfV.d.mts} +240 -227
- package/dist/{commands-D6gvrOh3.d.mts → commands-DK4W3HCJ.d.mts} +47 -10
- package/dist/{commands-C3R1AWgm.mjs → commands-L1JSGz2Z.mjs} +212 -54
- package/dist/conformance-DK43HiHf.mjs +1000 -0
- package/dist/conformance.d.mts +8 -2
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-DQxXk7dX.mjs → engine-DurYIRt8.mjs} +29 -210
- package/dist/{errors-DDktVeUa.d.mts → errors-2vGs49Db.d.mts} +1 -1
- package/dist/{client-D0Px7KzF.mjs → errors-CJXhhzvT.mjs} +132 -27
- package/dist/{provider-BaVmi4Hi.mjs → events-lujPA3gQ.mjs} +80 -17
- package/dist/{exports-BG-0Wyn-.mjs → exports-CWIdN0Ub.mjs} +195 -103
- package/dist/exports-FBOQWT7O.d.mts +5 -0
- package/dist/{folds-BDMSxBUf.mjs → folds-Cu9Q9zyQ.mjs} +3 -22
- package/dist/{folds-ZkHM4M-v.d.mts → folds-DLUzfibJ.d.mts} +15 -40
- package/dist/{index-D3VvNfh_.d.mts → index-Ho-ayyQL.d.mts} +2 -2
- package/dist/index.d.mts +7 -7
- package/dist/{index.default-rB8sJ9RX.mjs → index.default-BUcwDaAe.mjs} +1 -1
- package/dist/index.mjs +5 -6
- package/dist/index.node.d.mts +7 -7
- package/dist/index.node.mjs +3 -4
- package/dist/inspect/app.js +62 -61
- package/dist/inspect/engine.js +3 -3
- package/dist/{internal-CvsrprnU-CRY89I-b.mjs → internal-CvsrprnU-zZMpDiRY.mjs} +1 -1
- package/dist/{local-DhkrAqPP.mjs → local-C76Fy5ad.mjs} +1 -1
- package/dist/{main-CVBqe3VO.mjs → main-Dz-8komG.mjs} +251 -127
- package/dist/{middleware-CXjEeef4.mjs → middleware-BavYSDUa.mjs} +1 -1
- package/dist/{node-Zd_C-gxP.mjs → node-BgeGcwX0.mjs} +7 -3
- package/dist/{platform-B4TnJtWu-CwcTyKg2.mjs → platform-B4TnJtWu-Do3agKr5.mjs} +1 -1
- package/dist/{project-CBXlLftU.mjs → project-CQ4VUibA.mjs} +1 -1
- package/dist/{provider-DmSTp9lI.d.mts → provider-gynDUoyW.d.mts} +13 -43
- package/dist/{redis-tables-BJQVUspu.mjs → redis-tables-DHf85ixI.mjs} +40 -42
- package/dist/{reducer-CjmvZSAb-D_OSISmj.mjs → reducer-Bswr11ar-P324o9vn.mjs} +2 -2
- package/dist/{reducer-CYPHsSmd-BQIfozGi.d.mts → reducer-CYPHsSmd-BXjGiptm.d.mts} +1 -1
- package/dist/{reducers-DJzVpXwM.mjs → reducers-DcENwtwZ.mjs} +74 -77
- package/dist/{retryable-lazy-DZWmHpii-Bye-W8q7.mjs → retryable-lazy-DZWmHpii-DpTc5PIP.mjs} +1 -1
- package/dist/{scheduler-task-X1SToDjP-BVoWuInI.mjs → scheduler-task-X1SToDjP-D2Il2rln.mjs} +1 -1
- package/dist/{serve-G9RuaDSr.mjs → serve-CTmow-YH.mjs} +7 -7
- package/dist/{server-qgez0Xny-D5mLfqFQ.d.mts → server-B9gu0qi1-DI1WvcVK.d.mts} +10 -4
- package/dist/{server-BixV_byP.mjs → server-DG8D6VV0.mjs} +4 -4
- package/dist/{server-DtFrWxi3-Boa6gdWO.mjs → server-DX7mM6nm-D7N_7bug.mjs} +32 -23
- package/dist/{serving-B75Y4BF-.mjs → serving-DBFRo6U_.mjs} +1 -1
- package/dist/{session-clear-wHd5GCpZ-BnKSxReK.mjs → session-clear-XJYpmUpg-2q8l_FJR.mjs} +14 -2
- package/dist/{sql-D4Z_efJX.mjs → sql-CT5xVuGr.mjs} +2 -8
- package/dist/{sqlite-db-Bz2JkfCU.mjs → sqlite-db-0WeSOEZN.mjs} +1 -1
- package/dist/{store-D0MhBvg6-4Z2L0Kqd.d.mts → store-DEzcHZOA-B-5vhxef.d.mts} +12 -2
- package/dist/store-N8PXxDAS-DWWgv-Vz.mjs +5 -0
- package/dist/{store-codec-DwrWlgQx-CYxGW0aD.mjs → store-codec-pAG2oPgQ-SqDXlPBR.mjs} +4 -4
- package/dist/store-memory-4IBl0Ztu.mjs +2 -0
- package/dist/{store-memory-Bas9Uq5C.mjs → store-memory-B0PkVSuQ.mjs} +14 -7
- package/dist/{store-polling-BZt2Rf3D-BiEM6_HB.mjs → store-polling-BZt2Rf3D-Div-bJ_D.mjs} +2 -2
- package/dist/{store-presence-polling-JuAy2Ck2-CYpuK75X.mjs → store-presence-polling-JuAy2Ck2-Dh-g_KYM.mjs} +3 -3
- package/dist/{store-sqlite-CxxPCcCT.mjs → store-sqlite-D85t3tE7.mjs} +14 -7
- package/dist/store-sqlite-iBTYYKBj.mjs +2 -0
- package/dist/{stream-buffer-DrTyiIuo-BKy-vI14.mjs → stream-buffer-DrTyiIuo-Be10yOBR.mjs} +2 -2
- package/dist/{sync-kapso.d.mts → sync-chat-sdk.d.mts} +33 -37
- package/dist/sync-chat-sdk.mjs +44 -0
- package/dist/sync-github.d.mts +5 -18
- package/dist/sync-github.mjs +4 -70
- package/dist/sync-linear.d.mts +3 -3
- package/dist/sync-linear.mjs +6 -5
- package/dist/sync-slack.d.mts +7 -7
- package/dist/sync-slack.mjs +33 -54
- package/dist/sync.d.mts +3 -3
- package/dist/sync.mjs +2 -4
- package/dist/{tokens-CC3izSNr.mjs → tokens-Dnyd_y1n.mjs} +15 -6
- package/dist/{types-No0WdhE5.d.mts → types-CmrFjpml.d.mts} +2 -4
- package/dist/{watchers-hash-BDhLVkV5.mjs → watchers-hash-BjwlnGD9.mjs} +124 -115
- package/docs/01-board/01-mounting.md +2 -2
- package/docs/01-board/03-identity.md +5 -6
- package/docs/01-board/04-messages.md +96 -2
- package/docs/01-board/05-reading.md +2 -1
- package/docs/01-board/06-watchers.md +8 -6
- package/docs/01-board/08-middleware.md +6 -3
- package/docs/01-board/09-access.md +145 -14
- package/docs/02-sync/01-overview.md +19 -26
- package/docs/02-sync/02-functions.md +8 -9
- package/docs/02-sync/03-slack.md +5 -5
- package/docs/02-sync/04-github.md +2 -2
- package/docs/02-sync/06-providers.md +91 -62
- package/docs/03-guides/01-coding-agents.md +1 -1
- package/docs/03-guides/03-ci.md +7 -4
- package/docs/04-reference/01-cli.md +109 -51
- package/docs/04-reference/02-board.md +76 -28
- package/docs/04-reference/03-http.md +76 -38
- package/docs/04-reference/04-log.md +8 -11
- package/docs/04-reference/05-conformance.md +47 -7
- package/docs/index.md +2 -2
- package/package.json +7 -10
- package/sql/postgres/board.sql +1 -1
- package/sql/postgres/schema.sql +1 -1
- package/dist/conformance-DlnfuzEB.mjs +0 -556
- package/dist/errors-CusN4x2l.mjs +0 -88
- package/dist/events-qf5XG5VQ.mjs +0 -75
- package/dist/exports-mhAYiU4i.d.mts +0 -5
- package/dist/store-N8PXxDAS-BWBgzBHp.mjs +0 -5
- package/dist/store-memory-ZLiZbH3R.mjs +0 -2
- package/dist/store-sqlite-BmwL3pUR.mjs +0 -2
- package/dist/sync-kapso.mjs +0 -89
- package/dist/sync.chat.d.mts +0 -2
- package/dist/sync.chat.mjs +0 -2
- package/dist/users-yryHKFMo.mjs +0 -42
- package/docs/02-sync/05-whatsapp.md +0 -152
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as LabelKey, p as Labels } from "./schemas-DBmUO6pr.mjs";
|
|
2
|
+
import { n as ExternalEvent, r as ExternalLocation } from "./external-D3p5T57R.mjs";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
//#region src/sync/fn.ts
|
|
4
5
|
const ROUTES = Symbol.for("board.syncRoutes");
|
|
@@ -122,8 +123,6 @@ var SyncProvider = class {
|
|
|
122
123
|
uploadsFiles;
|
|
123
124
|
startsThreadsWithFiles;
|
|
124
125
|
fn;
|
|
125
|
-
/** People's tokens, when the adapter has them: messages they write post as them. */
|
|
126
|
-
users;
|
|
127
126
|
options;
|
|
128
127
|
constructor(options, sync) {
|
|
129
128
|
if (typeof sync !== "function") throw new Error(`${options.platform} needs a sync function: what each event does, like channels({ '#dev': 'dev' })`);
|
|
@@ -131,23 +130,12 @@ var SyncProvider = class {
|
|
|
131
130
|
const routes = routesOf(sync);
|
|
132
131
|
if (routes) routed.set(this, routes);
|
|
133
132
|
this.options = options;
|
|
134
|
-
this.users = options.users ?? null;
|
|
135
133
|
this.platform = options.platform;
|
|
136
134
|
this.canStartThreads = options.canStartThreads ?? false;
|
|
137
135
|
this.singleReaction = options.singleReaction ?? false;
|
|
138
136
|
this.uploadsFiles = options.uploadsFiles ?? false;
|
|
139
137
|
this.startsThreadsWithFiles = this.uploadsFiles && (options.startsThreadsWithFiles ?? false);
|
|
140
138
|
}
|
|
141
|
-
/** The platform account a person's token belongs to. */
|
|
142
|
-
async whoAmI(_token) {
|
|
143
|
-
throw new Error(`${this.platform} can't post as a person`);
|
|
144
|
-
}
|
|
145
|
-
/** `post`, as the person whose token this is. */
|
|
146
|
-
async postAs(_token, _target, _kind, _body) {
|
|
147
|
-
throw new Error(`${this.platform} can't post as a person`);
|
|
148
|
-
}
|
|
149
|
-
async editAs(_token, _thread, _message, _body) {}
|
|
150
|
-
async deleteAs(_token, _thread, _message) {}
|
|
151
139
|
async findByEmail(_email) {
|
|
152
140
|
return null;
|
|
153
141
|
}
|
|
@@ -164,8 +152,12 @@ var SyncProvider = class {
|
|
|
164
152
|
async permalink(_event) {
|
|
165
153
|
return null;
|
|
166
154
|
}
|
|
167
|
-
/**
|
|
168
|
-
|
|
155
|
+
/**
|
|
156
|
+
* A linked message the platform didn't describe, read from the platform. `linker` is the
|
|
157
|
+
* platform message that links it, by its own IDs: what the platform says about the link may be
|
|
158
|
+
* there by now.
|
|
159
|
+
*/
|
|
160
|
+
async fetchLink(_link, _linker) {
|
|
169
161
|
return {
|
|
170
162
|
snapshot: null,
|
|
171
163
|
unavailable: `${this.platform} can't read it`
|
|
@@ -223,4 +215,75 @@ var SyncProvider = class {
|
|
|
223
215
|
}
|
|
224
216
|
};
|
|
225
217
|
//#endregion
|
|
226
|
-
|
|
218
|
+
//#region src/sync/events.ts
|
|
219
|
+
const EventBase = z.object({
|
|
220
|
+
id: z.string().min(1),
|
|
221
|
+
platform: z.string().min(1),
|
|
222
|
+
key: z.string().min(1),
|
|
223
|
+
...ExternalLocation.shape,
|
|
224
|
+
raw: z.unknown()
|
|
225
|
+
});
|
|
226
|
+
const eventTypes = {
|
|
227
|
+
message: "message",
|
|
228
|
+
edited: "message.edited",
|
|
229
|
+
deleted: "message.deleted",
|
|
230
|
+
reaction: "reaction.added",
|
|
231
|
+
lifecycle: "lifecycle"
|
|
232
|
+
};
|
|
233
|
+
function envelope(platform, key, event, raw, id) {
|
|
234
|
+
return {
|
|
235
|
+
...event,
|
|
236
|
+
...EventBase.parse({
|
|
237
|
+
id,
|
|
238
|
+
platform,
|
|
239
|
+
key,
|
|
240
|
+
channel: event.channel,
|
|
241
|
+
thread: event.thread,
|
|
242
|
+
subthread: "subthread" in event ? event.subthread : null,
|
|
243
|
+
raw
|
|
244
|
+
}),
|
|
245
|
+
type: event.kind === "lifecycle" ? event.type : event.kind === "reaction" && !event.added ? "reaction.removed" : eventTypes[event.kind]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function eventSchema(platform, messageType) {
|
|
249
|
+
const [message, edited, deleted, reaction, lifecycle] = ExternalEvent.options;
|
|
250
|
+
const common = {
|
|
251
|
+
...EventBase.shape,
|
|
252
|
+
platform: z.literal(platform)
|
|
253
|
+
};
|
|
254
|
+
return z.discriminatedUnion("kind", [
|
|
255
|
+
message.extend({
|
|
256
|
+
...common,
|
|
257
|
+
type: z.literal(messageType)
|
|
258
|
+
}),
|
|
259
|
+
edited.extend({
|
|
260
|
+
...common,
|
|
261
|
+
type: z.literal("message.edited")
|
|
262
|
+
}),
|
|
263
|
+
deleted.extend({
|
|
264
|
+
...common,
|
|
265
|
+
type: z.literal("message.deleted")
|
|
266
|
+
}),
|
|
267
|
+
reaction.extend({
|
|
268
|
+
...common,
|
|
269
|
+
type: z.enum(["reaction.added", "reaction.removed"])
|
|
270
|
+
}),
|
|
271
|
+
lifecycle.extend(common)
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
const SyncOverrides = z.object({
|
|
275
|
+
root: z.custom((value) => typeof value === "function").optional(),
|
|
276
|
+
text: z.string().optional(),
|
|
277
|
+
labels: Labels.optional(),
|
|
278
|
+
unset: z.array(LabelKey).optional(),
|
|
279
|
+
noSync: z.boolean().optional()
|
|
280
|
+
}).strict();
|
|
281
|
+
function immutable(value) {
|
|
282
|
+
if (value && typeof value === "object") {
|
|
283
|
+
for (const item of Object.values(value)) immutable(item);
|
|
284
|
+
Object.freeze(value);
|
|
285
|
+
}
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
export { immutable as a, SyncProvider as c, routesFor as d, channels as f, withRoutes as g, render as h, eventSchema as i, UnreadableError as l, mirror as m, SyncOverrides as n, ReceivedEvent as o, into as p, envelope as r, ResolvedTarget as s, EventBase as t, channelMissing as u };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import "./schemas-DBmUO6pr.mjs";
|
|
3
|
-
import { A as
|
|
4
|
-
import {
|
|
3
|
+
import { A as RegisterBody, B as ThreadsQuery, C as LabelBody, D as PostBody, E as MessagesQuery, F as SyncBody, H as TypingBody, I as SyncCopiesQuery, L as SyncFindQuery, M as SchedulesQuery, N as SourceJoinBody, O as ReactBody, P as SourceReadQuery, R as SyncImportBody, S as InboxQuery, T as MatchesQuery, U as UploadStartBody, V as TokenBody, _ as EventsQuery, a as METHODS, b as InboxNextQuery, c as queryObject, d as VERSION, f as SYNC, g as EditBody, h as DeleteBody, j as ScheduleBody, k as Ref, l as readCapped, m as CreateChannelBody, o as createRouter, p as AckBody, s as openapi, u as readJson, v as GrantBody, w as LinkedQuery, x as InboxPushBody, y as InboxAckBody, z as SyncReceiptBody } from "./watchers-hash-BjwlnGD9.mjs";
|
|
4
|
+
import { a as forbidden, c as toErrorResponse, d as createBoardClient, l as unauthorized, n as RejectedError, o as json, r as badRequest, s as notFound, t as HttpError, u as BoardError } from "./errors-CJXhhzvT.mjs";
|
|
5
5
|
import { r as derivedId } from "./wait-until-Dw4XrIDQ.mjs";
|
|
6
|
-
import { a as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { r as runMiddleware, t as DroppedError } from "./middleware-
|
|
6
|
+
import { a as scopeOf, i as parseScope, r as compileAccess } from "./access-CY5xVMIK.mjs";
|
|
7
|
+
import { i as resolveActor, r as claimsOf, t as KNOWN_AGENTS } from "./actors-Bm-ojWWT.mjs";
|
|
8
|
+
import { S as verifiesTokens, a as VERCEL_ISSUER, f as grantIssuers, h as issuersOf, l as createAuthority, p as hostPrincipal, u as fromTokens, y as tokenHash } from "./auth-CyO2aw5n.mjs";
|
|
9
|
+
import { r as runMiddleware, t as DroppedError } from "./middleware-BavYSDUa.mjs";
|
|
10
10
|
import { n as syncTarget, r as withEmailMentions, t as mentionsEmails } from "./targets-Cgjs29T0.mjs";
|
|
11
11
|
//#region src/server/policy.ts
|
|
12
12
|
/**
|
|
@@ -14,10 +14,11 @@ import { n as syncTarget, r as withEmailMentions, t as mentionsEmails } from "./
|
|
|
14
14
|
* principals its backend stores. `keeps`: the backend keeps the board, so the defaults let its
|
|
15
15
|
* people and own deployments in when `access` is left out.
|
|
16
16
|
*/
|
|
17
|
-
function createPolicy(options, keeps, serving) {
|
|
17
|
+
function createPolicy(options, keeps, serving, trust = null) {
|
|
18
18
|
/** Whether requests are served at all: a board on `http()` serves none without an `access` of its own. */
|
|
19
19
|
const open = keeps || options.access !== void 0;
|
|
20
20
|
const access = options.access ?? [];
|
|
21
|
+
const grants = trust && typeof access !== "function" ? () => trust.grants() : null;
|
|
21
22
|
/** The board's own Vercel project and team, from `project` or the environment, asked once. */
|
|
22
23
|
let own = null;
|
|
23
24
|
const project = () => (own ??= (async () => {
|
|
@@ -49,12 +50,39 @@ function createPolicy(options, keeps, serving) {
|
|
|
49
50
|
const authority = createAuthority({
|
|
50
51
|
access,
|
|
51
52
|
project,
|
|
52
|
-
hosts: options.authenticate !== void 0 && !verifiesTokens(options.authenticate)
|
|
53
|
+
hosts: options.authenticate !== void 0 && !verifiesTokens(options.authenticate),
|
|
54
|
+
grants
|
|
53
55
|
});
|
|
56
|
+
const ruleIssuers = typeof access === "function" ? [] : issuersOf(access);
|
|
57
|
+
/** One for every request, so a verifier keeps what it built: its issuers change only when grants name new ones. */
|
|
54
58
|
const context = {
|
|
55
59
|
project,
|
|
56
|
-
issuers:
|
|
60
|
+
issuers: ruleIssuers
|
|
57
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Trusts the issuers grants name, before a token from one is verified: only for a token from an
|
|
64
|
+
* issuer neither Vercel, GitHub, nor a rule is, so most requests read no grants for it.
|
|
65
|
+
*/
|
|
66
|
+
async function learnIssuers(credential) {
|
|
67
|
+
if (!grants) return;
|
|
68
|
+
const iss = issuerOf(credential);
|
|
69
|
+
if (iss === null || !iss.startsWith("https://") || iss === "https://token.actions.githubusercontent.com" || VERCEL_ISSUER.test(iss) || ruleIssuers.includes(iss)) return;
|
|
70
|
+
const named = grantIssuers(await grants());
|
|
71
|
+
const issuers = [...ruleIssuers, ...named.filter((issuer) => !ruleIssuers.includes(issuer))];
|
|
72
|
+
if (issuers.join("\n") !== context.issuers.join("\n")) context.issuers = issuers;
|
|
73
|
+
}
|
|
74
|
+
/** Who a board token is: the principal that minted it, narrowed, until it's revoked or expires. */
|
|
75
|
+
async function tokenPrincipal(secret) {
|
|
76
|
+
if (!trust) throw unauthorized("this board issues no tokens: send a board token to the board that minted it");
|
|
77
|
+
const found = await trust.token(await tokenHash(secret));
|
|
78
|
+
if (!found || found.expiresAt.getTime() <= Date.now()) throw unauthorized("invalid token: revoked, expired, or not this board's");
|
|
79
|
+
return {
|
|
80
|
+
...found.minter,
|
|
81
|
+
expiresAt: found.expiresAt,
|
|
82
|
+
token: found.id,
|
|
83
|
+
pinned: found.pinned
|
|
84
|
+
};
|
|
85
|
+
}
|
|
58
86
|
/** Verified principals by credential, until they expire, so a stream's polls don't verify from scratch. */
|
|
59
87
|
const verified = /* @__PURE__ */ new Map();
|
|
60
88
|
/** And by request, for one with no credential (a session cookie), for as long as it's served. */
|
|
@@ -80,7 +108,7 @@ function createPolicy(options, keeps, serving) {
|
|
|
80
108
|
const principal = verify(request);
|
|
81
109
|
perRequest.set(request, principal);
|
|
82
110
|
if (key !== null) principal.then((found) => {
|
|
83
|
-
if (found.facts?.issuer === "host" || found.expiresAt === null) return;
|
|
111
|
+
if (found.facts?.issuer === "host" || found.expiresAt === null || found.token !== void 0) return;
|
|
84
112
|
if (verified.size >= 1e3) verified.delete(verified.keys().next().value);
|
|
85
113
|
verified.set(key, {
|
|
86
114
|
principal,
|
|
@@ -90,6 +118,8 @@ function createPolicy(options, keeps, serving) {
|
|
|
90
118
|
return principal;
|
|
91
119
|
}
|
|
92
120
|
async function verify(request) {
|
|
121
|
+
if (request.credential?.startsWith("board_")) return tokenPrincipal(request.credential);
|
|
122
|
+
if (request.credential !== null) await learnIssuers(request.credential);
|
|
93
123
|
const authenticate = await verifier();
|
|
94
124
|
let result;
|
|
95
125
|
try {
|
|
@@ -108,6 +138,10 @@ function createPolicy(options, keeps, serving) {
|
|
|
108
138
|
return {
|
|
109
139
|
open,
|
|
110
140
|
project,
|
|
141
|
+
/** The OIDC issuers the rules name. */
|
|
142
|
+
issuers: ruleIssuers,
|
|
143
|
+
/** Whether grants decide anything here: not where `access` is one function, which decides alone. */
|
|
144
|
+
takesGrants: typeof access !== "function",
|
|
111
145
|
discoveryIdentity,
|
|
112
146
|
/**
|
|
113
147
|
* Who a request is, and what it may do now: verified, under the rules, with the profile it
|
|
@@ -116,11 +150,13 @@ function createPolicy(options, keeps, serving) {
|
|
|
116
150
|
*/
|
|
117
151
|
async identify(request) {
|
|
118
152
|
const found = await principalOf(request);
|
|
119
|
-
const
|
|
153
|
+
const claims = found.pinned ? pinnedClaims(found.pinned, request.claims) : request.claims;
|
|
154
|
+
const scope = found.scope === void 0 ? claims.scope ?? void 0 : claims.scope === null ? found.scope : scopeOf(compileAccess(parseScope(found.scope)).narrow(parseScope(claims.scope)));
|
|
155
|
+
const principal = scope === void 0 ? found : {
|
|
120
156
|
...found,
|
|
121
|
-
scope
|
|
157
|
+
scope
|
|
122
158
|
};
|
|
123
|
-
const { author, actor } = resolveActor(principal,
|
|
159
|
+
const { author, actor } = resolveActor(principal, claims, request.audience);
|
|
124
160
|
return {
|
|
125
161
|
principal: {
|
|
126
162
|
...principal,
|
|
@@ -130,10 +166,38 @@ function createPolicy(options, keeps, serving) {
|
|
|
130
166
|
actor
|
|
131
167
|
};
|
|
132
168
|
},
|
|
133
|
-
/** What a stored principal (a schedule's creator, a watcher's registrant) may do now: null once nothing. */
|
|
134
|
-
accessNow
|
|
169
|
+
/** What a stored principal (a schedule's creator, a watcher's registrant) may do now: null once nothing, its board token's revoking included. */
|
|
170
|
+
async accessNow(principal) {
|
|
171
|
+
if (principal.token !== void 0 && !await trust?.live(principal.token)) return null;
|
|
172
|
+
return authority.accessNow(principal);
|
|
173
|
+
}
|
|
135
174
|
};
|
|
136
175
|
}
|
|
176
|
+
/** A board token's pins, over what the request says: its profile and session win, and a person's name is the minter's. */
|
|
177
|
+
function pinnedClaims(pinned, claims) {
|
|
178
|
+
return {
|
|
179
|
+
...claims,
|
|
180
|
+
...pinned.profile !== null ? {
|
|
181
|
+
profile: pinned.profile,
|
|
182
|
+
avatar: pinned.avatar
|
|
183
|
+
} : {},
|
|
184
|
+
...pinned.session !== null ? { session: pinned.session } : {},
|
|
185
|
+
handle: pinned.handle,
|
|
186
|
+
name: pinned.name
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** A JWT's `iss`, read but not verified: which issuer's keys would check it. Null for anything else. */
|
|
190
|
+
function issuerOf(credential) {
|
|
191
|
+
const payload = credential.split(".")[1];
|
|
192
|
+
if (payload === void 0) return null;
|
|
193
|
+
try {
|
|
194
|
+
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payload.length / 4) * 4, "="));
|
|
195
|
+
const { iss } = JSON.parse(json);
|
|
196
|
+
return typeof iss === "string" ? iss : null;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
137
201
|
//#endregion
|
|
138
202
|
//#region src/contract/logos.ts
|
|
139
203
|
const LOGOS = {
|
|
@@ -183,6 +247,10 @@ const CREATED = /* @__PURE__ */ new Set([
|
|
|
183
247
|
"upload",
|
|
184
248
|
"uploadFinish"
|
|
185
249
|
]);
|
|
250
|
+
/** An email, as `--mentions` names one: sync looks its platform user up. */
|
|
251
|
+
const EMAIL = /^[^\s@]+@[^\s@]+$/;
|
|
252
|
+
/** How long a filter's email lookup holds. */
|
|
253
|
+
const EMAIL_TTL_MS = 3e5;
|
|
186
254
|
/** Shown inline only when a browser can't run it: everything else downloads. */
|
|
187
255
|
const INLINE = /^(image\/(png|jpeg|gif|webp|avif)|video\/(mp4|webm)|text\/plain)$/;
|
|
188
256
|
/**
|
|
@@ -211,13 +279,15 @@ async function inputOf(name, request, url, params) {
|
|
|
211
279
|
case "me":
|
|
212
280
|
case "channels":
|
|
213
281
|
case "actors":
|
|
214
|
-
case "
|
|
215
|
-
case "
|
|
282
|
+
case "watchers":
|
|
283
|
+
case "grants":
|
|
284
|
+
case "tokens": return {};
|
|
216
285
|
case "message":
|
|
217
286
|
case "posted":
|
|
218
287
|
case "synced":
|
|
219
|
-
case "delete":
|
|
220
288
|
case "cancelSchedule":
|
|
289
|
+
case "revokeGrant":
|
|
290
|
+
case "revokeToken":
|
|
221
291
|
case "attachment":
|
|
222
292
|
case "uploadFinish": return { id };
|
|
223
293
|
case "messages": return MessagesQuery.parse(queryObject(url));
|
|
@@ -231,12 +301,19 @@ async function inputOf(name, request, url, params) {
|
|
|
231
301
|
return query;
|
|
232
302
|
}
|
|
233
303
|
case "post": return PostBody.parse(await body());
|
|
304
|
+
case "createChannel": return CreateChannelBody.parse(await body());
|
|
234
305
|
case "schedule": return ScheduleBody.parse(await body());
|
|
306
|
+
case "createGrant": return GrantBody.parse(await body());
|
|
307
|
+
case "createToken": return TokenBody.parse(await body());
|
|
235
308
|
case "register": return RegisterBody.parse(await body());
|
|
236
309
|
case "edit": return {
|
|
237
310
|
id,
|
|
238
311
|
...EditBody.parse(await body())
|
|
239
312
|
};
|
|
313
|
+
case "delete": return {
|
|
314
|
+
id,
|
|
315
|
+
...DeleteBody.parse(await body())
|
|
316
|
+
};
|
|
240
317
|
case "react": return {
|
|
241
318
|
id,
|
|
242
319
|
...ReactBody.parse(await body())
|
|
@@ -269,7 +346,6 @@ async function inputOf(name, request, url, params) {
|
|
|
269
346
|
case "syncThread": return { thread: params.thread ?? "" };
|
|
270
347
|
case "syncFind": return SyncFindQuery.parse(queryObject(url));
|
|
271
348
|
case "syncCopies": return SyncCopiesQuery.parse(queryObject(url));
|
|
272
|
-
case "syncRoots": return SyncRootsQuery.parse(queryObject(url));
|
|
273
349
|
case "inboxPush": return {
|
|
274
350
|
key,
|
|
275
351
|
...InboxPushBody.parse(await body())
|
|
@@ -287,9 +363,6 @@ async function inputOf(name, request, url, params) {
|
|
|
287
363
|
key,
|
|
288
364
|
...InboxQuery.parse(queryObject(url))
|
|
289
365
|
};
|
|
290
|
-
case "syncCode": return SyncCodeBody.parse(await body());
|
|
291
|
-
case "syncAccount": return SyncAccountBody.parse(await body());
|
|
292
|
-
case "syncUnlink": return SyncUnlinkBody.parse(await body());
|
|
293
366
|
case "typing": return {
|
|
294
367
|
id,
|
|
295
368
|
...TypingBody.parse(await body()) ?? {}
|
|
@@ -369,17 +442,48 @@ async function createServe(context) {
|
|
|
369
442
|
* board learns who they are; the rest stay plain text.
|
|
370
443
|
*/
|
|
371
444
|
async function withEmails(text) {
|
|
372
|
-
return (await withEmailMentions(text, engine ?
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
445
|
+
return (await withEmailMentions(text, engine ? learnEmail : void 0)).text;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* The synced platform's user with this email, whom the board learns: a post can mention only
|
|
449
|
+
* an actor it knows, and a platform mention of them resolves only once it does.
|
|
450
|
+
*/
|
|
451
|
+
async function learnEmail(email) {
|
|
452
|
+
const actor = await engine.findByEmail(email);
|
|
453
|
+
if (actor) await backend.syncImport(engine.caller, {
|
|
454
|
+
op: "seen",
|
|
455
|
+
author: {
|
|
456
|
+
...actor,
|
|
457
|
+
isBot: false
|
|
458
|
+
}
|
|
459
|
+
}, new AbortController().signal);
|
|
460
|
+
return actor;
|
|
461
|
+
}
|
|
462
|
+
/** Emails filters named, as sync found them, for a few minutes: `tail` and `wait` ask at every page. */
|
|
463
|
+
const mentioned = /* @__PURE__ */ new Map();
|
|
464
|
+
/**
|
|
465
|
+
* `--mentions ada@vercel.com`: the synced platform's user with that email, for a caller that may
|
|
466
|
+
* read somewhere. Anything else, and an email sync doesn't know, goes on as it came.
|
|
467
|
+
*/
|
|
468
|
+
async function withMentioned(caller, query) {
|
|
469
|
+
const email = query.mentions?.toLowerCase();
|
|
470
|
+
if (!engine || email === void 0 || !EMAIL.test(email)) return query;
|
|
471
|
+
const access = accessOf(caller);
|
|
472
|
+
if (access !== null && !access.anywhere("read")) return query;
|
|
473
|
+
let found = mentioned.get(email);
|
|
474
|
+
if (!found || Date.now() - found.at > EMAIL_TTL_MS) {
|
|
475
|
+
if (mentioned.size >= 1e3) mentioned.clear();
|
|
476
|
+
found = {
|
|
477
|
+
at: Date.now(),
|
|
478
|
+
actor: learnEmail(email).catch(() => null)
|
|
479
|
+
};
|
|
480
|
+
mentioned.set(email, found);
|
|
481
|
+
}
|
|
482
|
+
const actor = await found.actor;
|
|
483
|
+
return actor ? {
|
|
484
|
+
...query,
|
|
485
|
+
mentions: actor.id
|
|
486
|
+
} : query;
|
|
383
487
|
}
|
|
384
488
|
/**
|
|
385
489
|
* A sync target as the board that syncs it resolves it. One for an entry this board doesn't
|
|
@@ -513,6 +617,25 @@ async function createServe(context) {
|
|
|
513
617
|
}));
|
|
514
618
|
}
|
|
515
619
|
/**
|
|
620
|
+
* A schedule, on a backend whose sends post elsewhere: its message through the middleware once,
|
|
621
|
+
* as the caller's post of it, since its text and labels never change and no send comes back here.
|
|
622
|
+
*/
|
|
623
|
+
function screenSchedule(caller, input, signal) {
|
|
624
|
+
const { channel, re, text, labels, noSync, attachments } = input;
|
|
625
|
+
return screenPost(caller, {
|
|
626
|
+
...channel !== void 0 ? { channel } : {},
|
|
627
|
+
...re !== void 0 ? { re } : {},
|
|
628
|
+
text,
|
|
629
|
+
...labels ? { labels } : {},
|
|
630
|
+
...noSync !== void 0 ? { noSync } : {},
|
|
631
|
+
...attachments ? { attachments } : {}
|
|
632
|
+
}, signal, (screened) => backend.schedule(caller, {
|
|
633
|
+
...input,
|
|
634
|
+
text: screened.text,
|
|
635
|
+
...screened.labels ? { labels: screened.labels } : {}
|
|
636
|
+
}, signal));
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
516
639
|
* The caller's own message, still there, where it may post: what an edit of it changes. One it
|
|
517
640
|
* can't read may still be its own, as board()'s own code finds it, only if it was written there
|
|
518
641
|
* without reading too: one its principal wrote where it reads (a repo's workflows share one) is
|
|
@@ -709,22 +832,27 @@ async function createServe(context) {
|
|
|
709
832
|
switch (name) {
|
|
710
833
|
case "post": return importPost(caller, input, signal);
|
|
711
834
|
case "edit": {
|
|
712
|
-
const { id, text } = input;
|
|
835
|
+
const { id, text, revision } = input;
|
|
713
836
|
return change({
|
|
714
837
|
op: "edit",
|
|
715
838
|
author,
|
|
716
839
|
from: null,
|
|
717
840
|
message: id,
|
|
718
841
|
text: await withEmails(text),
|
|
719
|
-
source
|
|
842
|
+
source,
|
|
843
|
+
...revision !== void 0 ? { revision } : {}
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
case "delete": {
|
|
847
|
+
const { id, revision } = input;
|
|
848
|
+
return change({
|
|
849
|
+
op: "delete",
|
|
850
|
+
author,
|
|
851
|
+
from: null,
|
|
852
|
+
message: id,
|
|
853
|
+
...revision !== void 0 ? { revision } : {}
|
|
720
854
|
});
|
|
721
855
|
}
|
|
722
|
-
case "delete": return change({
|
|
723
|
-
op: "delete",
|
|
724
|
-
author,
|
|
725
|
-
from: null,
|
|
726
|
-
message: input.id
|
|
727
|
-
});
|
|
728
856
|
case "react": {
|
|
729
857
|
const { id, emoji, remove } = input;
|
|
730
858
|
return change({
|
|
@@ -737,14 +865,15 @@ async function createServe(context) {
|
|
|
737
865
|
});
|
|
738
866
|
}
|
|
739
867
|
case "label": {
|
|
740
|
-
const { id, set, unset } = input;
|
|
868
|
+
const { id, set, unset, if: expected } = input;
|
|
741
869
|
return change({
|
|
742
870
|
op: "labels",
|
|
743
871
|
author,
|
|
744
872
|
from: null,
|
|
745
873
|
message: id,
|
|
746
874
|
...set ? { set } : {},
|
|
747
|
-
...unset ? { unset } : {}
|
|
875
|
+
...unset ? { unset } : {},
|
|
876
|
+
...expected ? { if: expected } : {}
|
|
748
877
|
});
|
|
749
878
|
}
|
|
750
879
|
default: return invoke(name, me, input, signal);
|
|
@@ -780,7 +909,8 @@ async function createServe(context) {
|
|
|
780
909
|
...input.labels ? { labels: input.labels } : {},
|
|
781
910
|
...input.noSync ? { noSync: true } : {},
|
|
782
911
|
...input.attachments?.length ? { attachments: input.attachments } : {},
|
|
783
|
-
...input.id !== void 0 ? { id: input.id } : {}
|
|
912
|
+
...input.id !== void 0 ? { id: input.id } : {},
|
|
913
|
+
...input.after !== void 0 ? { after: input.after } : {}
|
|
784
914
|
}, signal));
|
|
785
915
|
for (const target of targets) await backend.syncReceipt(me, {
|
|
786
916
|
kind: "thread",
|
|
@@ -791,32 +921,36 @@ async function createServe(context) {
|
|
|
791
921
|
}, signal);
|
|
792
922
|
return { message: await read(message) };
|
|
793
923
|
}
|
|
794
|
-
/** The protocol method `name`, as board() serves it: what it posts and edits
|
|
924
|
+
/** The protocol method `name`, as board() serves it: what it posts and edits, and the filters it reads by, with their emails resolved. */
|
|
795
925
|
async function invoke(name, caller, input, signal) {
|
|
926
|
+
if (name === "messages" || name === "threads" || name === "events") input = await withMentioned(caller, input);
|
|
796
927
|
switch (name) {
|
|
797
928
|
case "post": return post(caller, input, signal);
|
|
798
929
|
case "edit": {
|
|
799
|
-
const
|
|
930
|
+
const edit = input;
|
|
800
931
|
const send = (body) => screenEdit(caller, body, signal, (patched) => backend.edit(caller, patched, signal));
|
|
801
|
-
if (!engine || !mentionsEmails(text) || !await mayPost(caller, { re: id }, signal)) return send(
|
|
802
|
-
id,
|
|
803
|
-
text
|
|
804
|
-
});
|
|
932
|
+
if (!engine || !mentionsEmails(edit.text) || !await mayPost(caller, { re: edit.id }, signal)) return send(edit);
|
|
805
933
|
return send({
|
|
806
|
-
|
|
807
|
-
text: await withEmails(text)
|
|
934
|
+
...edit,
|
|
935
|
+
text: await withEmails(edit.text)
|
|
808
936
|
});
|
|
809
937
|
}
|
|
810
938
|
case "syncImport": return syncImport(caller, input, signal);
|
|
939
|
+
case "grants":
|
|
940
|
+
case "createGrant":
|
|
941
|
+
case "revokeGrant":
|
|
942
|
+
if (backend.keeps && !policy.takesGrants) throw new HttpError(501, "no_grants", "this board's access is one function, which decides alone: it keeps no grants");
|
|
943
|
+
return backend[name].call(backend, caller, input, signal);
|
|
811
944
|
case "schedule": {
|
|
812
945
|
const body = input;
|
|
946
|
+
const schedule = (made) => backend.sendsSchedulesHere === false ? screenSchedule(caller, made, signal) : backend.schedule(caller, made, signal);
|
|
813
947
|
const allowed = engine && mentionsEmails(body.text) ? await mayPost(caller, body, signal) : null;
|
|
814
|
-
if (!allowed) return
|
|
948
|
+
if (!allowed) return schedule(body);
|
|
815
949
|
const text = await withEmails(body.text);
|
|
816
|
-
const answer = await
|
|
950
|
+
const answer = await schedule({
|
|
817
951
|
...body,
|
|
818
952
|
text
|
|
819
|
-
}
|
|
953
|
+
});
|
|
820
954
|
const { access } = allowed;
|
|
821
955
|
return text !== body.text && access !== null && !access.readable(answer.schedule.channel) ? {
|
|
822
956
|
...answer,
|
|
@@ -844,35 +978,6 @@ async function createServe(context) {
|
|
|
844
978
|
}
|
|
845
979
|
}
|
|
846
980
|
}
|
|
847
|
-
/**
|
|
848
|
-
* A sign-in that carries a code, where the entry's sync runs: a browser opens it with no token,
|
|
849
|
-
* so the code is all it has. `signIn` sends the person to the platform as whoever the code was
|
|
850
|
-
* issued to; `signedIn`, where the platform sends them back, links the account they approved.
|
|
851
|
-
*/
|
|
852
|
-
async function signingIn(name, url, signal) {
|
|
853
|
-
const { sync: key, code } = SignInQuery.parse(queryObject(url));
|
|
854
|
-
if (!engine || !entries[key]) throw notFound(`no sync entry named ${key}`);
|
|
855
|
-
const page = (status, text) => new Response(`${text}\n`, {
|
|
856
|
-
status,
|
|
857
|
-
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
858
|
-
});
|
|
859
|
-
const { holder } = await backend.syncFind(engine.caller, { code }, signal);
|
|
860
|
-
if (holder === null) return page(404, "That link has expired or been used. Run `vb identity link` again for a new one.");
|
|
861
|
-
if (name === "signIn") {
|
|
862
|
-
const back = ownUrl();
|
|
863
|
-
const at = await engine.signIn(key, holder, back ? `${back}/identity/signed-in?${new URLSearchParams({
|
|
864
|
-
sync: key,
|
|
865
|
-
code
|
|
866
|
-
})}` : void 0);
|
|
867
|
-
return new Response(null, {
|
|
868
|
-
status: 302,
|
|
869
|
-
headers: { location: at }
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
const linked = await engine.redeemSignIn(key, holder, code);
|
|
873
|
-
if (!linked) return page(409, `${entries[key].platform} hasn't said you approved yet: finish signing in, then open this page again.`);
|
|
874
|
-
return page(200, `Linked ${linked.handle ? `@${linked.handle}` : linked.actor}. You can close this tab.`);
|
|
875
|
-
}
|
|
876
981
|
/** board()'s own routes for a caller: sync's, which run where board() does. */
|
|
877
982
|
async function serve(name, caller, request, url, params) {
|
|
878
983
|
const signal = request.signal;
|
|
@@ -899,19 +1004,6 @@ async function createServe(context) {
|
|
|
899
1004
|
attached: attached ?? true
|
|
900
1005
|
});
|
|
901
1006
|
}
|
|
902
|
-
case "identityUnlink": {
|
|
903
|
-
const body = IdentityUnlinkBody.parse(await readJson(request));
|
|
904
|
-
const { links } = await backend.identity(caller, {}, signal);
|
|
905
|
-
const wanted = body.actor.replace(/^@/, "").toLowerCase();
|
|
906
|
-
const found = links.filter((link) => link.actor === body.actor || link.handle?.toLowerCase() === wanted).map((link) => link.actor);
|
|
907
|
-
if (found.length === 0) throw notFound(`${body.actor} isn't linked to you`, "not_linked");
|
|
908
|
-
if (found.length > 1) throw badRequest(`${body.actor} names several of your accounts: use ${found.join(" or ")}`, "ambiguous_actor");
|
|
909
|
-
await backend.syncUnlink(caller, { account: found[0] }, signal);
|
|
910
|
-
return json({
|
|
911
|
-
ok: true,
|
|
912
|
-
actor: found[0]
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
1007
|
default: throw notFound(`no route: ${request.method} ${url.pathname}`);
|
|
916
1008
|
}
|
|
917
1009
|
}
|
|
@@ -1020,7 +1112,6 @@ async function createServe(context) {
|
|
|
1020
1112
|
if (!engine || !entries[match.params.key ?? ""]) throw notFound(`no sync entry named ${match.params.key}`);
|
|
1021
1113
|
return await engine.handleWebhook(match.params.key, request);
|
|
1022
1114
|
}
|
|
1023
|
-
if (target.kind === "board" && (target.name === "signIn" || target.name === "signedIn")) return await signingIn(target.name, url, request.signal);
|
|
1024
1115
|
if (target.kind === "board" && target.name === "avatar") {
|
|
1025
1116
|
const name = match.param ?? "";
|
|
1026
1117
|
if (!KNOWN_AGENTS.includes(name)) throw notFound(`no avatar: ${name}`);
|
|
@@ -1042,10 +1133,11 @@ async function createServe(context) {
|
|
|
1042
1133
|
const caller = own ?? await callerOf(asked);
|
|
1043
1134
|
if (target.kind === "board") return await serve(target.name, caller, request, url, match.params);
|
|
1044
1135
|
const input = await inputOf(target.name, request, url, match.params);
|
|
1045
|
-
if (target.name === "events" && (request.headers.get("accept") ?? "").includes("text/event-stream")) return await sse(caller, input, request, asked ? () => callerOf(asked) : null);
|
|
1136
|
+
if (target.name === "events" && (request.headers.get("accept") ?? "").includes("text/event-stream")) return await sse(caller, await withMentioned(caller, input), request, asked ? () => callerOf(asked) : null);
|
|
1046
1137
|
const output = injected && "platform" in injected ? await asPlatform(target.name, injected, input, request.signal) : await invoke(target.name, caller, input, request.signal);
|
|
1047
1138
|
if (target.name === "attachment") return attachmentResponse(output);
|
|
1048
|
-
|
|
1139
|
+
const made = CREATED.has(target.name) || target.name === "createChannel" && output.created;
|
|
1140
|
+
return json(output, made ? 201 : 200);
|
|
1049
1141
|
} catch (error) {
|
|
1050
1142
|
return toErrorResponse(error);
|
|
1051
1143
|
}
|
|
@@ -1110,7 +1202,7 @@ const EVENTS_WAIT_S = 25;
|
|
|
1110
1202
|
*/
|
|
1111
1203
|
function createBoard(options, runtime) {
|
|
1112
1204
|
const backend = options.backend ?? runtime.http();
|
|
1113
|
-
const policy = createPolicy(options, backend.keeps, runtime.serving);
|
|
1205
|
+
const policy = createPolicy(options, backend.keeps, runtime.serving, backend.trust ?? null);
|
|
1114
1206
|
const watch = options.watch ?? {};
|
|
1115
1207
|
const entries = options.sync ?? {};
|
|
1116
1208
|
const state = { learned: null };
|
|
@@ -1218,8 +1310,7 @@ function createBoard(options, runtime) {
|
|
|
1218
1310
|
/** This board's sync entries, as its sync and discovery see them. */
|
|
1219
1311
|
const ownEntries = Object.entries(entries).map(([key, entry]) => ({
|
|
1220
1312
|
key,
|
|
1221
|
-
platform: entry.platform
|
|
1222
|
-
signIn: entry.users !== null
|
|
1313
|
+
platform: entry.platform
|
|
1223
1314
|
}));
|
|
1224
1315
|
const binding = {
|
|
1225
1316
|
subscriptions,
|
|
@@ -1252,6 +1343,7 @@ function createBoard(options, runtime) {
|
|
|
1252
1343
|
typing: async (messages, status) => (await server()).typing(messages, status),
|
|
1253
1344
|
accessNow: (principal) => policy.accessNow(principal),
|
|
1254
1345
|
project: () => policy.project(),
|
|
1346
|
+
issuers: () => policy.issuers,
|
|
1255
1347
|
handle: (caller) => handleOf(caller, null),
|
|
1256
1348
|
url: ownUrl
|
|
1257
1349
|
};
|