@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/dist/index.d.cts CHANGED
@@ -12,50 +12,21 @@ declare enum VerificationType {
12
12
  BOTH = "BOTH"
13
13
  }
14
14
  /**
15
- * Status of a verification session
15
+ * Status of a verification session.
16
+ * SCANNED has been removed — the in-flight credential exchange state
17
+ * is an internal wallet concern, not a session lifecycle stage.
16
18
  */
17
19
  declare enum VerificationStatus {
18
- /** Session created, waiting for wallet scan */
20
+ /** Session created, waiting for wallet to initiate the flow */
19
21
  PENDING = "PENDING",
20
- /** QR code scanned, credential exchange in progress */
21
- SCANNED = "SCANNED",
22
22
  /** Verification completed successfully */
23
23
  VERIFIED = "VERIFIED",
24
- /** Verification rejected (age, country, or policy) */
24
+ /** Verification rejected (age, country, or policy failure) */
25
25
  REJECTED = "REJECTED",
26
26
  /** Session expired before completion */
27
27
  EXPIRED = "EXPIRED"
28
28
  }
29
29
 
30
- /**
31
- * A verification session tracked by the service
32
- */
33
- interface VerificationSession {
34
- /** Unique session identifier (UUIDv4) */
35
- id: string;
36
- /** Type of verification requested */
37
- type: VerificationType;
38
- /** Current session status */
39
- status: VerificationStatus;
40
- /** URL for wallet to initiate OpenID4VP flow */
41
- walletUrl: string;
42
- /** Allowed countries (ISO 3166-1 alpha-2) */
43
- countryWhitelist?: string[];
44
- /** Blocked countries (ISO 3166-1 alpha-2) */
45
- countryBlacklist?: string[];
46
- /** Redirect URL after verification completes */
47
- redirectUrl?: string;
48
- /** Custom metadata attached to session */
49
- metadata?: Record<string, unknown>;
50
- /** Session creation timestamp */
51
- createdAt: Date;
52
- /** Session expiry timestamp */
53
- expiresAt: Date;
54
- /** Completion timestamp (if verified/rejected) */
55
- completedAt?: Date;
56
- /** Verification result (populated after completion) */
57
- result?: VerificationResult;
58
- }
59
30
  /**
60
31
  * Result of a completed verification
61
32
  */
