@helyx/module-account-gate 1.0.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +725 -0
  3. package/README.md +90 -0
  4. package/dist/challenges.d.ts +16 -0
  5. package/dist/challenges.js +233 -0
  6. package/dist/configuration.d.ts +41 -0
  7. package/dist/configuration.js +224 -0
  8. package/dist/contracts.d.ts +82 -0
  9. package/dist/contracts.js +56 -0
  10. package/dist/enforcement.d.ts +19 -0
  11. package/dist/enforcement.js +110 -0
  12. package/dist/index.d.ts +27 -0
  13. package/dist/index.js +3324 -0
  14. package/dist/managed-resource-summary.d.ts +3 -0
  15. package/dist/managed-resource-summary.js +6 -0
  16. package/dist/member-resource.d.ts +3 -0
  17. package/dist/member-resource.js +348 -0
  18. package/dist/panel-resource.d.ts +15 -0
  19. package/dist/panel-resource.js +527 -0
  20. package/dist/password-entry.d.ts +8 -0
  21. package/dist/password-entry.js +59 -0
  22. package/dist/password-resource.d.ts +8 -0
  23. package/dist/password-resource.js +208 -0
  24. package/dist/privacy.d.ts +4 -0
  25. package/dist/privacy.js +20 -0
  26. package/dist/public-channel-resource.d.ts +4 -0
  27. package/dist/public-channel-resource.js +135 -0
  28. package/dist/question-resource.d.ts +3 -0
  29. package/dist/question-resource.js +246 -0
  30. package/dist/records.d.ts +508 -0
  31. package/dist/records.js +456 -0
  32. package/dist/workflow.d.ts +43 -0
  33. package/dist/workflow.js +167 -0
  34. package/manifest.json +750 -0
  35. package/migrations/0001_account_gate_foundation.sql +303 -0
  36. package/migrations/0002_account_gate_panel_message.sql +27 -0
  37. package/migrations/0003_account_gate_revoked_member_status.sql +17 -0
  38. package/migrations/0004_account_gate_multiple_panels.sql +23 -0
  39. package/migrations/0005_account_gate_panel_passwords.sql +45 -0
  40. package/package.json +54 -0
