ngx-webauthn 0.0.2 → 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.
@@ -1,23 +1,56 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable } from '@angular/core';
3
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
3
4
  import { throwError, from } from 'rxjs';
4
- import { map, catchError } from 'rxjs/operators';
5
+ import { map, catchError, timeout, switchMap } from 'rxjs/operators';
5
6
 
6
7
  /**
7
- * High-level configuration interfaces for WebAuthn operations
8
+ * Type guard utilities for WebAuthn operations
8
9
  *
9
- * These interfaces provide a convenient, preset-driven API while still allowing
10
- * full customization through override properties. They derive from standard WebAuthn
11
- * types where possible to reduce duplication and ensure compatibility.
10
+ * These functions help determine the type of input provided to WebAuthn operations,
11
+ * enabling proper handling of different input formats (high-level configs vs native WebAuthn options).
12
12
  */
13
13
  /**
14
- * Type guard to check if input is a RegisterConfig
14
+ * Type guard to determine if input is a high-level RegisterConfig object.
15
+ * Distinguishes between RegisterConfig and direct WebAuthn creation options.
16
+ *
17
+ * @param input The input to check
18
+ * @returns True if the input is a RegisterConfig, false otherwise
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * if (isRegisterConfig(input)) {
23
+ * // Handle high-level config with preset support
24
+ * const options = buildCreationOptionsFromConfig(input, config);
25
+ * } else {
26
+ * // Handle direct WebAuthn options
27
+ * const options = parseRegistrationOptions(input);
28
+ * }
29
+ * ```
15
30
  */
16
31
  function isRegisterConfig(input) {
17
32
  return typeof input === 'object' && input !== null && 'username' in input;
18
33
  }
19
34
  /**
20
- * Type guard to check if input is an AuthenticateConfig
35
+ * Type guard to determine if input is a high-level AuthenticateConfig object.
36
+ * Distinguishes between AuthenticateConfig and direct WebAuthn request options.
37
+ *
38
+ * Uses the presence of 'username' or 'preset' fields and absence of WebAuthn-specific
39
+ * fields ('rp', 'user') to identify AuthenticateConfig objects.
40
+ *
41
+ * @param input The input to check
42
+ * @returns True if the input is an AuthenticateConfig, false otherwise
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * if (isAuthenticateConfig(input)) {
47
+ * // Handle high-level config with preset support
48
+ * const options = buildRequestOptionsFromConfig(input, config);
49
+ * } else {
50
+ * // Handle direct WebAuthn options
51
+ * const options = parseAuthenticationOptions(input);
52
+ * }
53
+ * ```
21
54
  */
