@seedcord/http 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import "reflect-metadata";
2
- import { GatedMetadataKey, PublishDefault, RegisterDefaults, RegisterSubscriber, accessorStore, asError, clearStore, decodeComponentRoute, deferFlags, guardedAccessor, isEmojiTuple, outcomeFor, queuedMsFor, reportDispatch, reportedWrite, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
2
+ import { GatedMetadataKey, MiddlewareRegistry, PublishDefault, RegisterDefaults, RegisterSubscriber, accessorStore, asError, clearStore, decodeComponentRoute, deferFlags, guardedAccessor, interactionMiddleware, isEmojiTuple, outcomeFor, queuedMsFor, reportDispatch, reportedWrite, resultFor, runAfter, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
3
3
  import { SeedcordError, SeedcordTypeError, applicationIdFromToken, validateDiscordToken } from "@seedcord/errors/internal";
4
4
  import { Logger } from "@seedcord/logger";
5
5
  import { Converters, Envapter } from "envapt";
@@ -46,9 +46,10 @@ function callbackData(reply, flags) {
46
46
  var ReplySender = class extends BaseReplySender {
47
47
  ref;
48
48
  rest;
49
- constructor(ref, rest, routeId, bus) {
50
- super(routeId, {
49
+ constructor(ref, rest, dispatch, bus) {
50
+ super({
51
51
  bus,
52
+ dispatch,
52
53
  interactionId: ref.id
53
54
  });
54
55
  this.ref = ref;
@@ -150,13 +151,13 @@ var RepliableHandler$1 = class extends RepliableHandler {
150
151
  getEvent() {
151
152
  return this.event;
152
153
  }
153
- buildSender(event, core, routeId) {
154
+ buildSender(event, core, dispatch) {
154
155
  const ref = {
155
156
  application_id: event.application_id,
156
157
  id: event.id,
157
158
  token: event.token
158
159
  };
159
- return new ReplySender(ref, core.rest, routeId, core.bus);
160
+ return new ReplySender(ref, core.rest, dispatch, core.bus);
160
161
  }
161
162
  /**
162
163
  * Typed Discord REST API (`@discordjs/core/http-only`) over `core.rest`, one instance per core.
@@ -170,12 +171,13 @@ var RepliableHandler$1 = class extends RepliableHandler {
170
171
 
171
172
  //#endregion
172
173
  //#region src/gates/context.ts
173
- function interactionGateContext(payload, core, routeId) {
174
+ function interactionGateContext(payload, core, dispatch) {
174
175
  const member = payload.member ?? null;
175
176
  const resolved = member?.user ?? payload.user;
176
177
  return {
177
178
  kind: "interaction",
178
179
  core,
180
+ dispatch,
179
181
  interaction: payload,
180
182
  user: resolved,
181
183
  member,
@@ -185,7 +187,7 @@ function interactionGateContext(payload, core, routeId) {
185
187
  memberRoleIds: member?.roles ?? [],
186
188
  memberPermissions: member ? BigInt(member.permissions) : null,
187
189
  appPermissions: BigInt(payload.app_permissions),
188
- routeId
190
+ declaredRoute: null
189
191
  };
190
192
  }
191
193
 
@@ -200,18 +202,21 @@ function slashRouteOf(data) {
200
202
 
201
203
  //#endregion
202
204
  //#region src/dispatch/reportFault.ts
203
- function reportFault(error, uuid, routeId, payload, core) {
205
+ function reportFault(error, uuid, dispatch, payload, core) {
204
206
  const source = interactionSource(payload);
207
+ const from = {
208
+ uuid,
209
+ dispatchId: dispatch.id,
210
+ origin: dispatch.routeId
211
+ };
205
212
  if (error instanceof Notice && source) core.bus[PublishDefault]("handledException", {
206
213
  denial: error,
207
- uuid,
208
- routeId,
214
+ ...from,
209
215
  source
210
216
  });
211
217
  else core.bus[PublishDefault]("unknownException", {
212
- uuid,
218
+ ...from,
213
219
  error,
214
- routeId,
215
220
  ...actors(payload),
216
221
  metadata: payload
217
222
  });
@@ -313,44 +318,50 @@ async function sendGuarded(routeId, send) {
313
318
  async function respondEmptyChoices(scope) {
314
319
  const telemetry = {
315
320
  bus: scope.core.bus,
321
+ dispatch: scope.dispatch,
316
322
  interactionId: scope.payload.id
317
323
  };
318
- await reportedWrite(telemetry, scope.routeId, "respond", () => scope.core.rest.post(Routes.interactionCallback(scope.payload.id, scope.payload.token), { body: {
324
+ await reportedWrite(telemetry, "respond", () => scope.core.rest.post(Routes.interactionCallback(scope.payload.id, scope.payload.token), { body: {
319
325
  type: InteractionResponseType.ApplicationCommandAutocompleteResult,
320
326
  data: { choices: [] }
321
327
  } }));
322
328
  }
323
- function renderContext(core, uuid) {
324
- const developerUsername = core.config.notifications?.developerUsername;
325
- return developerUsername === void 0 ? { uuid } : {
329
+ function renderContext(scope, uuid) {
330
+ const { dispatch } = scope;
331
+ const developerUsername = scope.core.config.notifications?.developerUsername;
332
+ return developerUsername === void 0 ? {
333
+ uuid,
334
+ dispatch
335
+ } : {
326
336
  uuid,
327
- developerUsername
337
+ developerUsername,
338
+ dispatch
328
339
  };
329
340
  }
330
341
  async function handleNotice(notice, uuid, scope) {
331
342
  if (notice.report) {
332
343
  logger().error(`${notice.name}: ${paint.mute(uuid)}`, notice);
333
- reportFault(notice, uuid, scope.routeId, scope.payload, scope.core);
344
+ reportFault(notice, uuid, scope.dispatch, scope.payload, scope.core);
334
345
  }
335
346
  const { sender } = scope;
336
347
  if (!sender) {
337
348
  await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
338
349
  return;
339
350
  }
340
- const response = notice.render(renderContext(scope.core, uuid));
351
+ const response = notice.render(renderContext(scope, uuid));
341
352
  await sendGuarded(scope.routeId, () => sender.send(response, { ephemeral: notice.ephemeral }));
342
353
  }
343
354
  async function handleRawFault(error, uuid, scope) {
344
355
  const { core, sender } = scope;
345
356
  if (core.config.errors?.errorStack ?? false) logger().error(paint.mute(uuid), error);
346
357
  else logger().error(`${paint.mute(uuid)} | ${error.message}`);
347
- reportFault(error, uuid, scope.routeId, scope.payload, core);
358
+ reportFault(error, uuid, scope.dispatch, scope.payload, core);
348
359
  if (!sender) {
349
360
  await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
350
361
  return;
351
362
  }
352
363
  const Override = core.config.errors?.defaultError;
353
- const response = (Override ? new Override(uuid) : new Fault()).render(renderContext(core, uuid));
364
+ const response = (Override ? new Override(uuid) : new Fault()).render(renderContext(scope, uuid));
354
365
  await sendGuarded(scope.routeId, () => sender.send(response, { ephemeral: true }));
355
366
  }
356
367
  async function handleFault(caught, scope) {
@@ -376,30 +387,33 @@ function unhandledRouteId(match) {
376
387
  const key = match.attemptedKey ?? "";
377
388
  return `${match.kind}:${key.length > 0 ? key : "unrouted"}`;
378
389
  }
379
- function freshScope(match, payload, core) {
390
+ function freshScope(match, payload, core, dispatch) {
380
391
  const ref = {
381
392
  application_id: payload.application_id,
382
393
  id: payload.id,
383
394
  token: payload.token
384
395
  };
385
- const routeId = unhandledRouteId(match);
386
396
  return {
387
397
  core,
388
398
  payload,
389
- routeId,
390
- sender: match.kind === "autocomplete" ? null : new ReplySender(ref, core.rest, routeId, core.bus)
399
+ routeId: unhandledRouteId(match),
400
+ dispatch,
401
+ sender: match.kind === InteractionKind.Autocomplete ? null : new ReplySender(ref, core.rest, dispatch, core.bus)
391
402
  };
392
403
  }
393
- function dispatchReporter(match, payload, core) {
404
+ function dispatchReporter(match, payload, core, dispatchId) {
394
405
  const startedAt = performance.now();
395
406
  const queuedMs = queuedMsFor(payload.id);
396
407
  return (outcome) => {
397
408
  reportDispatch(core.bus, {
409
+ dispatchId,
398
410
  routeId: unhandledRouteId(match),
399
411
  interactionId: payload.id,
400
412
  kind: match.kind,
401
413
  outcome,
402
414
  fallback: match.routeId === null,
415
+ userId: (payload.member?.user ?? payload.user)?.id ?? null,
416
+ guildId: payload.guild_id ?? null,
403
417
  startedAt,
404
418
  queuedMs
405
419
  });
@@ -412,61 +426,98 @@ async function answer(caught, scope, report, outcome = outcomeFor(caught)) {
412
426
  report(outcome);
413
427
  }
414
428
  }
415
- async function loadHandlerCtor(match, payload, core, report) {
429
+ async function loadHandlerCtor(match, payload, core, dispatch, report) {
416
430
  const routeId = unhandledRouteId(match);
417
431
  let exported;
418
432
  try {
419
433
  exported = await match.load();
420
434
  } catch (caught) {
421
435
  logger().error(`Route ${paint.sky.bold(routeId)} failed to load its handler.`, caught);
422
- await answer(caught, freshScope(match, payload, core), report, "failed");
436
+ await answer(caught, freshScope(match, payload, core, dispatch), report, "failed");
423
437
  return null;
424
438
  }
425
439
  if (isHandlerCtor(exported)) return exported;
426
440
  logger().error(`Route ${paint.sky.bold(routeId)} loaded an export that is not a handler class.`);
427
- await answer(/* @__PURE__ */ new Error(`route ${routeId} loaded an export that is not a handler class`), freshScope(match, payload, core), report, "failed");
441
+ await answer(/* @__PURE__ */ new Error(`route ${routeId} loaded an export that is not a handler class`), freshScope(match, payload, core, dispatch), report, "failed");
428
442
  return null;
429
443
  }
444
+ async function runMiddlewares(step, kind, sender) {
445
+ const { args, dispatch, ran } = step;
446
+ for (const Middleware of args.middlewares.chainFor(kind)) {
447
+ const event = args.payload;
448
+ const instance = new Middleware(event, args.core, dispatch, sender);
449
+ ran.push(instance);
450
+ await instance.execute();
451
+ }
452
+ }
453
+ async function refusalBeforeHandler(step) {
454
+ const { args, scope } = step;
455
+ const { kind } = args.match;
456
+ if (kind !== InteractionKind.Autocomplete && scope.sender) try {
457
+ await runMiddlewares(step, kind, scope.sender);
458
+ } catch (caught) {
459
+ return { caught };
460
+ }
461
+ return gateRefusal(step);
462
+ }
430
463
  async function dispatchInteraction(args) {
431
464
  const { match, payload, core } = args;
432
- const report = dispatchReporter(match, payload, core);
433
- const Handler = await loadHandlerCtor(match, payload, core, report);
434
- if (!Handler) return null;
435
465
  const routeId = unhandledRouteId(match);
436
- logger().debug(`Processing ${paint.sky.bold(routeId)} with ${paint.mute(Handler.name)}`);
437
466
  const dispatch = new DispatchContext(routeId);
467
+ const report = dispatchReporter(match, payload, core, dispatch.id);
468
+ const Handler = await loadHandlerCtor(match, payload, core, dispatch, report);
469
+ if (!Handler) return null;
470
+ logger().debug(`Processing ${paint.sky.bold(routeId)} with ${paint.mute(Handler.name)}`);
438
471
  let handler;
439
472
  try {
440
473
  handler = new Handler(payload, core, dispatch);
441
474
  } catch (caught) {
442
- await answer(caught, freshScope(match, payload, core), report);
475
+ await answer(caught, freshScope(match, payload, core, dispatch), report);
443
476
  return null;
444
477
  }
445
478
  const scope = {
446
479
  core,
447
480
  payload,
448
481
  routeId,
482
+ dispatch,
449
483
  sender: handler instanceof RepliableHandler$1 ? handler.sender : null
450
484
  };
451
- const refusal = await gateRefusal(Handler, match, payload, core);
485
+ const ran = [];
486
+ const refusal = await refusalBeforeHandler({
487
+ args,
488
+ Handler,
489
+ dispatch,
490
+ scope,
491
+ ran
492
+ });
452
493
  if (refusal) {
453
- await answer(refusal.caught, scope, report);
494
+ try {
495
+ await answer(refusal.caught, scope, report);
496
+ } finally {
497
+ await runAfter(ran, resultFor(refusal.caught), logger());
498
+ }
454
499
  return null;
455
500
  }
456
501
  return async () => {
502
+ let result = { outcome: "handled" };
457
503
  try {
458
504
  await handler.execute();
459
505
  report("handled");
460
506
  } catch (caught) {
507
+ result = resultFor(caught);
461
508
  await answer(caught, scope, report);
509
+ } finally {
510
+ await runAfter(ran, result, logger());
462
511
  }
463
512
  };
464
513
  }
465
- async function gateRefusal(ctor, match, payload, core) {
466
- if (match.kind === "autocomplete" || payload.type === InteractionType.ApplicationCommandAutocomplete) return null;
514
+ async function gateRefusal(step) {
515
+ const { Handler, dispatch } = step;
516
+ const { match, payload, core } = step.args;
517
+ if (match.kind === InteractionKind.Autocomplete || payload.type === InteractionType.ApplicationCommandAutocomplete) return null;
467
518
  const monitor = slowGateMonitor();
468
519
  try {
469
- await runHandlerGates(ctor, interactionGateContext(payload, core, match.routeId), match.routeId ?? void 0, monitor?.observe);
520
+ await runHandlerGates(Handler, interactionGateContext(payload, core, dispatch), match.routeId ?? void 0, monitor?.observe);
470
521
  return null;
471
522
  } catch (caught) {
472
523
  return { caught };
@@ -637,11 +688,11 @@ var AutocompleteHandler = class extends BaseHandler {
637
688
  }
638
689
  /** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
639
690
  async respond(choices) {
640
- const routeId = this.dispatch?.routeId ?? "autocomplete";
641
691
  await reportedWrite({
642
692
  bus: this.core.bus,
693
+ dispatch: this.dispatch,
643
694
  interactionId: this.event.id
644
- }, routeId, "respond", () => this.core.rest.post(Routes.interactionCallback(this.event.id, this.event.token), { body: {
695
+ }, "respond", () => this.core.rest.post(Routes.interactionCallback(this.event.id, this.event.token), { body: {
645
696
  type: InteractionResponseType.ApplicationCommandAutocompleteResult,
646
697
  data: { choices }
647
698
  } }));
@@ -899,7 +950,7 @@ const decoder = new TextDecoder();
899
950
  function hasInteractionType(payload) {
900
951
  return typeof payload === "object" && payload !== null && "type" in payload && typeof payload.type === "number";
901
952
  }
902
- function buildEngine(core, maps) {
953
+ function buildEngine(core, maps, middlewares) {
903
954
  if (!Envapter.has("DISCORD_PUBLIC_KEY")) throw new SeedcordError(SeedcordErrorCode.ConfigMissingEnv, ["DISCORD_PUBLIC_KEY"]);
904
955
  const publicKey = Envapter.getRequired("DISCORD_PUBLIC_KEY", Converters.String);
905
956
  const verifier = new Ed25519Verifier(publicKey);
@@ -910,7 +961,8 @@ function buildEngine(core, maps) {
910
961
  const start = await dispatchInteraction({
911
962
  match,
912
963
  payload,
913
- core
964
+ core,
965
+ middlewares
914
966
  });
915
967
  if (!start) return;
916
968
  const work = start().catch(rootFault);
@@ -990,7 +1042,7 @@ function createSeedcord(config, manifest) {
990
1042
  const core = createCore(config, token);
991
1043
  core.bus[RegisterDefaults]();
992
1044
  registerSubscribers(core.bus, manifest);
993
- return buildEngine(core, buildRouteMaps(manifest)).handle;
1045
+ return buildEngine(core, buildRouteMaps(manifest), new MiddlewareRegistry(interactionMiddleware)).handle;
994
1046
  }
995
1047
 
996
1048
  //#endregion
@@ -1238,6 +1290,34 @@ var EmojiInjector = class {
1238
1290
  }
1239
1291
  };
1240
1292
 
1293
+ //#endregion
1294
+ //#region src/handlers/interaction/InteractionMiddleware.ts
1295
+ /**
1296
+ * Base class for interaction middleware on the HTTP transport.
1297
+ *
1298
+ * Middleware runs after the dispatcher builds the handler and before the gates, over the handler's own
1299
+ * reply surface. Name the kinds in the generic and list the same ones in `{ kinds }`. Several kinds give
1300
+ * `this.event` a union. Narrow it on `type` and `data.component_type`. A catchall reads every repliable
1301
+ * kind.
1302
+ *
1303
+ * @typeParam Kind - One or more {@link InteractionKind} members. Defaults to every repliable kind.
1304
+ *
1305
+ * @example
1306
+ * ```ts
1307
+ * \@RegisterInteractionMiddleware({ kinds: [InteractionKind.Button] })
1308
+ * class Audit extends InteractionMiddleware<InteractionKind.Button> {
1309
+ * async execute() {
1310
+ * this.logger.info(this.event.data.custom_id);
1311
+ * }
1312
+ * }
1313
+ * ```
1314
+ */
1315
+ var InteractionMiddleware = class extends RepliableHandler$1 {
1316
+ constructor(event, core, dispatch, sender) {
1317
+ super(event, core, dispatch, sender);
1318
+ }
1319
+ };
1320
+
1241
1321
  //#endregion
1242
1322
  //#region src/handlers/interaction/SlashHandler.ts
1243
1323
  /**
@@ -1813,8 +1893,8 @@ function Gated(...gates) {
1813
1893
 
1814
1894
  //#endregion
1815
1895
  //#region src/version.ts
1816
- const version = "0.7.0";
1896
+ const version = "0.8.1";
1817
1897
 
1818
1898
  //#endregion
1819
- export { createSeedcord as C, AutocompleteHandler as D, InteractionHandler as E, RepliableHandler$1 as O, ModalFields as S, buildRouteMaps as T, MessageContextMenuHandler as _, Paginator as a, EmojiInjector as b, ChannelMenuHandler as c, SelectMenuHandler as d, StringMenuHandler as f, ComponentHandler as g, ButtonHandler as h, CursorSource as i, ReplySender as k, MentionableMenuHandler as l, ModalHandler as m, Gated as n, Subscriber$1 as o, UserMenuHandler as p, ArraySource as r, WebhookLog$1 as s, version as t, RoleMenuHandler as u, UserContextMenuHandler as v, buildEngine as w, Emojis as x, SlashHandler as y };
1820
- //# sourceMappingURL=edge.index-CWUU_d_X.mjs.map
1899
+ export { ReplySender as A, ModalFields as C, InteractionHandler as D, buildRouteMaps as E, AutocompleteHandler as O, Emojis as S, buildEngine as T, MessageContextMenuHandler as _, Paginator as a, InteractionMiddleware as b, ChannelMenuHandler as c, SelectMenuHandler as d, StringMenuHandler as f, ComponentHandler as g, ButtonHandler as h, CursorSource as i, RepliableHandler$1 as k, MentionableMenuHandler as l, ModalHandler as m, Gated as n, Subscriber$1 as o, UserMenuHandler as p, ArraySource as r, WebhookLog$1 as s, version as t, RoleMenuHandler as u, UserContextMenuHandler as v, createSeedcord as w, EmojiInjector as x, SlashHandler as y };
1900
+ //# sourceMappingURL=edge.index-CdX0NeZA.mjs.map