@@ -77,15 +48,77 @@ interface VerificationResult {
77
48
  interface CreateSessionInput {
78
49
  /** Type of verification to perform */
79
50
  type: VerificationType;
51
+ /** Allowed countries (ISO 3166-1 alpha-2). Cannot be combined with countryBlacklist. */
52
+ countryWhitelist?: string[];
53
+ /** Blocked countries (ISO 3166-1 alpha-2). Cannot be combined with countryWhitelist. */
54
+ countryBlacklist?: string[];
55
+ /** Redirect URL after verification completes */
56
+ redirectUrl?: string;
57
+ /** Custom metadata to attach to session */
58
+ metadata?: Record<string, unknown>;
59
+ }
60
+ /**
61
+ * Fields common to all verification session states.
62
+ * Use the discriminated union {@link VerificationSession} in most cases.
63
+ */
64
+ interface BaseSession {
65
+ /** Unique session identifier (UUIDv4) */
66
+ id: string;
67
+ /** Type of verification requested */
68
+ type: VerificationType;
69
+ /** URL for the wallet to initiate the OpenID4VP flow */
70
+ walletUrl: string;
80
71
  /** Allowed countries (ISO 3166-1 alpha-2) */
81
72
  countryWhitelist?: string[];
82
73
  /** Blocked countries (ISO 3166-1 alpha-2) */
83
74
  countryBlacklist?: string[];
84
75
  /** Redirect URL after verification completes */
85
76
  redirectUrl?: string;
86
- /** Custom metadata to attach to session */
77
+ /** Custom metadata attached to session */
87
78
  metadata?: Record<string, unknown>;
79
+ /** Session creation timestamp */
80
+ createdAt: Date;
81
+ /** Session expiry timestamp */
82
+ expiresAt: Date;
83
+ }
84
+ /**
85
+ * A session that is waiting for wallet interaction.
86
+ * No result or completion timestamp is available.
87
+ */
88
+ interface PendingSession extends BaseSession {
89
+ /** Discriminant: session is awaiting wallet scan */
90
+ status: VerificationStatus.PENDING;
91
+ }
92
+ /**
93
+ * A session where the wallet completed the credential exchange,
94
+ * either successfully (VERIFIED) or with a policy failure (REJECTED).
95
+ */
96
+ interface CompletedSession extends BaseSession {
97
+ /** Discriminant: session finished with a terminal verified or rejected outcome */
98
+ status: VerificationStatus.VERIFIED | VerificationStatus.REJECTED;
99
+ /** Full result of the credential exchange */
100
+ result: VerificationResult;
101
+ /** Timestamp when the session transitioned to this status */
102
+ completedAt: Date;
103
+ }
104
+ /**
105
+ * A session that timed out before the wallet completed the flow.
106
+ */
107
+ interface ExpiredSession extends BaseSession {
108
+ /** Discriminant: session reached its TTL without completion */
109
+ status: VerificationStatus.EXPIRED;
110
+ /** Timestamp when expiry was detected and recorded */
111
+ completedAt: Date;
88
112
  }
113
+ /**
114
+ * Discriminated union of all possible verification session states.
115
+ *
116
+ * Narrow by checking `session.status`:
117
+ * - `PENDING` => {@link PendingSession}
118
+ * - `VERIFIED` | `REJECTED` => {@link CompletedSession}
119
+ * - `EXPIRED` => {@link ExpiredSession}
120
+ */
121
+ type VerificationSession = PendingSession | CompletedSession | ExpiredSession;
89
122
 
90
123
  /**
91
124
  * Strategy interface for verification modes.
@@ -98,11 +131,11 @@ interface IVerificationMode {
98
131
  * Process a callback from a wallet (or simulated callback).
99
132
  * Called when the wallet sends credential data back.
100
133
  *
101
- * @param session - The current verification session
134
+ * @param session - The current verification session (any state)
102
135
  * @param walletResponse - Raw data from the wallet (format depends on mode)
103
136
  * @returns Verification result
104
137
  */
105
- processCallback(session: VerificationSession, walletResponse: unknown): Promise<VerificationResult>;
138
+ processCallback(session: BaseSession, walletResponse: unknown): Promise<VerificationResult>;
106
139
  /**
107
140
  * Optional: simulate automatic completion (used by DemoMode).
108
141
  * If implemented, the service calls this after session creation
@@ -111,7 +144,7 @@ interface IVerificationMode {
111
144
  * @param session - The session to auto-complete
112
145
  * @returns Verification result after simulated delay
113
146
  */
114
- simulateCompletion?(session: VerificationSession): Promise<VerificationResult>;
147
+ simulateCompletion?(session: BaseSession): Promise<VerificationResult>;
115
148
  /**
116
149
  * Optional: build a custom wallet URL for the session.
117
150
  * Used by ProductionMode to generate proper OpenID4VP authorization requests.
@@ -151,6 +184,63 @@ interface VerificationServiceConfig {
151
184
  walletBaseUrl?: string;
152
185
  }
153
186
 
187
+ /**
188
+ * Typed event map for {@link VerificationService}.
189
+ *
190
+ * Use this interface to get full type inference when listening to events:
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const service = new VerificationService(config);
195
+ *
196
+ * service.on('session:verified', (session, result) => {
197
+ * // session: CompletedSession, result: VerificationResult
198
+ * });
199
+ * ```
200
+ *
201
+ * Each key maps to a tuple of the listener argument types, following the
202
+ * Node.js `EventEmitter` typed-overload convention.
203
+ */
204
+ interface VerificationEvents {
205
+ /**
206
+ * Fired after a new session is persisted to the store.
207
+ * @param session - The freshly created pending session
208
+ */
209
+ 'session:created': [session: PendingSession];
210
+ /**
211
+ * Fired after the wallet successfully completes credential exchange
212
+ * and all policy checks pass.
213
+ * @param session - The session now carrying VERIFIED status and result
214
+ * @param result - The full verification result
215
+ */
216
+ 'session:verified': [session: CompletedSession, result: VerificationResult];
217
+ /**
218
+ * Fired after the wallet completes credential exchange but a policy
219
+ * check fails (age, country, or custom rule).
220
+ * @param session - The session now carrying REJECTED status
221
+ * @param reason - Human-readable rejection reason (from result.rejectionReason)
222
+ */
223
+ 'session:rejected': [session: CompletedSession, reason: string];
224
+ /**
225
+ * Fired when a pending session is detected to have passed its TTL
226
+ * during a {@link VerificationService.cleanupExpired} sweep.
227
+ * @param session - The session now carrying EXPIRED status
228
+ */
229
+ 'session:expired': [session: ExpiredSession];
230
+ /**
231
+ * Fired when a caller explicitly cancels a pending session.
232
+ * @param session - The session at the time of cancellation
233
+ */
234
+ 'session:cancelled': [session: BaseSession];
235
+ /**
236
+ * Fired when an internal error occurs that cannot be surfaced via a
237
+ * rejected Promise (e.g. an error inside a fire-and-forget simulation).
238
+ * @param error - The underlying error
239
+ * @param sessionId - Session ID if the error is session-scoped
240
+ */
241
+ 'error': [error: Error, sessionId?: string];
242
+ }
243
+
154
244
  /**
155
245
  * Thrown when a session ID is not found in the store
156
246
  */
@@ -163,6 +253,24 @@ declare class SessionNotFoundError extends Error {
163
253
  declare class SessionExpiredError extends Error {
164
254
  constructor(sessionId: string);
165
255
  }
256
+ /**
257
+ * Thrown when an operation requires a session to be in PENDING status
258
+ * but it is in a different status (e.g., cancelling an already-completed session).
259
+ */
260
+ declare class SessionNotPendingError extends Error {
261
+ /** The session ID that was not in PENDING status */
262
+ readonly sessionId: string;
263
+ /** The status the session was actually in */
264
+ readonly currentStatus: string;
265
+ constructor(sessionId: string, currentStatus: string);
266
+ }
267
+ /**
268
+ * Thrown when any method is called on a {@link VerificationService} instance
269
+ * after {@link VerificationService.destroy} has been invoked.
270
+ */
271
+ declare class ServiceDestroyedError extends Error {
272
+ constructor();
273
+ }
166
274
 
167
275
  /**
168
276
  * In-memory session store using a Map.
@@ -181,15 +289,15 @@ interface DemoModeConfig {
181
289
  delayMs?: number;
182
290
  }
183
291
  /**
184
- * Demo mode auto-completes verification with randomized EU data.
292
+ * Demo mode -- auto-completes verification with randomized EU data.
185
293
  * Used for product demos and landing page previews.
186
294
  */
187
295
  declare class DemoMode implements IVerificationMode {
188
296
  readonly name = "demo";
189
297
  private delayMs;
190
298
  constructor(config?: DemoModeConfig);
191
- processCallback(session: VerificationSession, _walletResponse: unknown): Promise<VerificationResult>;
192
- simulateCompletion(session: VerificationSession): Promise<VerificationResult>;
299
+ processCallback(session: BaseSession, _walletResponse: unknown): Promise<VerificationResult>;
300
+ simulateCompletion(session: BaseSession): Promise<VerificationResult>;
193
301
  private pickCountry;
194
302
  private buildResult;
195
303
  }
@@ -201,7 +309,7 @@ interface MockModeConfig {
201
309
  delayMs?: number;
202
310
  }
203
311
  /**
204
- * Mock mode configurable responses for integration testing.
312
+ * Mock mode -- configurable responses for integration testing.
205
313
  * Supports global defaults and per-session overrides.
206
314
  */
207
315
  declare class MockMode implements IVerificationMode {
@@ -214,17 +322,51 @@ declare class MockMode implements IVerificationMode {
214
322
  setSessionResult(sessionId: string, result: VerificationResult): void;
215
323
  /** Clear a per-session result override */
216
324
  clearSessionResult(sessionId: string): void;
217
- processCallback(session: VerificationSession, _walletResponse: unknown): Promise<VerificationResult>;
218
- simulateCompletion(session: VerificationSession): Promise<VerificationResult>;
325
+ processCallback(session: BaseSession, _walletResponse: unknown): Promise<VerificationResult>;
326
+ simulateCompletion(session: BaseSession): Promise<VerificationResult>;
219
327
  }
220
328
 
329
+ /**
330
+ * Check whether a value is a valid ISO 3166-1 alpha-2 country code.
331
+ *
332
+ * The check is case-sensitive: codes must be uppercase two-letter strings
333
+ * (e.g. `'DE'`, `'FR'`). Lowercase variants (`'de'`), three-letter codes
334
+ * (`'DEU'`), empty strings, and unknown codes all return `false`.
335
+ *
336
+ * @param code - The string to validate
337
+ * @returns `true` if `code` is a recognised ISO 3166-1 alpha-2 code
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * isValidCountryCode('DE'); // true
342
+ * isValidCountryCode('de'); // false — must be uppercase
343
+ * isValidCountryCode('DEU'); // false — alpha-3, not alpha-2
344
+ * isValidCountryCode('XX'); // false — not a real country
345
+ * ```
346
+ */
347
+ declare function isValidCountryCode(code: string): boolean;
348
+
221
349
  /**
222
350
  * EUDI Wallet verification session orchestrator.
223
351
  *
224
352
  * Manages the lifecycle of verification sessions: creation, status tracking,
225
- * wallet callback handling, expiration, and event emission.
353
+ * wallet callback handling, cancellation, expiration, and event emission.
226
354
  *
227
- * Transport-agnostic consumers wire events to SSE, WebSocket, polling, etc.
355
+ * Transport-agnostic -- consumers wire events to SSE, WebSocket, polling, etc.
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * const service = new VerificationService({
360
+ * mode: new DemoMode({ delayMs: 2000 }),
361
+ * sessionTtlMs: 60_000,
362
+ * });
363
+ *
364
+ * service.on('session:verified', (session, result) => {
365
+ * console.log('Verified:', session.id, result.country);
366
+ * });
367
+ *
368
+ * const session = await service.createSession({ type: VerificationType.AGE });
369
+ * ```
228
370
  */
229
371
  declare class VerificationService extends EventEmitter {
230
372
  private store;
@@ -233,30 +375,183 @@ declare class VerificationService extends EventEmitter {
233
375
  private walletBaseUrl;
234
376
  /** Track session IDs for cleanup (store interface has no list method) */
235
377
  private sessionIds;
378
+ /** Whether {@link destroy} has been called */
379
+ private destroyed;
380
+ /**
381
+ * Create a new VerificationService instance.
382
+ *
383
+ * @param config - Service configuration including mode, store, TTL, and wallet URL
384
+ * @throws {Error} If config.sessionTtlMs is not positive or config.walletBaseUrl is empty
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * const service = new VerificationService({
389
+ * mode: new DemoMode(),
390
+ * store: new InMemorySessionStore(),
391
+ * sessionTtlMs: 300_000,
392
+ * walletBaseUrl: 'openid4vp://verify',
393
+ * });
394
+ * ```
395
+ */
236
396
  constructor(config: VerificationServiceConfig);
237
397
  /**
238
- * Create a new verification session
398
+ * Register a listener for a typed event.
399
+ *
400
+ * @param event - Event name from {@link VerificationEvents}
401
+ * @param listener - Callback receiving the event's typed arguments
402
+ * @returns this (for chaining)
403
+ */
404
+ on<K extends keyof VerificationEvents>(event: K, listener: (...args: VerificationEvents[K]) => void): this;
405
+ /**
406
+ * Register a one-time listener for a typed event.
407
+ *
408
+ * @param event - Event name from {@link VerificationEvents}
409
+ * @param listener - Callback receiving the event's typed arguments (called at most once)
410
+ * @returns this (for chaining)
239
411
  */
240
- createSession(input: CreateSessionInput): Promise<VerificationSession>;
412
+ once<K extends keyof VerificationEvents>(event: K, listener: (...args: VerificationEvents[K]) => void): this;
241
413
  /**
242
- * Get a session by ID
243
- * @throws SessionNotFoundError
414
+ * Remove a previously registered listener for a typed event.
415
+ *
416
+ * @param event - Event name from {@link VerificationEvents}
417
+ * @param listener - The exact function reference that was registered
418
+ * @returns this (for chaining)
419
+ */
420
+ off<K extends keyof VerificationEvents>(event: K, listener: (...args: VerificationEvents[K]) => void): this;
421
+ /**
422
+ * Emit a typed event.
423
+ *
424
+ * @param event - Event name from {@link VerificationEvents}
425
+ * @param args - Arguments matching the event's type signature
426
+ * @returns true if the event had listeners, false otherwise
427
+ */
428
+ emit<K extends keyof VerificationEvents>(event: K, ...args: VerificationEvents[K]): boolean;
429
+ /**
430
+ * Create a new verification session.
431
+ *
432
+ * Builds the wallet URL, persists the session, emits `session:created`,
433
+ * and (if the mode supports it) kicks off background auto-completion.
434
+ *
435
+ * @param input - Session creation parameters (type, country filters, metadata)
436
+ * @returns The newly created pending session with a populated walletUrl
437
+ * @throws {ServiceDestroyedError} If the service has been destroyed
438
+ * @throws {Error} If input validation fails (e.g. invalid country codes)
439
+ * @emits session:created When the session is persisted
440
+ *
441
+ * @example
442
+ * ```typescript
443
+ * const session = await service.createSession({
444
+ * type: VerificationType.AGE,
445
+ * countryWhitelist: ['DE', 'FR'],
446
+ * });
447
+ * console.log(session.walletUrl); // 'openid4vp://verify?session=...'
448
+ * ```
449
+ */
450
+ createSession(input: CreateSessionInput): Promise<PendingSession>;
451
+ /**
452
+ * Retrieve a session by its ID.
453
+ *
454
+ * @param id - The session UUID to look up
455
+ * @returns The session in its current state
456
+ * @throws {ServiceDestroyedError} If the service has been destroyed
457
+ * @throws {SessionNotFoundError} If no session exists with the given ID
458
+ *
459
+ * @example
460
+ * ```typescript
461
+ * const session = await service.getSession('550e8400-e29b-41d4-a716-446655440000');
462
+ * if (session.status === VerificationStatus.VERIFIED) {
463
+ * console.log('Already verified:', session.result);
464
+ * }
465
+ * ```
244
466
  */
245
467
  getSession(id: string): Promise<VerificationSession>;
246
468
  /**
247
- * Handle a callback from the wallet
248
- * @throws SessionNotFoundError
249
- * @throws SessionExpiredError
469
+ * Handle a callback from the wallet containing credential data.
470
+ *
471
+ * Delegates to the mode's `processCallback` to evaluate the wallet response,
472
+ * then transitions the session to VERIFIED or REJECTED.
473
+ *
474
+ * @param sessionId - The session UUID the callback belongs to
475
+ * @param walletResponse - Raw credential data from the wallet
476
+ * @returns The verification result
477
+ * @throws {ServiceDestroyedError} If the service has been destroyed
478
+ * @throws {SessionNotFoundError} If no session exists with the given ID
479
+ * @throws {SessionExpiredError} If the session has passed its TTL
480
+ * @emits session:verified When verification succeeds
481
+ * @emits session:rejected When verification fails
482
+ *
483
+ * @example
484
+ * ```typescript
485
+ * const result = await service.handleCallback(sessionId, walletData);
486
+ * if (result.verified) {
487
+ * grantAccess(result.country);
488
+ * }
489
+ * ```
250
490
  */
251
491
  handleCallback(sessionId: string, walletResponse: unknown): Promise<VerificationResult>;
252
492
  /**
253
- * Remove expired sessions from the store
254
- * @returns Number of sessions cleaned up
493
+ * Cancel a pending verification session.
494
+ *
495
+ * Only sessions in PENDING status can be cancelled. Completed or expired
496
+ * sessions will cause a {@link SessionNotPendingError}.
497
+ *
498
+ * @param id - The session UUID to cancel
499
+ * @throws {ServiceDestroyedError} If the service has been destroyed
500
+ * @throws {SessionNotFoundError} If no session exists with the given ID
501
+ * @throws {SessionNotPendingError} If the session is not in PENDING status
502
+ * @emits session:cancelled When the session is removed
503
+ *
504
+ * @example
505
+ * ```typescript
506
+ * await service.cancelSession(session.id);
507
+ * // session is now deleted from the store
508
+ * ```
509
+ */
510
+ cancelSession(id: string): Promise<void>;
511
+ /**
512
+ * Remove expired sessions from the store.
513
+ *
514
+ * Iterates all tracked session IDs and transitions any pending session
515
+ * whose TTL has elapsed to EXPIRED status before deleting it.
516
+ *
517
+ * @returns Number of sessions that were expired and removed
518
+ * @throws {ServiceDestroyedError} If the service has been destroyed
519
+ * @emits session:expired For each session that is expired
520
+ *
521
+ * @example
522
+ * ```typescript
523
+ * // Run periodically (e.g. every 60 seconds)
524
+ * const count = await service.cleanupExpired();
525
+ * console.log(`Cleaned up ${count} expired sessions`);
526
+ * ```
255
527
  */
256
528
  cleanupExpired(): Promise<number>;
529
+ /**
530
+ * Permanently destroy this service instance.
531
+ *
532
+ * Removes all event listeners, clears tracked session IDs, and marks
533
+ * the instance as destroyed. All subsequent public method calls will
534
+ * throw {@link ServiceDestroyedError}.
535
+ *
536
+ * @example
537
+ * ```typescript
538
+ * service.destroy();
539
+ * // Any further call throws ServiceDestroyedError
540
+ * await service.createSession({ type: VerificationType.AGE }); // throws
541
+ * ```
542
+ */
543
+ destroy(): void;
544
+ /**
545
+ * Guard that throws if the service has been destroyed.
546
+ * Called at the top of every public method.
547
+ */
548
+ private assertNotDestroyed;
549
+ /**
550
+ * Transition a session to VERIFIED or REJECTED and emit the appropriate event.
551
+ */
257
552
  private completeSession;
258
553
  }
259
554
 
260
- declare const VERSION = "0.1.0";
555
+ declare const VERSION = "0.2.0";
261
556
 
262
- export { type CreateSessionInput, DemoMode, type DemoModeConfig, type ISessionStore, type IVerificationMode, InMemorySessionStore, MockMode, type MockModeConfig, SessionExpiredError, SessionNotFoundError, VERSION, type VerificationResult, VerificationService, type VerificationServiceConfig, type VerificationSession, VerificationStatus, VerificationType };
557
+ export { type BaseSession, type CompletedSession, type CreateSessionInput, DemoMode, type DemoModeConfig, type ExpiredSession, type ISessionStore, type IVerificationMode, InMemorySessionStore, MockMode, type MockModeConfig, type PendingSession, ServiceDestroyedError, SessionExpiredError, SessionNotFoundError, SessionNotPendingError, VERSION, type VerificationEvents, type VerificationResult, VerificationService, type VerificationServiceConfig, type VerificationSession, VerificationStatus, VerificationType, isValidCountryCode };