22
55
  function isAuthenticateConfig(input) {
23
56
  return (typeof input === 'object' &&
@@ -27,7 +60,20 @@ function isAuthenticateConfig(input) {
27
60
  !('user' in input)); // WebAuthn options have 'user'
28
61
  }
29
62
  /**
30
- * Type guard to check if input is WebAuthn creation options
63
+ * Type guard to check if input contains WebAuthn creation options.
64
+ * Identifies objects that have the structure of PublicKeyCredentialCreationOptions
65
+ * by checking for required fields like 'rp' and 'user'.
66
+ *
67
+ * @param input The input to check
68
+ * @returns True if the input has creation options structure, false otherwise
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * if (isCreationOptions(input)) {
73
+ * // Input is already in WebAuthn format
74
+ * return navigator.credentials.create({ publicKey: input });
75
+ * }
76
+ * ```
31
77
  */
32
78
  function isCreationOptions(input) {
33
79
  return (typeof input === 'object' &&
@@ -36,7 +82,20 @@ function isCreationOptions(input) {
36
82
  'user' in input);
37
83
  }
38
84
  /**
39
- * Type guard to check if input is WebAuthn request options
85
+ * Type guard to check if input contains WebAuthn request options.
86
+ * Identifies objects that have the structure of PublicKeyCredentialRequestOptions
87
+ * by checking for the required 'challenge' field.
88
+ *
89
+ * @param input The input to check
90
+ * @returns True if the input has request options structure, false otherwise
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * if (isRequestOptions(input)) {
95
+ * // Input is already in WebAuthn format
96
+ * return navigator.credentials.get({ publicKey: input });
97
+ * }
98
+ * ```
40
99
  */
41
100
  function isRequestOptions(input) {
42
101
  return (typeof input === 'object' &&
@@ -82,7 +141,7 @@ const PASSKEY_PRESET = {
82
141
  },
83
142
  };
84
143
  /**
85
- * Preset for using a security key as a second factor after a password.
144
+ * Preset for using an external security key as a second factor after a password.
86
145
  *
87
146
  * Best for: Traditional 2FA scenarios where users already have a password
88
147
  * and want to add hardware security key as a second factor.
@@ -93,7 +152,7 @@ const PASSKEY_PRESET = {
93
152
  * - Favors cross-platform authenticators (USB/NFC security keys)
94
153
  * - Credentials typically not synced between devices
95
154
  */
96
- const SECOND_FACTOR_PRESET = {
155
+ const EXTERNAL_SECURITY_KEY_PRESET = {
97
156
  ...COMMON_PUB_KEY_CRED_PARAMS,
98
157
  authenticatorSelection: {
99
158
  residentKey: 'discouraged',
@@ -102,10 +161,10 @@ const SECOND_FACTOR_PRESET = {
102
161
  },
103
162
  };
104
163
  /**
105
- * Preset for high-security, non-synced, single-device credentials.
164
+ * Preset for high-security, non-synced, platform authenticator credentials.
106
165
  *
107
166
  * Best for: High-security scenarios where credentials must stay on a single
108
- * device and user verification is mandatory.
167
+ * device and user verification is mandatory using built-in platform authenticators.
109
168
  *
110
169
  * Features:
111
170
  * - Requires platform authenticators (built-in biometrics/PIN)
@@ -113,7 +172,7 @@ const SECOND_FACTOR_PRESET = {
113
172
  * - Requires user verification (biometric/PIN)
114
173
  * - Credentials bound to specific device (no syncing)
115
174
  */
116
- const DEVICE_BOUND_PRESET = {
175
+ const PLATFORM_AUTHENTICATOR_PRESET = {
117
176
  ...COMMON_PUB_KEY_CRED_PARAMS,
118
177
  authenticatorSelection: {
119
178
  authenticatorAttachment: 'platform',
@@ -127,8 +186,8 @@ const DEVICE_BOUND_PRESET = {
127
186
  */
128
187
  const PRESET_MAP = {
129
188
  passkey: PASSKEY_PRESET,
130
- secondFactor: SECOND_FACTOR_PRESET,
131
- deviceBound: DEVICE_BOUND_PRESET,
189
+ externalSecurityKey: EXTERNAL_SECURITY_KEY_PRESET,
190
+ platformAuthenticator: PLATFORM_AUTHENTICATOR_PRESET,
132
191
  };
133
192
 
134
193
  /**
@@ -139,8 +198,21 @@ const PRESET_MAP = {
139
198
  * WebAuthn options ready for the browser API.
140
199
  */
141
200
  /**
142
- * Deep merge utility that properly handles nested objects
143
- * Later properties override earlier ones
201
+ * Deep merge utility that properly handles nested objects.
202
+ * Later properties override earlier ones, with recursive merging for nested objects.
203
+ *
204
+ * @param target The target object to merge into
205
+ * @param sources Source objects to merge from (processed left to right)
206
+ * @returns The merged target object
207
+ * @example
208
+ * ```typescript
209
+ * const result = deepMerge(
210
+ * { a: 1, b: { x: 1 } },
211
+ * { b: { y: 2 } },
212
+ * { c: 3 }
213
+ * );
214
+ * // Result: { a: 1, b: { x: 1, y: 2 }, c: 3 }
215
+ * ```
144
216
  */
145
217
  function deepMerge(target, ...sources) {
146
218
  if (!sources.length)
@@ -161,27 +233,40 @@ function deepMerge(target, ...sources) {
161
233
  return deepMerge(target, ...sources);
162
234
  }
163
235
  /**
164
- * Check if a value is a plain object
236
+ * Type guard to check if a value is a plain object (not array, null, or primitive).
237
+ *
238
+ * @param item The value to check
239
+ * @returns True if the item is a plain object, false otherwise
165
240
  */
166
241
  function isObject(item) {
167
242
  return item && typeof item === 'object' && !Array.isArray(item);
168
243
  }
169
244
  /**
170
- * Generate a secure random challenge as Uint8Array
245
+ * Generates a cryptographically secure random challenge for WebAuthn operations.
246
+ * Uses the Web Crypto API for secure random number generation.
247
+ *
248
+ * @returns A 32-byte Uint8Array containing the random challenge
171
249
  */
172
250
  function generateChallenge$1() {
173
251
  return crypto.getRandomValues(new Uint8Array(32));
174
252
  }
175
253
  /**
176
- * Generate a user ID from username
177
- * Uses TextEncoder to convert username to Uint8Array for consistency
254
+ * Generates a unique user ID based on the username.
255
+ * Creates a consistent, URL-safe identifier for the user.
256
+ *
257
+ * @param username The username to generate an ID from
258
+ * @returns A Uint8Array containing the encoded user ID
178
259
  */
179
260
  function generateUserId$1(username) {
180
261
  return new TextEncoder().encode(username);
181
262
  }
182
263
  /**
183
- * Convert challenge to appropriate format
184
- * Handles both string (base64url) and Uint8Array inputs
264
+ * Processes and normalizes challenge values from various input formats.
265
+ * Handles string (base64url), Uint8Array, or generates a new challenge if none provided.
266
+ *
267
+ * @param challenge Optional challenge as string or Uint8Array
268
+ * @returns Normalized Uint8Array challenge ready for WebAuthn API
269
+ * @throws {Error} When provided string challenge is not valid base64url
185
270
  */
186
271
  function processChallenge(challenge) {
187
272
  if (!challenge) {
@@ -194,8 +279,14 @@ function processChallenge(challenge) {
194
279
  return challenge;
195
280
  }
196
281
  /**
197
- * Convert user ID to appropriate format
198
- * Handles both string (base64url) and Uint8Array inputs
282
+ * Processes and normalizes user ID values from various input formats.
283
+ * Handles string (base64url), Uint8Array, or generates from username if none provided.
284
+ *
285
+ * @param userId Optional user ID as string or Uint8Array
286
+ * @param username Username to generate ID from if userId not provided
287
+ * @returns Normalized Uint8Array user ID ready for WebAuthn API
288
+ * @throws {Error} When no userId provided and no username available for generation
289
+ * @throws {Error} When provided string userId is not valid base64url
199
290
  */
200
291
  function processUserId(userId, username) {
201
292
  if (userId) {
@@ -212,7 +303,12 @@ function processUserId(userId, username) {
212
303
  return crypto.getRandomValues(new Uint8Array(16));
213
304
  }
214
305
  /**
215
- * Convert string credential IDs to PublicKeyCredentialDescriptor format
306
+ * Processes and normalizes credential descriptors from various input formats.
307
+ * Converts string credential IDs to proper PublicKeyCredentialDescriptor objects.
308
+ *
309
+ * @param credentials Optional array of credential IDs (strings) or full descriptors
310
+ * @returns Array of normalized PublicKeyCredentialDescriptor objects, or undefined if none provided
311
+ * @throws {Error} When a credential ID string is not valid base64url
216
312
  */
217
313
  function processCredentialDescriptors(credentials) {
218
314
  if (!credentials || credentials.length === 0) {
@@ -229,7 +325,18 @@ function processCredentialDescriptors(credentials) {
229
325
  }));
230
326
  }
231
327
  /**
232
- * Resolve a preset configuration by name
328
+ * Resolves a preset configuration by name.
329
+ * Returns the complete preset configuration object for the specified preset.
330
+ *
331
+ * @param presetName The name of the preset to resolve
332
+ * @returns The preset configuration object
333
+ * @throws {Error} When the preset name is not found in PRESET_MAP
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * const preset = resolvePreset('passkey');
338
+ * console.log(preset.authenticatorSelection.userVerification); // 'preferred'
339
+ * ```
233
340
  */
234
341
  function resolvePreset(presetName) {
235
342
  const preset = PRESET_MAP[presetName];
@@ -239,53 +346,85 @@ function resolvePreset(presetName) {
239
346
  return preset;
240
347
  }
241
348
  /**
242
- * Build complete PublicKeyCredentialCreationOptions from RegisterConfig
243
- * Now uses WebAuthnConfig for better defaults and relying party information
349
+ * Creates base creation options from WebAuthn service configuration.
350
+ * Establishes default timeout and attestation settings that can be overridden later.
351
+ *
352
+ * @param webAuthnConfig The global WebAuthn service configuration
353
+ * @returns Partial creation options with base settings applied
244
354
  */
245
- function buildCreationOptionsFromConfig(config, webAuthnConfig) {
246
- // Start with base configuration from WebAuthnConfig
247
- let options = {
355
+ function createBaseCreationOptions(webAuthnConfig) {
356
+ return {
248
357
  timeout: webAuthnConfig.defaultTimeout || 60000,
249
358
  attestation: webAuthnConfig.defaultAttestation || 'none',
250
359
  };
251
- // Apply preset if specified
360
+ }
361
+ /**
362
+ * Applies preset configuration to base creation options.
363
+ * Merges preset-specific authenticator selection and public key parameters into the options.
364
+ *
365
+ * @param config The register configuration containing preset information
366
+ * @param baseOptions The base options to apply preset configuration to
367
+ * @param webAuthnConfig The global WebAuthn service configuration
368
+ * @returns Options with preset configuration applied
369
+ */
370
+ function applyPresetConfiguration(config, baseOptions, webAuthnConfig) {
252
371
  if (config.preset) {
253
372
  const preset = resolvePreset(config.preset);
254
- options = deepMerge(options, {
373
+ return deepMerge(baseOptions, {
255
374
  authenticatorSelection: preset.authenticatorSelection,
256
375
  pubKeyCredParams: [...preset.pubKeyCredParams], // Convert readonly to mutable
257
376
  });
258
377
  }
259
- else {
260
- // Apply default authenticator selection from config
261
- if (webAuthnConfig.defaultAuthenticatorSelection) {
262
- options.authenticatorSelection =
263
- webAuthnConfig.defaultAuthenticatorSelection;
264
- }
378
+ // Apply default authenticator selection from config when no preset
379
+ if (webAuthnConfig.defaultAuthenticatorSelection) {
380
+ return {
381
+ ...baseOptions,
382
+ authenticatorSelection: webAuthnConfig.defaultAuthenticatorSelection,
383
+ };
265
384
  }
266
- // Apply user overrides
385
+ return baseOptions;
386
+ }
387
+ /**
388
+ * Applies user-specified overrides to creation options.
389
+ * Allows users to override any preset or default settings with their own values.
390
+ *
391
+ * @param config The register configuration containing user overrides
392
+ * @param options The options to apply user overrides to
393
+ * @returns Options with user overrides applied
394
+ */
395
+ function applyUserOverrides(config, options) {
396
+ const result = { ...options };
267
397
  if (config.timeout !== undefined) {
268
- options.timeout = config.timeout;
398
+ result.timeout = config.timeout;
269
399
  }
270
400
  if (config.attestation !== undefined) {
271
- options.attestation = config.attestation;
401
+ result.attestation = config.attestation;
272
402
  }
273
403
  if (config.authenticatorSelection !== undefined) {
274
- options.authenticatorSelection = deepMerge(options.authenticatorSelection || {}, config.authenticatorSelection);
404
+ result.authenticatorSelection = deepMerge(result.authenticatorSelection || {}, config.authenticatorSelection);
275
405
  }
276
406
  if (config.pubKeyCredParams !== undefined) {
277
- options.pubKeyCredParams = config.pubKeyCredParams;
407
+ result.pubKeyCredParams = config.pubKeyCredParams;
278
408
  }
279
409
  if (config.extensions !== undefined) {
280
- options.extensions = config.extensions;
410
+ result.extensions = config.extensions;
281
411
  }
282
- // Handle required fields
412
+ return result;
413
+ }
414
+ /**
415
+ * Assembles final creation options with all required WebAuthn fields.
416
+ * Processes user information, challenge, and applies final service configuration.
417
+ *
418
+ * @param options The partially built options from previous steps
419
+ * @param config The register configuration containing user and RP information
420
+ * @param webAuthnConfig The global WebAuthn service configuration
421
+ * @returns Complete PublicKeyCredentialCreationOptions ready for WebAuthn API
422
+ */
423
+ function assembleFinalCreationOptions(options, config, webAuthnConfig) {
283
424
  const challenge = processChallenge(config.challenge);
284
425
  const userId = processUserId(config.userId, config.username);
285
- // Use relying party from config, with user override capability
286
426
  const relyingParty = config.rp || webAuthnConfig.relyingParty;
287
- // Build final options
288
- const finalOptions = {
427
+ return {
289
428
  ...options,
290
429
  rp: relyingParty,
291
430
  user: {
@@ -301,11 +440,63 @@ function buildCreationOptionsFromConfig(config, webAuthnConfig) {
301
440
  ],
302
441
  excludeCredentials: processCredentialDescriptors(config.excludeCredentials),
303
442
  };
304
- return finalOptions;
305
443
  }
306
444
  /**
307
- * Build complete PublicKeyCredentialRequestOptions from AuthenticateConfig
308
- * Now uses WebAuthnConfig for better defaults
445
+ * Builds complete WebAuthn creation options from a high-level register configuration.
446
+ *
447
+ * This function orchestrates the creation option building process by:
448
+ * 1. Creating base options from service configuration
449
+ * 2. Applying preset-specific settings if specified
450
+ * 3. Applying user overrides for customization
451
+ * 4. Assembling final options with all required fields
452
+ *
453
+ * @param config The high-level register configuration with preset support
454
+ * @param webAuthnConfig The global WebAuthn service configuration
455
+ * @returns Complete PublicKeyCredentialCreationOptions ready for navigator.credentials.create()
456
+ *
457
+ * @example
458
+ * ```typescript
459
+ * const config: RegisterConfig = {
460
+ * preset: 'passkey',
461
+ * user: {
462
+ * id: 'user123',
463
+ * name: 'user@example.com',
464
+ * displayName: 'John Doe'
465
+ * },
466
+ * challenge: 'custom-challenge'
467
+ * };
468
+ *
469
+ * const options = buildCreationOptionsFromConfig(config, webAuthnConfig);
470
+ * // Returns complete creation options ready for WebAuthn API
471
+ * ```
472
+ */
473
+ function buildCreationOptionsFromConfig(config, webAuthnConfig) {
474
+ const baseOptions = createBaseCreationOptions(webAuthnConfig);
475
+ const presetOptions = applyPresetConfiguration(config, baseOptions, webAuthnConfig);
476
+ const finalOptions = applyUserOverrides(config, presetOptions);
477
+ return assembleFinalCreationOptions(finalOptions, config, webAuthnConfig);
478
+ }
479
+ /**
480
+ * Builds complete WebAuthn request options from a high-level authenticate configuration.
481
+ *
482
+ * Handles preset resolution, user overrides, and proper field processing to create
483
+ * request options suitable for navigator.credentials.get().
484
+ *
485
+ * @param config The high-level authenticate configuration with preset support
486
+ * @param webAuthnConfig The global WebAuthn service configuration
487
+ * @returns Complete PublicKeyCredentialRequestOptions ready for navigator.credentials.get()
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * const config: AuthenticateConfig = {
492
+ * preset: 'passkey',
493
+ * challenge: 'auth-challenge',
494
+ * allowCredentials: ['cred-id-1', 'cred-id-2']
495
+ * };
496
+ *
497
+ * const options = buildRequestOptionsFromConfig(config, webAuthnConfig);
498
+ * // Returns complete request options ready for WebAuthn API
499
+ * ```
309
500
  */
310
501
  function buildRequestOptionsFromConfig(config, webAuthnConfig) {
311
502
  // Start with base configuration from WebAuthnConfig
@@ -344,7 +535,21 @@ function buildRequestOptionsFromConfig(config, webAuthnConfig) {
344
535
  return finalOptions;
345
536
  }
346
537
  /**
347
- * Validate that a RegisterConfig has all required fields
538
+ * Validates a register configuration for completeness and correctness.
539
+ * Ensures all required fields are present and properly formatted.
540
+ *
541
+ * @param config The register configuration to validate
542
+ * @throws {Error} When required fields are missing or invalid
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * try {
547
+ * validateRegisterConfig(config);
548
+ * // Config is valid, proceed with registration
549
+ * } catch (error) {
550
+ * console.error('Invalid register config:', error.message);
551
+ * }
552
+ * ```
348
553
  */
349
554
  function validateRegisterConfig(config) {
350
555
  if (!config.username || typeof config.username !== 'string') {
@@ -355,7 +560,21 @@ function validateRegisterConfig(config) {
355
560
  }
356
561
  }
357
562
  /**
358
- * Validate that an AuthenticateConfig has all required fields
563
+ * Validates an authenticate configuration for completeness and correctness.
564
+ * Ensures all required fields are present and properly formatted.
565
+ *
566
+ * @param config The authenticate configuration to validate
567
+ * @throws {Error} When required fields are missing or invalid
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * try {
572
+ * validateAuthenticateConfig(config);
573
+ * // Config is valid, proceed with authentication
574
+ * } catch (error) {
575
+ * console.error('Invalid authenticate config:', error.message);
576
+ * }
577
+ * ```
359
578
  */
360
579
  function validateAuthenticateConfig(config) {
361
580
  if (config.preset && !PRESET_MAP[config.preset]) {
@@ -367,6 +586,10 @@ function validateAuthenticateConfig(config) {
367
586
  * Enhanced WebAuthn Error Classes
368
587
  * Provides specific, actionable error types for better developer experience
369
588
  */
589
+ /**
590
+ * Enumeration of WebAuthn error types for categorizing different failure scenarios.
591
+ * Provides semantic error classification for better error handling and user experience.
592
+ */
370
593
  var WebAuthnErrorType;
371
594
  (function (WebAuthnErrorType) {
372
595
  WebAuthnErrorType["NOT_SUPPORTED"] = "NOT_SUPPORTED";
@@ -377,24 +600,61 @@ var WebAuthnErrorType;
377
600
  WebAuthnErrorType["NETWORK_ERROR"] = "NETWORK_ERROR";
378
601
  WebAuthnErrorType["SECURITY_ERROR"] = "SECURITY_ERROR";
379
602
  WebAuthnErrorType["TIMEOUT_ERROR"] = "TIMEOUT_ERROR";
603
+ WebAuthnErrorType["REMOTE_ENDPOINT_ERROR"] = "REMOTE_ENDPOINT_ERROR";
604
+ WebAuthnErrorType["INVALID_REMOTE_OPTIONS"] = "INVALID_REMOTE_OPTIONS";
380
605
  WebAuthnErrorType["UNKNOWN"] = "UNKNOWN";
381
606
  })(WebAuthnErrorType || (WebAuthnErrorType = {}));
382
607
  /**
383
- * Base WebAuthn error class with additional context
608
+ * Base WebAuthn error class that provides enhanced error information.
609
+ * All WebAuthn-specific errors extend from this class for consistent error handling.
610
+ *
611
+ * @example
612
+ * ```typescript
613
+ * try {
614
+ * await webAuthnService.register(config);
615
+ * } catch (error) {
616
+ * if (error instanceof WebAuthnError) {
617
+ * console.log('Error type:', error.type);
618
+ * console.log('Original error:', error.originalError);
619
+ * }
620
+ * }
621
+ * ```
384
622
  */
385
623
  class WebAuthnError extends Error {
386
624
  type;
387
625
  originalError;
626
+ /**
627
+ * Creates a new WebAuthnError instance.
628
+ *
629
+ * @param type The semantic error type
630
+ * @param message Human-readable error message
631
+ * @param originalError The original error that caused this WebAuthn error (optional)
632
+ */
388
633
  constructor(type, message, originalError) {
389
634
  super(message);
390
635
  this.type = type;
391
636
  this.originalError = originalError;
392
637
  this.name = 'WebAuthnError';
393
638
  }
639
+ /**
640
+ * Factory method to create WebAuthnError from DOMException.
641
+ * Maps browser DOMException types to semantic WebAuthn error types.
642
+ *
643
+ * @param error The DOMException thrown by WebAuthn API
644
+ * @returns Appropriate WebAuthnError subclass based on the DOMException type
645
+ */
394
646
  static fromDOMException(error) {
395
647
  const type = WebAuthnError.mapDOMExceptionToType(error.name);
396
648
  return new WebAuthnError(type, error.message, error);
397
649
  }
650
+ /**
651
+ * Maps DOMException names to WebAuthn error types.
652
+ * Provides semantic classification of browser-level errors.
653
+ *
654
+ * @param name The DOMException name
655
+ * @returns Corresponding WebAuthnErrorType
656
+ * @private
657
+ */
398
658
  static mapDOMExceptionToType(name) {
399
659
  switch (name) {
400
660
  case 'NotSupportedError':
@@ -417,72 +677,268 @@ class WebAuthnError extends Error {
417
677
  }
418
678
  }
419
679
  /**
420
- * Error thrown when user cancels the WebAuthn operation
680
+ * Error thrown when the user cancels a WebAuthn operation.
681
+ * This is the most common error and typically requires no action from the developer.
682
+ *
683
+ * @example
684
+ * ```typescript
685
+ * try {
686
+ * await webAuthnService.register(config);
687
+ * } catch (error) {
688
+ * if (error instanceof UserCancelledError) {
689
+ * // User chose not to proceed - this is normal behavior
690
+ * console.log('User cancelled the operation');
691
+ * }
692
+ * }
693
+ * ```
421
694
  */
422
695
  class UserCancelledError extends WebAuthnError {
696
+ /**
697
+ * Creates a new UserCancelledError.
698
+ *
699
+ * @param originalError The original DOMException that triggered this error (optional)
700
+ */
423
701
  constructor(originalError) {
424
702
  super(WebAuthnErrorType.USER_CANCELLED, 'User cancelled the WebAuthn operation', originalError);
425
703
  this.name = 'UserCancelledError';
426
704
  }
427
705
  }
428
706
  /**
429
- * Error thrown when there's an issue with the authenticator
707
+ * Error thrown when there's an issue with the authenticator device.
708
+ * This could indicate hardware problems, invalid state, or other device-specific issues.
709
+ *
710
+ * @example
711
+ * ```typescript
712
+ * try {
713
+ * await webAuthnService.authenticate(config);
714
+ * } catch (error) {
715
+ * if (error instanceof AuthenticatorError) {
716
+ * // Show user-friendly message about trying again or using different authenticator
717
+ * console.log('Authenticator issue:', error.message);
718
+ * }
719
+ * }
720
+ * ```
430
721
  */
431
722
  class AuthenticatorError extends WebAuthnError {
723
+ /**
724
+ * Creates a new AuthenticatorError.
725
+ *
726
+ * @param message Descriptive error message
727
+ * @param originalError The original error that caused this authenticator error (optional)
728
+ */
432
729
  constructor(message, originalError) {
433
730
  super(WebAuthnErrorType.AUTHENTICATOR_ERROR, `Authenticator error: ${message}`, originalError);
434
731
  this.name = 'AuthenticatorError';
435
732
  }
436
733
  }
437
734
  /**
438
- * Error thrown when the provided options are invalid
735
+ * Error thrown when the provided WebAuthn options are invalid or malformed.
736
+ * This typically indicates a programming error in option construction.
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * try {
741
+ * await webAuthnService.register(invalidConfig);
742
+ * } catch (error) {
743
+ * if (error instanceof InvalidOptionsError) {
744
+ * // Check your configuration and options
745
+ * console.error('Invalid options provided:', error.message);
746
+ * }
747
+ * }
748
+ * ```
439
749
  */
440
750
  class InvalidOptionsError extends WebAuthnError {
751
+ /**
752
+ * Creates a new InvalidOptionsError.
753
+ *
754
+ * @param message Descriptive error message explaining what's invalid
755
+ * @param originalError The original error that revealed the invalid options (optional)
756
+ */
441
757
  constructor(message, originalError) {
442
758
  super(WebAuthnErrorType.INVALID_OPTIONS, `Invalid options: ${message}`, originalError);
443
759
  this.name = 'InvalidOptionsError';
444
760
  }
445
761
  }
446
762
  /**
447
- * Error thrown when the requested operation is not supported
763
+ * Error thrown when a WebAuthn operation is not supported in the current environment.
764
+ * This could be due to browser limitations or missing hardware capabilities.
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * if (!webAuthnService.isSupported()) {
769
+ * // Handle unsupported environment
770
+ * }
771
+ *
772
+ * try {
773
+ * await webAuthnService.register(config);
774
+ * } catch (error) {
775
+ * if (error instanceof UnsupportedOperationError) {
776
+ * // Show fallback authentication method
777
+ * console.log('WebAuthn not supported, using fallback');
778
+ * }
779
+ * }
780
+ * ```
448
781
  */
449
782
  class UnsupportedOperationError extends WebAuthnError {
783
+ /**
784
+ * Creates a new UnsupportedOperationError.
785
+ *
786
+ * @param message Descriptive error message explaining what's not supported
787
+ * @param originalError The original error that indicated lack of support (optional)
788
+ */
450
789
  constructor(message, originalError) {
451
790
  super(WebAuthnErrorType.UNSUPPORTED_OPERATION, `Unsupported operation: ${message}`, originalError);
452
791
  this.name = 'UnsupportedOperationError';
453
792
  }
454
793
  }
455
794
  /**
456
- * Error thrown when there's a network-related issue
795
+ * Error thrown when there's a network-related issue during WebAuthn operations.
796
+ * This is rare but can occur in certain network conditions.
797
+ *
798
+ * @example
799
+ * ```typescript
800
+ * try {
801
+ * await webAuthnService.authenticate(config);
802
+ * } catch (error) {
803
+ * if (error instanceof NetworkError) {
804
+ * // Suggest user check connection and retry
805
+ * console.log('Network issue during authentication:', error.message);
806
+ * }
807
+ * }
808
+ * ```
457
809
  */
458
810
  class NetworkError extends WebAuthnError {
811
+ /**
812
+ * Creates a new NetworkError.
813
+ *
814
+ * @param message Descriptive error message about the network issue
815
+ * @param originalError The original network-related error (optional)
816
+ */
459
817
  constructor(message, originalError) {
460
818
  super(WebAuthnErrorType.NETWORK_ERROR, `Network error: ${message}`, originalError);
461
819
  this.name = 'NetworkError';
462
820
  }
463
821
  }
464
822
  /**
465
- * Error thrown when there's a security-related issue
823
+ * Error thrown when a security violation occurs during WebAuthn operations.
824
+ * This typically indicates issues with origin validation or other security checks.
825
+ *
826
+ * @example
827
+ * ```typescript
828
+ * try {
829
+ * await webAuthnService.register(config);
830
+ * } catch (error) {
831
+ * if (error instanceof SecurityError) {
832
+ * // Security issue - check origin, HTTPS, etc.
833
+ * console.error('Security violation:', error.message);
834
+ * }
835
+ * }
836
+ * ```
466
837
  */
467
838
  class SecurityError extends WebAuthnError {
839
+ /**
840
+ * Creates a new SecurityError.
841
+ *
842
+ * @param message Descriptive error message about the security issue
843
+ * @param originalError The original security-related error (optional)
844
+ */
468
845
  constructor(message, originalError) {
469
846
  super(WebAuthnErrorType.SECURITY_ERROR, `Security error: ${message}`, originalError);
470
847
  this.name = 'SecurityError';
471
848
  }
472
849
  }
473
850
  /**
474
- * Error thrown when the operation times out
851
+ * Error thrown when a WebAuthn operation times out.
852
+ * This can happen when the user takes too long to interact with their authenticator.
853
+ *
854
+ * @example
855
+ * ```typescript
856
+ * try {
857
+ * await webAuthnService.register(config);
858
+ * } catch (error) {
859
+ * if (error instanceof TimeoutError) {
860
+ * // Suggest user try again and respond more quickly
861
+ * console.log('Operation timed out - please try again');
862
+ * }
863
+ * }
864
+ * ```
475
865
  */
476
866
  class TimeoutError extends WebAuthnError {
867
+ /**
868
+ * Creates a new TimeoutError.
869
+ *
870
+ * @param message Descriptive error message about the timeout
871
+ * @param originalError The original timeout-related error (optional)
872
+ */
477
873
  constructor(message, originalError) {
478
874
  super(WebAuthnErrorType.TIMEOUT_ERROR, `Timeout error: ${message}`, originalError);
479
875
  this.name = 'TimeoutError';
480
876
  }
481
877
  }
878
+ /**
879
+ * Error thrown when remote endpoint request fails.
880
+ * Includes network errors, HTTP errors, and timeout errors.
881
+ *
882
+ * @example
883
+ * ```typescript
884
+ * try {
885
+ * await webAuthnService.registerRemote(request);
886
+ * } catch (error) {
887
+ * if (error instanceof RemoteEndpointError) {
888
+ * console.log('Endpoint:', error.context.url);
889
+ * console.log('Status:', error.context.status);
890
+ * }
891
+ * }
892
+ * ```
893
+ */
894
+ class RemoteEndpointError extends WebAuthnError {
895
+ context;
896
+ /**
897
+ * Creates a new RemoteEndpointError.
898
+ *
899
+ * @param message Descriptive error message about the remote endpoint failure
900
+ * @param context Contextual information about the failed request
901
+ * @param originalError The original error that caused this remote endpoint error (optional)
902
+ */
903
+ constructor(message, context, originalError) {
904
+ super(WebAuthnErrorType.REMOTE_ENDPOINT_ERROR, `Remote endpoint error: ${message}`, originalError);
905
+ this.context = context;
906
+ this.name = 'RemoteEndpointError';
907
+ }
908
+ }
909
+ /**
910
+ * Error thrown when remote server returns invalid WebAuthn options.
911
+ * This indicates the server response doesn't match expected WebAuthn option format.
912
+ *
913
+ * @example
914
+ * ```typescript
915
+ * try {
916
+ * await webAuthnService.registerRemote(request);
917
+ * } catch (error) {
918
+ * if (error instanceof InvalidRemoteOptionsError) {
919
+ * console.log('Invalid server response:', error.message);
920
+ * }
921
+ * }
922
+ * ```
923
+ */
924
+ class InvalidRemoteOptionsError extends WebAuthnError {
925
+ /**
926
+ * Creates a new InvalidRemoteOptionsError.
927
+ *
928
+ * @param message Descriptive error message about the invalid remote options
929
+ * @param originalError The original error that revealed the invalid options (optional)
930
+ */
931
+ constructor(message, originalError) {
932
+ super(WebAuthnErrorType.INVALID_REMOTE_OPTIONS, `Invalid remote options: ${message}`, originalError);
933
+ this.name = 'InvalidRemoteOptionsError';
934
+ }
935
+ }
482
936
 
483
937
  /**
484
- * WebAuthn Configuration
485
- * Provides configuration interfaces and injection token for WebAuthn service
938
+ * Service-level configuration for WebAuthn operations
939
+ *
940
+ * These interfaces define the global configuration that affects the entire WebAuthn service,
941
+ * including relying party information and default settings.
486
942
  */
487
943
  /**
488
944
  * Default configuration for WebAuthn service
@@ -707,6 +1163,202 @@ function isPublicKeyCredential(credential) {
707
1163
  return credential !== null && credential.type === 'public-key';
708
1164
  }
709
1165
 
1166
+ /**
1167
+ * Utility functions for remote WebAuthn option validation
1168
+ *
1169
+ * These utilities provide comprehensive validation of server responses
1170
+ * to ensure they contain valid WebAuthn options before processing.
1171
+ * All validation is done without external dependencies for minimal
1172
+ * bundle size impact.
1173
+ */
1174
+ /**
1175
+ * Validates that server response contains valid WebAuthn creation options.
1176
+ * Performs essential validation without external dependencies.
1177
+ *
1178
+ * @param options Response from registration endpoint
1179
+ * @throws {InvalidRemoteOptionsError} When options are invalid or incomplete
1180
+ *
1181
+ * @example
1182
+ * ```typescript
1183
+ * try {
1184
+ * validateRemoteCreationOptions(serverResponse);
1185
+ * // Options are valid, proceed with registration
1186
+ * } catch (error) {
1187
+ * console.error('Invalid server response:', error.message);
1188
+ * }
1189
+ * ```
1190
+ */
1191
+ function validateRemoteCreationOptions(options) {
1192
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
1193
+ throw new InvalidRemoteOptionsError('Response must be an object');
1194
+ }
1195
+ const opts = options;
1196
+ // Validate relying party (required)
1197
+ if (!opts.rp || typeof opts.rp !== 'object') {
1198
+ throw new InvalidRemoteOptionsError('Missing or invalid rp (relying party) field');
1199
+ }
1200
+ if (typeof opts.rp.name !== 'string' || opts.rp.name.trim() === '') {
1201
+ throw new InvalidRemoteOptionsError('rp.name must be a non-empty string');
1202
+ }
1203
+ if (opts.rp.id !== undefined && typeof opts.rp.id !== 'string') {
1204
+ throw new InvalidRemoteOptionsError('rp.id must be a string when provided');
1205
+ }
1206
+ // Validate user (required)
1207
+ if (!opts.user || typeof opts.user !== 'object') {
1208
+ throw new InvalidRemoteOptionsError('Missing or invalid user field');
1209
+ }
1210
+ if (typeof opts.user.id !== 'string' || opts.user.id.trim() === '') {
1211
+ throw new InvalidRemoteOptionsError('user.id must be a non-empty base64url string');
1212
+ }
1213
+ if (typeof opts.user.name !== 'string' || opts.user.name.trim() === '') {
1214
+ throw new InvalidRemoteOptionsError('user.name must be a non-empty string');
1215
+ }
1216
+ if (typeof opts.user.displayName !== 'string' ||
1217
+ opts.user.displayName.trim() === '') {
1218
+ throw new InvalidRemoteOptionsError('user.displayName must be a non-empty string');
1219
+ }
1220
+ // Validate challenge (required)
1221
+ if (!opts.challenge ||
1222
+ typeof opts.challenge !== 'string' ||
1223
+ opts.challenge.trim() === '') {
1224
+ throw new InvalidRemoteOptionsError('Missing or invalid challenge field - must be a non-empty base64url string');
1225
+ }
1226
+ // Validate pubKeyCredParams (required)
1227
+ if (!Array.isArray(opts.pubKeyCredParams)) {
1228
+ throw new InvalidRemoteOptionsError('pubKeyCredParams must be an array');
1229
+ }
1230
+ if (opts.pubKeyCredParams.length === 0) {
1231
+ throw new InvalidRemoteOptionsError('pubKeyCredParams cannot be empty');
1232
+ }
1233
+ // Validate each pubKeyCredParams entry
1234
+ for (let i = 0; i < opts.pubKeyCredParams.length; i++) {
1235
+ const param = opts.pubKeyCredParams[i];
1236
+ if (!param || typeof param !== 'object') {
1237
+ throw new InvalidRemoteOptionsError(`pubKeyCredParams[${i}] must be an object`);
1238
+ }
1239
+ if (param.type !== 'public-key') {
1240
+ throw new InvalidRemoteOptionsError(`pubKeyCredParams[${i}].type must be "public-key"`);
1241
+ }
1242
+ if (typeof param.alg !== 'number') {
1243
+ throw new InvalidRemoteOptionsError(`pubKeyCredParams[${i}].alg must be a number`);
1244
+ }
1245
+ }
1246
+ // Validate optional fields if present
1247
+ if (opts.timeout !== undefined &&
1248
+ (typeof opts.timeout !== 'number' || opts.timeout <= 0)) {
1249
+ throw new InvalidRemoteOptionsError('timeout must be a positive number when provided');
1250
+ }
1251
+ if (opts.attestation !== undefined &&
1252
+ !['none', 'indirect', 'direct', 'enterprise'].includes(opts.attestation)) {
1253
+ throw new InvalidRemoteOptionsError('attestation must be one of: none, indirect, direct, enterprise');
1254
+ }
1255
+ // Validate authenticatorSelection if present
1256
+ if (opts.authenticatorSelection !== undefined) {
1257
+ if (typeof opts.authenticatorSelection !== 'object') {
1258
+ throw new InvalidRemoteOptionsError('authenticatorSelection must be an object when provided');
1259
+ }
1260
+ const authSel = opts.authenticatorSelection;
1261
+ if (authSel.authenticatorAttachment !== undefined &&
1262
+ !['platform', 'cross-platform'].includes(authSel.authenticatorAttachment)) {
1263
+ throw new InvalidRemoteOptionsError('authenticatorAttachment must be "platform" or "cross-platform"');
1264
+ }
1265
+ if (authSel.userVerification !== undefined &&
1266
+ !['required', 'preferred', 'discouraged'].includes(authSel.userVerification)) {
1267
+ throw new InvalidRemoteOptionsError('userVerification must be "required", "preferred", or "discouraged"');
1268
+ }
1269
+ if (authSel.residentKey !== undefined &&
1270
+ !['discouraged', 'preferred', 'required'].includes(authSel.residentKey)) {
1271
+ throw new InvalidRemoteOptionsError('residentKey must be "discouraged", "preferred", or "required"');
1272
+ }
1273
+ }
1274
+ // Validate excludeCredentials if present
1275
+ if (opts.excludeCredentials !== undefined) {
1276
+ if (!Array.isArray(opts.excludeCredentials)) {
1277
+ throw new InvalidRemoteOptionsError('excludeCredentials must be an array when provided');
1278
+ }
1279
+ for (let i = 0; i < opts.excludeCredentials.length; i++) {
1280
+ const cred = opts.excludeCredentials[i];
1281
+ if (!cred || typeof cred !== 'object') {
1282
+ throw new InvalidRemoteOptionsError(`excludeCredentials[${i}] must be an object`);
1283
+ }
1284
+ if (cred.type !== 'public-key') {
1285
+ throw new InvalidRemoteOptionsError(`excludeCredentials[${i}].type must be "public-key"`);
1286
+ }
1287
+ if (typeof cred.id !== 'string' || cred.id.trim() === '') {
1288
+ throw new InvalidRemoteOptionsError(`excludeCredentials[${i}].id must be a non-empty base64url string`);
1289
+ }
1290
+ }
1291
+ }
1292
+ }
1293
+ /**
1294
+ * Validates that server response contains valid WebAuthn request options.
1295
+ * Performs essential validation without external dependencies.
1296
+ *
1297
+ * @param options Response from authentication endpoint
1298
+ * @throws {InvalidRemoteOptionsError} When options are invalid or incomplete
1299
+ *
1300
+ * @example
1301
+ * ```typescript
1302
+ * try {
1303
+ * validateRemoteRequestOptions(serverResponse);
1304
+ * // Options are valid, proceed with authentication
1305
+ * } catch (error) {
1306
+ * console.error('Invalid server response:', error.message);
1307
+ * }
1308
+ * ```
1309
+ */
1310
+ function validateRemoteRequestOptions(options) {
1311
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
1312
+ throw new InvalidRemoteOptionsError('Response must be an object');
1313
+ }
1314
+ const opts = options;
1315
+ // Validate challenge (required)
1316
+ if (!opts.challenge ||
1317
+ typeof opts.challenge !== 'string' ||
1318
+ opts.challenge.trim() === '') {
1319
+ throw new InvalidRemoteOptionsError('Missing or invalid challenge field - must be a non-empty base64url string');
1320
+ }
1321
+ // Validate optional fields if present
1322
+ if (opts.timeout !== undefined &&
1323
+ (typeof opts.timeout !== 'number' || opts.timeout <= 0)) {
1324
+ throw new InvalidRemoteOptionsError('timeout must be a positive number when provided');
1325
+ }
1326
+ if (opts.userVerification !== undefined &&
1327
+ !['required', 'preferred', 'discouraged'].includes(opts.userVerification)) {
1328
+ throw new InvalidRemoteOptionsError('userVerification must be "required", "preferred", or "discouraged"');
1329
+ }
1330
+ // Validate allowCredentials if present
1331
+ if (opts.allowCredentials !== undefined) {
1332
+ if (!Array.isArray(opts.allowCredentials)) {
1333
+ throw new InvalidRemoteOptionsError('allowCredentials must be an array when provided');
1334
+ }
1335
+ for (let i = 0; i < opts.allowCredentials.length; i++) {
1336
+ const cred = opts.allowCredentials[i];
1337
+ if (!cred || typeof cred !== 'object') {
1338
+ throw new InvalidRemoteOptionsError(`allowCredentials[${i}] must be an object`);
1339
+ }
1340
+ if (cred.type !== 'public-key') {
1341
+ throw new InvalidRemoteOptionsError(`allowCredentials[${i}].type must be "public-key"`);
1342
+ }
1343
+ if (typeof cred.id !== 'string' || cred.id.trim() === '') {
1344
+ throw new InvalidRemoteOptionsError(`allowCredentials[${i}].id must be a non-empty base64url string`);
1345
+ }
1346
+ // Validate transports if present
1347
+ if (cred.transports !== undefined) {
1348
+ if (!Array.isArray(cred.transports)) {
1349
+ throw new InvalidRemoteOptionsError(`allowCredentials[${i}].transports must be an array when provided`);
1350
+ }
1351
+ const validTransports = ['usb', 'nfc', 'ble', 'internal'];
1352
+ for (let j = 0; j < cred.transports.length; j++) {
1353
+ if (!validTransports.includes(cred.transports[j])) {
1354
+ throw new InvalidRemoteOptionsError(`allowCredentials[${i}].transports[${j}] must be one of: ${validTransports.join(', ')}`);
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+
710
1362
  /**
711
1363
  * Enhanced WebAuthn Service
712
1364
  *
@@ -724,14 +1376,35 @@ function isPublicKeyCredential(credential) {
724
1376
  */
725
1377
  class WebAuthnService {
726
1378
  config = inject(WEBAUTHN_CONFIG);
1379
+ http = inject(HttpClient);
727
1380
  /**
728
- * Checks if WebAuthn is supported in the current browser
1381
+ * Checks if WebAuthn is supported in the current browser environment.
1382
+ *
1383
+ * @returns True if WebAuthn is supported, false otherwise
1384
+ * @example
1385
+ * ```typescript
1386
+ * if (this.webAuthnService.isSupported()) {
1387
+ * // Proceed with WebAuthn operations
1388
+ * } else {
1389
+ * // Show fallback authentication method
1390
+ * }
1391
+ * ```
729
1392
  */
730
1393
  isSupported() {
731
1394
  return isWebAuthnSupported();
732
1395
  }
733
1396
  /**
734
- * Gets comprehensive WebAuthn support information
1397
+ * Gets detailed WebAuthn support information for the current browser.
1398
+ * Provides information about available authenticator types and capabilities.
1399
+ *
1400
+ * @returns Observable containing WebAuthn support details
1401
+ * @example
1402
+ * ```typescript
1403
+ * this.webAuthnService.getSupport().subscribe(support => {
1404
+ * console.log('Platform authenticator:', support.platformAuthenticator);
1405
+ * console.log('Cross-platform authenticator:', support.crossPlatformAuthenticator);
1406
+ * });
1407
+ * ```
735
1408
  */
736
1409
  getSupport() {
737
1410
  if (!this.isSupported()) {
@@ -744,40 +1417,57 @@ class WebAuthnService {
744
1417
  })), catchError((error) => throwError(() => new WebAuthnError(WebAuthnErrorType.UNKNOWN, 'Failed to check WebAuthn support', error))));
745
1418
  }
746
1419
  /**
747
- * Registers a new WebAuthn credential with flexible configuration support
1420
+ * Registers a new WebAuthn credential for a user.
748
1421
  *
749
- * @param input Either a high-level RegisterConfig with presets, or direct WebAuthn creation options
750
- * @returns Observable of RegistrationResponse with clean, developer-friendly format
1422
+ * Supports two input formats:
1423
+ * 1. High-level RegisterConfig with preset support and automatic option building
1424
+ * 2. Direct PublicKeyCredentialCreationOptions for full control
751
1425
  *
752
- * @example
753
- * ```typescript
754
- * // Simple preset usage
755
- * this.webAuthnService.register({ username: 'john.doe', preset: 'passkey' });
1426
+ * @param input Either a RegisterConfig object or raw WebAuthn creation options
1427
+ * @returns Observable containing the registration response with credential details
756
1428
  *
757
- * // Preset with overrides
758
- * this.webAuthnService.register({
759
- * username: 'john.doe',
760
- * preset: 'passkey',
761
- * authenticatorSelection: { userVerification: 'required' }
762
- * });
1429
+ * @throws {UnsupportedOperationError} When WebAuthn is not supported
1430
+ * @throws {InvalidOptionsError} When provided options are invalid
1431
+ * @throws {UserCancelledError} When user cancels the registration
1432
+ * @throws {AuthenticatorError} When authenticator encounters an error
1433
+ * @throws {TimeoutError} When the operation times out
1434
+ * @throws {SecurityError} When a security violation occurs
763
1435
  *
764
- * // Direct WebAuthn options (native)
765
- * const nativeOptions: PublicKeyCredentialCreationOptions = {
766
- * challenge: new Uint8Array([...]),
767
- * rp: { name: "My App" },
768
- * user: { id: new Uint8Array([...]), name: "user@example.com", displayName: "User" },
769
- * pubKeyCredParams: [{ type: "public-key", alg: -7 }]
1436
+ * @example Using high-level config:
1437
+ * ```typescript
1438
+ * const config: RegisterConfig = {
1439
+ * preset: 'passkey',
1440
+ * user: {
1441
+ * id: 'user123',
1442
+ * name: 'user@example.com',
1443
+ * displayName: 'John Doe'
1444
+ * },
1445
+ * challenge: 'random-challenge'
770
1446
  * };
771
- * this.webAuthnService.register(nativeOptions);
772
1447
  *
773
- * // Direct WebAuthn options (JSON)
774
- * const jsonOptions: PublicKeyCredentialCreationOptionsJSON = {
775
- * challenge: "Y2hhbGxlbmdl",
776
- * rp: { name: "My App" },
777
- * user: { id: "dXNlcklk", name: "user@example.com", displayName: "User" },
1448
+ * this.webAuthnService.register(config).subscribe({
1449
+ * next: (response) => {
1450
+ * console.log('Registration successful:', response.credential.id);
1451
+ * },
1452
+ * error: (error) => {
1453
+ * if (error instanceof UserCancelledError) {
1454
+ * console.log('User cancelled registration');
1455
+ * }
1456
+ * }
1457
+ * });
1458
+ * ```
1459
+ *
1460
+ * @example Using direct options:
1461
+ * ```typescript
1462
+ * const options: PublicKeyCredentialCreationOptions = {
1463
+ * rp: { name: "Example Corp" },
1464
+ * user: { id: new Uint8Array([1,2,3]), name: "user@example.com", displayName: "User" },
1465
+ * challenge: new Uint8Array([4,5,6]),
778
1466
  * pubKeyCredParams: [{ type: "public-key", alg: -7 }]
779
1467
  * };
780
- * this.webAuthnService.register(jsonOptions);
1468
+ * this.webAuthnService.register(options).subscribe(response => {
1469
+ * // Handle response
1470
+ * });
781
1471
  * ```
782
1472
  */
783
1473
  register(input) {
@@ -803,42 +1493,51 @@ class WebAuthnService {
803
1493
  }
804
1494
  }
805
1495
  /**
806
- * Authenticates using an existing WebAuthn credential with flexible configuration support
1496
+ * Authenticates a user using an existing WebAuthn credential.
807
1497
  *
808
- * @param input Either a high-level AuthenticateConfig with presets, or direct WebAuthn request options
809
- * @returns Observable of AuthenticationResponse with clean, developer-friendly format
1498
+ * Supports two input formats:
1499
+ * 1. High-level AuthenticateConfig with preset support and automatic option building
1500
+ * 2. Direct PublicKeyCredentialRequestOptions for full control
810
1501
  *
811
- * @example
812
- * ```typescript
813
- * // Simple preset usage
814
- * this.webAuthnService.authenticate({ preset: 'passkey' });
1502
+ * @param input Either an AuthenticateConfig object or raw WebAuthn request options
1503
+ * @returns Observable containing the authentication response with assertion details
815
1504
  *
816
- * // Config with credential filtering
817
- * this.webAuthnService.authenticate({
818
- * username: 'john.doe',
819
- * preset: 'secondFactor',
820
- * allowCredentials: ['credential-id-1', 'credential-id-2']
821
- * });
1505
+ * @throws {UnsupportedOperationError} When WebAuthn is not supported
1506
+ * @throws {InvalidOptionsError} When provided options are invalid
1507
+ * @throws {UserCancelledError} When user cancels the authentication
1508
+ * @throws {AuthenticatorError} When authenticator encounters an error
1509
+ * @throws {TimeoutError} When the operation times out
1510
+ * @throws {SecurityError} When a security violation occurs
822
1511
  *
823
- * // Direct WebAuthn options (JSON)
824
- * const jsonOptions: PublicKeyCredentialRequestOptionsJSON = {
825
- * challenge: "Y2hhbGxlbmdl",
826
- * allowCredentials: [{
827
- * type: "public-key",
828
- * id: "Y3JlZElk"
829
- * }]
1512
+ * @example Using high-level config:
1513
+ * ```typescript
1514
+ * const config: AuthenticateConfig = {
1515
+ * preset: 'passkey',
1516
+ * challenge: 'auth-challenge',
1517
+ * allowCredentials: ['credential-id-1', 'credential-id-2']
830
1518
  * };
831
- * this.webAuthnService.authenticate(jsonOptions);
832
1519
  *
833
- * // Direct WebAuthn options (native)
834
- * const nativeOptions: PublicKeyCredentialRequestOptions = {
835
- * challenge: new Uint8Array([...]),
836
- * allowCredentials: [{
837
- * type: "public-key",
838
- * id: new Uint8Array([...])
839
- * }]
1520
+ * this.webAuthnService.authenticate(config).subscribe({
1521
+ * next: (response) => {
1522
+ * console.log('Authentication successful:', response.credential.id);
1523
+ * },
1524
+ * error: (error) => {
1525
+ * if (error instanceof UserCancelledError) {
1526
+ * console.log('User cancelled authentication');
1527
+ * }
1528
+ * }
1529
+ * });
1530
+ * ```
1531
+ *
1532
+ * @example Using direct options:
1533
+ * ```typescript
1534
+ * const options: PublicKeyCredentialRequestOptions = {
1535
+ * challenge: new Uint8Array([1,2,3]),
1536
+ * allowCredentials: [{ type: 'public-key', id: new Uint8Array([4,5,6]) }]
840
1537
  * };
841
- * this.webAuthnService.authenticate(nativeOptions);
1538
+ * this.webAuthnService.authenticate(options).subscribe(response => {
1539
+ * // Handle response
1540
+ * });
842
1541
  * ```
843
1542
  */
844
1543
  authenticate(input) {
@@ -848,12 +1547,10 @@ class WebAuthnService {
848
1547
  try {
849
1548
  let requestOptions;
850
1549
  if (isAuthenticateConfig(input)) {
851
- // High-level config path: validate, resolve preset, build options
852
1550
  validateAuthenticateConfig(input);
853
1551
  requestOptions = buildRequestOptionsFromConfig(input, this.config);
854
1552
  }
855
1553
  else {
856
- // Direct options path: use provided options
857
1554
  requestOptions = input;
858
1555
  }
859
1556
  const parsedOptions = this.parseAuthenticationOptions(requestOptions);
@@ -864,72 +1561,273 @@ class WebAuthnService {
864
1561
  }
865
1562
  }
866
1563
  /**
867
- * Parses registration options, handling both JSON and native formats
1564
+ * Registers a new WebAuthn credential using options fetched from a remote server.
1565
+ *
1566
+ * Sends request data via POST to the configured registration endpoint,
1567
+ * receives PublicKeyCredentialCreationOptionsJSON, then proceeds with
1568
+ * standard WebAuthn registration flow.
1569
+ *
1570
+ * @param request Data to send to server (can be any object your server expects)
1571
+ * @template T Type constraint for the request payload
1572
+ * @returns Observable containing the registration response
1573
+ *
1574
+ * @throws {InvalidOptionsError} When remote registration endpoint is not configured
1575
+ * @throws {RemoteEndpointError} When server request fails
1576
+ * @throws {InvalidRemoteOptionsError} When server returns invalid options
1577
+ *
1578
+ * @example
1579
+ * ```typescript
1580
+ * // Simple request
1581
+ * this.webAuthnService.registerRemote({
1582
+ * username: 'john.doe@example.com'
1583
+ * }).subscribe(response => console.log('Success:', response));
1584
+ *
1585
+ * // Typed request with additional context
1586
+ * interface ServerPayload {
1587
+ * tenantId: string;
1588
+ * department: string;
1589
+ * }
1590
+ *
1591
+ * this.webAuthnService.registerRemote<ServerPayload>({
1592
+ * tenantId: 'acme-corp',
1593
+ * department: 'engineering'
1594
+ * }).subscribe(response => console.log('Success:', response));
1595
+ * ```
1596
+ */
1597
+ registerRemote(request) {
1598
+ this.validateRemoteRegistrationConfig();
1599
+ const endpoint = this.config.remoteEndpoints.registration;
1600
+ const timeoutMs = this.config.remoteEndpoints?.requestOptions?.timeout || 10000;
1601
+ return this.http
1602
+ .post(endpoint, request)
1603
+ .pipe(timeout(timeoutMs), map((options) => {
1604
+ validateRemoteCreationOptions(options);
1605
+ return options;
1606
+ }), switchMap((options) => this.register(options)), catchError((error) => this.handleRemoteError(error, endpoint, 'registration')));
1607
+ }
1608
+ /**
1609
+ * Authenticates using WebAuthn with options fetched from a remote server.
1610
+ *
1611
+ * Sends request data via POST to the configured authentication endpoint,
1612
+ * receives PublicKeyCredentialRequestOptionsJSON, then proceeds with
1613
+ * standard WebAuthn authentication flow.
1614
+ *
1615
+ * @param request Optional data to send to server (defaults to empty object)
1616
+ * @template T Type constraint for the request payload
1617
+ * @returns Observable containing the authentication response
1618
+ *
1619
+ * @throws {InvalidOptionsError} When remote authentication endpoint is not configured
1620
+ * @throws {RemoteEndpointError} When server request fails
1621
+ * @throws {InvalidRemoteOptionsError} When server returns invalid options
1622
+ *
1623
+ * @example
1624
+ * ```typescript
1625
+ * // Simple request
1626
+ * this.webAuthnService.authenticateRemote({
1627
+ * username: 'john.doe@example.com'
1628
+ * }).subscribe(response => console.log('Success:', response));
1629
+ *
1630
+ * // Request with no payload (server uses session/context)
1631
+ * this.webAuthnService.authenticateRemote()
1632
+ * .subscribe(response => console.log('Success:', response));
1633
+ * ```
1634
+ */
1635
+ authenticateRemote(request = {}) {
1636
+ this.validateRemoteAuthenticationConfig();
1637
+ const endpoint = this.config.remoteEndpoints.authentication;
1638
+ const timeoutMs = this.config.remoteEndpoints?.requestOptions?.timeout || 10000;
1639
+ return this.http
1640
+ .post(endpoint, request)
1641
+ .pipe(timeout(timeoutMs), map((options) => {
1642
+ validateRemoteRequestOptions(options);
1643
+ return options;
1644
+ }), switchMap((options) => this.authenticate(options)), catchError((error) => this.handleRemoteError(error, endpoint, 'authentication')));
1645
+ }
1646
+ /**
1647
+ * Validates that remote registration endpoint is configured
1648
+ * @private
1649
+ */
1650
+ validateRemoteRegistrationConfig() {
1651
+ if (!this.config.remoteEndpoints?.registration) {
1652
+ throw new InvalidOptionsError('Remote registration endpoint not configured. Add remoteEndpoints.registration to your WebAuthn config.');
1653
+ }
1654
+ }
1655
+ /**
1656
+ * Validates that remote authentication endpoint is configured
1657
+ * @private
1658
+ */
1659
+ validateRemoteAuthenticationConfig() {
1660
+ if (!this.config.remoteEndpoints?.authentication) {
1661
+ throw new InvalidOptionsError('Remote authentication endpoint not configured. Add remoteEndpoints.authentication to your WebAuthn config.');
1662
+ }
1663
+ }
1664
+ /**
1665
+ * Handles errors from remote HTTP requests
1666
+ * @private
1667
+ */
1668
+ handleRemoteError(error, url, operation) {
1669
+ if (error instanceof HttpErrorResponse) {
1670
+ const context = {
1671
+ url,
1672
+ method: 'POST',
1673
+ operation,
1674
+ status: error.status,
1675
+ statusText: error.statusText,
1676
+ };
1677
+ return throwError(() => new RemoteEndpointError(`${operation} request failed`, context, error));
1678
+ }
1679
+ // Preserve InvalidRemoteOptionsError instances (validation errors)
1680
+ if (error instanceof InvalidRemoteOptionsError) {
1681
+ return throwError(() => error);
1682
+ }
1683
+ return throwError(() => new WebAuthnError(WebAuthnErrorType.UNKNOWN, `Unexpected error during remote ${operation}`, error));
1684
+ }
1685
+ /**
1686
+ * Parses and normalizes registration options from either native or JSON format.
1687
+ * Converts base64url-encoded strings to Uint8Array where necessary.
1688
+ *
1689
+ * @param options Registration options in either native or JSON format
1690
+ * @returns Normalized PublicKeyCredentialCreationOptions for browser API
1691
+ * @private
868
1692
  */
869
1693
  parseRegistrationOptions(options) {
870
1694
  if (isJSONOptions(options)) {
871
- // Use native browser function for JSON options
872
1695
  return PublicKeyCredential.parseCreationOptionsFromJSON(options);
873
1696
  }
874
1697
  else {
875
- // Options are already in native format
876
1698
  return options;
877
1699
  }
878
1700
  }
879
1701
  /**
880
- * Parses authentication options, handling both JSON and native formats
1702
+ * Parses and normalizes authentication options from either native or JSON format.
1703
+ * Converts base64url-encoded strings to Uint8Array where necessary.
1704
+ *
1705
+ * @param options Authentication options in either native or JSON format
1706
+ * @returns Normalized PublicKeyCredentialRequestOptions for browser API
1707
+ * @private
881
1708
  */
882
1709
  parseAuthenticationOptions(options) {
883
1710
  if (isJSONOptions(options)) {
884
- // Use native browser function for JSON options
885
1711
  return PublicKeyCredential.parseRequestOptionsFromJSON(options);
886
1712
  }
887
1713
  else {
888
- // Options are already in native format
889
1714
  return options;
890
1715
  }
891
1716
  }
892
1717
  /**
893
- * Processes the raw credential result into a clean RegistrationResponse
1718
+ * Validates that the credential is a valid PublicKeyCredential.
1719
+ *
1720
+ * @param credential Raw credential from navigator.credentials.create()
1721
+ * @returns Validated PublicKeyCredential
1722
+ * @throws {AuthenticatorError} When credential is invalid or null
1723
+ * @private
894
1724
  */
895
- processRegistrationResult(credential) {
1725
+ validateRegistrationCredential(credential) {
896
1726
  if (!isPublicKeyCredential(credential)) {
897
1727
  throw new AuthenticatorError('No credential returned from authenticator');
898
1728
  }
1729
+ return credential;
1730
+ }
1731
+ /**
1732
+ * Extracts basic credential information (ID and transports).
1733
+ *
1734
+ * @param credential Validated PublicKeyCredential
1735
+ * @returns Object containing credential ID and supported transports
1736
+ * @private
1737
+ */
1738
+ extractCredentialInfo(credential) {
899
1739
  const response = credential.response;
900
- // Extract data using the response methods
901
- const credentialId = arrayBufferToBase64url(credential.rawId);
902
- const transports = (response.getTransports?.() ||
903
- []);
904
- let publicKey;
1740
+ return {
1741
+ credentialId: arrayBufferToBase64url(credential.rawId),
1742
+ transports: (response.getTransports?.() ||
1743
+ []),
1744
+ };
1745
+ }
1746
+ /**
1747
+ * Safely extracts the public key with proper error handling.
1748
+ *
1749
+ * @param response AuthenticatorAttestationResponse
1750
+ * @returns Public key as base64url string, or undefined if extraction fails
1751
+ * @private
1752
+ */
1753
+ extractPublicKey(response) {
905
1754
  try {
906
1755
  const publicKeyBuffer = response.getPublicKey?.();
907
1756
  if (publicKeyBuffer) {
908
- publicKey = arrayBufferToBase64url(publicKeyBuffer);
1757
+ return arrayBufferToBase64url(publicKeyBuffer);
909
1758
  }
910
1759
  }
911
1760
  catch {
912
1761
  // Public key extraction failed - this is okay, not all algorithms are supported
1762
+ // Some authenticators or algorithms don't provide extractable public keys
913
1763
  }
914
- // Create the raw response for backward compatibility
915
- const rawResponse = {
1764
+ return undefined;
1765
+ }
1766
+ /**
1767
+ * Creates the complete raw WebAuthn response for advanced use cases.
1768
+ * Provides access to all WebAuthn data including attestation objects and metadata.
1769
+ *
1770
+ * @param credential Validated PublicKeyCredential
1771
+ * @param credentialId Already extracted credential ID
1772
+ * @param publicKey Extracted public key (may be undefined)
1773
+ * @returns Complete WebAuthnRegistrationResult with all WebAuthn data
1774
+ * @private
1775
+ */
1776
+ createRawRegistrationResponse(credential, credentialId, publicKey) {
1777
+ const response = credential.response;
1778
+ return {
916
1779
  credentialId,
917
- publicKey: publicKey ||
918
- arrayBufferToBase64url(response.getPublicKey?.() || new ArrayBuffer(0)),
1780
+ publicKey: publicKey || arrayBufferToBase64url(new ArrayBuffer(0)), // Clean fallback
919
1781
  attestationObject: arrayBufferToBase64url(response.attestationObject),
920
1782
  clientDataJSON: arrayBufferToBase64url(response.clientDataJSON),
921
- transports: transports,
1783
+ transports: (response.getTransports?.() ||
1784
+ []),
922
1785
  };
1786
+ }
1787
+ /**
1788
+ * Assembles the final registration response.
1789
+ * Combines all extracted data into the final response format.
1790
+ *
1791
+ * @param credentialInfo Basic credential information
1792
+ * @param publicKey Extracted public key
1793
+ * @param rawResponse Raw WebAuthn response
1794
+ * @returns Complete RegistrationResponse
1795
+ * @private
1796
+ */
1797
+ assembleRegistrationResponse(credentialInfo, publicKey, rawResponse) {
923
1798
  return {
924
1799
  success: true,
925
- credentialId,
1800
+ credentialId: credentialInfo.credentialId,
926
1801
  publicKey,
927
- transports,
1802
+ transports: credentialInfo.transports,
928
1803
  rawResponse,
929
1804
  };
930
1805
  }
931
1806
  /**
932
- * Processes the raw credential result into a clean AuthenticationResponse
1807
+ * Processes the result of a WebAuthn registration operation.
1808
+ * Converts the browser credential response into a structured RegistrationResponse.
1809
+ *
1810
+ * @param credential The credential returned by navigator.credentials.create()
1811
+ * @returns Structured registration response with parsed credential data
1812
+ * @throws {AuthenticatorError} When credential creation fails or returns null
1813
+ * @private
1814
+ */
1815
+ processRegistrationResult(credential) {
1816
+ const validCredential = this.validateRegistrationCredential(credential);
1817
+ const credentialInfo = this.extractCredentialInfo(validCredential);
1818
+ const response = validCredential.response;
1819
+ const publicKey = this.extractPublicKey(response);
1820
+ const rawResponse = this.createRawRegistrationResponse(validCredential, credentialInfo.credentialId, publicKey);
1821
+ return this.assembleRegistrationResponse(credentialInfo, publicKey, rawResponse);
1822
+ }
1823
+ /**
1824
+ * Processes the result of a WebAuthn authentication operation.
1825
+ * Converts the browser credential response into a structured AuthenticationResponse.
1826
+ *
1827
+ * @param credential The credential returned by navigator.credentials.get()
1828
+ * @returns Structured authentication response with parsed assertion data
1829
+ * @throws {AuthenticatorError} When authentication fails or returns null
1830
+ * @private
933
1831
  */
934
1832
  processAuthenticationResult(credential) {
935
1833
  if (!isPublicKeyCredential(credential)) {
@@ -941,7 +1839,7 @@ class WebAuthnService {
941
1839
  if (response.userHandle) {
942
1840
  userHandle = arrayBufferToBase64url(response.userHandle);
943
1841
  }
944
- // Create the raw response for backward compatibility
1842
+ // Create the complete raw response for advanced use cases
945
1843
  const rawResponse = {
946
1844
  credentialId,
947
1845
  authenticatorData: arrayBufferToBase64url(response.authenticatorData),
@@ -957,36 +1855,81 @@ class WebAuthnService {
957
1855
  };
958
1856
  }
959
1857
  /**
960
- * Enhanced error handling that maps DOMExceptions to specific error types
1858
+ * Central error handling dispatcher for WebAuthn operations.
1859
+ * Routes different error types to specialized handlers for proper error classification.
1860
+ *
1861
+ * @param error The error to handle (can be DOMException, TypeError, or any other error)
1862
+ * @returns Observable that throws an appropriate WebAuthnError subclass
1863
+ * @private
961
1864
  */
962
1865
  handleWebAuthnError(error) {
963
- // Handle DOMExceptions from WebAuthn API
964
1866
  if (error instanceof DOMException) {
965
- switch (error.name) {
966
- case 'NotAllowedError':
967
- return throwError(() => new UserCancelledError(error));
968
- case 'InvalidStateError':
969
- return throwError(() => new AuthenticatorError('Invalid authenticator state', error));
970
- case 'NotSupportedError':
971
- return throwError(() => new UnsupportedOperationError('Operation not supported', error));
972
- case 'SecurityError':
973
- return throwError(() => new SecurityError('Security error occurred', error));
974
- case 'TimeoutError':
975
- return throwError(() => new TimeoutError('Operation timed out', error));
976
- case 'EncodingError':
977
- return throwError(() => new InvalidOptionsError('Encoding error in options', error));
978
- default:
979
- return throwError(() => new WebAuthnError(WebAuthnErrorType.UNKNOWN, `Unknown WebAuthn error: ${error.message}`, error));
980
- }
1867
+ return this.handleDOMException(error);
981
1868
  }
982
- // Handle JSON parsing errors
983
- if (error instanceof TypeError &&
984
- (error.message.includes('parseCreationOptionsFromJSON') ||
985
- error.message.includes('parseRequestOptionsFromJSON'))) {
986
- return throwError(() => new InvalidOptionsError('Invalid JSON options format', error));
1869
+ if (this.isJSONParsingError(error)) {
1870
+ return this.handleJSONParsingError(error);
987
1871
  }
988
- // Handle other errors
989
- return throwError(() => new WebAuthnError(WebAuthnErrorType.UNKNOWN, `Unexpected error: ${error.message}`, error));
1872
+ return this.handleUnknownError(error);
1873
+ }
1874
+ /**
1875
+ * Handles DOMExceptions from the WebAuthn API using a mapping approach.
1876
+ * Maps specific DOMException names to appropriate WebAuthnError subclasses.
1877
+ *
1878
+ * @param error The DOMException thrown by the WebAuthn API
1879
+ * @returns Observable that throws an appropriate WebAuthnError subclass
1880
+ * @private
1881
+ */
1882
+ handleDOMException(error) {
1883
+ const errorMap = {
1884
+ NotAllowedError: () => new UserCancelledError(error),
1885
+ InvalidStateError: () => new AuthenticatorError('Invalid authenticator state', error),
1886
+ NotSupportedError: () => new UnsupportedOperationError('Operation not supported', error),
1887
+ SecurityError: () => new SecurityError('Security error occurred', error),
1888
+ TimeoutError: () => new TimeoutError('Operation timed out', error),
1889
+ EncodingError: () => new InvalidOptionsError('Encoding error in options', error),
1890
+ };
1891
+ const errorFactory = errorMap[error.name];
1892
+ const webAuthnError = errorFactory
1893
+ ? errorFactory()
1894
+ : new WebAuthnError(WebAuthnErrorType.UNKNOWN, `Unknown WebAuthn error: ${error.message}`, error);
1895
+ return throwError(() => webAuthnError);
1896
+ }
1897
+ /**
1898
+ * Determines if an error is related to JSON parsing issues.
1899
+ * Specifically checks for TypeError messages indicating JSON parsing failures.
1900
+ *
1901
+ * @param error The error to check
1902
+ * @returns True if the error is a JSON parsing error, false otherwise
1903
+ * @private
1904
+ */
1905
+ isJSONParsingError(error) {
1906
+ return (error instanceof TypeError &&
1907
+ (error.message.includes('parseCreationOptionsFromJSON') ||
1908
+ error.message.includes('parseRequestOptionsFromJSON')));
1909
+ }
1910
+ /**
1911
+ * Handles JSON parsing errors specifically.
1912
+ * These errors occur when invalid JSON format options are provided.
1913
+ *
1914
+ * @param error The TypeError from JSON parsing
1915
+ * @returns Observable that throws an InvalidOptionsError
1916
+ * @private
1917
+ */
1918
+ handleJSONParsingError(error) {
1919
+ // At this point we know it's a TypeError from isJSONParsingError check
1920
+ return throwError(() => new InvalidOptionsError('Invalid JSON options format', error));
1921
+ }
1922
+ /**
1923
+ * Handles any unexpected errors that don't fall into other categories.
1924
+ * Provides a fallback for errors that aren't DOMExceptions or JSON parsing errors.
1925
+ *
1926
+ * @param error The unexpected error
1927
+ * @returns Observable that throws a generic WebAuthnError
1928
+ * @private
1929
+ */
1930
+ handleUnknownError(error) {
1931
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
1932
+ return throwError(() => new WebAuthnError(WebAuthnErrorType.UNKNOWN, `Unexpected error: ${message}`, error instanceof Error ? error : new Error(String(error))));
990
1933
  }
991
1934
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: WebAuthnService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
992
1935
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: WebAuthnService, providedIn: 'root' });
@@ -1031,22 +1974,6 @@ function provideWebAuthn(relyingParty, config = {}) {
1031
1974
  WebAuthnService,
1032
1975
  ];
1033
1976
  }
1034
- /**
1035
- * @deprecated Use provideWebAuthn(relyingParty, config) instead.
1036
- * This version is kept for backward compatibility but requires relying party information.
1037
- */
1038
- function provideWebAuthnLegacy(config) {
1039
- if (!config.relyingParty) {
1040
- throw new Error('WebAuthn configuration must include relying party information. Use provideWebAuthn(relyingParty, config) instead.');
1041
- }
1042
- return [
1043
- {
1044
- provide: WEBAUTHN_CONFIG,
1045
- useValue: config,
1046
- },
1047
- WebAuthnService,
1048
- ];
1049
- }
1050
1977
 
1051
1978
  // Core service
1052
1979
 
@@ -1054,5 +1981,5 @@ function provideWebAuthnLegacy(config) {
1054
1981
  * Generated bundle index. Do not edit.
1055
1982
  */
1056
1983
 
1057
- export { AuthenticatorError, DEFAULT_WEBAUTHN_CONFIG, DEVICE_BOUND_PRESET, InvalidOptionsError, NetworkError, PASSKEY_PRESET, PRESET_MAP, SECOND_FACTOR_PRESET, SecurityError, TimeoutError, UnsupportedOperationError, UserCancelledError, WEBAUTHN_CONFIG, WebAuthnError, WebAuthnErrorType, WebAuthnService, arrayBufferToBase64, arrayBufferToBase64url, arrayBufferToCredentialId, arrayBufferToString, base64ToArrayBuffer, base64urlToArrayBuffer, createWebAuthnConfig, credentialIdToArrayBuffer, generateChallenge, generateUserId, getDefaultPubKeyCredParams, getSupportedTransports, isJSONOptions, isPlatformAuthenticatorAvailable, isPublicKeyCredential, isWebAuthnSupported, provideWebAuthn, provideWebAuthnLegacy, stringToArrayBuffer, validateRegistrationOptions };
1984
+ export { AuthenticatorError, DEFAULT_WEBAUTHN_CONFIG, EXTERNAL_SECURITY_KEY_PRESET, InvalidOptionsError, InvalidRemoteOptionsError, NetworkError, PASSKEY_PRESET, PLATFORM_AUTHENTICATOR_PRESET, PRESET_MAP, RemoteEndpointError, SecurityError, TimeoutError, UnsupportedOperationError, UserCancelledError, WEBAUTHN_CONFIG, WebAuthnError, WebAuthnErrorType, WebAuthnService, arrayBufferToBase64, arrayBufferToBase64url, arrayBufferToCredentialId, arrayBufferToString, base64ToArrayBuffer, base64urlToArrayBuffer, createWebAuthnConfig, credentialIdToArrayBuffer, generateChallenge, generateUserId, getDefaultPubKeyCredParams, getSupportedTransports, isAuthenticateConfig, isCreationOptions, isJSONOptions, isPlatformAuthenticatorAvailable, isPublicKeyCredential, isRegisterConfig, isRequestOptions, isWebAuthnSupported, provideWebAuthn, stringToArrayBuffer, validateRegistrationOptions, validateRemoteCreationOptions, validateRemoteRequestOptions };
1058
1985
  //# sourceMappingURL=ngx-webauthn.mjs.map