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