@helyx/bot 1.3.1 → 1.5.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/CHANGELOG.md +100 -28
- package/README.md +65 -11
- package/dist/channel-audit-configuration.d.ts +7 -0
- package/dist/channel-audit-configuration.js +40 -0
- package/dist/channel-audit-control.d.ts +14 -0
- package/dist/channel-audit-control.js +61 -0
- package/dist/channel-audit-permissions.d.ts +56 -0
- package/dist/channel-audit-permissions.js +417 -0
- package/dist/channel-audit-routes.d.ts +4 -0
- package/dist/channel-audit-routes.js +42 -0
- package/dist/cloud-connector.d.ts +11 -4
- package/dist/cloud-connector.js +54 -15
- package/dist/cloud-control-dispatcher.d.ts +8 -3
- package/dist/cloud-control-dispatcher.js +84 -4
- package/dist/control-errors.d.ts +12 -0
- package/dist/control-errors.js +32 -0
- package/dist/control-server-schemas.d.ts +84 -0
- package/dist/control-server-schemas.js +101 -0
- package/dist/control-server.d.ts +25 -8
- package/dist/control-server.js +101 -152
- package/dist/core-retention.d.ts +26 -0
- package/dist/core-retention.js +49 -0
- package/dist/creator-guild-join-service.d.ts +25 -0
- package/dist/creator-guild-join-service.js +218 -0
- package/dist/creator-invite-service.d.ts +37 -0
- package/dist/creator-invite-service.js +686 -0
- package/dist/creator-onboarding-control.d.ts +12 -0
- package/dist/creator-onboarding-control.js +65 -0
- package/dist/creator-onboarding-platform.d.ts +20 -0
- package/dist/creator-onboarding-platform.js +58 -0
- package/dist/dashboard-action-control.d.ts +2 -2
- package/dist/dashboard-action-control.js +2 -3
- package/dist/database-bootstrap.d.ts +36 -0
- package/dist/database-bootstrap.js +101 -0
- package/dist/developer-operations-module.d.ts +1 -0
- package/dist/developer-operations-module.js +34 -16
- package/dist/discord-channel-audit.d.ts +9 -0
- package/dist/discord-channel-audit.js +195 -0
- package/dist/discord-guild-health.d.ts +2 -2
- package/dist/discord-owned-message-service.d.ts +1 -0
- package/dist/discord-owned-message-service.js +3 -0
- package/dist/discord-role-safety.d.ts +4 -0
- package/dist/discord-role-safety.js +30 -0
- package/dist/events.d.ts +2 -0
- package/dist/events.js +5 -20
- package/dist/guild-command-read-service.d.ts +8 -3
- package/dist/guild-command-read-service.js +14 -7
- package/dist/guild-command-release.d.ts +15 -8
- package/dist/guild-command-release.js +10 -3
- package/dist/guild-command-worker.d.ts +17 -4
- package/dist/guild-command-worker.js +17 -6
- package/dist/guilds.d.ts +24 -3
- package/dist/guilds.js +185 -64
- package/dist/hosted-server-moderation.js +10 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +200 -82
- package/dist/interactions.js +16 -3
- package/dist/module-configuration.d.ts +1 -1
- package/dist/module-configuration.js +5 -2
- package/dist/module-logging.d.ts +8 -3
- package/dist/module-logging.js +13 -7
- package/dist/modules.d.ts +1 -0
- package/dist/modules.js +8 -1
- package/dist/mysql-runtime-persistence.d.ts +46 -0
- package/dist/mysql-runtime-persistence.js +91 -0
- package/dist/oauth-module.js +2 -2
- package/dist/oauth-service.d.ts +7 -0
- package/dist/oauth-service.js +3 -0
- package/dist/scheduled-task-runner.d.ts +9 -85
- package/dist/scheduled-task-runner.js +1 -1
- package/dist/shard-contracts.d.ts +222 -0
- package/dist/shard-contracts.js +52 -1
- package/dist/shard-router.d.ts +4 -1
- package/dist/shard-router.js +19 -0
- package/dist/shard-supervisor.js +43 -9
- package/dist/shard-worker.d.ts +5 -2
- package/dist/shard-worker.js +41 -5
- package/dist/supervisor-cloud-directory.d.ts +9 -3
- package/dist/supervisor-cloud-directory.js +8 -12
- package/dist/trivia-data-provider.d.ts +11 -0
- package/dist/trivia-data-provider.js +46 -26
- package/dist/verification-service.d.ts +2 -2
- package/dist/verification-service.js +6 -3
- package/documentation/README.md +5 -3
- package/documentation/examples/.env.example +15 -11
- package/documentation/guides/getting-started.md +29 -12
- package/documentation/guides/privacy-and-support.md +6 -5
- package/documentation/guides/self-hosting.md +16 -11
- package/documentation/modules/auto-moderation.md +7 -5
- package/documentation/modules/building-a-module.md +48 -2
- package/documentation/modules/moderation.md +5 -5
- package/documentation/modules/publishing-a-module.md +6 -4
- package/documentation/modules/testing-a-module.md +39 -4
- package/documentation/reference/environment.md +43 -9
- package/documentation/runbooks/backup-and-recovery.md +26 -7
- package/documentation/runbooks/installation-and-upgrades.md +97 -27
- package/package.json +20 -16
package/dist/shard-contracts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { channelAuditModuleIdSchema, channelAuditInputSchema, channelAuditConfigurationSchema, channelAuditReportSchema, } from "@helyx/control-protocol/channel-audit";
|
|
3
|
+
import { CLOUD_MAX_RESOURCE_IMPORT_BYTES, creatorOnboardingRequestSchema, } from "@helyx/control-protocol";
|
|
3
4
|
import { AUTOMOD_SCANNER_LIMITS } from "@helyx/sdk";
|
|
4
5
|
import { signedAssessmentEnvelopeSchema } from "@helyx/verification-contracts";
|
|
5
6
|
export const SHARD_CONTRACT_VERSION = 1;
|
|
@@ -254,6 +255,14 @@ export const shardHealthSnapshotSchema = z
|
|
|
254
255
|
});
|
|
255
256
|
});
|
|
256
257
|
export const shardControlOperationSchema = z.discriminatedUnion("kind", [
|
|
258
|
+
z
|
|
259
|
+
.object({
|
|
260
|
+
kind: z.literal("creator.onboarding"),
|
|
261
|
+
guildId: snowflake,
|
|
262
|
+
input: creatorOnboardingRequestSchema,
|
|
263
|
+
})
|
|
264
|
+
.strict()
|
|
265
|
+
.refine((value) => value.guildId === value.input.guildId),
|
|
257
266
|
z
|
|
258
267
|
.object({
|
|
259
268
|
kind: z.literal("automod.enhanced.configuration.page"),
|
|
@@ -347,6 +356,23 @@ export const shardControlOperationSchema = z.discriminatedUnion("kind", [
|
|
|
347
356
|
...mutationContext.shape,
|
|
348
357
|
})
|
|
349
358
|
.strict(),
|
|
359
|
+
z
|
|
360
|
+
.object({
|
|
361
|
+
kind: z.literal("guild.module.channel-audit.config.read"),
|
|
362
|
+
guildId: snowflake,
|
|
363
|
+
moduleId: channelAuditModuleIdSchema,
|
|
364
|
+
...mutationContext.shape,
|
|
365
|
+
})
|
|
366
|
+
.strict(),
|
|
367
|
+
z
|
|
368
|
+
.object({
|
|
369
|
+
kind: z.literal("guild.module.channel-audit.check"),
|
|
370
|
+
guildId: snowflake,
|
|
371
|
+
moduleId: channelAuditModuleIdSchema,
|
|
372
|
+
assumptions: channelAuditInputSchema,
|
|
373
|
+
...mutationContext.shape,
|
|
374
|
+
})
|
|
375
|
+
.strict(),
|
|
350
376
|
z
|
|
351
377
|
.object({
|
|
352
378
|
kind: z.literal("guild.module.configuration.update"),
|
|
@@ -1109,6 +1135,27 @@ export async function executeShardControlOperation(handler, operation) {
|
|
|
1109
1135
|
correlationId: operation.correlationId,
|
|
1110
1136
|
});
|
|
1111
1137
|
}
|
|
1138
|
+
case "guild.module.channel-audit.config.read": {
|
|
1139
|
+
if (!handler.readChannelAuditConfiguration)
|
|
1140
|
+
throw new Error("Channel audit is not supported by this shard version");
|
|
1141
|
+
return channelAuditConfigurationSchema.parse(await handler.readChannelAuditConfiguration({
|
|
1142
|
+
guildId: operation.guildId,
|
|
1143
|
+
moduleId: operation.moduleId,
|
|
1144
|
+
actorUserId: operation.actorUserId,
|
|
1145
|
+
correlationId: operation.correlationId,
|
|
1146
|
+
}));
|
|
1147
|
+
}
|
|
1148
|
+
case "guild.module.channel-audit.check": {
|
|
1149
|
+
if (!handler.checkChannelAudit)
|
|
1150
|
+
throw new Error("Channel audit is not supported by this shard version");
|
|
1151
|
+
return channelAuditReportSchema.parse(await handler.checkChannelAudit({
|
|
1152
|
+
guildId: operation.guildId,
|
|
1153
|
+
moduleId: operation.moduleId,
|
|
1154
|
+
actorUserId: operation.actorUserId,
|
|
1155
|
+
correlationId: operation.correlationId,
|
|
1156
|
+
assumptions: operation.assumptions,
|
|
1157
|
+
}));
|
|
1158
|
+
}
|
|
1112
1159
|
case "guild.module.configuration.update": {
|
|
1113
1160
|
return handler.updateConfiguration({
|
|
1114
1161
|
guildId: operation.guildId,
|
|
@@ -1273,6 +1320,10 @@ export async function executeShardControlOperation(handler, operation) {
|
|
|
1273
1320
|
? { actorAuthenticatedAt: operation.actorAuthenticatedAt }
|
|
1274
1321
|
: {}),
|
|
1275
1322
|
});
|
|
1323
|
+
case "creator.onboarding":
|
|
1324
|
+
if (!handler.creatorOnboarding)
|
|
1325
|
+
throw new Error("Creator onboarding is unavailable");
|
|
1326
|
+
return handler.creatorOnboarding(operation.input);
|
|
1276
1327
|
case "oauth.consumer.cleanup":
|
|
1277
1328
|
return handler.cleanupOAuthConsumer({
|
|
1278
1329
|
consumerBindingId: operation.consumerBindingId,
|
package/dist/shard-router.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AutoModerationEnhancedResultOutcome } from "@helyx/sdk";
|
|
1
|
+
import { type AutoModerationEnhancedResultOutcome, type CreatorOnboardingRequest, type CreatorOnboardingResponse } from "@helyx/sdk";
|
|
2
2
|
import { type BotControlHandler } from "./control-server.js";
|
|
3
3
|
import { type ShardControlRequest, type ShardControlResponse } from "./shard-contracts.js";
|
|
4
4
|
import type { ShardCoordinator, ShardLeaderLease, ShardLease } from "./shard-coordinator.js";
|
|
@@ -8,6 +8,7 @@ type LocalDispatcher = (shardId: number, request: ShardControlRequest) => Promis
|
|
|
8
8
|
export declare class ShardControlRouter implements BotControlHandler {
|
|
9
9
|
#private;
|
|
10
10
|
private readonly input;
|
|
11
|
+
creatorOnboarding(input: CreatorOnboardingRequest): Promise<CreatorOnboardingResponse>;
|
|
11
12
|
constructor(input: {
|
|
12
13
|
generation: string;
|
|
13
14
|
shardCount: number;
|
|
@@ -47,6 +48,8 @@ export declare class ShardControlRouter implements BotControlHandler {
|
|
|
47
48
|
listGuildPresence(input: Parameters<BotControlHandler["listGuildPresence"]>[0]): ReturnType<BotControlHandler["listGuildPresence"]>;
|
|
48
49
|
setModuleStatus(input: Parameters<BotControlHandler["setModuleStatus"]>[0]): ReturnType<BotControlHandler["setModuleStatus"]>;
|
|
49
50
|
completeFirstTimeCommandSetup(input: Parameters<BotControlHandler["completeFirstTimeCommandSetup"]>[0]): ReturnType<BotControlHandler["completeFirstTimeCommandSetup"]>;
|
|
51
|
+
readChannelAuditConfiguration(input: Parameters<NonNullable<BotControlHandler["readChannelAuditConfiguration"]>>[0]): ReturnType<NonNullable<BotControlHandler["readChannelAuditConfiguration"]>>;
|
|
52
|
+
checkChannelAudit(input: Parameters<NonNullable<BotControlHandler["checkChannelAudit"]>>[0]): ReturnType<NonNullable<BotControlHandler["checkChannelAudit"]>>;
|
|
50
53
|
updateConfiguration(input: Parameters<BotControlHandler["updateConfiguration"]>[0]): ReturnType<BotControlHandler["updateConfiguration"]>;
|
|
51
54
|
previewConfiguration(input: Parameters<BotControlHandler["previewConfiguration"]>[0]): ReturnType<BotControlHandler["previewConfiguration"]>;
|
|
52
55
|
updatePermissions(input: Parameters<BotControlHandler["updatePermissions"]>[0]): ReturnType<BotControlHandler["updatePermissions"]>;
|
package/dist/shard-router.js
CHANGED
|
@@ -11,6 +11,13 @@ const LEASE_VALIDATION_TIMEOUT_MS = 3_000;
|
|
|
11
11
|
const ECKO_RESULT_TIMEOUT_MS = AUTOMOD_SCANNER_LIMITS.resultAcknowledgementSeconds * 1_000;
|
|
12
12
|
export class ShardControlRouter {
|
|
13
13
|
input;
|
|
14
|
+
creatorOnboarding(input) {
|
|
15
|
+
return this.#route({
|
|
16
|
+
kind: "creator.onboarding",
|
|
17
|
+
guildId: input.guildId,
|
|
18
|
+
input,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
14
21
|
constructor(input) {
|
|
15
22
|
this.input = input;
|
|
16
23
|
}
|
|
@@ -110,6 +117,18 @@ export class ShardControlRouter {
|
|
|
110
117
|
expectedRevision: input.expectedRevision.toString(),
|
|
111
118
|
});
|
|
112
119
|
}
|
|
120
|
+
readChannelAuditConfiguration(input) {
|
|
121
|
+
return this.#route(shardControlOperationSchema.parse({
|
|
122
|
+
kind: "guild.module.channel-audit.config.read",
|
|
123
|
+
...input,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
checkChannelAudit(input) {
|
|
127
|
+
return this.#route(shardControlOperationSchema.parse({
|
|
128
|
+
kind: "guild.module.channel-audit.check",
|
|
129
|
+
...input,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
113
132
|
updateConfiguration(input) {
|
|
114
133
|
return this.#route(shardControlOperationSchema.parse({
|
|
115
134
|
kind: "guild.module.configuration.update",
|
package/dist/shard-supervisor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
2
|
-
import { checkDatabaseConnection, createDatabasePool, AuditRepository, GuildCommandRepository, HostedServerSafetyRepository, InteractionSessionRepository, EckoOperationsRepository, PremiumAllocationRepository, RateLimitRepository, runConfiguredModuleMigrations, runCoreMigrations, RuntimeStateRepository, } from "@helyx/database";
|
|
2
|
+
import { checkDatabaseConnection, createDatabasePool, AuditRepository, GuildCommandRepository, HostedServerSafetyRepository, InstallationRepository, inspectConfiguredModuleMigrations, inspectCoreMigrations, inspectCreatorOnboardingMigrations, runCreatorOnboardingMigrations, InteractionSessionRepository, EckoOperationsRepository, PremiumAllocationRepository, RateLimitRepository, runConfiguredModuleMigrations, runCoreMigrations, RuntimeStateRepository, } from "@helyx/database";
|
|
3
|
+
import { assertRepositoryEngineSupported } from "@helyx/database/adapters";
|
|
3
4
|
import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, } from "@helyx/platform";
|
|
4
5
|
import { AUTOMOD_ENHANCED_CONTRACT_VERSION, AUTOMOD_ENHANCED_PROVIDER_ID, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
5
6
|
import { Client, ShardingManager } from "discord.js";
|
|
@@ -102,6 +103,7 @@ function sameNumericIdentity(left, right) {
|
|
|
102
103
|
left.fencingToken === right.fencingToken);
|
|
103
104
|
}
|
|
104
105
|
export async function startShardSupervisor(environment, processEnvironment, workerEntry) {
|
|
106
|
+
assertRepositoryEngineSupported(environment.DATABASE_ENGINE, ["postgresql"], "Bot shard supervisor");
|
|
105
107
|
if (environment.HELYX_SHARD_MODE === "off")
|
|
106
108
|
throw new Error("Shard supervisor requires sharding to be enabled");
|
|
107
109
|
const shardMode = environment.HELYX_SHARD_MODE;
|
|
@@ -185,7 +187,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
185
187
|
maxConcurrency: gateway.session_start_limit.max_concurrency,
|
|
186
188
|
observedAt: new Date().toISOString(),
|
|
187
189
|
};
|
|
188
|
-
const supervisorDirectory = new SupervisorCloudDiscordDirectory(environment.DISCORD_TOKEN, database, environment.HELYX_OWNER_USER_IDS);
|
|
190
|
+
const supervisorDirectory = new SupervisorCloudDiscordDirectory(environment.DISCORD_TOKEN, new InstallationRepository(database), environment.HELYX_OWNER_USER_IDS);
|
|
189
191
|
const eckoOperations = new EckoOperationsRepository(database);
|
|
190
192
|
cloudConnector = new CloudConnectorController({
|
|
191
193
|
environment,
|
|
@@ -255,6 +257,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
255
257
|
internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID,
|
|
256
258
|
approvedRoleIds: environment.APPROVED_ROLES,
|
|
257
259
|
botVersion: botVersion(),
|
|
260
|
+
databaseEngine: environment.DATABASE_ENGINE,
|
|
258
261
|
...(processEnvironment.RAILWAY_GIT_COMMIT_SHA
|
|
259
262
|
? {
|
|
260
263
|
deploymentReference: processEnvironment.RAILWAY_GIT_COMMIT_SHA.slice(0, 12),
|
|
@@ -282,6 +285,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
282
285
|
...(operationsModule ? [operationsModule] : []),
|
|
283
286
|
];
|
|
284
287
|
commandModules = modules;
|
|
288
|
+
const guildCommandReads = new GuildCommandReadService(database, environment.DISCORD_CLIENT_ID, () => guildCommandWorker?.getHealth(), environment.DISCORD_TOKEN, environment.HELYX_GUILD_COMMAND_WRITES_ENABLED, environment.HELYX_GUILD_COMMAND_WRITE_GUILD_IDS, environment.HELYX_GUILD_COMMAND_WRITE_SCOPE === "all", commandModules);
|
|
285
289
|
const availabilityModules = availabilityCatalogueDefinitions(modules);
|
|
286
290
|
const statusModules = statusCatalogueDefinitions(featureModules);
|
|
287
291
|
let runtimeAvailability;
|
|
@@ -328,7 +332,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
328
332
|
notificationGuildId: environment.DISCORD_INTERNAL_GUILD_ID,
|
|
329
333
|
}
|
|
330
334
|
: {}),
|
|
331
|
-
}));
|
|
335
|
+
}), guildCommandReads);
|
|
332
336
|
await startLeaderSingletons();
|
|
333
337
|
assertStartupLeaseHealthy();
|
|
334
338
|
};
|
|
@@ -425,7 +429,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
425
429
|
: {}),
|
|
426
430
|
});
|
|
427
431
|
if (!leaderLease)
|
|
428
|
-
cloudConnector.setControlHandler(router);
|
|
432
|
+
cloudConnector.setControlHandler(router, guildCommandReads);
|
|
429
433
|
const claimedIds = await claimInitialShards();
|
|
430
434
|
manager = new ShardingManager(workerEntry, {
|
|
431
435
|
totalShards: shardCount,
|
|
@@ -480,7 +484,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
480
484
|
},
|
|
481
485
|
shardHealth: buildHealth,
|
|
482
486
|
readiness: buildDeploymentReadiness,
|
|
483
|
-
guildCommands:
|
|
487
|
+
guildCommands: guildCommandReads,
|
|
484
488
|
...(ecko ? { ecko } : {}),
|
|
485
489
|
});
|
|
486
490
|
await controlServer.listen({
|
|
@@ -1529,12 +1533,31 @@ export function splitNumericCommittedBatch(batch, index) {
|
|
|
1529
1533
|
};
|
|
1530
1534
|
}
|
|
1531
1535
|
async function runLeaderStartup(database, environment, modulePackages, modules, logger) {
|
|
1532
|
-
|
|
1533
|
-
await runConfiguredModuleMigrations(database, modulePackages.map(({ definition, packageRoot }) => ({
|
|
1536
|
+
const moduleMigrationSources = modulePackages.map(({ definition, packageRoot }) => ({
|
|
1534
1537
|
moduleId: definition.manifest.id,
|
|
1535
1538
|
moduleVersion: definition.manifest.version,
|
|
1536
1539
|
migrationsDirectory: join(packageRoot, "migrations"),
|
|
1537
|
-
}))
|
|
1540
|
+
}));
|
|
1541
|
+
if (environment.HELYX_RUNTIME_PROFILE === "hosted" &&
|
|
1542
|
+
modulePackages.some(({ definition }) => definition.manifest.id === "helyx.creator-connections")) {
|
|
1543
|
+
if (environment.HELYX_DATABASE_STARTUP_MODE === "inspect") {
|
|
1544
|
+
const inspection = await inspectCreatorOnboardingMigrations(database);
|
|
1545
|
+
assertNoPendingLeaderMigrations("hosted creator onboarding", inspection.pending);
|
|
1546
|
+
}
|
|
1547
|
+
else
|
|
1548
|
+
await runCreatorOnboardingMigrations(database);
|
|
1549
|
+
}
|
|
1550
|
+
if (environment.HELYX_DATABASE_STARTUP_MODE === "inspect") {
|
|
1551
|
+
const coreInspection = await inspectCoreMigrations(database);
|
|
1552
|
+
assertNoPendingLeaderMigrations("core", coreInspection.pending);
|
|
1553
|
+
const moduleInspections = await inspectConfiguredModuleMigrations(database, moduleMigrationSources);
|
|
1554
|
+
for (const inspection of moduleInspections)
|
|
1555
|
+
assertNoPendingLeaderMigrations(`module ${inspection.moduleId}`, inspection.pending);
|
|
1556
|
+
}
|
|
1557
|
+
else {
|
|
1558
|
+
await runCoreMigrations(database);
|
|
1559
|
+
await runConfiguredModuleMigrations(database, moduleMigrationSources);
|
|
1560
|
+
}
|
|
1538
1561
|
await enforcePremiumCatalogue(database, logger);
|
|
1539
1562
|
const repository = new RuntimeStateRepository(database);
|
|
1540
1563
|
await synchroniseApplicationCommands({
|
|
@@ -1545,7 +1568,8 @@ async function runLeaderStartup(database, environment, modulePackages, modules,
|
|
|
1545
1568
|
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
1546
1569
|
? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
|
|
1547
1570
|
: {}),
|
|
1548
|
-
|
|
1571
|
+
// Worker-owned guild collections stay untouched while command writes are off.
|
|
1572
|
+
synchroniseInternalGuild: false,
|
|
1549
1573
|
state: new RuntimeCommandHashStore(repository),
|
|
1550
1574
|
});
|
|
1551
1575
|
await setRuntimeState(repository, `shards:${environment.HELYX_SHARD_GENERATION ?? "local"}:startup`, {
|
|
@@ -1554,6 +1578,10 @@ async function runLeaderStartup(database, environment, modulePackages, modules,
|
|
|
1554
1578
|
});
|
|
1555
1579
|
logger.info({ event: "shard.singleton_startup_complete" }, "Shard singleton startup complete");
|
|
1556
1580
|
}
|
|
1581
|
+
function assertNoPendingLeaderMigrations(scope, pending) {
|
|
1582
|
+
if (pending.length > 0)
|
|
1583
|
+
throw new Error(`Hosted ${scope} migrations must be applied by the dedicated primary migration identity before bot startup: ${pending.join(", ")}`);
|
|
1584
|
+
}
|
|
1557
1585
|
class RuntimeCommandHashStore {
|
|
1558
1586
|
repository;
|
|
1559
1587
|
constructor(repository) {
|
|
@@ -1635,6 +1663,7 @@ async function fetchGatewayBotInformation(token) {
|
|
|
1635
1663
|
}
|
|
1636
1664
|
export function buildShardWorkerEnvironment(source, identity, restartCount) {
|
|
1637
1665
|
const allowed = new Set([
|
|
1666
|
+
"DATABASE_ENGINE",
|
|
1638
1667
|
"DATABASE_URL",
|
|
1639
1668
|
"DATABASE_SSL",
|
|
1640
1669
|
"DISCORD_TOKEN",
|
|
@@ -1680,8 +1709,13 @@ export function buildShardWorkerEnvironment(source, identity, restartCount) {
|
|
|
1680
1709
|
const selectionKey = source.HELYX_DATA_ENCRYPTION_KEY
|
|
1681
1710
|
? Buffer.from(deriveSecureRandomSelectionKey(source.HELYX_DATA_ENCRYPTION_KEY)).toString("base64")
|
|
1682
1711
|
: undefined;
|
|
1712
|
+
const hostedModerationCredential = source.HELYX_HOSTED_MODERATION_ENABLED === "true" &&
|
|
1713
|
+
source.HELYX_BOT_CONTROL_SECRET
|
|
1714
|
+
? { HELYX_BOT_CONTROL_SECRET: source.HELYX_BOT_CONTROL_SECRET }
|
|
1715
|
+
: {};
|
|
1683
1716
|
return {
|
|
1684
1717
|
...environment,
|
|
1718
|
+
...hostedModerationCredential,
|
|
1685
1719
|
...(selectionKey
|
|
1686
1720
|
? { [secureRandomSelectionWorkerKeyName]: selectionKey }
|
|
1687
1721
|
: {}),
|
package/dist/shard-worker.d.ts
CHANGED
|
@@ -28,8 +28,6 @@ export declare function parseShardWorkerEnvironment(processEnvironment: NodeJS.P
|
|
|
28
28
|
HELYX_SHARD_WORKERS_PER_REPLICA: number;
|
|
29
29
|
HELYX_SHARD_SUPERVISOR_DATABASE_POOL_MAX: number | undefined;
|
|
30
30
|
HELYX_SHARD_WORKER_DATABASE_POOL_MAX: number | undefined;
|
|
31
|
-
DATABASE_URL: string;
|
|
32
|
-
DATABASE_SSL: "disable" | "require" | "verify-full";
|
|
33
31
|
HELYX_RUNTIME_PROFILE: "hosted" | "self-hosted";
|
|
34
32
|
DISCORD_TOKEN: string;
|
|
35
33
|
DISCORD_CLIENT_ID: string;
|
|
@@ -70,6 +68,10 @@ export declare function parseShardWorkerEnvironment(processEnvironment: NodeJS.P
|
|
|
70
68
|
HELYX_CLOUD_CREDENTIAL_AUDIT_RETENTION_DAYS: number;
|
|
71
69
|
LOG_LEVEL: "error" | "fatal" | "warn" | "info" | "debug" | "trace" | "silent";
|
|
72
70
|
NODE_ENV: "development" | "test" | "production";
|
|
71
|
+
DATABASE_ENGINE: "postgresql" | "mysql";
|
|
72
|
+
DATABASE_URL: string;
|
|
73
|
+
DATABASE_SSL: "disable" | "require" | "verify-full";
|
|
74
|
+
HELYX_DATABASE_STARTUP_MODE: "apply" | "inspect";
|
|
73
75
|
HELYX_STATUS_ENABLED: boolean;
|
|
74
76
|
HELYX_STATUS_URL: string;
|
|
75
77
|
HELYX_STATUS_HEARTBEAT_INTERVAL_SECONDS: number;
|
|
@@ -80,6 +82,7 @@ export declare function parseShardWorkerEnvironment(processEnvironment: NodeJS.P
|
|
|
80
82
|
HELYX_SUPPORT_SERVICE_SECRET?: string | undefined;
|
|
81
83
|
HELYX_VERIFICATION_INSTALLATION_ID?: string | undefined;
|
|
82
84
|
HELYX_VERIFICATION_URL?: string | undefined;
|
|
85
|
+
HELYX_VERIFICATION_PUBLIC_URL?: string | undefined;
|
|
83
86
|
HELYX_VERIFICATION_INTERNAL_SECRET?: string | undefined;
|
|
84
87
|
HELYX_GUILD_COMMAND_RELEASE_ID?: string | undefined;
|
|
85
88
|
HELYX_PRIVACY_EXECUTOR_DATABASE_URL?: string | undefined;
|
package/dist/shard-worker.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseBotEnvironment } from "@helyx/config";
|
|
2
2
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
3
3
|
import { checkDatabaseConnection, createDatabasePool, PremiumAllocationRepository, } from "@helyx/database";
|
|
4
|
+
import { assertRepositoryEngineSupported } from "@helyx/database/adapters";
|
|
4
5
|
import { REVIEWED_PREMIUM_MODULE_IDS } from "@helyx/module-manifest";
|
|
5
6
|
import { createRuntime } from "@helyx/framework";
|
|
6
7
|
import { createPlatformServices, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
|
|
@@ -13,6 +14,7 @@ import { BotControlService, BotControlTransportError, DiscordGuildResourceDirect
|
|
|
13
14
|
import { HttpVerificationService } from "./verification-service.js";
|
|
14
15
|
import { DiscordMessageDeliveryService, DiscordMemberRoleService, DiscordVerificationMemberService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
|
|
15
16
|
import { DiscordModerationMemberEnforcementAdapter } from "./discord-member-enforcement-service.js";
|
|
17
|
+
import { installCreatorInvitePlatform } from "./creator-onboarding-platform.js";
|
|
16
18
|
import { installGuildTracking, } from "./guilds.js";
|
|
17
19
|
import { installInteractions, } from "./interactions.js";
|
|
18
20
|
import { createManagementModule } from "./management-module.js";
|
|
@@ -64,6 +66,7 @@ export function createLeaseHeartbeatWatchdog(onExpired, timeoutMs = SHARD_LEASE_
|
|
|
64
66
|
}
|
|
65
67
|
export async function startShardWorker(processEnvironment = process.env, identity = parseShardWorkerLaunchContract(processEnvironment.HELYX_SHARD_WORKER_CONTRACT)) {
|
|
66
68
|
const environment = parseShardWorkerEnvironment(processEnvironment, identity);
|
|
69
|
+
assertRepositoryEngineSupported(environment.DATABASE_ENGINE, ["postgresql"], "Bot shard worker");
|
|
67
70
|
const logger = pino({
|
|
68
71
|
level: environment.LOG_LEVEL,
|
|
69
72
|
base: {
|
|
@@ -109,14 +112,16 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
109
112
|
let hostedLeaveWorker;
|
|
110
113
|
let leaseHeartbeatWatchdog;
|
|
111
114
|
let forcedLeaseExit;
|
|
115
|
+
let creatorInvitePlatform;
|
|
112
116
|
let numericWriteBehind;
|
|
113
117
|
async function shutdown(reason = "programmatic") {
|
|
114
118
|
if (shuttingDown)
|
|
115
119
|
return;
|
|
116
120
|
shuttingDown = true;
|
|
117
121
|
status = "stopped";
|
|
118
|
-
guildTracking?.stop();
|
|
122
|
+
await guildTracking?.stop();
|
|
119
123
|
await hostedLeaveWorker?.stop();
|
|
124
|
+
await creatorInvitePlatform?.stop();
|
|
120
125
|
if (healthTimer)
|
|
121
126
|
clearInterval(healthTimer);
|
|
122
127
|
leaseHeartbeatWatchdog?.stop();
|
|
@@ -193,6 +198,7 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
193
198
|
internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID,
|
|
194
199
|
approvedRoleIds: environment.APPROVED_ROLES,
|
|
195
200
|
botVersion: readBotVersion(),
|
|
201
|
+
databaseEngine: environment.DATABASE_ENGINE,
|
|
196
202
|
...(processEnvironment.RAILWAY_GIT_COMMIT_SHA
|
|
197
203
|
? {
|
|
198
204
|
deploymentReference: processEnvironment.RAILWAY_GIT_COMMIT_SHA.slice(0, 12),
|
|
@@ -232,6 +238,15 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
232
238
|
})) {
|
|
233
239
|
platformServices.set(name, service);
|
|
234
240
|
}
|
|
241
|
+
const applicationUrls = serviceAccess.get(HELYX_SERVICE_NAMES.applicationUrls);
|
|
242
|
+
platformServices.set(HELYX_SERVICE_NAMES.applicationUrls, {
|
|
243
|
+
dashboardBaseUrl: applicationUrls.dashboardBaseUrl,
|
|
244
|
+
get dashboardInstallationId() {
|
|
245
|
+
return environment.HELYX_RUNTIME_PROFILE === "self-hosted"
|
|
246
|
+
? cloudProxy?.status().installationId
|
|
247
|
+
: undefined;
|
|
248
|
+
},
|
|
249
|
+
});
|
|
235
250
|
await serviceAccess
|
|
236
251
|
.get(HELYX_SERVICE_NAMES.moduleAvailability)
|
|
237
252
|
.refresh();
|
|
@@ -262,6 +277,25 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
262
277
|
platformServices.set(HELYX_SERVICE_NAMES.memberRoles, memberRoles);
|
|
263
278
|
platformServices.set(HELYX_SERVICE_NAMES.memberRoleGrants, new PlatformMemberRoleGrantOwnershipService(new PostgresMemberRoleGrantOwnershipStore(database), memberRoles));
|
|
264
279
|
platformServices.set(HELYX_SERVICE_NAMES.discordResources, new DiscordResourceInspection(client));
|
|
280
|
+
if (environment.HELYX_RUNTIME_PROFILE === "hosted")
|
|
281
|
+
creatorInvitePlatform = await installCreatorInvitePlatform({
|
|
282
|
+
client,
|
|
283
|
+
database,
|
|
284
|
+
services: serviceAccess,
|
|
285
|
+
modules: publicModules,
|
|
286
|
+
encryptionKey: environment.HELYX_DATA_ENCRYPTION_KEY,
|
|
287
|
+
...(environment.HELYX_CLOUD_API_URL &&
|
|
288
|
+
environment.HELYX_OAUTH_INTERNAL_SECRET
|
|
289
|
+
? {
|
|
290
|
+
oauthBroker: {
|
|
291
|
+
baseUrl: environment.HELYX_CLOUD_API_URL,
|
|
292
|
+
secret: environment.HELYX_OAUTH_INTERNAL_SECRET,
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
: {}),
|
|
296
|
+
register: (name, service) => platformServices.set(name, service),
|
|
297
|
+
warn: (event) => logger.warn({ event }, "Creator invite cleanup will retry"),
|
|
298
|
+
});
|
|
265
299
|
const moderationPlatformServices = await installModerationPlatformServices({
|
|
266
300
|
services: platformServices,
|
|
267
301
|
serviceAccess,
|
|
@@ -310,10 +344,12 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
310
344
|
};
|
|
311
345
|
const prepareGuilds = installGuildTracking(client, database, publicModules, logger, enabledCoreModuleIds, shardOwnership, environment.HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS * 1_000, identity.shardCount, environment.DISCORD_CLIENT_ID, hostedSafety, hostedInviteGate, hostedOwnerObserver);
|
|
312
346
|
guildTracking = prepareGuilds;
|
|
313
|
-
hostedLeaveWorker =
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
347
|
+
hostedLeaveWorker = environment.HELYX_HOSTED_MODERATION_ENABLED
|
|
348
|
+
? new HostedServerLeaveWorker(database, client, logger, {
|
|
349
|
+
shardId: identity.shardId,
|
|
350
|
+
shardCount: identity.shardCount,
|
|
351
|
+
})
|
|
352
|
+
: undefined;
|
|
317
353
|
const commandsReady = installInteractions(client, modules, shardWorkerCommandRegistration({
|
|
318
354
|
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
319
355
|
? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import type { DatabasePool } from "@helyx/database";
|
|
2
1
|
import type { CloudDiscordDirectory, CloudDiscordSnapshot } from "./cloud-connector.js";
|
|
3
2
|
export declare const CLOUD_HELLO_GUILD_LIMIT = 1000;
|
|
3
|
+
export interface SupervisorInstallationDirectory {
|
|
4
|
+
listActiveGuilds(): Promise<readonly {
|
|
5
|
+
guildId: string;
|
|
6
|
+
name: string | null;
|
|
7
|
+
ownerUserId: string | null;
|
|
8
|
+
}[]>;
|
|
9
|
+
}
|
|
4
10
|
export declare class SupervisorCloudDiscordDirectory implements CloudDiscordDirectory {
|
|
5
11
|
#private;
|
|
6
|
-
private readonly
|
|
12
|
+
private readonly installations;
|
|
7
13
|
private readonly configuredOwnerUserIds;
|
|
8
|
-
constructor(token: string,
|
|
14
|
+
constructor(token: string, installations: SupervisorInstallationDirectory, configuredOwnerUserIds: readonly string[]);
|
|
9
15
|
snapshot(): Promise<CloudDiscordSnapshot>;
|
|
10
16
|
}
|
|
11
17
|
//# sourceMappingURL=supervisor-cloud-directory.d.ts.map
|
|
@@ -25,23 +25,19 @@ const userSchema = z
|
|
|
25
25
|
.object({ id: snowflake, username: z.string().min(1).max(100) })
|
|
26
26
|
.passthrough();
|
|
27
27
|
export class SupervisorCloudDiscordDirectory {
|
|
28
|
-
|
|
28
|
+
installations;
|
|
29
29
|
configuredOwnerUserIds;
|
|
30
30
|
#rest;
|
|
31
|
-
constructor(token,
|
|
32
|
-
this.
|
|
31
|
+
constructor(token, installations, configuredOwnerUserIds) {
|
|
32
|
+
this.installations = installations;
|
|
33
33
|
this.configuredOwnerUserIds = configuredOwnerUserIds;
|
|
34
34
|
this.#rest = new REST({ version: "10" }).setToken(token);
|
|
35
35
|
}
|
|
36
36
|
async snapshot() {
|
|
37
|
-
const [applicationValue, botValue,
|
|
37
|
+
const [applicationValue, botValue, activeGuilds] = await Promise.all([
|
|
38
38
|
this.#rest.get(Routes.oauth2CurrentApplication()),
|
|
39
39
|
this.#rest.get(Routes.user("@me")),
|
|
40
|
-
this.
|
|
41
|
-
FROM discord_guilds
|
|
42
|
-
WHERE active = true
|
|
43
|
-
ORDER BY guild_id
|
|
44
|
-
LIMIT ${String(CLOUD_HELLO_GUILD_LIMIT)}`),
|
|
40
|
+
this.installations.listActiveGuilds(),
|
|
45
41
|
]);
|
|
46
42
|
const application = applicationSchema.parse(applicationValue);
|
|
47
43
|
const bot = userSchema.parse(botValue);
|
|
@@ -55,10 +51,10 @@ export class SupervisorCloudDiscordDirectory {
|
|
|
55
51
|
ownerUserIds: [
|
|
56
52
|
...new Set([...this.configuredOwnerUserIds, ...applicationOwnerIds]),
|
|
57
53
|
],
|
|
58
|
-
guilds:
|
|
59
|
-
guildId: guild.
|
|
54
|
+
guilds: activeGuilds.slice(0, CLOUD_HELLO_GUILD_LIMIT).map((guild) => ({
|
|
55
|
+
guildId: guild.guildId,
|
|
60
56
|
name: guild.name ?? "Unknown server",
|
|
61
|
-
ownerUserId: guild.
|
|
57
|
+
ownerUserId: guild.ownerUserId,
|
|
62
58
|
})),
|
|
63
59
|
};
|
|
64
60
|
}
|
|
@@ -18,9 +18,20 @@ export interface TriviaDataSummary {
|
|
|
18
18
|
export interface TriviaDataProvider {
|
|
19
19
|
getSummary(): Promise<TriviaDataSummary>;
|
|
20
20
|
}
|
|
21
|
+
interface MysqlTriviaExecutor {
|
|
22
|
+
query<Row extends Record<string, unknown>>(sql: string): Promise<{
|
|
23
|
+
rows: readonly Row[];
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
21
26
|
export declare class PostgresTriviaDataProvider implements TriviaDataProvider {
|
|
22
27
|
#private;
|
|
23
28
|
constructor(database: DatabasePool);
|
|
24
29
|
getSummary(): Promise<TriviaDataSummary>;
|
|
25
30
|
}
|
|
31
|
+
export declare class MysqlTriviaDataProvider implements TriviaDataProvider {
|
|
32
|
+
#private;
|
|
33
|
+
constructor(database: MysqlTriviaExecutor);
|
|
34
|
+
getSummary(): Promise<TriviaDataSummary>;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
26
37
|
//# sourceMappingURL=trivia-data-provider.d.ts.map
|
|
@@ -14,34 +14,54 @@ export class PostgresTriviaDataProvider {
|
|
|
14
14
|
GROUP BY category, difficulty, type
|
|
15
15
|
ORDER BY category, difficulty, type`),
|
|
16
16
|
]);
|
|
17
|
-
|
|
18
|
-
for (const row of countResult.rows) {
|
|
19
|
-
if (!TRIVIA_CATEGORIES.includes(row.category) ||
|
|
20
|
-
!difficulties.includes(row.difficulty) ||
|
|
21
|
-
!["text_choice", "text_input", "true_false"].includes(row.type))
|
|
22
|
-
throw new Error("Trivia data contains an unsupported group");
|
|
23
|
-
counts.set(`${row.category}:${row.difficulty}`, (counts.get(`${row.category}:${row.difficulty}`) ?? 0) +
|
|
24
|
-
nonNegativeInteger(row.questions, "Trivia question count"));
|
|
25
|
-
}
|
|
26
|
-
const typeCount = (type) => countResult.rows
|
|
27
|
-
.filter((row) => row.type === type)
|
|
28
|
-
.reduce((total, row) => total + nonNegativeInteger(row.questions, "Trivia type count"), 0);
|
|
29
|
-
return {
|
|
30
|
-
uniqueQuestions: nonNegativeInteger(uniqueResult.rows[0]?.unique_questions, "Unique trivia question count"),
|
|
31
|
-
types: {
|
|
32
|
-
multipleChoice: typeCount("text_choice"),
|
|
33
|
-
guess: typeCount("text_input"),
|
|
34
|
-
trueFalse: typeCount("true_false"),
|
|
35
|
-
},
|
|
36
|
-
categories: TRIVIA_CATEGORIES.map((category) => ({
|
|
37
|
-
category,
|
|
38
|
-
easy: counts.get(`${category}:easy`) ?? 0,
|
|
39
|
-
medium: counts.get(`${category}:medium`) ?? 0,
|
|
40
|
-
hard: counts.get(`${category}:hard`) ?? 0,
|
|
41
|
-
})),
|
|
42
|
-
};
|
|
17
|
+
return summarizeTriviaData(uniqueResult.rows[0]?.unique_questions, countResult.rows);
|
|
43
18
|
}
|
|
44
19
|
}
|
|
20
|
+
export class MysqlTriviaDataProvider {
|
|
21
|
+
#database;
|
|
22
|
+
constructor(database) {
|
|
23
|
+
this.#database = database;
|
|
24
|
+
}
|
|
25
|
+
async getSummary() {
|
|
26
|
+
const [uniqueResult, countResult] = await Promise.all([
|
|
27
|
+
this.#database.query(`SELECT count(DISTINCT lower(trim(JSON_UNQUOTE(JSON_EXTRACT(question, '$.text'))))) AS unique_questions
|
|
28
|
+
FROM trivia_questions`),
|
|
29
|
+
this.#database.query(`SELECT category, difficulty, type, count(*) AS questions
|
|
30
|
+
FROM trivia_questions
|
|
31
|
+
GROUP BY category, difficulty, type
|
|
32
|
+
ORDER BY category, difficulty, type`),
|
|
33
|
+
]);
|
|
34
|
+
return summarizeTriviaData(uniqueResult.rows[0]?.unique_questions, countResult.rows);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function summarizeTriviaData(uniqueQuestions, groupedRows) {
|
|
38
|
+
const counts = new Map();
|
|
39
|
+
for (const row of groupedRows) {
|
|
40
|
+
if (!TRIVIA_CATEGORIES.includes(row.category) ||
|
|
41
|
+
!difficulties.includes(row.difficulty) ||
|
|
42
|
+
!["text_choice", "text_input", "true_false"].includes(row.type))
|
|
43
|
+
throw new Error("Trivia data contains an unsupported group");
|
|
44
|
+
counts.set(`${row.category}:${row.difficulty}`, (counts.get(`${row.category}:${row.difficulty}`) ?? 0) +
|
|
45
|
+
nonNegativeInteger(row.questions, "Trivia question count"));
|
|
46
|
+
}
|
|
47
|
+
const typeCount = (type) => groupedRows
|
|
48
|
+
.filter((row) => row.type === type)
|
|
49
|
+
.reduce((total, row) => total + nonNegativeInteger(row.questions, "Trivia type count"), 0);
|
|
50
|
+
return {
|
|
51
|
+
uniqueQuestions: nonNegativeInteger(uniqueQuestions, "Unique trivia question count"),
|
|
52
|
+
types: {
|
|
53
|
+
multipleChoice: typeCount("text_choice"),
|
|
54
|
+
guess: typeCount("text_input"),
|
|
55
|
+
trueFalse: typeCount("true_false"),
|
|
56
|
+
},
|
|
57
|
+
categories: TRIVIA_CATEGORIES.map((category) => ({
|
|
58
|
+
category,
|
|
59
|
+
easy: counts.get(`${category}:easy`) ?? 0,
|
|
60
|
+
medium: counts.get(`${category}:medium`) ?? 0,
|
|
61
|
+
hard: counts.get(`${category}:hard`) ?? 0,
|
|
62
|
+
})),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
45
65
|
function nonNegativeInteger(value, label) {
|
|
46
66
|
const parsed = typeof value === "string" ? Number(value) : value;
|
|
47
67
|
if (typeof parsed !== "number" || !Number.isSafeInteger(parsed) || parsed < 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type ActionReceipt, type ChallengeCreateRequest, type ChallengeCreateResponse, type DashboardAttemptPage, type DashboardAttemptQuery, type DashboardSafeAttemptDetail, type SignedAssessmentEnvelope, type VerificationInstallationClaims } from "@helyx/verification-contracts";
|
|
1
|
+
import { type ActionReceipt, type ChallengeCreateRequest, type ChallengeCreateResponse, type DashboardAttemptPage, type DashboardAttemptQuery, type DashboardSafeAttemptDetail, type SignedAssessmentEnvelope, type VerificationInstallationClaims, type VerificationResponseUrlOptions } from "@helyx/verification-contracts";
|
|
2
2
|
import type { VerificationResultContext, VerificationService } from "@helyx/sdk";
|
|
3
|
-
export interface HttpVerificationServiceOptions {
|
|
3
|
+
export interface HttpVerificationServiceOptions extends VerificationResponseUrlOptions {
|
|
4
4
|
baseUrl: string;
|
|
5
5
|
internalSecret: string;
|
|
6
6
|
publicSigningKeys: Readonly<Record<string, string>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { actionReceiptSchema, challengeCreateRequestSchema,
|
|
1
|
+
import { actionReceiptSchema, challengeCreateRequestSchema, createChallengeCreateResponseSchema, dashboardAttemptPageSchema, dashboardAttemptQuerySchema, dashboardSafeAttemptDetailSchema, signedAssessmentEnvelopeSchema, } from "@helyx/verification-contracts";
|
|
2
2
|
import { VerificationChallengeActiveError } from "@helyx/sdk";
|
|
3
3
|
import { createPublicKey, verify } from "node:crypto";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -21,6 +21,7 @@ export class HttpVerificationService {
|
|
|
21
21
|
#publicSigningKeys;
|
|
22
22
|
#fetch;
|
|
23
23
|
#installationId;
|
|
24
|
+
#challengeResponseSchema;
|
|
24
25
|
#challengeCache = new Map();
|
|
25
26
|
#handler;
|
|
26
27
|
constructor(options) {
|
|
@@ -29,6 +30,8 @@ export class HttpVerificationService {
|
|
|
29
30
|
this.#publicSigningKeys = { ...options.publicSigningKeys };
|
|
30
31
|
this.#installationId = options.installationId;
|
|
31
32
|
this.#fetch = options.fetchImplementation ?? fetch;
|
|
33
|
+
this.#challengeResponseSchema =
|
|
34
|
+
createChallengeCreateResponseSchema(options);
|
|
32
35
|
}
|
|
33
36
|
getInstallationContext() {
|
|
34
37
|
return Promise.resolve({
|
|
@@ -54,9 +57,9 @@ export class HttpVerificationService {
|
|
|
54
57
|
if (input.replaceActive !== true &&
|
|
55
58
|
cached &&
|
|
56
59
|
Date.parse(cached.expiresAt) > Date.now()) {
|
|
57
|
-
return
|
|
60
|
+
return this.#challengeResponseSchema.parse({ ...cached, reused: true });
|
|
58
61
|
}
|
|
59
|
-
const created =
|
|
62
|
+
const created = this.#challengeResponseSchema.parse(await this.#request("/internal/v1/challenges", {
|
|
60
63
|
method: "POST",
|
|
61
64
|
body: challengeCreateRequestSchema.parse(input),
|
|
62
65
|
}));
|
package/documentation/README.md
CHANGED
|
@@ -23,9 +23,11 @@ and module guides.
|
|
|
23
23
|
- [Operate the Moderation module](modules/moderation.md)
|
|
24
24
|
- [Operate the Auto Moderation module](modules/auto-moderation.md)
|
|
25
25
|
|
|
26
|
-
The
|
|
27
|
-
`@helyx/module-example`.
|
|
28
|
-
|
|
26
|
+
The standalone authoring reference is published separately as
|
|
27
|
+
`@helyx/module-example`. Adapt its compatibility manifest and compose the
|
|
28
|
+
required privacy contribution before bot installation, as described in
|
|
29
|
+
[the author guide](modules/building-a-module.md). Module-specific commands,
|
|
30
|
+
settings, tests and lifecycle behaviour remain inside that module.
|
|
29
31
|
|
|
30
32
|
## Choose the right path
|
|
31
33
|
|