@@ -0,0 +1,456 @@
1
+ export const accountGateRecords = {
2
+ gate_panels: {
3
+ table: "account_gate_gate_panels",
4
+ fields: {
5
+ panel_id: { type: "string" },
6
+ guild_id: { type: "string" },
7
+ channel_id: { type: "string" },
8
+ message_id: { type: "string" },
9
+ message_body: { type: "string" },
10
+ access_type: { type: "string" },
11
+ password_hash: { type: "string", optional: true },
12
+ password_ignore_case: { type: "boolean", optional: true },
13
+ password_trim_whitespace: { type: "boolean", optional: true },
14
+ revision: { type: "number" },
15
+ created_at: { type: "date" },
16
+ updated_at: { type: "date" },
17
+ },
18
+ uniqueKeys: [["panel_id"], ["guild_id", "panel_id"]],
19
+ readableFields: [
20
+ "panel_id",
21
+ "guild_id",
22
+ "channel_id",
23
+ "message_id",
24
+ "message_body",
25
+ "access_type",
26
+ "password_hash",
27
+ "password_ignore_case",
28
+ "password_trim_whitespace",
29
+ "revision",
30
+ "created_at",
31
+ "updated_at",
32
+ ],
33
+ writableFields: [
34
+ "channel_id",
35
+ "message_id",
36
+ "message_body",
37
+ "access_type",
38
+ "password_hash",
39
+ "password_ignore_case",
40
+ "password_trim_whitespace",
41
+ "revision",
42
+ "updated_at",
43
+ ],
44
+ orderKeys: [["guild_id", "created_at", "panel_id"]],
45
+ },
46
+ public_channels: {
47
+ table: "account_gate_public_channels",
48
+ fields: {
49
+ guild_id: { type: "string" },
50
+ channel_id: { type: "string" },
51
+ created_at: { type: "date" },
52
+ },
53
+ uniqueKeys: [["guild_id", "channel_id"]],
54
+ readableFields: ["guild_id", "channel_id", "created_at"],
55
+ orderKeys: [["guild_id", "channel_id"]],
56
+ },
57
+ questions: {
58
+ table: "account_gate_questions",
59
+ fields: {
60
+ question_id: { type: "string" },
61
+ guild_id: { type: "string" },
62
+ question_text: { type: "string" },
63
+ answers_json: { type: "string" },
64
+ correct_option_index: { type: "number" },
65
+ enabled: { type: "boolean" },
66
+ sort_order: { type: "number" },
67
+ revision: { type: "number" },
68
+ created_at: { type: "date" },
69
+ updated_at: { type: "date" },
70
+ },
71
+ uniqueKeys: [["question_id"], ["guild_id", "question_id"]],
72
+ readableFields: [
73
+ "question_id",
74
+ "guild_id",
75
+ "question_text",
76
+ "answers_json",
77
+ "correct_option_index",
78
+ "enabled",
79
+ "sort_order",
80
+ "revision",
81
+ "created_at",
82
+ "updated_at",
83
+ ],
84
+ writableFields: [
85
+ "question_text",
86
+ "answers_json",
87
+ "correct_option_index",
88
+ "enabled",
89
+ "sort_order",
90
+ "revision",
91
+ "updated_at",
92
+ ],
93
+ orderKeys: [["guild_id", "sort_order", "question_id"]],
94
+ searchableFields: ["question_text"],
95
+ groupableFields: ["guild_id", "enabled"],
96
+ },
97
+ question_creators: {
98
+ table: "account_gate_question_creators",
99
+ fields: {
100
+ attribution_id: { type: "string" },
101
+ question_id: { type: "string" },
102
+ guild_id: { type: "string" },
103
+ creator_user_id: { type: "string" },
104
+ created_at: { type: "date" },
105
+ },
106
+ uniqueKeys: [["attribution_id"], ["question_id"]],
107
+ readableFields: [
108
+ "attribution_id",
109
+ "question_id",
110
+ "guild_id",
111
+ "creator_user_id",
112
+ "created_at",
113
+ ],
114
+ orderKeys: [["creator_user_id", "created_at", "attribution_id"]],
115
+ },
116
+ password_secrets: {
117
+ table: "account_gate_password_secrets",
118
+ fields: {
119
+ guild_id: { type: "string" },
120
+ encoded_hash: { type: "string" },
121
+ ignore_case: { type: "boolean" },
122
+ trim_whitespace: { type: "boolean" },
123
+ revision: { type: "number" },
124
+ created_at: { type: "date" },
125
+ updated_at: { type: "date" },
126
+ },
127
+ uniqueKeys: [["guild_id"]],
128
+ readableFields: [
129
+ "guild_id",
130
+ "encoded_hash",
131
+ "ignore_case",
132
+ "trim_whitespace",
133
+ "revision",
134
+ "created_at",
135
+ "updated_at",
136
+ ],
137
+ writableFields: [
138
+ "encoded_hash",
139
+ "ignore_case",
140
+ "trim_whitespace",
141
+ "revision",
142
+ "updated_at",
143
+ ],
144
+ orderKeys: [["guild_id"]],
145
+ },
146
+ attempts: {
147
+ table: "account_gate_attempts",
148
+ fields: {
149
+ attempt_id: { type: "string" },
150
+ guild_id: { type: "string" },
151
+ member_user_id: { type: "string" },
152
+ session_id: { type: "string" },
153
+ challenge_type: { type: "string" },
154
+ outcome: { type: "string" },
155
+ reason_code: { type: "string" },
156
+ duration_ms: { type: "number" },
157
+ retention_until: { type: "date" },
158
+ created_at: { type: "date" },
159
+ },
160
+ uniqueKeys: [
161
+ ["attempt_id"],
162
+ ["guild_id", "attempt_id"],
163
+ ["guild_id", "session_id", "challenge_type"],
164
+ ],
165
+ readableFields: [
166
+ "attempt_id",
167
+ "guild_id",
168
+ "member_user_id",
169
+ "session_id",
170
+ "challenge_type",
171
+ "outcome",
172
+ "reason_code",
173
+ "duration_ms",
174
+ "retention_until",
175
+ "created_at",
176
+ ],
177
+ writableFields: ["retention_until"],
178
+ orderKeys: [["guild_id", "created_at", "attempt_id"]],
179
+ groupableFields: [
180
+ "guild_id",
181
+ "member_user_id",
182
+ "challenge_type",
183
+ "outcome",
184
+ ],
185
+ },
186
+ member_summaries: {
187
+ table: "account_gate_member_summaries",
188
+ fields: {
189
+ summary_id: { type: "string" },
190
+ guild_id: { type: "string" },
191
+ member_user_id: { type: "string" },
192
+ previous_success_at: { type: "date", optional: true },
193
+ failure_count: { type: "number" },
194
+ lockout_until: { type: "date", optional: true },
195
+ rejoin_count: { type: "number" },
196
+ join_generation: { type: "number" },
197
+ account_created_at: { type: "date" },
198
+ joined_at: { type: "date" },
199
+ deadline_at: { type: "date" },
200
+ pending_reason: { type: "string" },
201
+ last_failure_terminal_id: { type: "string", optional: true },
202
+ left_at: { type: "date", optional: true },
203
+ last_decision: { type: "string" },
204
+ revision: { type: "number" },
205
+ retention_until: { type: "date" },
206
+ created_at: { type: "date" },
207
+ updated_at: { type: "date" },
208
+ },
209
+ uniqueKeys: [["summary_id"], ["guild_id", "member_user_id"]],
210
+ readableFields: [
211
+ "summary_id",
212
+ "guild_id",
213
+ "member_user_id",
214
+ "previous_success_at",
215
+ "failure_count",
216
+ "lockout_until",
217
+ "rejoin_count",
218
+ "join_generation",
219
+ "account_created_at",
220
+ "joined_at",
221
+ "deadline_at",
222
+ "pending_reason",
223
+ "last_failure_terminal_id",
224
+ "left_at",
225
+ "last_decision",
226
+ "revision",
227
+ "retention_until",
228
+ "created_at",
229
+ "updated_at",
230
+ ],
231
+ writableFields: [
232
+ "previous_success_at",
233
+ "failure_count",
234
+ "lockout_until",
235
+ "rejoin_count",
236
+ "join_generation",
237
+ "account_created_at",
238
+ "joined_at",
239
+ "deadline_at",
240
+ "pending_reason",
241
+ "last_failure_terminal_id",
242
+ "left_at",
243
+ "last_decision",
244
+ "revision",
245
+ "retention_until",
246
+ "updated_at",
247
+ ],
248
+ orderKeys: [["guild_id", "member_user_id"]],
249
+ groupableFields: ["guild_id", "last_decision"],
250
+ },
251
+ reviews: {
252
+ table: "account_gate_reviews",
253
+ fields: {
254
+ review_id: { type: "string" },
255
+ pending_key: { type: "string" },
256
+ guild_id: { type: "string" },
257
+ member_user_id: { type: "string" },
258
+ join_generation: { type: "number" },
259
+ status: { type: "string" },
260
+ delivery_status: { type: "string" },
261
+ claim_action: { type: "string", optional: true },
262
+ claim_expires_at: { type: "date", optional: true },
263
+ reason_code: { type: "string" },
264
+ challenge_types_json: { type: "string" },
265
+ failed_attempts: { type: "number" },
266
+ expired_attempts: { type: "number" },
267
+ review_channel_id: { type: "string", optional: true },
268
+ review_message_id: { type: "string", optional: true },
269
+ decided_by_user_id: { type: "string", optional: true },
270
+ decided_at: { type: "date", optional: true },
271
+ revision: { type: "number" },
272
+ retention_until: { type: "date" },
273
+ created_at: { type: "date" },
274
+ updated_at: { type: "date" },
275
+ },
276
+ uniqueKeys: [["review_id"], ["pending_key"], ["guild_id", "review_id"]],
277
+ readableFields: [
278
+ "review_id",
279
+ "pending_key",
280
+ "guild_id",
281
+ "member_user_id",
282
+ "join_generation",
283
+ "status",
284
+ "delivery_status",
285
+ "claim_action",
286
+ "claim_expires_at",
287
+ "reason_code",
288
+ "challenge_types_json",
289
+ "failed_attempts",
290
+ "expired_attempts",
291
+ "review_channel_id",
292
+ "review_message_id",
293
+ "decided_by_user_id",
294
+ "decided_at",
295
+ "revision",
296
+ "retention_until",
297
+ "created_at",
298
+ "updated_at",
299
+ ],
300
+ writableFields: [
301
+ "pending_key",
302
+ "status",
303
+ "delivery_status",
304
+ "claim_action",
305
+ "claim_expires_at",
306
+ "review_channel_id",
307
+ "review_message_id",
308
+ "decided_by_user_id",
309
+ "decided_at",
310
+ "revision",
311
+ "retention_until",
312
+ "updated_at",
313
+ ],
314
+ orderKeys: [["guild_id", "created_at", "review_id"]],
315
+ searchableFields: ["member_user_id"],
316
+ groupableFields: ["guild_id", "status", "reason_code"],
317
+ },
318
+ terminal_finalizations: {
319
+ table: "account_gate_terminal_finalizations",
320
+ fields: {
321
+ finalization_id: { type: "string" },
322
+ guild_id: { type: "string" },
323
+ member_user_id: { type: "string" },
324
+ session_id: { type: "string" },
325
+ join_generation: { type: "number" },
326
+ outcome: { type: "string" },
327
+ challenge_type: { type: "string" },
328
+ challenge_types_json: { type: "string" },
329
+ work_created_at: { type: "date" },
330
+ status: { type: "string" },
331
+ revision: { type: "number" },
332
+ retention_until: { type: "date" },
333
+ created_at: { type: "date" },
334
+ updated_at: { type: "date" },
335
+ },
336
+ uniqueKeys: [["finalization_id"], ["guild_id", "session_id"]],
337
+ readableFields: [
338
+ "finalization_id",
339
+ "guild_id",
340
+ "member_user_id",
341
+ "session_id",
342
+ "join_generation",
343
+ "outcome",
344
+ "challenge_type",
345
+ "challenge_types_json",
346
+ "work_created_at",
347
+ "status",
348
+ "revision",
349
+ "retention_until",
350
+ "created_at",
351
+ "updated_at",
352
+ ],
353
+ writableFields: ["status", "revision", "retention_until", "updated_at"],
354
+ orderKeys: [["guild_id", "created_at", "finalization_id"]],
355
+ groupableFields: ["guild_id", "status", "outcome"],
356
+ },
357
+ role_transitions: {
358
+ table: "account_gate_role_transitions",
359
+ fields: {
360
+ transition_id: { type: "string" },
361
+ guild_id: { type: "string" },
362
+ member_user_id: { type: "string" },
363
+ verified_role_id: { type: "string" },
364
+ pending_remove_role_ids_json: { type: "string" },
365
+ status: { type: "string" },
366
+ revision: { type: "number" },
367
+ next_retry_at: { type: "date", optional: true },
368
+ completed_at: { type: "date", optional: true },
369
+ created_at: { type: "date" },
370
+ updated_at: { type: "date" },
371
+ },
372
+ uniqueKeys: [
373
+ ["transition_id"],
374
+ ["guild_id", "transition_id"],
375
+ ["guild_id", "member_user_id"],
376
+ ],
377
+ readableFields: [
378
+ "transition_id",
379
+ "guild_id",
380
+ "member_user_id",
381
+ "verified_role_id",
382
+ "pending_remove_role_ids_json",
383
+ "status",
384
+ "revision",
385
+ "next_retry_at",
386
+ "completed_at",
387
+ "created_at",
388
+ "updated_at",
389
+ ],
390
+ writableFields: [
391
+ "verified_role_id",
392
+ "pending_remove_role_ids_json",
393
+ "status",
394
+ "revision",
395
+ "next_retry_at",
396
+ "completed_at",
397
+ "updated_at",
398
+ ],
399
+ orderKeys: [["guild_id", "updated_at", "transition_id"]],
400
+ groupableFields: ["guild_id", "status"],
401
+ },
402
+ strict_modes: {
403
+ table: "account_gate_strict_modes",
404
+ fields: {
405
+ guild_id: { type: "string" },
406
+ status: { type: "string" },
407
+ ends_at: { type: "date" },
408
+ generation: { type: "number" },
409
+ activated_by_user_id: { type: "string", optional: true },
410
+ revision: { type: "number" },
411
+ created_at: { type: "date" },
412
+ updated_at: { type: "date" },
413
+ },
414
+ uniqueKeys: [["guild_id"]],
415
+ readableFields: [
416
+ "guild_id",
417
+ "status",
418
+ "ends_at",
419
+ "generation",
420
+ "activated_by_user_id",
421
+ "revision",
422
+ "created_at",
423
+ "updated_at",
424
+ ],
425
+ writableFields: [
426
+ "status",
427
+ "ends_at",
428
+ "generation",
429
+ "activated_by_user_id",
430
+ "revision",
431
+ "updated_at",
432
+ ],
433
+ orderKeys: [["guild_id"]],
434
+ groupableFields: ["status"],
435
+ },
436
+ privacy_suppressions: {
437
+ table: "account_gate_privacy_suppressions",
438
+ fields: {
439
+ suppression_id: { type: "string" },
440
+ guild_id: { type: "string" },
441
+ member_user_id: { type: "string" },
442
+ suppressed_at: { type: "date" },
443
+ created_at: { type: "date" },
444
+ },
445
+ uniqueKeys: [["suppression_id"], ["guild_id", "member_user_id"]],
446
+ readableFields: [
447
+ "suppression_id",
448
+ "guild_id",
449
+ "member_user_id",
450
+ "suppressed_at",
451
+ "created_at",
452
+ ],
453
+ orderKeys: [["guild_id", "member_user_id"]],
454
+ },
455
+ };
456
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1,43 @@
1
+ import type { AccountGateSessionState, Challenge, GeneratedChallengeType, RandomSource } from "./contracts.js";
2
+ export type ChallengeAction = {
3
+ type: "option";
4
+ option: number;
5
+ } | {
6
+ type: "sequence";
7
+ value: "red" | "blue" | "green";
8
+ } | {
9
+ type: "recall";
10
+ value: string;
11
+ } | {
12
+ type: "static-failed";
13
+ challenge: "password_entry" | "emoji_entry";
14
+ } | {
15
+ type: "static-complete";
16
+ challenge: "password_entry" | "emoji_entry";
17
+ };
18
+ export type WorkflowResult = {
19
+ outcome: "advanced";
20
+ state: AccountGateSessionState;
21
+ } | {
22
+ outcome: "progress";
23
+ state: AccountGateSessionState;
24
+ } | {
25
+ outcome: "passed";
26
+ state: AccountGateSessionState;
27
+ } | {
28
+ outcome: "failed";
29
+ state: AccountGateSessionState;
30
+ } | {
31
+ outcome: "stale";
32
+ state: AccountGateSessionState;
33
+ };
34
+ export declare function createAccountGateSessionState(chain: readonly Challenge[], joinGeneration: number, startedAt?: Date, panelId?: string): AccountGateSessionState;
35
+ export declare function applyChallengeAction(state: AccountGateSessionState, action: ChallengeAction): WorkflowResult;
36
+ export declare function rerollForAccessibility(input: {
37
+ state: AccountGateSessionState;
38
+ enabledTypes: readonly GeneratedChallengeType[];
39
+ random: RandomSource;
40
+ operationContext: string;
41
+ }): AccountGateSessionState | null;
42
+ export declare function parseAccountGateSessionState(input: unknown): AccountGateSessionState;
43
+ //# sourceMappingURL=workflow.d.ts.map
@@ -0,0 +1,167 @@
1
+ import { generateChallenge } from "./challenges.js";
2
+ export function createAccountGateSessionState(chain, joinGeneration, startedAt = new Date(), panelId) {
3
+ if (chain.length < 1 || chain.length > 4) {
4
+ throw new Error("Access Control session requires one to four challenges");
5
+ }
6
+ if (!Number.isSafeInteger(joinGeneration) || joinGeneration < 1)
7
+ throw new Error("Access Control session requires a valid join generation");
8
+ if (panelId !== undefined &&
9
+ (panelId.length < 1 || panelId.length > 100 || panelId.includes(":")))
10
+ throw new Error("Access Control session requires a valid panel ID");
11
+ return {
12
+ schemaVersion: 1,
13
+ status: "active",
14
+ chain,
15
+ step: 0,
16
+ currentStep: 0,
17
+ accessibilityRerollUsed: false,
18
+ joinGeneration,
19
+ ...(panelId === undefined ? {} : { panelId }),
20
+ startedAt: startedAt.toISOString(),
21
+ };
22
+ }
23
+ export function applyChallengeAction(state, action) {
24
+ if (state.status !== "active")
25
+ return { outcome: "stale", state };
26
+ const challenge = state.chain[state.currentStep];
27
+ if (!challenge)
28
+ return { outcome: "stale", state };
29
+ const result = evaluate(challenge, action);
30
+ if (result.outcome === "stale")
31
+ return { outcome: "stale", state };
32
+ if (result.outcome === "failed") {
33
+ return {
34
+ outcome: "failed",
35
+ state: { ...state, status: "failed" },
36
+ };
37
+ }
38
+ if (result.outcome === "progress") {
39
+ const chain = [...state.chain];
40
+ chain[state.currentStep] = result.challenge;
41
+ return { outcome: "progress", state: { ...state, chain } };
42
+ }
43
+ const nextStep = state.currentStep + 1;
44
+ if (nextStep >= state.chain.length) {
45
+ return {
46
+ outcome: "passed",
47
+ state: {
48
+ ...state,
49
+ status: "passed",
50
+ step: nextStep,
51
+ currentStep: nextStep,
52
+ },
53
+ };
54
+ }
55
+ return {
56
+ outcome: "advanced",
57
+ state: { ...state, step: nextStep, currentStep: nextStep },
58
+ };
59
+ }
60
+ export function rerollForAccessibility(input) {
61
+ if (input.state.status !== "active" || input.state.accessibilityRerollUsed) {
62
+ return null;
63
+ }
64
+ const current = input.state.chain[input.state.currentStep];
65
+ if (!current)
66
+ return null;
67
+ const alreadyUsed = new Set(input.state.chain.map(({ type }) => type));
68
+ const candidates = input.enabledTypes.filter((type) => type !== current.type && !alreadyUsed.has(type));
69
+ if (candidates.length < 1)
70
+ return null;
71
+ const replacementType = candidates[input.random.integer(candidates.length, `${input.operationContext}:replacement`)];
72
+ const replacement = generateChallenge(replacementType, input.random, `${input.operationContext}:challenge`);
73
+ const chain = [...input.state.chain];
74
+ chain[input.state.currentStep] = replacement;
75
+ return {
76
+ ...input.state,
77
+ chain,
78
+ accessibilityRerollUsed: true,
79
+ };
80
+ }
81
+ export function parseAccountGateSessionState(input) {
82
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
83
+ throw new Error("Access Control session state is invalid");
84
+ }
85
+ const state = input;
86
+ if (state.schemaVersion !== 1 ||
87
+ !["active", "passed", "failed", "cancelled"].includes(state.status ?? "") ||
88
+ !Array.isArray(state.chain) ||
89
+ state.chain.length < 1 ||
90
+ state.chain.length > 4 ||
91
+ !Number.isInteger(state.currentStep) ||
92
+ !Number.isInteger(state.step) ||
93
+ state.step !== state.currentStep ||
94
+ state.currentStep < 0 ||
95
+ state.currentStep > state.chain.length ||
96
+ typeof state.accessibilityRerollUsed !== "boolean" ||
97
+ !Number.isSafeInteger(state.joinGeneration) ||
98
+ state.joinGeneration < 1 ||
99
+ (state.panelId !== undefined &&
100
+ (typeof state.panelId !== "string" ||
101
+ state.panelId.length < 1 ||
102
+ state.panelId.length > 100 ||
103
+ state.panelId.includes(":"))) ||
104
+ typeof state.startedAt !== "string" ||
105
+ Number.isNaN(Date.parse(state.startedAt))) {
106
+ throw new Error("Access Control session state is invalid");
107
+ }
108
+ return state;
109
+ }
110
+ function evaluate(challenge, action) {
111
+ switch (challenge.type) {
112
+ case "quick_sum":
113
+ case "symbol_match":
114
+ case "server_question":
115
+ if (action.type !== "option")
116
+ return { outcome: "stale" };
117
+ return {
118
+ outcome: action.option === challenge.expectedOption ? "complete" : "failed",
119
+ };
120
+ case "number_order": {
121
+ if (action.type !== "option")
122
+ return { outcome: "stale" };
123
+ if (action.option !== challenge.expectedSequence[challenge.progress]) {
124
+ return { outcome: "failed" };
125
+ }
126
+ const progress = challenge.progress + 1;
127
+ return progress === challenge.expectedSequence.length
128
+ ? { outcome: "complete" }
129
+ : { outcome: "progress", challenge: { ...challenge, progress } };
130
+ }
131
+ case "button_sequence": {
132
+ if (action.type !== "sequence")
133
+ return { outcome: "stale" };
134
+ if (action.value !== challenge.expectedSequence[challenge.progress]) {
135
+ return { outcome: "failed" };
136
+ }
137
+ const progress = challenge.progress + 1;
138
+ return progress === challenge.expectedSequence.length
139
+ ? { outcome: "complete" }
140
+ : { outcome: "progress", challenge: { ...challenge, progress } };
141
+ }
142
+ case "modal_recall":
143
+ if (action.type !== "recall")
144
+ return { outcome: "stale" };
145
+ return {
146
+ outcome: normalizeRecall(action.value) ===
147
+ normalizeRecall(challenge.expectedText)
148
+ ? "complete"
149
+ : "failed",
150
+ };
151
+ case "password_entry":
152
+ case "emoji_entry":
153
+ if (action.type === "static-failed" &&
154
+ action.challenge === challenge.type) {
155
+ return { outcome: "failed" };
156
+ }
157
+ if (action.type !== "static-complete" ||
158
+ action.challenge !== challenge.type) {
159
+ return { outcome: "stale" };
160
+ }
161
+ return { outcome: "complete" };
162
+ }
163
+ }
164
+ function normalizeRecall(value) {
165
+ return value.trim().toLocaleUpperCase("en-GB");
166
+ }
167
+ //# sourceMappingURL=workflow.js.map