@openeudi/core 0.1.0 → 0.2.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 +249 -64
- package/dist/index.cjs +532 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +352 -57
- package/dist/index.d.ts +352 -57
- package/dist/index.js +530 -17
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,6 @@ var VerificationType = /* @__PURE__ */ ((VerificationType2) => {
|
|
|
10
10
|
})(VerificationType || {});
|
|
11
11
|
var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
|
|
12
12
|
VerificationStatus2["PENDING"] = "PENDING";
|
|
13
|
-
VerificationStatus2["SCANNED"] = "SCANNED";
|
|
14
13
|
VerificationStatus2["VERIFIED"] = "VERIFIED";
|
|
15
14
|
VerificationStatus2["REJECTED"] = "REJECTED";
|
|
16
15
|
VerificationStatus2["EXPIRED"] = "EXPIRED";
|
|
@@ -32,6 +31,26 @@ var SessionExpiredError = class extends Error {
|
|
|
32
31
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
33
32
|
}
|
|
34
33
|
};
|
|
34
|
+
var SessionNotPendingError = class extends Error {
|
|
35
|
+
/** The session ID that was not in PENDING status */
|
|
36
|
+
sessionId;
|
|
37
|
+
/** The status the session was actually in */
|
|
38
|
+
currentStatus;
|
|
39
|
+
constructor(sessionId, currentStatus) {
|
|
40
|
+
super(`Session ${sessionId} is not pending (current status: ${currentStatus})`);
|
|
41
|
+
this.name = "SessionNotPendingError";
|
|
42
|
+
this.sessionId = sessionId;
|
|
43
|
+
this.currentStatus = currentStatus;
|
|
44
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var ServiceDestroyedError = class extends Error {
|
|
48
|
+
constructor() {
|
|
49
|
+
super("VerificationService has been destroyed and cannot accept new operations");
|
|
50
|
+
this.name = "ServiceDestroyedError";
|
|
51
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
35
54
|
|
|
36
55
|
// src/storage/memory.store.ts
|
|
37
56
|
var InMemorySessionStore = class {
|
|
@@ -141,6 +160,299 @@ var MockMode = class {
|
|
|
141
160
|
return this.processCallback(session, {});
|
|
142
161
|
}
|
|
143
162
|
};
|
|
163
|
+
|
|
164
|
+
// src/validation.ts
|
|
165
|
+
var ISO_3166_1_ALPHA2 = /* @__PURE__ */ new Set([
|
|
166
|
+
"AF",
|
|
167
|
+
"AX",
|
|
168
|
+
"AL",
|
|
169
|
+
"DZ",
|
|
170
|
+
"AS",
|
|
171
|
+
"AD",
|
|
172
|
+
"AO",
|
|
173
|
+
"AI",
|
|
174
|
+
"AQ",
|
|
175
|
+
"AG",
|
|
176
|
+
"AR",
|
|
177
|
+
"AM",
|
|
178
|
+
"AW",
|
|
179
|
+
"AU",
|
|
180
|
+
"AT",
|
|
181
|
+
"AZ",
|
|
182
|
+
"BS",
|
|
183
|
+
"BH",
|
|
184
|
+
"BD",
|
|
185
|
+
"BB",
|
|
186
|
+
"BY",
|
|
187
|
+
"BE",
|
|
188
|
+
"BZ",
|
|
189
|
+
"BJ",
|
|
190
|
+
"BM",
|
|
191
|
+
"BT",
|
|
192
|
+
"BO",
|
|
193
|
+
"BQ",
|
|
194
|
+
"BA",
|
|
195
|
+
"BW",
|
|
196
|
+
"BV",
|
|
197
|
+
"BR",
|
|
198
|
+
"IO",
|
|
199
|
+
"BN",
|
|
200
|
+
"BG",
|
|
201
|
+
"BF",
|
|
202
|
+
"BI",
|
|
203
|
+
"CV",
|
|
204
|
+
"KH",
|
|
205
|
+
"CM",
|
|
206
|
+
"CA",
|
|
207
|
+
"KY",
|
|
208
|
+
"CF",
|
|
209
|
+
"TD",
|
|
210
|
+
"CL",
|
|
211
|
+
"CN",
|
|
212
|
+
"CX",
|
|
213
|
+
"CC",
|
|
214
|
+
"CO",
|
|
215
|
+
"KM",
|
|
216
|
+
"CG",
|
|
217
|
+
"CD",
|
|
218
|
+
"CK",
|
|
219
|
+
"CR",
|
|
220
|
+
"CI",
|
|
221
|
+
"HR",
|
|
222
|
+
"CU",
|
|
223
|
+
"CW",
|
|
224
|
+
"CY",
|
|
225
|
+
"CZ",
|
|
226
|
+
"DK",
|
|
227
|
+
"DJ",
|
|
228
|
+
"DM",
|
|
229
|
+
"DO",
|
|
230
|
+
"EC",
|
|
231
|
+
"EG",
|
|
232
|
+
"SV",
|
|
233
|
+
"GQ",
|
|
234
|
+
"ER",
|
|
235
|
+
"EE",
|
|
236
|
+
"SZ",
|
|
237
|
+
"ET",
|
|
238
|
+
"FK",
|
|
239
|
+
"FO",
|
|
240
|
+
"FJ",
|
|
241
|
+
"FI",
|
|
242
|
+
"FR",
|
|
243
|
+
"GF",
|
|
244
|
+
"PF",
|
|
245
|
+
"TF",
|
|
246
|
+
"GA",
|
|
247
|
+
"GM",
|
|
248
|
+
"GE",
|
|
249
|
+
"DE",
|
|
250
|
+
"GH",
|
|
251
|
+
"GI",
|
|
252
|
+
"GR",
|
|
253
|
+
"GL",
|
|
254
|
+
"GD",
|
|
255
|
+
"GP",
|
|
256
|
+
"GU",
|
|
257
|
+
"GT",
|
|
258
|
+
"GG",
|
|
259
|
+
"GN",
|
|
260
|
+
"GW",
|
|
261
|
+
"GY",
|
|
262
|
+
"HT",
|
|
263
|
+
"HM",
|
|
264
|
+
"VA",
|
|
265
|
+
"HN",
|
|
266
|
+
"HK",
|
|
267
|
+
"HU",
|
|
268
|
+
"IS",
|
|
269
|
+
"IN",
|
|
270
|
+
"ID",
|
|
271
|
+
"IR",
|
|
272
|
+
"IQ",
|
|
273
|
+
"IE",
|
|
274
|
+
"IM",
|
|
275
|
+
"IL",
|
|
276
|
+
"IT",
|
|
277
|
+
"JM",
|
|
278
|
+
"JP",
|
|
279
|
+
"JE",
|
|
280
|
+
"JO",
|
|
281
|
+
"KZ",
|
|
282
|
+
"KE",
|
|
283
|
+
"KI",
|
|
284
|
+
"KP",
|
|
285
|
+
"KR",
|
|
286
|
+
"KW",
|
|
287
|
+
"KG",
|
|
288
|
+
"LA",
|
|
289
|
+
"LV",
|
|
290
|
+
"LB",
|
|
291
|
+
"LS",
|
|
292
|
+
"LR",
|
|
293
|
+
"LY",
|
|
294
|
+
"LI",
|
|
295
|
+
"LT",
|
|
296
|
+
"LU",
|
|
297
|
+
"MO",
|
|
298
|
+
"MG",
|
|
299
|
+
"MW",
|
|
300
|
+
"MY",
|
|
301
|
+
"MV",
|
|
302
|
+
"ML",
|
|
303
|
+
"MT",
|
|
304
|
+
"MH",
|
|
305
|
+
"MQ",
|
|
306
|
+
"MR",
|
|
307
|
+
"MU",
|
|
308
|
+
"YT",
|
|
309
|
+
"MX",
|
|
310
|
+
"FM",
|
|
311
|
+
"MD",
|
|
312
|
+
"MC",
|
|
313
|
+
"MN",
|
|
314
|
+
"ME",
|
|
315
|
+
"MS",
|
|
316
|
+
"MA",
|
|
317
|
+
"MZ",
|
|
318
|
+
"MM",
|
|
319
|
+
"NA",
|
|
320
|
+
"NR",
|
|
321
|
+
"NP",
|
|
322
|
+
"NL",
|
|
323
|
+
"NC",
|
|
324
|
+
"NZ",
|
|
325
|
+
"NI",
|
|
326
|
+
"NE",
|
|
327
|
+
"NG",
|
|
328
|
+
"NU",
|
|
329
|
+
"NF",
|
|
330
|
+
"MK",
|
|
331
|
+
"MP",
|
|
332
|
+
"NO",
|
|
333
|
+
"OM",
|
|
334
|
+
"PK",
|
|
335
|
+
"PW",
|
|
336
|
+
"PS",
|
|
337
|
+
"PA",
|
|
338
|
+
"PG",
|
|
339
|
+
"PY",
|
|
340
|
+
"PE",
|
|
341
|
+
"PH",
|
|
342
|
+
"PN",
|
|
343
|
+
"PL",
|
|
344
|
+
"PT",
|
|
345
|
+
"PR",
|
|
346
|
+
"QA",
|
|
347
|
+
"RE",
|
|
348
|
+
"RO",
|
|
349
|
+
"RU",
|
|
350
|
+
"RW",
|
|
351
|
+
"BL",
|
|
352
|
+
"SH",
|
|
353
|
+
"KN",
|
|
354
|
+
"LC",
|
|
355
|
+
"MF",
|
|
356
|
+
"PM",
|
|
357
|
+
"VC",
|
|
358
|
+
"WS",
|
|
359
|
+
"SM",
|
|
360
|
+
"ST",
|
|
361
|
+
"SA",
|
|
362
|
+
"SN",
|
|
363
|
+
"RS",
|
|
364
|
+
"SC",
|
|
365
|
+
"SL",
|
|
366
|
+
"SG",
|
|
367
|
+
"SX",
|
|
368
|
+
"SK",
|
|
369
|
+
"SI",
|
|
370
|
+
"SB",
|
|
371
|
+
"SO",
|
|
372
|
+
"ZA",
|
|
373
|
+
"GS",
|
|
374
|
+
"SS",
|
|
375
|
+
"ES",
|
|
376
|
+
"LK",
|
|
377
|
+
"SD",
|
|
378
|
+
"SR",
|
|
379
|
+
"SJ",
|
|
380
|
+
"SE",
|
|
381
|
+
"CH",
|
|
382
|
+
"SY",
|
|
383
|
+
"TW",
|
|
384
|
+
"TJ",
|
|
385
|
+
"TZ",
|
|
386
|
+
"TH",
|
|
387
|
+
"TL",
|
|
388
|
+
"TG",
|
|
389
|
+
"TK",
|
|
390
|
+
"TO",
|
|
391
|
+
"TT",
|
|
392
|
+
"TN",
|
|
393
|
+
"TR",
|
|
394
|
+
"TM",
|
|
395
|
+
"TC",
|
|
396
|
+
"TV",
|
|
397
|
+
"UG",
|
|
398
|
+
"UA",
|
|
399
|
+
"AE",
|
|
400
|
+
"GB",
|
|
401
|
+
"US",
|
|
402
|
+
"UM",
|
|
403
|
+
"UY",
|
|
404
|
+
"UZ",
|
|
405
|
+
"VU",
|
|
406
|
+
"VE",
|
|
407
|
+
"VN",
|
|
408
|
+
"VG",
|
|
409
|
+
"VI",
|
|
410
|
+
"WF",
|
|
411
|
+
"EH",
|
|
412
|
+
"YE",
|
|
413
|
+
"ZM",
|
|
414
|
+
"ZW"
|
|
415
|
+
]);
|
|
416
|
+
function isValidCountryCode(code) {
|
|
417
|
+
return ISO_3166_1_ALPHA2.has(code);
|
|
418
|
+
}
|
|
419
|
+
function validateConfig(config) {
|
|
420
|
+
if (config.sessionTtlMs !== void 0) {
|
|
421
|
+
if (config.sessionTtlMs <= 0) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`sessionTtlMs must be a positive number, received: ${config.sessionTtlMs}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (config.walletBaseUrl !== void 0) {
|
|
428
|
+
if (config.walletBaseUrl.trim().length === 0) {
|
|
429
|
+
throw new Error("walletBaseUrl must be a non-empty string");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function validateSessionInput(input) {
|
|
434
|
+
if (input.countryWhitelist !== void 0 && input.countryBlacklist !== void 0) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
"countryWhitelist and countryBlacklist cannot both be provided; use one or the other"
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (input.countryWhitelist !== void 0) {
|
|
440
|
+
const invalid = input.countryWhitelist.filter((code) => !isValidCountryCode(code));
|
|
441
|
+
if (invalid.length > 0) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`countryWhitelist contains invalid ISO 3166-1 alpha-2 codes: ${invalid.join(", ")}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (input.countryBlacklist !== void 0) {
|
|
448
|
+
const invalid = input.countryBlacklist.filter((code) => !isValidCountryCode(code));
|
|
449
|
+
if (invalid.length > 0) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`countryBlacklist contains invalid ISO 3166-1 alpha-2 codes: ${invalid.join(", ")}`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
144
456
|
var DEFAULT_TTL_MS = 3e5;
|
|
145
457
|
var DEFAULT_WALLET_BASE_URL = "openid4vp://verify";
|
|
146
458
|
var VerificationService = class extends EventEmitter {
|
|
@@ -150,25 +462,110 @@ var VerificationService = class extends EventEmitter {
|
|
|
150
462
|
walletBaseUrl;
|
|
151
463
|
/** Track session IDs for cleanup (store interface has no list method) */
|
|
152
464
|
sessionIds = /* @__PURE__ */ new Set();
|
|
465
|
+
/** Whether {@link destroy} has been called */
|
|
466
|
+
destroyed = false;
|
|
467
|
+
/**
|
|
468
|
+
* Create a new VerificationService instance.
|
|
469
|
+
*
|
|
470
|
+
* @param config - Service configuration including mode, store, TTL, and wallet URL
|
|
471
|
+
* @throws {Error} If config.sessionTtlMs is not positive or config.walletBaseUrl is empty
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* ```typescript
|
|
475
|
+
* const service = new VerificationService({
|
|
476
|
+
* mode: new DemoMode(),
|
|
477
|
+
* store: new InMemorySessionStore(),
|
|
478
|
+
* sessionTtlMs: 300_000,
|
|
479
|
+
* walletBaseUrl: 'openid4vp://verify',
|
|
480
|
+
* });
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
153
483
|
constructor(config) {
|
|
154
484
|
super();
|
|
485
|
+
validateConfig(config);
|
|
155
486
|
this.mode = config.mode;
|
|
156
487
|
this.store = config.store ?? new InMemorySessionStore();
|
|
157
488
|
this.sessionTtlMs = config.sessionTtlMs ?? DEFAULT_TTL_MS;
|
|
158
489
|
this.walletBaseUrl = config.walletBaseUrl ?? DEFAULT_WALLET_BASE_URL;
|
|
159
490
|
}
|
|
491
|
+
// -----------------------------------------------------------------------
|
|
492
|
+
// Typed EventEmitter overrides
|
|
493
|
+
// -----------------------------------------------------------------------
|
|
494
|
+
/**
|
|
495
|
+
* Register a listener for a typed event.
|
|
496
|
+
*
|
|
497
|
+
* @param event - Event name from {@link VerificationEvents}
|
|
498
|
+
* @param listener - Callback receiving the event's typed arguments
|
|
499
|
+
* @returns this (for chaining)
|
|
500
|
+
*/
|
|
501
|
+
on(event, listener) {
|
|
502
|
+
return super.on(event, listener);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Register a one-time listener for a typed event.
|
|
506
|
+
*
|
|
507
|
+
* @param event - Event name from {@link VerificationEvents}
|
|
508
|
+
* @param listener - Callback receiving the event's typed arguments (called at most once)
|
|
509
|
+
* @returns this (for chaining)
|
|
510
|
+
*/
|
|
511
|
+
once(event, listener) {
|
|
512
|
+
return super.once(event, listener);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Remove a previously registered listener for a typed event.
|
|
516
|
+
*
|
|
517
|
+
* @param event - Event name from {@link VerificationEvents}
|
|
518
|
+
* @param listener - The exact function reference that was registered
|
|
519
|
+
* @returns this (for chaining)
|
|
520
|
+
*/
|
|
521
|
+
off(event, listener) {
|
|
522
|
+
return super.off(event, listener);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Emit a typed event.
|
|
526
|
+
*
|
|
527
|
+
* @param event - Event name from {@link VerificationEvents}
|
|
528
|
+
* @param args - Arguments matching the event's type signature
|
|
529
|
+
* @returns true if the event had listeners, false otherwise
|
|
530
|
+
*/
|
|
531
|
+
emit(event, ...args) {
|
|
532
|
+
return super.emit(event, ...args);
|
|
533
|
+
}
|
|
534
|
+
// -----------------------------------------------------------------------
|
|
535
|
+
// Public API
|
|
536
|
+
// -----------------------------------------------------------------------
|
|
160
537
|
/**
|
|
161
|
-
* Create a new verification session
|
|
538
|
+
* Create a new verification session.
|
|
539
|
+
*
|
|
540
|
+
* Builds the wallet URL, persists the session, emits `session:created`,
|
|
541
|
+
* and (if the mode supports it) kicks off background auto-completion.
|
|
542
|
+
*
|
|
543
|
+
* @param input - Session creation parameters (type, country filters, metadata)
|
|
544
|
+
* @returns The newly created pending session with a populated walletUrl
|
|
545
|
+
* @throws {ServiceDestroyedError} If the service has been destroyed
|
|
546
|
+
* @throws {Error} If input validation fails (e.g. invalid country codes)
|
|
547
|
+
* @emits session:created When the session is persisted
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* ```typescript
|
|
551
|
+
* const session = await service.createSession({
|
|
552
|
+
* type: VerificationType.AGE,
|
|
553
|
+
* countryWhitelist: ['DE', 'FR'],
|
|
554
|
+
* });
|
|
555
|
+
* console.log(session.walletUrl); // 'openid4vp://verify?session=...'
|
|
556
|
+
* ```
|
|
162
557
|
*/
|
|
163
558
|
async createSession(input) {
|
|
559
|
+
this.assertNotDestroyed();
|
|
560
|
+
validateSessionInput(input);
|
|
164
561
|
const id = v4();
|
|
165
562
|
const now = /* @__PURE__ */ new Date();
|
|
563
|
+
const walletUrl = this.mode.buildWalletUrl ? await this.mode.buildWalletUrl(id, input) : `${this.walletBaseUrl}?session=${id}`;
|
|
166
564
|
const session = {
|
|
167
565
|
id,
|
|
168
566
|
type: input.type,
|
|
169
567
|
status: "PENDING" /* PENDING */,
|
|
170
|
-
walletUrl
|
|
171
|
-
// set below, may be async from mode
|
|
568
|
+
walletUrl,
|
|
172
569
|
countryWhitelist: input.countryWhitelist,
|
|
173
570
|
countryBlacklist: input.countryBlacklist,
|
|
174
571
|
redirectUrl: input.redirectUrl,
|
|
@@ -176,7 +573,6 @@ var VerificationService = class extends EventEmitter {
|
|
|
176
573
|
createdAt: now,
|
|
177
574
|
expiresAt: new Date(now.getTime() + this.sessionTtlMs)
|
|
178
575
|
};
|
|
179
|
-
session.walletUrl = this.mode.buildWalletUrl ? await this.mode.buildWalletUrl(id, input) : `${this.walletBaseUrl}?session=${id}`;
|
|
180
576
|
await this.store.set(session);
|
|
181
577
|
this.sessionIds.add(id);
|
|
182
578
|
this.emit("session:created", session);
|
|
@@ -186,16 +582,30 @@ var VerificationService = class extends EventEmitter {
|
|
|
186
582
|
if (current && current.status === "PENDING" /* PENDING */) {
|
|
187
583
|
await this.completeSession(current, result);
|
|
188
584
|
}
|
|
189
|
-
}).catch(() => {
|
|
585
|
+
}).catch((error) => {
|
|
586
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)), id);
|
|
190
587
|
});
|
|
191
588
|
}
|
|
192
589
|
return session;
|
|
193
590
|
}
|
|
194
591
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
592
|
+
* Retrieve a session by its ID.
|
|
593
|
+
*
|
|
594
|
+
* @param id - The session UUID to look up
|
|
595
|
+
* @returns The session in its current state
|
|
596
|
+
* @throws {ServiceDestroyedError} If the service has been destroyed
|
|
597
|
+
* @throws {SessionNotFoundError} If no session exists with the given ID
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```typescript
|
|
601
|
+
* const session = await service.getSession('550e8400-e29b-41d4-a716-446655440000');
|
|
602
|
+
* if (session.status === VerificationStatus.VERIFIED) {
|
|
603
|
+
* console.log('Already verified:', session.result);
|
|
604
|
+
* }
|
|
605
|
+
* ```
|
|
197
606
|
*/
|
|
198
607
|
async getSession(id) {
|
|
608
|
+
this.assertNotDestroyed();
|
|
199
609
|
const session = await this.store.get(id);
|
|
200
610
|
if (!session) {
|
|
201
611
|
throw new SessionNotFoundError(id);
|
|
@@ -203,11 +613,30 @@ var VerificationService = class extends EventEmitter {
|
|
|
203
613
|
return session;
|
|
204
614
|
}
|
|
205
615
|
/**
|
|
206
|
-
* Handle a callback from the wallet
|
|
207
|
-
*
|
|
208
|
-
*
|
|
616
|
+
* Handle a callback from the wallet containing credential data.
|
|
617
|
+
*
|
|
618
|
+
* Delegates to the mode's `processCallback` to evaluate the wallet response,
|
|
619
|
+
* then transitions the session to VERIFIED or REJECTED.
|
|
620
|
+
*
|
|
621
|
+
* @param sessionId - The session UUID the callback belongs to
|
|
622
|
+
* @param walletResponse - Raw credential data from the wallet
|
|
623
|
+
* @returns The verification result
|
|
624
|
+
* @throws {ServiceDestroyedError} If the service has been destroyed
|
|
625
|
+
* @throws {SessionNotFoundError} If no session exists with the given ID
|
|
626
|
+
* @throws {SessionExpiredError} If the session has passed its TTL
|
|
627
|
+
* @emits session:verified When verification succeeds
|
|
628
|
+
* @emits session:rejected When verification fails
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* ```typescript
|
|
632
|
+
* const result = await service.handleCallback(sessionId, walletData);
|
|
633
|
+
* if (result.verified) {
|
|
634
|
+
* grantAccess(result.country);
|
|
635
|
+
* }
|
|
636
|
+
* ```
|
|
209
637
|
*/
|
|
210
638
|
async handleCallback(sessionId, walletResponse) {
|
|
639
|
+
this.assertNotDestroyed();
|
|
211
640
|
const session = await this.store.get(sessionId);
|
|
212
641
|
if (!session) {
|
|
213
642
|
throw new SessionNotFoundError(sessionId);
|
|
@@ -220,10 +649,55 @@ var VerificationService = class extends EventEmitter {
|
|
|
220
649
|
return result;
|
|
221
650
|
}
|
|
222
651
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
652
|
+
* Cancel a pending verification session.
|
|
653
|
+
*
|
|
654
|
+
* Only sessions in PENDING status can be cancelled. Completed or expired
|
|
655
|
+
* sessions will cause a {@link SessionNotPendingError}.
|
|
656
|
+
*
|
|
657
|
+
* @param id - The session UUID to cancel
|
|
658
|
+
* @throws {ServiceDestroyedError} If the service has been destroyed
|
|
659
|
+
* @throws {SessionNotFoundError} If no session exists with the given ID
|
|
660
|
+
* @throws {SessionNotPendingError} If the session is not in PENDING status
|
|
661
|
+
* @emits session:cancelled When the session is removed
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* ```typescript
|
|
665
|
+
* await service.cancelSession(session.id);
|
|
666
|
+
* // session is now deleted from the store
|
|
667
|
+
* ```
|
|
668
|
+
*/
|
|
669
|
+
async cancelSession(id) {
|
|
670
|
+
this.assertNotDestroyed();
|
|
671
|
+
const session = await this.store.get(id);
|
|
672
|
+
if (!session) {
|
|
673
|
+
throw new SessionNotFoundError(id);
|
|
674
|
+
}
|
|
675
|
+
if (session.status !== "PENDING" /* PENDING */) {
|
|
676
|
+
throw new SessionNotPendingError(id, session.status);
|
|
677
|
+
}
|
|
678
|
+
await this.store.delete(id);
|
|
679
|
+
this.sessionIds.delete(id);
|
|
680
|
+
this.emit("session:cancelled", session);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Remove expired sessions from the store.
|
|
684
|
+
*
|
|
685
|
+
* Iterates all tracked session IDs and transitions any pending session
|
|
686
|
+
* whose TTL has elapsed to EXPIRED status before deleting it.
|
|
687
|
+
*
|
|
688
|
+
* @returns Number of sessions that were expired and removed
|
|
689
|
+
* @throws {ServiceDestroyedError} If the service has been destroyed
|
|
690
|
+
* @emits session:expired For each session that is expired
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```typescript
|
|
694
|
+
* // Run periodically (e.g. every 60 seconds)
|
|
695
|
+
* const count = await service.cleanupExpired();
|
|
696
|
+
* console.log(`Cleaned up ${count} expired sessions`);
|
|
697
|
+
* ```
|
|
225
698
|
*/
|
|
226
699
|
async cleanupExpired() {
|
|
700
|
+
this.assertNotDestroyed();
|
|
227
701
|
const now = /* @__PURE__ */ new Date();
|
|
228
702
|
let count = 0;
|
|
229
703
|
for (const id of this.sessionIds) {
|
|
@@ -233,7 +707,11 @@ var VerificationService = class extends EventEmitter {
|
|
|
233
707
|
continue;
|
|
234
708
|
}
|
|
235
709
|
if (now > session.expiresAt && session.status === "PENDING" /* PENDING */) {
|
|
236
|
-
const expired = {
|
|
710
|
+
const expired = {
|
|
711
|
+
...session,
|
|
712
|
+
status: "EXPIRED" /* EXPIRED */,
|
|
713
|
+
completedAt: now
|
|
714
|
+
};
|
|
237
715
|
await this.store.set(expired);
|
|
238
716
|
await this.store.delete(id);
|
|
239
717
|
this.sessionIds.delete(id);
|
|
@@ -243,6 +721,40 @@ var VerificationService = class extends EventEmitter {
|
|
|
243
721
|
}
|
|
244
722
|
return count;
|
|
245
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* Permanently destroy this service instance.
|
|
726
|
+
*
|
|
727
|
+
* Removes all event listeners, clears tracked session IDs, and marks
|
|
728
|
+
* the instance as destroyed. All subsequent public method calls will
|
|
729
|
+
* throw {@link ServiceDestroyedError}.
|
|
730
|
+
*
|
|
731
|
+
* @example
|
|
732
|
+
* ```typescript
|
|
733
|
+
* service.destroy();
|
|
734
|
+
* // Any further call throws ServiceDestroyedError
|
|
735
|
+
* await service.createSession({ type: VerificationType.AGE }); // throws
|
|
736
|
+
* ```
|
|
737
|
+
*/
|
|
738
|
+
destroy() {
|
|
739
|
+
this.removeAllListeners();
|
|
740
|
+
this.sessionIds.clear();
|
|
741
|
+
this.destroyed = true;
|
|
742
|
+
}
|
|
743
|
+
// -----------------------------------------------------------------------
|
|
744
|
+
// Private helpers
|
|
745
|
+
// -----------------------------------------------------------------------
|
|
746
|
+
/**
|
|
747
|
+
* Guard that throws if the service has been destroyed.
|
|
748
|
+
* Called at the top of every public method.
|
|
749
|
+
*/
|
|
750
|
+
assertNotDestroyed() {
|
|
751
|
+
if (this.destroyed) {
|
|
752
|
+
throw new ServiceDestroyedError();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Transition a session to VERIFIED or REJECTED and emit the appropriate event.
|
|
757
|
+
*/
|
|
246
758
|
async completeSession(session, result) {
|
|
247
759
|
const status = result.verified ? "VERIFIED" /* VERIFIED */ : "REJECTED" /* REJECTED */;
|
|
248
760
|
const updated = {
|
|
@@ -252,17 +764,18 @@ var VerificationService = class extends EventEmitter {
|
|
|
252
764
|
result
|
|
253
765
|
};
|
|
254
766
|
await this.store.set(updated);
|
|
767
|
+
this.sessionIds.delete(session.id);
|
|
255
768
|
if (result.verified) {
|
|
256
769
|
this.emit("session:verified", updated, result);
|
|
257
770
|
} else {
|
|
258
|
-
this.emit("session:rejected", updated, result.rejectionReason);
|
|
771
|
+
this.emit("session:rejected", updated, result.rejectionReason ?? "");
|
|
259
772
|
}
|
|
260
773
|
}
|
|
261
774
|
};
|
|
262
775
|
|
|
263
776
|
// src/index.ts
|
|
264
|
-
var VERSION = "0.
|
|
777
|
+
var VERSION = "0.2.0";
|
|
265
778
|
|
|
266
|
-
export { DemoMode, InMemorySessionStore, MockMode, SessionExpiredError, SessionNotFoundError, VERSION, VerificationService, VerificationStatus, VerificationType };
|
|
779
|
+
export { DemoMode, InMemorySessionStore, MockMode, ServiceDestroyedError, SessionExpiredError, SessionNotFoundError, SessionNotPendingError, VERSION, VerificationService, VerificationStatus, VerificationType, isValidCountryCode };
|
|
267
780
|
//# sourceMappingURL=index.js.map
|
|
268
781
|
//# sourceMappingURL=index.js.map
|