@explorins/pers-signer 1.0.11 → 1.0.16

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.
@@ -49,7 +49,17 @@ class FetchHttpClient {
49
49
  setTimeout(() => controller.abort(), options.timeout);
50
50
  }
51
51
  const response = await fetch(url, fetchOptions);
52
- const data = await response.json();
52
+ // Check if response is actually JSON before parsing
53
+ const responseText = await response.text();
54
+ let data;
55
+ try {
56
+ data = JSON.parse(responseText);
57
+ }
58
+ catch (parseError) {
59
+ console.error('[HttpClient] JSON parse error:', parseError);
60
+ console.error('[HttpClient] Failed to parse response text:', responseText);
61
+ throw new Error(`Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
62
+ }
53
63
  return {
54
64
  data,
55
65
  status: response.status,
@@ -159,74 +169,31 @@ function getServiceConfig() {
159
169
 
160
170
  /**
161
171
  * Wallet management service
162
- * Handles wallet listing and management operations
172
+ * Updated for v1 API endpoints per migration reference
163
173
  */
164
174
  class WalletService {
165
175
  /**
166
176
  * List all wallets for authenticated user
167
- * @param authToken - Authentication token
168
- * @returns Promise resolving to wallet list
177
+ * @param signerToken - Signer JWT token
178
+ * @returns Promise resolving to wallet data
169
179
  */
170
- static async listWallets(authToken) {
180
+ static async listWallets(signerToken) {
171
181
  const httpClient = getHttpClient();
172
182
  const config = getServiceConfig();
173
183
  try {
174
- const response = await httpClient.post(`${config.apiUrl}/wallets/list`, {
175
- authToken,
176
- appId: config.appId,
177
- });
178
- console.log('[WalletService] Raw API response:', response.data);
179
- return response.data;
184
+ const requestBody = {};
185
+ const response = await httpClient.post(`${config.apiUrl}/wallets/list`, requestBody, { Authorization: `Bearer ${signerToken}` });
186
+ const backendResponse = response.data;
187
+ if (backendResponse.success && backendResponse.data) {
188
+ return backendResponse.data;
189
+ }
190
+ throw new Error('Failed to list wallets: Invalid response');
180
191
  }
181
192
  catch (error) {
182
193
  console.error('[WalletService] Error listing wallets:', error);
183
194
  throw error;
184
195
  }
185
196
  }
186
- /**
187
- * Get wallet by ID (when backend supports it)
188
- * @param authToken - Authentication token
189
- * @param walletId - Wallet identifier
190
- * @returns Promise resolving to wallet details
191
- */
192
- static async getWallet(authToken, walletId) {
193
- const httpClient = getHttpClient();
194
- const config = getServiceConfig();
195
- try {
196
- const response = await httpClient.post(`${config.apiUrl}/wallets/get`, {
197
- authToken,
198
- walletId,
199
- appId: config.appId,
200
- });
201
- return response.data;
202
- }
203
- catch (error) {
204
- console.error(`[WalletService] Error getting wallet ${walletId}:`, error);
205
- throw error;
206
- }
207
- }
208
- /**
209
- * Create new wallet (when backend supports it)
210
- * @param authToken - Authentication token
211
- * @param walletConfig - Wallet configuration
212
- * @returns Promise resolving to wallet creation result
213
- */
214
- static async createWallet(authToken, walletConfig) {
215
- const httpClient = getHttpClient();
216
- const config = getServiceConfig();
217
- try {
218
- const response = await httpClient.post(`${config.apiUrl}/wallets/create`, {
219
- authToken,
220
- ...walletConfig,
221
- appId: config.appId,
222
- });
223
- return response.data;
224
- }
225
- catch (error) {
226
- console.error('[WalletService] Error creating wallet:', error);
227
- throw error;
228
- }
229
- }
230
197
  }
231
198
 
232
199
  /**
@@ -305,10 +272,10 @@ class PersService {
305
272
  * @returns Promise with tenant public information
306
273
  */
307
274
  static async getTenantById(tenantId, authToken) {
308
- // Check memory cache first
275
+ // Check memory cache first with expiration
309
276
  const cached = this.tenantCache.get(tenantId);
310
- if (cached) {
311
- return cached;
277
+ if (cached && Date.now() < cached.expiresAt) {
278
+ return cached.tenant;
312
279
  }
313
280
  try {
314
281
  const headers = {
@@ -346,10 +313,16 @@ class PersService {
346
313
  }
347
314
  }
348
315
  const tenantData = await response.json();
349
- // Cache the tenant data in memory only
350
- this.tenantCache.set(tenantId, tenantData);
316
+ // Cache the tenant data with expiration
317
+ const now = Date.now();
318
+ this.tenantCache.set(tenantId, {
319
+ tenant: tenantData,
320
+ cachedAt: now,
321
+ expiresAt: now + this.TENANT_CACHE_TTL
322
+ });
351
323
  // Update current project key (check multiple possible property names)
352
324
  this.currentProjectKey = tenantData.projectKey || tenantData.projectApiKey || tenantData.apiKey;
325
+ this.currentTenantId = tenantId;
353
326
  return tenantData;
354
327
  }
355
328
  catch (error) {
@@ -369,14 +342,35 @@ class PersService {
369
342
  const projectKey = tenantData.projectApiKey;
370
343
  if (projectKey) {
371
344
  this.currentProjectKey = projectKey;
345
+ this.currentTenantId = tenantId;
372
346
  }
373
347
  return tenantData;
374
348
  }
349
+ /**
350
+ * Ensure tenant is initialized and current, with automatic revalidation
351
+ * @param tenantId - The tenant ID to ensure is initialized
352
+ * @param authToken - Optional auth token for authentication
353
+ * @returns Promise with tenant information
354
+ */
355
+ static async ensureTenantInitialized(tenantId, authToken) {
356
+ // Check if we already have the right tenant initialized and it's still valid
357
+ const cached = this.tenantCache.get(tenantId);
358
+ if (this.currentTenantId === tenantId && cached && Date.now() < cached.expiresAt) {
359
+ return cached.tenant;
360
+ }
361
+ // Initialize or refresh tenant data
362
+ return await this.initializeTenant(tenantId, authToken);
363
+ }
375
364
  /**
376
365
  * Get the current project key (either from tenant or fallback)
366
+ * @param tenantId - Optional tenant ID to ensure is initialized
377
367
  * @returns The project key to use for API calls
378
368
  */
379
- static getProjectKey() {
369
+ static async getProjectKey(tenantId) {
370
+ // If tenantId provided and different from current, initialize it
371
+ if (tenantId && this.currentTenantId !== tenantId) {
372
+ await this.ensureTenantInitialized(tenantId);
373
+ }
380
374
  if (this.currentProjectKey) {
381
375
  return this.currentProjectKey;
382
376
  }
@@ -392,17 +386,19 @@ class PersService {
392
386
  static clearTenantCache() {
393
387
  this.tenantCache.clear();
394
388
  this.currentProjectKey = null;
389
+ this.currentTenantId = null;
395
390
  }
396
391
  /**
397
392
  * Authenticates a user with the PERS backend using their auth token
398
393
  *
399
394
  * @param authToken - The authentication token received from DFNS after login/registration
395
+ * @param tenantId - Optional tenant ID for automatic initialization
400
396
  * @returns A promise that resolves to the authentication response
401
397
  * @throws If the request fails
402
398
  */
403
- static async authenticateUser(authToken) {
399
+ static async authenticateUser(authToken, tenantId) {
404
400
  try {
405
- const projectKey = this.getProjectKey();
401
+ const projectKey = await this.getProjectKey(tenantId);
406
402
  const headers = {
407
403
  'accept': 'application/json',
408
404
  'x-project-key': projectKey,
@@ -452,30 +448,6 @@ class PersService {
452
448
  };
453
449
  }
454
450
  }
455
- /**
456
- * Prepares a transaction by calling the backend endpoint
457
- *
458
- * @param form - The transaction details
459
- * @param persAccessToken - The PERS access token for authentication (Bearer)
460
- * @returns A promise that resolves to the transaction preparation response
461
- * @throws If the request fails
462
- */
463
- static async prepareTransaction(form, persAccessToken) {
464
- // ✅ UPDATED: /transaction/auth/prepare-signing → /transactions/prepare
465
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions`, {
466
- method: "POST",
467
- headers: {
468
- "Content-Type": "application/json",
469
- 'x-project-key': this.getProjectKey(),
470
- Authorization: `Bearer ${persAccessToken}`,
471
- },
472
- body: JSON.stringify(form),
473
- });
474
- if (!res.ok)
475
- throw new Error("Failed to prepare transaction");
476
- const response = await res.json();
477
- return response;
478
- }
479
451
  /**
480
452
  * Submits a transaction by calling the backend endpoint
481
453
  *
@@ -483,208 +455,86 @@ class PersService {
483
455
  * @param signedTransactionOrSignature - The signed transaction data or EIP-712 signature
484
456
  * @param persAccessToken - The PERS access token for authentication (Bearer)
485
457
  * @param submissionType - The transaction format type
458
+ * @param tenantId - Optional tenant ID for automatic initialization
486
459
  * @returns A promise that resolves to the transaction submission response
487
460
  * @throws If the request fails
488
461
  */
489
- static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType) {
490
- // Map TransactionFormat to the backend's expected submission type
491
- const dto = {
492
- transactionId,
493
- type: submissionType,
494
- ...(submissionType === browser.TRANSACTION_FORMATS.EIP_712
495
- ? { signature: signedTransactionOrSignature }
496
- : { signedTransaction: signedTransactionOrSignature })
497
- };
498
- // ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
499
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
500
- method: "POST",
501
- headers: {
502
- "Content-Type": "application/json",
503
- 'x-project-key': this.getProjectKey(),
504
- Authorization: `Bearer ${persAccessToken}`,
505
- },
506
- body: JSON.stringify(dto),
507
- });
508
- if (!res.ok)
509
- throw new Error("Failed to submit transaction");
510
- return await res.json();
511
- }
512
- /**
513
- * Fetches a prepared transaction for signing by transactionId
514
- * @param transactionId - The transaction ID to fetch
515
- * @param persAccessToken - The PERS access token for authentication (Bearer)
516
- * @returns The prepared transaction data
517
- * @throws If the request fails
518
- */
519
- static async fetchPreparedTransaction(transactionId, persAccessToken) {
520
- // ✅ UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
521
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
522
- headers: {
523
- "Content-Type": "application/json",
524
- "x-project-key": this.getProjectKey(),
525
- Authorization: `Bearer ${persAccessToken}`,
526
- },
527
- });
528
- const response = await res.json();
529
- if (!res.ok) {
530
- // Throw structured error with the backend response
531
- throw {
532
- status: res.status,
533
- message: response.message || "Failed to fetch prepared transaction",
534
- error: response
535
- };
536
- }
537
- return response;
538
- }
539
- /**
540
- * Claims a reward from the PERS blockchain system
541
- * @param rewardId - The ID of the reward to claim
542
- * @param pointsCost - The points cost for the reward
543
- * @param persAccessToken - The PERS access token for authentication (Bearer)
544
- * @returns The reward claim response including reward image URL
545
- * @throws If the request fails
546
- */
547
- static async claimReward(rewardId, pointsCost, persAccessToken) {
548
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/rewards/auth/claim`, {
549
- method: "POST",
550
- headers: {
551
- "Content-Type": "application/json",
552
- 'x-project-key': this.getProjectKey(),
553
- Authorization: `Bearer ${persAccessToken}`,
554
- },
555
- body: JSON.stringify({
556
- rewardId,
557
- pointsCost
558
- }),
559
- });
560
- if (!res.ok) {
561
- const error = await res.text();
562
- throw new Error(`Failed to claim reward: ${error}`);
563
- }
564
- return await res.json();
565
- }
566
- /**
567
- * Get all active campaigns
568
- * @returns Promise with list of active campaigns
569
- */
570
- static async getActiveCampaigns() {
571
- try {
572
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign`, {
573
- method: 'GET',
574
- headers: {
575
- 'accept': 'application/json',
576
- 'x-project-key': this.getProjectKey()
577
- }
578
- });
579
- if (!response.ok) {
580
- const errorData = await response.json().catch(() => ({}));
581
- throw {
582
- status: response.status,
583
- message: errorData.message || 'Failed to fetch active campaigns',
584
- error: errorData
585
- };
586
- }
587
- const data = await response.json();
588
- return data;
589
- }
590
- catch (error) {
591
- throw error;
592
- }
593
- }
594
- /**
595
- * Claims a campaign for a user
596
- * @param campaignId - The ID of the campaign to claim
597
- * @param persAccessToken - The PERS access token for authentication (Bearer)
598
- * @returns Promise with the campaign claim response
599
- * @throws If the request fails
600
- */
601
- static async claimCampaign(campaignId, persAccessToken) {
462
+ static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType, tenantId) {
602
463
  try {
603
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign/auth/claim`, {
604
- method: 'POST',
464
+ // Map TransactionFormat to the backend's expected submission type
465
+ const dto = {
466
+ transactionId,
467
+ type: submissionType,
468
+ ...(submissionType === browser.TRANSACTION_FORMATS.EIP_712
469
+ ? { signature: signedTransactionOrSignature }
470
+ : { signedTransaction: signedTransactionOrSignature })
471
+ };
472
+ // ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
473
+ const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
474
+ method: "POST",
605
475
  headers: {
606
- 'accept': 'application/json',
607
- 'x-project-key': this.getProjectKey(),
608
- 'Authorization': `Bearer ${persAccessToken}`,
609
- 'Content-Type': 'application/json'
476
+ "Content-Type": "application/json",
477
+ 'x-project-key': await this.getProjectKey(tenantId),
478
+ Authorization: `Bearer ${persAccessToken}`,
610
479
  },
611
- body: JSON.stringify({
612
- campaignId
613
- })
480
+ body: JSON.stringify(dto),
614
481
  });
615
- if (!response.ok) {
616
- const errorData = await response.json().catch(() => ({}));
482
+ const response = await res.json();
483
+ if (!res.ok) {
484
+ // Throw structured error with the backend response
617
485
  throw {
618
- status: response.status,
619
- message: errorData.message || 'Failed to claim campaign',
620
- error: errorData
486
+ status: res.status,
487
+ message: response.message || "Failed to submit transaction",
488
+ error: response
621
489
  };
622
490
  }
623
- const data = await response.json();
624
- return data;
491
+ return response;
625
492
  }
626
493
  catch (error) {
627
- throw error;
494
+ // Rethrow a structured error
495
+ throw {
496
+ status: error.status || 500,
497
+ message: error.message || 'Failed to submit transaction to PERS backend',
498
+ error
499
+ };
628
500
  }
629
501
  }
630
502
  /**
631
- * Gets all campaign claims for the authenticated user
503
+ * Fetches a prepared transaction for signing by transactionId
504
+ * @param transactionId - The transaction ID to fetch
632
505
  * @param persAccessToken - The PERS access token for authentication (Bearer)
633
- * @returns Promise with list of user's campaign claims
634
- * @throws If the request fails
635
- */
636
- static async getUserCampaignClaims(persAccessToken) {
637
- try {
638
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign/auth/claim`, {
639
- method: 'GET',
640
- headers: {
641
- 'accept': 'application/json',
642
- 'x-project-key': this.getProjectKey(),
643
- 'Authorization': `Bearer ${persAccessToken}`
644
- }
645
- });
646
- if (!response.ok) {
647
- const errorData = await response.json().catch(() => ({}));
648
- throw {
649
- status: response.status,
650
- message: errorData.message || 'Failed to fetch user campaign claims',
651
- error: errorData
652
- };
653
- }
654
- const data = await response.json();
655
- return data;
656
- }
657
- catch (error) {
658
- throw error;
659
- }
660
- }
661
- /**
662
- * Gets all available rewards for redemption
663
- * @returns Promise with list of available rewards that can be exchanged for points
506
+ * @param tenantId - Optional tenant ID for automatic initialization
507
+ * @returns The prepared transaction data
664
508
  * @throws If the request fails
665
509
  */
666
- static async getAvailableRedemptions() {
510
+ static async fetchPreparedTransaction(transactionId, persAccessToken, tenantId) {
667
511
  try {
668
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/redemption`, {
669
- method: 'GET',
512
+ // UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
513
+ const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
670
514
  headers: {
671
- 'accept': 'application/json',
672
- 'x-project-key': this.getProjectKey()
673
- }
515
+ "Content-Type": "application/json",
516
+ "x-project-key": await this.getProjectKey(tenantId),
517
+ Authorization: `Bearer ${persAccessToken}`,
518
+ },
674
519
  });
675
- if (!response.ok) {
676
- const errorData = await response.json().catch(() => ({}));
520
+ const response = await res.json();
521
+ if (!res.ok) {
522
+ // Throw structured error with the backend response
677
523
  throw {
678
- status: response.status,
679
- message: errorData.message || 'Failed to fetch available redemptions',
680
- error: errorData
524
+ status: res.status,
525
+ message: response.message || "Failed to fetch prepared transaction",
526
+ error: response
681
527
  };
682
528
  }
683
- const data = await response.json();
684
- return data;
529
+ return response;
685
530
  }
686
531
  catch (error) {
687
- throw error;
532
+ // Rethrow a structured error
533
+ throw {
534
+ status: error.status || 500,
535
+ message: error.message || 'Failed to fetch prepared transaction from PERS backend',
536
+ error
537
+ };
688
538
  }
689
539
  }
690
540
  /**
@@ -702,7 +552,7 @@ class PersService {
702
552
  * @throws If transaction data is not available
703
553
  */
704
554
  static getTransactionDataForSigning(transactionResponse) {
705
- if (!this.isTransactionReadyForSigning(transactionResponse)) {
555
+ if (!transactionResponse.signingData) {
706
556
  throw new Error('Transaction data is not ready for signing');
707
557
  }
708
558
  return transactionResponse.signingData;
@@ -711,8 +561,53 @@ class PersService {
711
561
  PersService.config = DEFAULT_PERS_CONFIG;
712
562
  PersService.tenantCache = new Map();
713
563
  PersService.currentProjectKey = null;
564
+ PersService.currentTenantId = null;
714
565
  PersService.useStaging = false;
566
+ PersService.TENANT_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours - tenant configs are essentially static
715
567
 
568
+ // Adapter function to convert backend signature response to legacy format
569
+ const adaptSignatureResponse = (newResponse) => {
570
+ if (!newResponse.signature) {
571
+ console.error('[KeyWallet] No signature in response:', newResponse);
572
+ throw new Error('Signature missing from response');
573
+ }
574
+ // Handle structured signature format (typically from EIP-712 signing)
575
+ if (typeof newResponse.signature === 'object' && 'r' in newResponse.signature) {
576
+ const { r, s, recid } = newResponse.signature;
577
+ return {
578
+ status: 'Signed',
579
+ signature: {
580
+ r,
581
+ s,
582
+ recid // Use recid directly from backend
583
+ }
584
+ };
585
+ }
586
+ // Handle string signature format (potentially from hash signing)
587
+ if (typeof newResponse.signature === 'string') {
588
+ // Parse string signature into components
589
+ // Assuming it's a hex string that can be split into r, s, v components
590
+ const signature = newResponse.signature;
591
+ if (signature.length === 132) { // 0x + 32 bytes r + 32 bytes s + 1 byte v
592
+ const r = '0x' + signature.slice(2, 66);
593
+ const s = '0x' + signature.slice(66, 130);
594
+ const v = parseInt(signature.slice(130, 132), 16);
595
+ const recid = v >= 27 ? v - 27 : v; // Convert v to recid
596
+ return {
597
+ status: 'Signed',
598
+ signature: {
599
+ r,
600
+ s,
601
+ recid
602
+ }
603
+ };
604
+ }
605
+ console.error('[KeyWallet] Invalid string signature length:', signature.length);
606
+ throw new Error(`Invalid string signature format - expected 132 characters, got ${signature.length}`);
607
+ }
608
+ console.error('[KeyWallet] Invalid signature format - expected structured object or string:', typeof newResponse.signature);
609
+ throw new Error('Invalid signature format in response - expected structured object or string from backend');
610
+ };
716
611
  let DfnsError$1 = class DfnsError extends Error {
717
612
  constructor(code, message, details) {
718
613
  super(message);
@@ -731,14 +626,16 @@ const assertSigned = (res) => {
731
626
  };
732
627
  const combineSignature = (res) => {
733
628
  if (!res.signature) {
629
+ console.error('[KeyWallet] No signature in response for combining:', res);
734
630
  throw new DfnsError$1(-1, "signature missing", res);
735
631
  }
736
632
  const { r, s, recid } = res.signature;
737
- return ethers.Signature.from({
633
+ const ethersSignature = ethers.Signature.from({
738
634
  r,
739
635
  s,
740
636
  v: recid ? 0x1c : 0x1b, // Assuming 0x1c for chain_id > 0, 0x1b otherwise. DFNS usually provides recid.
741
- }).serialized;
637
+ });
638
+ return ethersSignature.serialized;
742
639
  };
743
640
  class KeyWallet extends ethers.AbstractSigner {
744
641
  constructor(options, provider) {
@@ -764,9 +661,11 @@ class KeyWallet extends ethers.AbstractSigner {
764
661
  return this.address;
765
662
  }
766
663
  async signHash(hash) {
767
- const res = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
664
+ const rawRes = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
665
+ const res = adaptSignatureResponse(rawRes);
768
666
  assertSigned(res);
769
- return combineSignature(res);
667
+ const combinedSignature = combineSignature(res);
668
+ return combinedSignature;
770
669
  }
771
670
  async signTransaction(tx) {
772
671
  // Resolve any addresses
@@ -803,9 +702,11 @@ class KeyWallet extends ethers.AbstractSigner {
803
702
  async signTypedData(domain, types, value) {
804
703
  try {
805
704
  // Use DFNS native EIP-712 support with correct structure
806
- const res = await this.signingService.signTypedData(this.authToken, this.metadata.id, domain, types, value);
705
+ const rawRes = await this.signingService.signTypedData(this.authToken, this.metadata.id, domain, types, value);
706
+ const res = adaptSignatureResponse(rawRes);
807
707
  assertSigned(res);
808
- return combineSignature(res);
708
+ const combinedSignature = combineSignature(res);
709
+ return combinedSignature;
809
710
  }
810
711
  catch (error) {
811
712
  console.error('[KeyWallet] EIP-712 signing failed:', error);
@@ -935,27 +836,36 @@ class SigningService {
935
836
  const config = getServiceConfig();
936
837
  try {
937
838
  // Step 1: Initialize signing and get challenge
938
- const initResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/init`, {
939
- authToken,
839
+ const initRequest = {
940
840
  walletId,
941
- request,
942
- appId: config.appId,
943
- });
944
- const { requestBody, challenge } = initResponse.data;
841
+ request
842
+ };
843
+ const initResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/init`, initRequest, { Authorization: `Bearer ${authToken}` });
844
+ const backendInitResponse = initResponse.data;
845
+ if (!backendInitResponse.success || !backendInitResponse.data) {
846
+ console.error('[SigningService] Init response invalid:', backendInitResponse);
847
+ throw new Error('Signature initialization failed');
848
+ }
849
+ const { requestBody, challenge } = backendInitResponse.data;
945
850
  // Step 2: Sign the challenge using WebAuthn
946
851
  const assertion = await this.webAuthnProvider.sign(challenge);
947
852
  // Step 3: Complete signing with signed challenge
948
- const completeResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/complete`, {
949
- authToken,
853
+ const completeRequest = {
950
854
  walletId,
951
855
  requestBody,
952
856
  signedChallenge: {
953
- challengeIdentifier: challenge.challengeIdentifier,
857
+ challengeIdentifier: challenge?.challengeIdentifier,
954
858
  firstFactor: assertion,
955
- },
956
- appId: config.appId,
957
- });
958
- return completeResponse.data;
859
+ }
860
+ };
861
+ const completeResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/complete`, completeRequest, { Authorization: `Bearer ${authToken}` });
862
+ const backendCompleteResponse = completeResponse.data;
863
+ if (!backendCompleteResponse.success || !backendCompleteResponse.data) {
864
+ console.error('[SigningService] Complete response invalid:', backendCompleteResponse);
865
+ throw new Error('Signature completion failed');
866
+ }
867
+ // Return the complete signature data from backend response
868
+ return backendCompleteResponse.data;
959
869
  }
960
870
  catch (error) {
961
871
  console.error(`[SigningService] Signing failed for wallet ${walletId}:`, error);
@@ -1152,8 +1062,8 @@ class TransactionValidator {
1152
1062
  if (!params.authTokens) {
1153
1063
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Authentication tokens are required', params.transactionId);
1154
1064
  }
1155
- if (!params.authTokens.backendAuthToken || !params.authTokens.persAccessToken) {
1156
- throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both backend and PERS authentication tokens are required', params.transactionId);
1065
+ if (!params.authTokens.signerAuthToken || !params.authTokens.persAccessToken) {
1066
+ throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both signer and PERS authentication tokens are required', params.transactionId);
1157
1067
  }
1158
1068
  if (!params.ethersProviderUrl || typeof params.ethersProviderUrl !== 'string') {
1159
1069
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Ethers provider URL is required', params.transactionId);
@@ -1165,81 +1075,18 @@ class TransactionValidator {
1165
1075
  * @returns true if valid, throws error if invalid
1166
1076
  */
1167
1077
  static validateAuthTokens(authTokens) {
1168
- if (!authTokens.persAccessToken || !authTokens.backendAuthToken) {
1169
- throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and backend auth token are required');
1078
+ if (!authTokens.persAccessToken || !authTokens.signerAuthToken) {
1079
+ throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and signer auth token are required');
1170
1080
  }
1171
1081
  return true;
1172
1082
  }
1173
1083
  }
1174
1084
 
1175
- /**
1176
- * Handles transaction submission, success flows, and redirect logic
1177
- */
1178
- class TransactionSubmissionHandler {
1179
- /**
1180
- * Create redirect URL with transaction parameters
1181
- * @param returnUrl - Base return URL
1182
- * @param transactionHash - Transaction hash to include
1183
- * @param metadata - Optional metadata to include as parameters
1184
- * @returns Promise resolving to the complete redirect URL
1185
- */
1186
- static async createRedirectUrl(returnUrl, transactionHash, metadata) {
1187
- const additionalParams = {
1188
- txHash: transactionHash,
1189
- success: 'true'
1190
- };
1191
- // Add metadata if available
1192
- if (metadata) {
1193
- Object.entries(metadata).forEach(([key, value]) => {
1194
- // metadata is already constrained to string values by TransactionMetadata interface
1195
- additionalParams[key] = value;
1196
- });
1197
- }
1198
- // Dynamic import to avoid build issues
1199
- const { createUrlWithSearchParams } = await Promise.resolve().then(function () { return searchParams; });
1200
- return createUrlWithSearchParams(returnUrl, ['transactionId', 'returnUrl', 'jwt', 'token'], additionalParams);
1201
- }
1202
- /**
1203
- * Submit transaction and handle success flow
1204
- * @param preparedTransaction - The prepared transaction
1205
- * @param signature - The transaction signature
1206
- * @param signingData - The signing data used
1207
- * @param authTokens - Authentication tokens
1208
- * @param transactionId - Transaction ID for tracking
1209
- * @param returnUrl - Optional return URL for redirect
1210
- * @param metadata - Optional metadata for redirect parameters
1211
- * @returns Promise resolving to submission result
1212
- */
1213
- static async handleTransactionSubmission(preparedTransaction, signature, signingData, authTokens, returnUrl, metadata) {
1214
- // Submit signed transaction
1215
- const submitResult = await PersService.submitTransaction(preparedTransaction.transaction.id, signature, authTokens.persAccessToken, signingData.format);
1216
- // Get transaction hash for return URL or response
1217
- const transactionHash = submitResult.transaction?.transactionHash || '';
1218
- // Handle success - check for return URL
1219
- if (returnUrl) {
1220
- const redirectUrl = await this.createRedirectUrl(returnUrl, transactionHash, metadata);
1221
- return { submitResult, shouldRedirect: true, redirectUrl };
1222
- }
1223
- else {
1224
- return { submitResult, shouldRedirect: false };
1225
- }
1226
- }
1227
- }
1228
-
1229
1085
  /**
1230
1086
  * Utility class for coordinating WebAuthn operations to prevent conflicts
1231
1087
  * Manages global state flags to ensure only one WebAuthn operation runs at a time
1232
1088
  */
1233
1089
  class WebAuthnCoordinator {
1234
- /**
1235
- * Clear the landing authentication flag
1236
- * Used when starting a new transaction signing flow
1237
- */
1238
- static clearLandingAuthentication() {
1239
- if (typeof window !== 'undefined') {
1240
- window.landingAuthenticationInProgress = false;
1241
- }
1242
- }
1243
1090
  /**
1244
1091
  * Check if a WebAuthn operation is currently in progress
1245
1092
  * @returns True if an operation is in progress, false otherwise
@@ -1272,16 +1119,16 @@ const SIGNABLE_STATUSES = [browser.TransactionStatus.PENDING_SIGNATURE, browser.
1272
1119
  * Uses constructor-based dependency injection for WebAuthn provider
1273
1120
  */
1274
1121
  class TransactionSigningService {
1275
- constructor(webAuthnProvider) {
1276
- this.webAuthnProvider = webAuthnProvider;
1122
+ constructor(config) {
1123
+ this.webAuthnProvider = config.webAuthnProvider;
1277
1124
  }
1278
1125
  /**
1279
1126
  * Prepare transaction for signing - fetch and validate
1280
1127
  */
1281
- async prepareTransaction(transactionId, authTokens) {
1128
+ async prepareTransaction(transactionId, authTokens, tenantId) {
1282
1129
  let preparedTransaction;
1283
1130
  try {
1284
- preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken);
1131
+ preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken, tenantId);
1285
1132
  }
1286
1133
  catch (err) {
1287
1134
  // Use TransactionErrorHandler to process PERS API errors
@@ -1302,7 +1149,7 @@ class TransactionSigningService {
1302
1149
  // Authenticate with PERS using backend signer token to set up signing account
1303
1150
  let updatedPersAccessToken = authTokens.persAccessToken;
1304
1151
  try {
1305
- const persSignerAuth = await PersService.authenticateUser(authTokens.backendAuthToken);
1152
+ const persSignerAuth = await PersService.authenticateUser(authTokens.signerAuthToken, tenantId);
1306
1153
  // Update PERS access token with the new one that has signing account linked
1307
1154
  const newPersAccessToken = persSignerAuth.accessToken;
1308
1155
  if (newPersAccessToken) {
@@ -1316,7 +1163,7 @@ class TransactionSigningService {
1316
1163
  }
1317
1164
  // If transaction is 'created' status but doesn't have signingData, fetch again with wallet-enabled token
1318
1165
  if (transactionStatus === browser.TransactionStatus.CREATED && !PersService.isTransactionReadyForSigning(preparedTransaction)) {
1319
- preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken);
1166
+ preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken, tenantId);
1320
1167
  }
1321
1168
  if (!PersService.isTransactionReadyForSigning(preparedTransaction)) {
1322
1169
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.TRANSACTION_NOT_READY, 'Transaction is not ready for signing', transactionId);
@@ -1331,19 +1178,21 @@ class TransactionSigningService {
1331
1178
  */
1332
1179
  async prepareWallet(authTokens, ethersProviderUrl) {
1333
1180
  // Wallet validation will be handled through API responses - no localStorage needed
1334
- let walletData;
1181
+ let walletListResult;
1335
1182
  try {
1336
- walletData = await WalletService.listWallets(authTokens.backendAuthToken);
1337
- console.log('[TransactionSigningService] Wallet list response:', walletData);
1183
+ // Use new WalletService API with signer token
1184
+ walletListResult = await WalletService.listWallets(authTokens.signerAuthToken);
1338
1185
  }
1339
1186
  catch (error) {
1340
1187
  console.error('[TransactionSigningService] Wallet list API failed:', error);
1341
1188
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'Failed to retrieve wallet information. Please refresh the page and try again.', undefined, error);
1342
1189
  }
1343
- // Check both possible response structures for compatibility
1344
- const wallets = walletData?.wallets || walletData?.items || [];
1190
+ // Handle multiple response formats for compatibility
1191
+ const wallets = walletListResult?.items ||
1192
+ walletListResult?.data?.wallets ||
1193
+ walletListResult?.wallets || [];
1345
1194
  if (!wallets?.length) {
1346
- console.error('[TransactionSigningService] No wallets found in response:', walletData);
1195
+ console.error('[TransactionSigningService] No wallets found in response:', walletListResult);
1347
1196
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'No wallet found for transaction signing. Please refresh the page and complete account setup.');
1348
1197
  }
1349
1198
  // Create SigningService with injected WebAuthn provider
@@ -1351,7 +1200,7 @@ class TransactionSigningService {
1351
1200
  // Create wallet instance with provider - use unknown type for flexibility
1352
1201
  const provider = new ethers.JsonRpcProvider(ethersProviderUrl);
1353
1202
  const wallet = new KeyWallet({
1354
- authToken: authTokens.backendAuthToken,
1203
+ authToken: authTokens.signerAuthToken,
1355
1204
  wallet: wallets[0],
1356
1205
  signingService: signingService,
1357
1206
  }).connect(provider);
@@ -1360,7 +1209,9 @@ class TransactionSigningService {
1360
1209
  /**
1361
1210
  * Execute the transaction signing with WebAuthn coordination
1362
1211
  */
1363
- async executeTransactionSigning(wallet, preparedTransaction) {
1212
+ async executeTransactionSigning(wallet,
1213
+ // preparedTransaction: TransactionRequestResponseDTO,
1214
+ signingData) {
1364
1215
  // Check for concurrent WebAuthn operations
1365
1216
  if (WebAuthnCoordinator.checkConcurrentOperations()) {
1366
1217
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WEBAUTHN_OPERATION_IN_PROGRESS, 'Another WebAuthn operation is in progress. Please wait and try again.');
@@ -1368,7 +1219,7 @@ class TransactionSigningService {
1368
1219
  // Set WebAuthn operation flag for transaction signing
1369
1220
  WebAuthnCoordinator.setOperationInProgress(true);
1370
1221
  try {
1371
- const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1222
+ // const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1372
1223
  const signature = await wallet.signPersTransaction(signingData);
1373
1224
  return signature;
1374
1225
  }
@@ -1387,6 +1238,13 @@ class TransactionSigningService {
1387
1238
  WebAuthnCoordinator.setOperationInProgress(false);
1388
1239
  }
1389
1240
  }
1241
+ async getPersSigningData(data) {
1242
+ // Step 1: Prepare transaction for signing
1243
+ const { preparedTransaction } = await this.prepareTransaction(data.transactionId, data.authTokens, data.tenantId);
1244
+ // const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1245
+ const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1246
+ return signingData;
1247
+ }
1390
1248
  /**
1391
1249
  * Main transaction signing orchestration method
1392
1250
  * Handles the complete flow from preparation to submission
@@ -1394,31 +1252,23 @@ class TransactionSigningService {
1394
1252
  * @returns Promise resolving to transaction signing result
1395
1253
  * @throws TransactionSigningError for validation and operation failures
1396
1254
  */
1397
- async signTransaction(params) {
1255
+ async signTransaction(params, signingData) {
1398
1256
  // Validate input parameters first using TransactionValidator
1399
1257
  TransactionValidator.validateSigningParams(params);
1400
- const { transactionId, authTokens, ethersProviderUrl, returnUrl, metadata } = params;
1258
+ const { transactionId, authTokens, ethersProviderUrl } = params;
1401
1259
  console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
1402
1260
  try {
1403
- // Clear previous landing authentication state
1404
- WebAuthnCoordinator.clearLandingAuthentication();
1405
- // Step 1: Prepare transaction for signing
1406
- const { preparedTransaction, updatedTokens } = await this.prepareTransaction(transactionId, authTokens);
1407
1261
  // Step 2: Prepare wallet for signing
1408
- const wallet = await this.prepareWallet(updatedTokens, ethersProviderUrl);
1262
+ const wallet = await this.prepareWallet(authTokens, ethersProviderUrl);
1409
1263
  // Step 3: Execute transaction signing
1410
- const signature = await this.executeTransactionSigning(wallet, preparedTransaction);
1411
- const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1412
- // Step 4: Submit transaction and handle success using TransactionSubmissionHandler
1413
- const { submitResult, shouldRedirect, redirectUrl } = await TransactionSubmissionHandler.handleTransactionSubmission(preparedTransaction, signature, signingData, updatedTokens, returnUrl, metadata);
1414
- console.info(`[TransactionSigningService] Completed successfully: ${transactionId}`);
1264
+ const signature = await this.executeTransactionSigning(wallet, signingData);
1265
+ console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
1415
1266
  return {
1416
1267
  success: true,
1417
1268
  transactionId,
1418
- transactionHash: submitResult.transaction?.transactionHash ?? undefined,
1269
+ signingData,
1270
+ // transactionHash: submitResult.transaction?.transactionHash ?? undefined,
1419
1271
  signature,
1420
- shouldRedirect,
1421
- redirectUrl
1422
1272
  };
1423
1273
  }
1424
1274
  catch (error) {
@@ -1444,710 +1294,727 @@ class TransactionSigningService {
1444
1294
  }
1445
1295
  }
1446
1296
 
1297
+ /**
1298
+ * Handles transaction submission, success flows, and redirect logic
1299
+ */
1300
+ class TransactionSubmissionHandler {
1301
+ /**
1302
+ * Create redirect URL with transaction parameters
1303
+ * @param returnUrl - Base return URL
1304
+ * @param transactionHash - Transaction hash to include
1305
+ * @param metadata - Optional metadata to include as parameters
1306
+ * @returns Promise resolving to the complete redirect URL
1307
+ */
1308
+ static async createRedirectUrl(returnUrl, transactionHash) {
1309
+ const additionalParams = {
1310
+ txHash: transactionHash,
1311
+ success: 'true'
1312
+ };
1313
+ // Build URL with required params
1314
+ const paramKeys = ['transactionId', 'returnUrl', 'jwt', 'token'];
1315
+ const url = new URL(returnUrl, window.location.origin);
1316
+ // Add required params if present in additionalParams
1317
+ paramKeys.forEach(key => {
1318
+ if (additionalParams[key]) {
1319
+ url.searchParams.set(key, additionalParams[key]);
1320
+ }
1321
+ });
1322
+ // Add all other params
1323
+ Object.entries(additionalParams).forEach(([key, value]) => {
1324
+ if (!paramKeys.includes(key)) {
1325
+ url.searchParams.set(key, value);
1326
+ }
1327
+ });
1328
+ return url.toString();
1329
+ }
1330
+ }
1331
+
1447
1332
  /**
1448
1333
  * Authentication service for user login and registration
1449
1334
  * Uses constructor-based dependency injection for WebAuthn provider
1335
+ * Updated for new v1 API endpoints
1450
1336
  */
1451
1337
  class AuthenticationService {
1452
- constructor(webAuthnProvider) {
1453
- this.webAuthnProvider = webAuthnProvider;
1338
+ constructor(config) {
1339
+ this.signerToken = null;
1340
+ this.config = config;
1341
+ this.webAuthnProvider = config.webAuthnProvider;
1454
1342
  }
1455
1343
  /**
1456
- * Authenticate user with username
1457
- * @param username - User identifier
1458
- * @returns Promise resolving to authentication token
1344
+ * Login with PERS token to get signer JWT
1345
+ * @param persToken - PERS JWT from PERS authentication
1346
+ * @returns Promise resolving to login response or provider challenge data
1459
1347
  */
1460
- async login(username) {
1348
+ async loginWithPersToken(persToken) {
1461
1349
  const httpClient = getHttpClient();
1462
1350
  const config = getServiceConfig();
1463
1351
  try {
1464
- const response = await httpClient.post(`${config.apiUrl}/login`, {
1465
- username,
1466
- appId: config.appId,
1467
- });
1468
- // Check if the response contains an error
1469
- if (response.data.status && response.data.status >= 400) {
1470
- throw new Error(response.data.message || 'Login failed');
1471
- }
1472
- if (!response.data.token) {
1473
- throw new Error(response.data.message || 'No token received from login');
1352
+ const requestBody = {
1353
+ authToken: persToken
1354
+ };
1355
+ const response = await httpClient.post(`${config.apiUrl}/auth/login`, requestBody
1356
+ // Note: Login endpoint doesn't require Authorization header (public endpoint)
1357
+ );
1358
+ const backendResponse = response.data;
1359
+ if (backendResponse && backendResponse.success) {
1360
+ // Check if it's JWT response or wrapped provider response
1361
+ if ('access_token' in backendResponse) {
1362
+ // JWT response - set token and return
1363
+ this.signerToken = backendResponse.access_token;
1364
+ return backendResponse;
1365
+ }
1366
+ else {
1367
+ // Wrapped provider response - return the data
1368
+ return backendResponse.data;
1369
+ }
1474
1370
  }
1475
- console.info(`[WebAuthn] User login successful: ${username}`);
1476
- return response.data.token;
1371
+ console.error('[AuthenticationService] Invalid response - no success or success=false:', backendResponse);
1372
+ throw new Error('Login failed: Invalid response format');
1477
1373
  }
1478
1374
  catch (error) {
1479
- console.error(`[AuthenticationService] Login failed for ${username}:`, error);
1375
+ // console.error(`[AuthenticationService] PERS token login failed:`, error);
1480
1376
  throw error;
1481
1377
  }
1482
1378
  }
1483
1379
  /**
1484
- * Register new user with WebAuthn credential
1485
- * @param username - User identifier
1486
- * @returns Promise resolving to registration result
1380
+ * Verify signer token validity
1381
+ * @param token - Signer JWT to verify
1382
+ * @returns Promise resolving to verification result
1487
1383
  */
1488
- async register(username) {
1384
+ async verifyToken(token) {
1489
1385
  const httpClient = getHttpClient();
1490
1386
  const config = getServiceConfig();
1491
1387
  try {
1492
- // Step 1: Initialize registration and get challenge
1493
- const initResponse = await httpClient.post(`${config.apiUrl}/register/init`, {
1494
- username,
1495
- appId: config.appId,
1496
- });
1497
- const challenge = initResponse.data;
1498
- // Check if the registration init failed
1499
- if (challenge.status && challenge.status >= 400) {
1500
- throw new Error(challenge.message || 'Registration initialization failed');
1501
- }
1502
- // Step 2: Create WebAuthn credential using the challenge
1503
- const attestation = await this.webAuthnProvider.create(challenge);
1504
- console.info(`[WebAuthn] Credential created successfully for user: ${username}`);
1505
- // Step 3: Complete registration with signed challenge
1506
- const completeResponse = await httpClient.post(`${config.apiUrl}/register/complete`, {
1507
- signedChallenge: { firstFactorCredential: attestation },
1508
- temporaryAuthenticationToken: challenge.temporaryAuthenticationToken,
1509
- appId: config.appId,
1510
- });
1511
- const result = completeResponse.data;
1512
- // Check if the registration completion failed
1513
- if (result.status && result.status >= 400) {
1514
- throw new Error(result.message || 'Registration completion failed');
1388
+ const requestBody = {
1389
+ token
1390
+ };
1391
+ const response = await httpClient.post(`${config.apiUrl}/auth/verify`, requestBody, { Authorization: `Bearer ${token}` });
1392
+ const verifyData = response.data;
1393
+ if (!verifyData.valid) {
1394
+ throw new Error('Token verification failed');
1515
1395
  }
1516
- console.info(`[AuthenticationService] Registration successful for: ${username}`);
1517
- return result;
1396
+ return verifyData;
1518
1397
  }
1519
1398
  catch (error) {
1520
- console.error(`[AuthenticationService] Registration failed for ${username}:`, error);
1399
+ console.error(`[AuthenticationService] Token verification failed:`, error);
1521
1400
  throw error;
1522
1401
  }
1523
1402
  }
1524
1403
  /**
1525
- * Validate authentication token (when backend supports it)
1526
- * @param authToken - Token to validate
1527
- * @returns Promise resolving to validation result
1404
+ * Initialize user registration
1405
+ * @param persToken - PERS JWT token (registration is public)
1406
+ * @returns Promise resolving to registration challenge
1528
1407
  */
1529
- async validateToken(authToken) {
1408
+ async initializeRegistration(persToken) {
1530
1409
  const httpClient = getHttpClient();
1531
1410
  const config = getServiceConfig();
1532
1411
  try {
1533
- const response = await httpClient.post(`${config.apiUrl}/validate-token`, {
1534
- authToken,
1535
- appId: config.appId,
1536
- });
1537
- return response.data.valid === true;
1412
+ const requestBody = {
1413
+ authToken: persToken
1414
+ };
1415
+ const response = await httpClient.post(`${config.apiUrl}/auth/register/init`, requestBody);
1416
+ const backendResponse = response.data;
1417
+ if (backendResponse.success && backendResponse.data) {
1418
+ return backendResponse.data;
1419
+ }
1420
+ throw new Error('Registration initialization failed: No data returned');
1538
1421
  }
1539
1422
  catch (error) {
1540
- console.error('[AuthenticationService] Error validating token:', error);
1541
- return false;
1423
+ // console.error(`[AuthenticationService] Registration initialization failed:`, error);
1424
+ throw error;
1542
1425
  }
1543
1426
  }
1544
1427
  /**
1545
- * Get user information (when backend supports it)
1546
- * @param authToken - Authentication token
1547
- * @returns Promise resolving to user info or null
1428
+ * Get current signer token
1429
+ * @returns The current signer JWT token
1430
+ */
1431
+ getSignerToken() {
1432
+ return this.signerToken;
1433
+ }
1434
+ /**
1435
+ * Set signer token (for external token management)
1436
+ * @param token - Signer JWT token
1437
+ */
1438
+ setSignerToken(token) {
1439
+ this.signerToken = token;
1440
+ }
1441
+ /**
1442
+ * Complete registration with WebAuthn challenge data (v1 API format)
1443
+ * @param tmpAuthToken - Temporary auth token from init registration (temporaryAuthenticationToken)
1444
+ * @param signedChallenge - WebAuthn credential response (will be restructured for backend)
1445
+ * @param persToken - PERS JWT token (authToken)
1446
+ * @returns Promise resolving to registration result
1548
1447
  */
1549
- async getUser(authToken) {
1448
+ async completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persToken) {
1550
1449
  const httpClient = getHttpClient();
1450
+ const config = getServiceConfig();
1551
1451
  try {
1552
- const response = await httpClient.get(`${getServiceConfig().apiUrl}/user`, {
1553
- 'Authorization': `Bearer ${authToken}`
1554
- });
1555
- return response.data;
1556
- }
1452
+ const requestBody = {
1453
+ temporaryAuthenticationToken: tmpAuthToken,
1454
+ signedChallenge: {
1455
+ firstFactorCredential: {
1456
+ credentialKind: signedChallenge.credentialKind || "Fido2",
1457
+ credentialInfo: {
1458
+ credId: signedChallenge.credentialInfo?.credId || "",
1459
+ clientData: signedChallenge.credentialInfo?.clientData || "",
1460
+ attestationData: signedChallenge.credentialInfo?.attestationData || ""
1461
+ }
1462
+ }
1463
+ },
1464
+ authToken: persToken
1465
+ };
1466
+ const response = await httpClient.post(`${config.apiUrl}/auth/register/complete`, requestBody);
1467
+ const backendResponse = response.data;
1468
+ if (backendResponse && backendResponse.success) {
1469
+ // Check if it's JWT response (has access_token at root level) or wrapped provider response
1470
+ if ('access_token' in backendResponse) {
1471
+ // JWT response - set token and return
1472
+ this.signerToken = backendResponse.access_token;
1473
+ return backendResponse;
1474
+ }
1475
+ else if ('data' in backendResponse) {
1476
+ // Wrapped provider response - registration is not complete yet, needs more steps
1477
+ return backendResponse.data;
1478
+ }
1479
+ else {
1480
+ // Wrapped provider response - return the data
1481
+ return backendResponse.data;
1482
+ }
1483
+ }
1484
+ console.error('[AuthenticationService] Registration with challenge failed - invalid response:', backendResponse);
1485
+ throw new Error('Registration completion with challenge failed');
1486
+ }
1557
1487
  catch (error) {
1558
- console.error('[AuthenticationService] Error getting user:', error);
1559
- return null;
1488
+ console.error(`[AuthenticationService] Registration completion with challenge failed:`, error);
1489
+ throw error;
1560
1490
  }
1561
1491
  }
1562
- }
1563
-
1564
- /**
1565
- * PERS Blockchain Signer SDK - Simple Orchestrator
1566
- *
1567
- * Lightweight SDK that orchestrates existing services.
1568
- * Uses environment configuration and existing infrastructure.
1569
- */
1570
- /**
1571
- * Main PERS Signer SDK class
1572
- *
1573
- * Simple orchestrator that uses existing services and environment configuration.
1574
- * No complex initialization needed - services are already configured.
1575
- */
1576
- class PersSignerSDK {
1577
- constructor(config) {
1578
- this.config = config;
1579
- this.webAuthnProvider = config.webAuthnProvider;
1580
- // Set up the configuration provider with stable defaults from constants
1581
- const serviceConfig = {
1582
- apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
1583
- relyingParty: {
1584
- id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
1585
- name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1586
- origin: typeof window !== 'undefined' ? window.location.origin : undefined,
1587
- },
1588
- };
1589
- setConfigProvider(new WebConfigProvider(serviceConfig));
1590
- // Initialize services with the WebAuthn provider
1591
- this.authService = new AuthenticationService(this.webAuthnProvider);
1592
- this.signingService = new SigningService(this.webAuthnProvider);
1593
- this.transactionSigningService = new TransactionSigningService(this.webAuthnProvider);
1594
- }
1595
- /**
1596
- * Re-export TransactionSigningService with WebAuthn provider already injected
1597
- * This provides the same interface as the original service but with automatic provider injection
1598
- */
1599
- get TransactionSigningService() {
1600
- return this.transactionSigningService;
1601
- }
1602
- // ========================================================================
1603
- // HIGH-LEVEL METHODS (Same as Web Project)
1604
- // ========================================================================
1605
1492
  /**
1606
- * Complete user onboarding flow - same as web project
1607
- * Uses existing AuthenticationService and PersService
1493
+ * Combined authentication flow - handles both login and registration
1494
+ * @param identifier - User identifier (email/userId)
1495
+ * @param persAccessToken - PERS JWT token for authentication
1496
+ * @param webAuthnProvider - WebAuthn provider for credential creation
1497
+ * @param relyingPartyConfig - Configuration for WebAuthn relying party
1498
+ * @returns Promise resolving to authenticated user with signer token
1608
1499
  */
1609
- async authenticateUser(userInfo) {
1610
- const identifier = userInfo.identifier;
1500
+ async combinedAuthentication(identifier, persAccessToken) {
1611
1501
  try {
1612
- // Step 1: Try login first (existing user)
1613
- let signerAuthToken;
1502
+ // Step 1: Try to login with PERS token first (this will get signer JWT if user exists)
1503
+ let signerToken;
1614
1504
  try {
1615
- signerAuthToken = await this.authService.login(identifier);
1505
+ const loginResult = await this.loginWithPersToken(persAccessToken);
1506
+ // Extract token from login result
1507
+ if (loginResult && typeof loginResult === 'object' && 'access_token' in loginResult) {
1508
+ signerToken = loginResult.access_token;
1509
+ }
1510
+ else {
1511
+ throw new Error('Invalid login response format');
1512
+ }
1616
1513
  }
1617
1514
  catch (loginError) {
1618
- // User doesn't exist - register new user
1619
- await this.authService.register(identifier);
1620
- signerAuthToken = await this.authService.login(identifier);
1621
- }
1622
- // Step 2: Authenticate with PERS backend
1623
- let persAccessToken = '';
1624
- try {
1625
- const persResponse = await PersService.authenticateUser(signerAuthToken);
1626
- persAccessToken = persResponse?.accessToken || '';
1627
- }
1628
- catch (persError) {
1629
- console.warn(`PERS authentication failed:`, persError);
1630
- // Continue without PERS but provide empty token
1631
- persAccessToken = '';
1515
+ // Step 2: User doesn't exist - register with v1 API
1516
+ // 2a. Initialize registration
1517
+ const initResult = await this.initializeRegistration(persAccessToken);
1518
+ let signedChallenge = null;
1519
+ let tmpAuthToken = null;
1520
+ // 2b. Create WebAuthn credential using the challenge (if available)
1521
+ if (initResult && typeof initResult === 'object') {
1522
+ // Extract challenge and tmpAuthToken from init result with better type safety
1523
+ const initData = initResult;
1524
+ const challenge = initData.challenge;
1525
+ tmpAuthToken = initData.temporaryAuthenticationToken || initData.tmpAuthToken || null;
1526
+ if (challenge && initData.user) {
1527
+ // Construct proper WebAuthn credential creation options
1528
+ const credentialCreationOptions = {
1529
+ challenge: challenge,
1530
+ rp: {
1531
+ name: this.config.relyingPartyName || 'PERS Signer',
1532
+ id: typeof window !== 'undefined' ? window.location.hostname : 'localhost'
1533
+ },
1534
+ user: {
1535
+ id: initData.user.id,
1536
+ name: initData.user.name,
1537
+ displayName: initData.user.displayName
1538
+ },
1539
+ pubKeyCredParams: initData.pubKeyCredParams || [
1540
+ { alg: -7, type: "public-key" },
1541
+ { alg: -257, type: "public-key" }
1542
+ ],
1543
+ authenticatorSelection: initData.authenticatorSelection || {},
1544
+ attestation: initData.attestation || "direct",
1545
+ excludeCredentials: initData.excludeCredentials || []
1546
+ };
1547
+ signedChallenge = await this.webAuthnProvider.create(credentialCreationOptions);
1548
+ }
1549
+ else {
1550
+ console.warn(`[PersSignerSDK] Missing challenge or user data in init result`);
1551
+ }
1552
+ }
1553
+ // 2c. Complete registration with proper challenge data - call AuthenticationService directly
1554
+ const completeResult = await this.completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persAccessToken);
1555
+ // Extract token from registration result
1556
+ if (completeResult && typeof completeResult === 'object') {
1557
+ // Check if this is a complete JWT response with access_token
1558
+ if ('access_token' in completeResult) {
1559
+ signerToken = completeResult.access_token;
1560
+ }
1561
+ else {
1562
+ // This is a wrapped provider response - registration is not complete
1563
+ // We have user created and wallet created, but need to get JWT token
1564
+ // For now, throw an error indicating we need additional backend integration steps
1565
+ throw new Error('Registration created user and wallet but JWT token not provided. Backend integration incomplete.');
1566
+ }
1567
+ }
1568
+ else {
1569
+ throw new Error('Registration completed but no response received');
1570
+ }
1632
1571
  }
1633
1572
  return {
1634
1573
  identifier,
1635
- signerAuthToken,
1574
+ signerAuthToken: signerToken,
1636
1575
  persAccessToken
1637
1576
  };
1638
1577
  }
1639
1578
  catch (error) {
1640
- throw new Error(`User authentication failed: ${error}`);
1579
+ console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, error);
1580
+ throw new Error(`Combined authentication failed: ${error}`);
1641
1581
  }
1642
1582
  }
1583
+ }
1584
+
1585
+ /**
1586
+ * JWT Utility Functions
1587
+ *
1588
+ * Browser-compatible JWT handling without external dependencies
1589
+ */
1590
+ /**
1591
+ * Extract and validate JWT token payload
1592
+ * Uses a browser-compatible approach without external dependencies
1593
+ */
1594
+ function extractJWTFromToken(jwtToken) {
1595
+ if (!jwtToken) {
1596
+ return { payload: null, isExpired: false };
1597
+ }
1598
+ try {
1599
+ // Use a more compatible JWT decoding approach
1600
+ // Split JWT into parts
1601
+ const parts = jwtToken.split('.');
1602
+ if (parts.length !== 3) {
1603
+ throw new Error('Invalid JWT format');
1604
+ }
1605
+ // Decode the payload (middle part)
1606
+ const payload = JSON.parse(atob(parts[1]));
1607
+ // Check expiration
1608
+ const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
1609
+ return { payload, isExpired };
1610
+ }
1611
+ catch (error) {
1612
+ console.warn('JWT decode failed:', error);
1613
+ return { payload: null, isExpired: false };
1614
+ }
1615
+ }
1616
+
1617
+ /**
1618
+ * In-memory User Cache
1619
+ *
1620
+ * Provides secure, storage-free user authentication caching
1621
+ * Uses memory-only storage with TTL expiration for security
1622
+ */
1623
+ /**
1624
+ * In-memory user cache with TTL expiration
1625
+ * Security-first approach - no persistent storage
1626
+ */
1627
+ class UserCache {
1643
1628
  /**
1644
- * Complete PERS transaction signing flow - exactly like web project
1645
- * Uses existing TransactionSigningService with injected WebAuthn provider
1629
+ * Get cached user by identifier
1630
+ * @param identifier - User identifier (email/userId)
1631
+ * @returns Cached user or null if not found/expired
1646
1632
  */
1647
- async signPersTransaction(user, transactionId) {
1648
- if (!user.signerAuthToken) {
1649
- throw new Error('Signer authentication token required');
1650
- }
1651
- if (!user.persAccessToken) {
1652
- throw new Error('PERS access token required for transaction signing');
1653
- }
1654
- try {
1655
- // Use the existing high-level service with injected WebAuthn provider
1656
- const result = await this.transactionSigningService.signTransaction({
1657
- transactionId,
1658
- authTokens: {
1659
- backendAuthToken: user.signerAuthToken,
1660
- persAccessToken: user.persAccessToken
1661
- },
1662
- ethersProviderUrl: this.config.ethersProviderUrl || 'https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e'
1663
- });
1664
- return {
1665
- success: result.success,
1666
- transactionHash: result.transactionHash,
1667
- error: result.error
1668
- };
1633
+ static get(identifier) {
1634
+ const cached = this.cache.get(identifier);
1635
+ if (!cached) {
1636
+ return null;
1669
1637
  }
1670
- catch (error) {
1671
- return {
1672
- success: false,
1673
- error: `Transaction signing failed: ${error}`
1674
- };
1638
+ // Check if expired
1639
+ if (Date.now() > cached.expiresAt) {
1640
+ this.cache.delete(identifier);
1641
+ return null;
1675
1642
  }
1643
+ return cached.user;
1676
1644
  }
1677
- // ========================================================================
1678
- // GRANULAR METHODS (For Custom Integrations)
1679
- // ========================================================================
1680
1645
  /**
1681
- * Register new user - uses existing AuthenticationService
1646
+ * Cache user with TTL
1647
+ * @param identifier - User identifier
1648
+ * @param user - Authenticated user data
1649
+ * @param ttlMs - Time to live in milliseconds (default: 5 minutes)
1682
1650
  */
1683
- async registerUser(identifier) {
1684
- try {
1685
- await this.authService.register(identifier);
1686
- const authToken = await this.authService.login(identifier);
1687
- return { authToken };
1688
- }
1689
- catch (error) {
1690
- throw new Error(`User registration failed: ${error}`);
1691
- }
1651
+ static set(identifier, user, ttlMs = this.DEFAULT_TTL_MS) {
1652
+ this.cache.set(identifier, {
1653
+ user,
1654
+ expiresAt: Date.now() + ttlMs
1655
+ });
1692
1656
  }
1693
1657
  /**
1694
- * Login existing user - uses existing AuthenticationService
1658
+ * Remove user from cache
1659
+ * @param identifier - User identifier
1695
1660
  */
1696
- async loginUser(identifier) {
1697
- try {
1698
- return await this.authService.login(identifier);
1699
- }
1700
- catch (error) {
1701
- throw new Error(`User login failed: ${error}`);
1702
- }
1661
+ static delete(identifier) {
1662
+ return this.cache.delete(identifier);
1703
1663
  }
1704
1664
  /**
1705
- * Add wallet to PERS user - uses existing PersService
1665
+ * Clear all cached users
1706
1666
  */
1707
- async addWalletToPersUser(signerAuthToken) {
1708
- try {
1709
- const persResponse = await PersService.authenticateUser(signerAuthToken);
1710
- if (!persResponse?.accessToken) {
1711
- throw new Error('Failed to get PERS access token');
1712
- }
1713
- return persResponse.accessToken;
1714
- }
1715
- catch (error) {
1716
- throw new Error(`Failed to add wallet to PERS user: ${error}`);
1717
- }
1667
+ static clear() {
1668
+ this.cache.clear();
1718
1669
  }
1719
1670
  /**
1720
- * Retrieve transaction data from PERS - uses existing PersService
1671
+ * Get cache size (for debugging)
1721
1672
  */
1722
- async retrieveTransactionData(transactionId, persAccessToken) {
1723
- try {
1724
- return await PersService.fetchPreparedTransaction(transactionId, persAccessToken);
1725
- }
1726
- catch (error) {
1727
- // Preserve original error structure for proper error handling
1728
- throw error;
1729
- }
1673
+ static size() {
1674
+ return this.cache.size;
1730
1675
  }
1731
1676
  /**
1732
- * Sign transaction data - uses existing SigningService
1733
- * Follows same patterns as KeyWallet.signPersTransaction
1677
+ * Clean up expired entries
1734
1678
  */
1735
- async signTransactionData(signerAuthToken, signingData) {
1736
- try {
1737
- const wallets = await WalletService.listWallets(signerAuthToken);
1738
- if (!wallets.items || wallets.items.length === 0) {
1739
- throw new Error('No wallet found for user');
1740
- }
1741
- const walletId = wallets.items[0].id;
1742
- // Sign based on transaction format - same pattern as KeyWallet
1743
- switch (signingData.format) {
1744
- case browser.TRANSACTION_FORMATS.EIP_712: {
1745
- const eip712Data = signingData;
1746
- const typedData = eip712Data.typedData;
1747
- // Sign using same pattern as KeyWallet
1748
- const signResponse = await this.signingService.signTypedData(signerAuthToken, walletId, typedData.domain, { Transaction: typedData.types.Transaction }, typedData.message);
1749
- return `${signResponse.signature?.r}${signResponse.signature?.s}`;
1750
- }
1751
- default: {
1752
- // For legacy and other formats, use raw signing data
1753
- const signResponse = await this.signingService.signHash(signerAuthToken, walletId, JSON.stringify(signingData));
1754
- return `${signResponse.signature?.r}${signResponse.signature?.s}`;
1755
- }
1679
+ static cleanup() {
1680
+ const now = Date.now();
1681
+ let cleaned = 0;
1682
+ for (const [identifier, cached] of this.cache.entries()) {
1683
+ if (now > cached.expiresAt) {
1684
+ this.cache.delete(identifier);
1685
+ cleaned++;
1756
1686
  }
1757
1687
  }
1758
- catch (error) {
1759
- throw new Error(`Transaction signing failed: ${error}`);
1760
- }
1688
+ return cleaned;
1761
1689
  }
1690
+ }
1691
+ UserCache.cache = new Map();
1692
+ UserCache.DEFAULT_TTL_MS = 300000; // 5 minutes
1693
+
1694
+ /**
1695
+ * PERS Blockchain Signer SDK
1696
+ *
1697
+ * A lightweight blockchain transaction signing SDK with WebAuthn authentication.
1698
+ * Provides 5 focused methods for complete transaction lifecycle management:
1699
+ *
1700
+ * 1. loginUser(jwtToken) - Authenticate user with 5-minute caching
1701
+ * 2. signTransaction(signingData, jwtToken) - Sign transactions with auto-login
1702
+ * 3. submitTransaction(signedTx, jwtToken) - Submit signed transactions to blockchain
1703
+ * 4. signPersTransaction(jwtToken) - Legacy one-liner for backward compatibility
1704
+ * 5. signAndSubmitPersTransaction(jwtToken) - Complete sign + submit flow
1705
+ *
1706
+ * @example
1707
+ * ```typescript
1708
+ * import { createPersSignerSDK } from '@explorins/pers-signer';
1709
+ *
1710
+ * const sdk = createPersSignerSDK({
1711
+ * webAuthnProvider: myWebAuthnProvider,
1712
+ * ethersProviderUrl: 'https://ethereum-rpc.com'
1713
+ * });
1714
+ *
1715
+ * // Quick sign and submit
1716
+ * const result = await sdk.signAndSubmitPersTransaction(jwtToken);
1717
+ * if (result.success) {
1718
+ * console.log('Transaction submitted:', result.transactionHash);
1719
+ * }
1720
+ * ```
1721
+ */
1722
+ /**
1723
+ * PERS Blockchain Signer SDK Class
1724
+ *
1725
+ * Main SDK class providing blockchain transaction signing capabilities with WebAuthn authentication.
1726
+ * Implements a clean 5-method API for complete transaction lifecycle management.
1727
+ *
1728
+ * Features:
1729
+ * - WebAuthn-based secure authentication
1730
+ * - 5-minute user session caching
1731
+ * - Automatic transaction data fetching
1732
+ * - Blockchain transaction signing and submission
1733
+ * - Multi-tenant support
1734
+ *
1735
+ * @class PersSignerSDK
1736
+ */
1737
+ class PersSignerSDK {
1762
1738
  /**
1763
- * Submit signed transaction to PERS - uses existing PersService
1739
+ * Initialize the PERS Signer SDK
1740
+ *
1741
+ * @param {PersSignerConfig} config - SDK configuration object
1742
+ * @throws {Error} If required configuration is missing
1764
1743
  */
1765
- async submitTransaction(transactionId, signature, persAccessToken, transactionFormat = browser.TRANSACTION_FORMATS.EIP_712) {
1766
- try {
1767
- const submitResult = await PersService.submitTransaction(transactionId, signature, persAccessToken, transactionFormat);
1768
- return {
1769
- transactionHash: submitResult.transaction?.transactionHash || undefined,
1770
- success: true
1771
- };
1772
- }
1773
- catch (error) {
1774
- throw new Error(`Transaction submission failed: ${error}`);
1775
- }
1744
+ constructor(config) {
1745
+ this.config = config;
1746
+ setConfigProvider(new WebConfigProvider({
1747
+ apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
1748
+ relyingParty: {
1749
+ id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
1750
+ name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1751
+ origin: typeof window !== 'undefined' ? window.location.origin : undefined,
1752
+ },
1753
+ }));
1754
+ this.authenticationService = new AuthenticationService(this.config);
1755
+ this.transactionSigningService = new TransactionSigningService(config);
1776
1756
  }
1777
1757
  /**
1778
- * Check if user exists - uses existing AuthenticationService
1758
+ * Authenticate user and cache session for 5 minutes
1759
+ *
1760
+ * Validates JWT token, authenticates with both signer and PERS backends,
1761
+ * and caches the authenticated user session to avoid repeated authentication.
1762
+ *
1763
+ * @param {string} jwtToken - JWT token containing user identifier and tenant info
1764
+ * @returns {Promise<AuthenticatedUser>} Authenticated user with access tokens
1765
+ * @throws {Error} If JWT is invalid, expired, or authentication fails
1766
+ *
1767
+ * @example
1768
+ * ```typescript
1769
+ * try {
1770
+ * const user = await sdk.loginUser(jwtToken);
1771
+ * console.log('Authenticated:', user.identifier);
1772
+ * } catch (error) {
1773
+ * console.error('Authentication failed:', error.message);
1774
+ * }
1775
+ * ```
1779
1776
  */
1780
- async checkUserExists(identifier) {
1781
- try {
1782
- await this.authService.login(identifier);
1783
- return true;
1777
+ async loginUser(jwtToken) {
1778
+ if (!jwtToken || typeof jwtToken !== 'string') {
1779
+ throw new Error('JWT token is required and must be a string');
1784
1780
  }
1785
- catch {
1786
- return false;
1781
+ const { payload, isExpired } = extractJWTFromToken(jwtToken);
1782
+ if (!payload || isExpired) {
1783
+ throw new Error('Invalid or expired JWT token');
1787
1784
  }
1788
- }
1789
- /**
1790
- * Get user wallets - uses existing WalletService
1791
- */
1792
- async getUserWallets(signerAuthToken) {
1793
- try {
1794
- return await WalletService.listWallets(signerAuthToken);
1785
+ const identifier = payload.identifierEmail || payload.email || payload.userId;
1786
+ const tenantId = payload.tenantId || this.config.tenantId || '';
1787
+ if (!identifier) {
1788
+ throw new Error('JWT token missing user identifier (identifierEmail, email, or userId)');
1795
1789
  }
1796
- catch (error) {
1797
- throw new Error(`Failed to get user wallets: ${error}`);
1790
+ // Check cache first
1791
+ const cachedUser = UserCache.get(identifier);
1792
+ if (cachedUser && cachedUser.tenantId === tenantId && Date.now() < cachedUser.expiresAt) {
1793
+ return cachedUser;
1798
1794
  }
1799
- }
1800
- /**
1801
- * Get transaction status - uses existing PersService
1802
- */
1803
- async getTransactionStatus(transactionId, persAccessToken) {
1804
1795
  try {
1805
- const transaction = await PersService.fetchPreparedTransaction(transactionId, persAccessToken);
1806
- return {
1807
- status: transaction.transactionStatus,
1808
- transactionHash: transaction.transaction?.transactionHash || undefined
1796
+ // Authenticate and cache
1797
+ const authResult = await this.authenticationService.combinedAuthentication(identifier, jwtToken);
1798
+ const user = {
1799
+ identifier: authResult.identifier,
1800
+ signerAuthToken: authResult.signerAuthToken,
1801
+ persAccessToken: authResult.persAccessToken,
1802
+ tenantId,
1803
+ expiresAt: Date.now() + 300000 // 5 minutes
1809
1804
  };
1805
+ UserCache.set(identifier, user);
1806
+ return user;
1810
1807
  }
1811
1808
  catch (error) {
1812
- throw new Error(`Failed to get transaction status: ${error}`);
1809
+ throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1813
1810
  }
1814
1811
  }
1815
1812
  /**
1816
- * Initialize tenant - uses existing PersService
1817
- */
1818
- async initializeTenant(tenantId) {
1819
- await PersService.initializeTenant(tenantId);
1820
- }
1821
- // ========================================================================
1822
- // JWT AUTHENTICATION METHODS (For Legacy PERS Flow)
1823
- // ========================================================================
1824
- /**
1825
- * Extract and validate JWT token from URL search parameters
1826
- * Uses a simpler approach compatible with browser environments
1813
+ * Sign a PERS transaction (legacy compatibility method)
1814
+ *
1815
+ * Automatically handles user authentication, transaction data fetching,
1816
+ * and transaction signing in a single call. This is the legacy method
1817
+ * maintained for backward compatibility.
1818
+ *
1819
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
1820
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
1821
+ * @throws {Error} If authentication fails, transaction ID missing, or signing fails
1822
+ *
1823
+ * @example
1824
+ * ```typescript
1825
+ * try {
1826
+ * const result = await sdk.signPersTransaction(jwtToken);
1827
+ * if (result.success) {
1828
+ * console.log('Transaction signed:', result.signature);
1829
+ * }
1830
+ * } catch (error) {
1831
+ * console.error('Signing failed:', error.message);
1832
+ * }
1833
+ * ```
1827
1834
  */
1828
- extractJWTFromURL(searchParams) {
1829
- const jwtToken = searchParams.get('jwt') || searchParams.get('token');
1830
- if (!jwtToken) {
1831
- return { payload: null, isExpired: false };
1832
- }
1835
+ async signPersTransaction(jwtToken) {
1836
+ if (!jwtToken || typeof jwtToken !== 'string') {
1837
+ throw new Error('JWT token is required and must be a string');
1838
+ }
1839
+ const user = await this.loginUser(jwtToken);
1840
+ const { payload } = extractJWTFromToken(jwtToken);
1841
+ if (!payload?.transactionId) {
1842
+ throw new Error('JWT token missing transactionId in payload');
1843
+ }
1844
+ const authTokens = {
1845
+ signerAuthToken: user.signerAuthToken,
1846
+ persAccessToken: user.persAccessToken
1847
+ };
1833
1848
  try {
1834
- // Use a more compatible JWT decoding approach
1835
- // Split JWT into parts
1836
- const parts = jwtToken.split('.');
1837
- if (parts.length !== 3) {
1838
- throw new Error('Invalid JWT format');
1849
+ const persSigningData = await this.transactionSigningService.getPersSigningData({
1850
+ transactionId: payload.transactionId,
1851
+ authTokens,
1852
+ tenantId: user.tenantId
1853
+ });
1854
+ const result = await this.signTransaction(persSigningData, jwtToken);
1855
+ if (!result.success) {
1856
+ throw new Error(result.error || 'Transaction signing failed');
1839
1857
  }
1840
- // Decode the payload (middle part)
1841
- const payload = JSON.parse(atob(parts[1]));
1842
- // Check expiration
1843
- const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
1844
- return { payload, isExpired };
1858
+ return result;
1845
1859
  }
1846
1860
  catch (error) {
1847
- console.warn('JWT decode failed:', error);
1848
- return { payload: null, isExpired: false };
1861
+ throw new Error(`PERS transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1849
1862
  }
1850
1863
  }
1851
1864
  /**
1852
- * Initialize tenant from JWT payload using existing PersService
1865
+ * Sign a transaction with provided signing data
1866
+ *
1867
+ * Low-level method to sign transactions when you already have the signing data.
1868
+ * Automatically handles user authentication and applies the blockchain signature.
1869
+ *
1870
+ * @param {CounterfactualWalletTransactionResponse | LegacyTransaction} signingData - Transaction data to sign
1871
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
1872
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
1873
+ * @throws {Error} If authentication fails, transaction ID missing, or signing fails
1874
+ *
1875
+ * @example
1876
+ * ```typescript
1877
+ * const signingData = await getTransactionData(transactionId);
1878
+ * const result = await sdk.signTransaction(signingData, jwtToken);
1879
+ * console.log('Signed transaction:', result.signature);
1880
+ * ```
1853
1881
  */
1854
- async initializeTenantFromJWT(payload) {
1855
- if (!payload.tenantId) {
1856
- return 'default';
1882
+ async signTransaction(signingData, jwtToken) {
1883
+ if (!signingData) {
1884
+ throw new Error('Signing data is required');
1885
+ }
1886
+ if (!jwtToken || typeof jwtToken !== 'string') {
1887
+ throw new Error('JWT token is required and must be a string');
1857
1888
  }
1889
+ const user = await this.loginUser(jwtToken);
1890
+ const { payload } = extractJWTFromToken(jwtToken);
1891
+ if (!payload?.transactionId) {
1892
+ throw new Error('JWT token missing transactionId in payload');
1893
+ }
1894
+ const authTokens = {
1895
+ signerAuthToken: user.signerAuthToken,
1896
+ persAccessToken: user.persAccessToken
1897
+ };
1858
1898
  try {
1859
- // Use existing PersService.initializeTenant method
1860
- const tenantData = await PersService.initializeTenant(payload.tenantId);
1861
- // Extract project key from tenant data
1862
- const projectKey = tenantData.projectKey ||
1863
- tenantData.projectApiKey ||
1864
- tenantData.apiKey ||
1865
- payload.tenantId;
1866
- return projectKey;
1899
+ const result = await this.transactionSigningService.signTransaction({
1900
+ transactionId: payload.transactionId,
1901
+ tenantId: user.tenantId,
1902
+ authTokens,
1903
+ ethersProviderUrl: this.config.ethersProviderUrl || ''
1904
+ }, signingData);
1905
+ return result;
1867
1906
  }
1868
1907
  catch (error) {
1869
- console.warn('Tenant initialization failed:', error);
1870
- return payload.tenantId;
1908
+ throw new Error(`Transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1871
1909
  }
1872
1910
  }
1873
1911
  /**
1874
- * Authenticate user with PERS API using existing PersService pattern
1875
- */
1876
- /**
1877
- * Authenticate user with PERS API using existing PersService
1912
+ * Complete transaction flow: sign and submit in one call
1913
+ *
1914
+ * Convenience method that combines signing and submission into a single operation.
1915
+ * This is the recommended method for most use cases as it handles the complete
1916
+ * transaction lifecycle automatically.
1917
+ *
1918
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
1919
+ * @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
1920
+ * @throws {Error} If authentication, signing, or submission fails
1921
+ *
1922
+ * @example
1923
+ * ```typescript
1924
+ * try {
1925
+ * const result = await sdk.signAndSubmitPersTransaction(jwtToken);
1926
+ * if (result.success) {
1927
+ * console.log('Transaction completed:', result.transactionHash);
1928
+ * if (result.shouldRedirect) {
1929
+ * window.location.href = result.redirectUrl;
1930
+ * }
1931
+ * }
1932
+ * } catch (error) {
1933
+ * console.error('Transaction failed:', error.message);
1934
+ * }
1935
+ * ```
1878
1936
  */
1879
- async authenticatePersUser(jwtToken, projectKey) {
1937
+ async signAndSubmitPersTransaction(jwtToken) {
1938
+ if (!jwtToken || typeof jwtToken !== 'string') {
1939
+ throw new Error('JWT token is required and must be a string');
1940
+ }
1880
1941
  try {
1881
- // Configure PersService with the project key from tenant initialization
1882
- PersService.configure({ projectKey });
1883
- // Use the existing PersService method which handles all the complexity
1884
- const result = await PersService.authenticateUser(jwtToken);
1885
- // Convert to expected PersAuthResult format
1886
- return {
1887
- user: {
1888
- email: result.user?.email || undefined,
1889
- id: result.user?.id || undefined
1890
- },
1891
- accessToken: result.accessToken
1892
- };
1942
+ const signedTx = await this.signPersTransaction(jwtToken);
1943
+ const submittedTx = await this.submitTransaction(signedTx, jwtToken);
1944
+ return submittedTx;
1893
1945
  }
1894
1946
  catch (error) {
1895
- throw new Error(`PERS authentication failed: ${error}`);
1947
+ throw new Error(`Sign and submit transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1896
1948
  }
1897
1949
  }
1898
1950
  /**
1899
- * Combined PERS + DFNS authentication flow
1951
+ * Submit a signed transaction to the blockchain
1952
+ *
1953
+ * Takes a signed transaction result and submits it to the blockchain network.
1954
+ * Returns detailed submission results including transaction hash and any
1955
+ * redirect information for UI flows.
1956
+ *
1957
+ * @param {TransactionSigningResult} signingResult - Result from a successful transaction signing
1958
+ * @param {string} jwtToken - JWT token containing tenant and user info
1959
+ * @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
1960
+ * @throws {Error} If signing result is invalid, JWT is missing tenantId, or submission fails
1961
+ *
1962
+ * @example
1963
+ * ```typescript
1964
+ * const signedTx = await sdk.signPersTransaction(jwtToken);
1965
+ * const result = await sdk.submitTransaction(signedTx, jwtToken);
1966
+ * console.log('Transaction submitted:', result.transactionHash);
1967
+ * ```
1900
1968
  */
1901
- async combinedAuthentication(identifier, persAccessToken) {
1969
+ async submitTransaction(signingResult, jwtToken) {
1970
+ if (!signingResult) {
1971
+ throw new Error('Signing result is required');
1972
+ }
1973
+ if (!jwtToken || typeof jwtToken !== 'string') {
1974
+ throw new Error('JWT token is required and must be a string');
1975
+ }
1976
+ if (!signingResult.success || !signingResult.signature || !signingResult.signingData) {
1977
+ throw new Error('Invalid signing result: must be successful with signature and signing data');
1978
+ }
1979
+ const { payload } = extractJWTFromToken(jwtToken);
1980
+ if (!payload?.tenantId) {
1981
+ throw new Error('JWT token missing tenantId in payload');
1982
+ }
1983
+ const user = await this.loginUser(jwtToken);
1902
1984
  try {
1903
- // Authenticate with DFNS only (we already have PERS token)
1904
- const signerAuthToken = await this.loginUser(identifier);
1985
+ const submitResult = await PersService.submitTransaction(signingResult.transactionId, signingResult.signature, user.persAccessToken, signingResult.signingData.format, payload.tenantId);
1986
+ // Transform response to SubmissionResult
1905
1987
  return {
1906
- identifier,
1907
- signerAuthToken,
1908
- persAccessToken
1988
+ success: true,
1989
+ transactionHash: submitResult.transaction.transactionHash,
1990
+ shouldRedirect: false, // Can be configured based on business requirements
1991
+ redirectUrl: undefined, // Can be set based on business logic
1992
+ error: undefined,
1993
+ submitResult: submitResult
1909
1994
  };
1910
1995
  }
1911
1996
  catch (error) {
1912
- throw new Error(`Combined authentication failed: ${error}`);
1997
+ throw new Error(`Transaction submission failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1913
1998
  }
1914
1999
  }
1915
2000
  /**
1916
- * Complete JWT-based authentication flow (legacy PERS flow)
2001
+ * Clear user authentication cache
2002
+ *
2003
+ * Removes all cached user sessions, forcing fresh authentication
2004
+ * on the next method call. Useful for logout scenarios or when
2005
+ * switching between different user contexts.
2006
+ *
2007
+ * @example
2008
+ * ```typescript
2009
+ * // Clear cache on user logout
2010
+ * sdk.clearCache();
2011
+ * console.log('User cache cleared');
2012
+ * ```
1917
2013
  */
1918
- async authenticateWithJWT(searchParams) {
1919
- // 1. Extract JWT from URL
1920
- const { payload, isExpired } = this.extractJWTFromURL(searchParams);
1921
- if (!payload) {
1922
- // Return null instead of throwing error to allow graceful handling
1923
- return null;
1924
- }
1925
- if (isExpired) {
1926
- return {
1927
- user: {
1928
- identifier: payload.email || payload.userId || 'unknown',
1929
- signerAuthToken: '',
1930
- persAccessToken: ''
1931
- },
1932
- isExpired: true
1933
- };
1934
- }
1935
- // 2. Initialize tenant and get project key
1936
- const projectKey = await this.initializeTenantFromJWT(payload);
1937
- // 3. Get JWT token from URL
1938
- const jwtToken = searchParams.get('jwt') || searchParams.get('token');
1939
- if (!jwtToken) {
1940
- throw new Error('JWT token not found in URL parameters');
1941
- }
1942
- // 4. Authenticate with PERS
1943
- const persResult = await this.authenticatePersUser(jwtToken, projectKey);
1944
- // 5. Get identifier for DFNS authentication
1945
- const identifier = persResult.user.email || persResult.user.id;
1946
- if (!identifier) {
1947
- throw new Error('No identifier found in PERS response');
1948
- }
1949
- // 6. Combined authentication
1950
- const user = await this.combinedAuthentication(identifier, persResult.accessToken);
1951
- return {
1952
- user,
1953
- isExpired: false
1954
- };
1955
- }
1956
- }
1957
-
1958
- /**
1959
- * Utility functions for handling URL search parameters in a consistent way across applications
1960
- */
1961
- /**
1962
- * Creates a URL search string that preserves all current parameters
1963
- * @param paramsToExclude Array of parameter names to exclude from the result
1964
- * @param paramsToAdd Object with additional parameters to add or override
1965
- * @param baseParams Optional URLSearchParams object to use instead of window.location.search
1966
- * @returns A search string with '?' prefix if there are parameters, or empty string if no parameters
1967
- */
1968
- function createSearchString(paramsToExclude = [], paramsToAdd = {}, baseParams) {
1969
- // Get base search params from the provided value or window location
1970
- const currentParams = new URLSearchParams(baseParams instanceof URLSearchParams
1971
- ? baseParams
1972
- : baseParams !== undefined
1973
- ? baseParams
1974
- : (typeof window !== 'undefined' ? window.location.search : ''));
1975
- // Remove excluded parameters
1976
- paramsToExclude.forEach(param => {
1977
- if (currentParams.has(param)) {
1978
- currentParams.delete(param);
1979
- }
1980
- });
1981
- // Add or update new parameters
1982
- Object.entries(paramsToAdd).forEach(([key, value]) => {
1983
- if (value !== undefined && value !== null) {
1984
- currentParams.set(key, value);
1985
- }
1986
- });
1987
- const searchString = currentParams.toString();
1988
- return searchString ? `?${searchString}` : '';
1989
- }
1990
- /**
1991
- * Creates a URL path with search parameters
1992
- * @param basePath The base path without search parameters
1993
- * @param paramsToExclude Array of parameter names to exclude from the result
1994
- * @param paramsToAdd Object with additional parameters to add or override
1995
- * @param baseParams Optional URLSearchParams object to use instead of window.location.search
1996
- * @returns A full path with search parameters
1997
- */
1998
- function createUrlWithSearchParams(basePath, paramsToExclude = [], paramsToAdd = {}, baseParams) {
1999
- const searchString = createSearchString(paramsToExclude, paramsToAdd, baseParams);
2000
- return `${basePath}${searchString}`;
2001
- }
2002
- /**
2003
- * Combines the current search parameters with new ones
2004
- * @param searchParams The current URLSearchParams object
2005
- * @param additionalParams Object with parameters to add or update
2006
- * @returns A new URLSearchParams object
2007
- */
2008
- function mergeSearchParams(searchParams, additionalParams = {}) {
2009
- const merged = new URLSearchParams(searchParams.toString());
2010
- Object.entries(additionalParams).forEach(([key, value]) => {
2011
- if (value !== undefined && value !== null) {
2012
- merged.set(key, value);
2013
- }
2014
- });
2015
- return merged;
2016
- }
2017
- /**
2018
- * Gets a specific search parameter value
2019
- * @param key The parameter name to get
2020
- * @param search Optional search string to parse (defaults to window.location.search)
2021
- * @returns The parameter value or null if not found
2022
- */
2023
- function getSearchParam(key, search) {
2024
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2025
- return params.get(key);
2026
- }
2027
- /**
2028
- * Gets all search parameters as a URLSearchParams object
2029
- * @param search Optional search string to parse (defaults to window.location.search)
2030
- * @returns A URLSearchParams object
2031
- */
2032
- function getAllSearchParams(search) {
2033
- return new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2034
- }
2035
- /**
2036
- * Removes a specific search parameter
2037
- * @param key The parameter name to remove
2038
- * @param search Optional search string to parse (defaults to window.location.search)
2039
- * @returns A new search string without the specified parameter
2040
- */
2041
- function removeSearchParam(key, search) {
2042
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2043
- params.delete(key);
2044
- const searchString = params.toString();
2045
- return searchString ? `?${searchString}` : '';
2046
- }
2047
- /**
2048
- * Updates or adds a specific search parameter
2049
- * @param key The parameter name to update
2050
- * @param value The new value for the parameter
2051
- * @param search Optional search string to parse (defaults to window.location.search)
2052
- * @returns A new search string with the updated parameter
2053
- */
2054
- function updateSearchParam(key, value, search) {
2055
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2056
- params.set(key, value);
2057
- const searchString = params.toString();
2058
- return searchString ? `?${searchString}` : '';
2059
- }
2060
-
2061
- var searchParams = /*#__PURE__*/Object.freeze({
2062
- __proto__: null,
2063
- createSearchString: createSearchString,
2064
- createUrlWithSearchParams: createUrlWithSearchParams,
2065
- getAllSearchParams: getAllSearchParams,
2066
- getSearchParam: getSearchParam,
2067
- mergeSearchParams: mergeSearchParams,
2068
- removeSearchParam: removeSearchParam,
2069
- updateSearchParam: updateSearchParam
2070
- });
2071
-
2072
- /**
2073
- * Utility for debugging search parameter handling
2074
- */
2075
- /**
2076
- * Logs the current search parameters with a custom message
2077
- * This is useful for debugging search parameter preservation across navigation
2078
- *
2079
- * @param message - A descriptive message to identify where the logging happens
2080
- * @param additionalData - Optional additional data to log
2081
- */
2082
- function logSearchParams(message, additionalData) {
2083
- if (process.env.NODE_ENV !== 'production') {
2084
- // Check if window exists (for SSR compatibility)
2085
- if (typeof window === 'undefined') {
2086
- return;
2087
- }
2088
- const searchParams = new URLSearchParams(window.location.search);
2089
- searchParams.forEach((value, key) => {
2090
- });
2091
- console.group(`🔍 Search Params: ${message}`);
2092
- console.groupEnd();
2014
+ clearCache() {
2015
+ UserCache.clear();
2093
2016
  }
2094
2017
  }
2095
- /**
2096
- * Creates a search parameter tracking hook that can be used in React components
2097
- * Logs search parameter changes when the component mounts/updates
2098
- *
2099
- * @param componentName - The name of the component (for logging)
2100
- */
2101
- function createSearchParamLogger(componentName) {
2102
- return () => {
2103
- logSearchParams(`${componentName} rendered`);
2104
- };
2105
- }
2106
-
2107
- /**
2108
- * Generates a RFC4122 version 4 compliant UUID
2109
- * @returns A randomly generated UUID
2110
- */
2111
- function generateUUID() {
2112
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
2113
- const r = Math.random() * 16 | 0;
2114
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
2115
- return v.toString(16);
2116
- });
2117
- }
2118
- /**
2119
- * Create a semi-anonymous username for users
2120
- * @returns A username in the format "user_[random string]"
2121
- */
2122
- function generateAnonymousUsername() {
2123
- const randomPart = Math.random().toString(36).substring(2, 10);
2124
- return `user_${randomPart}`;
2125
- }
2126
- /**
2127
- * Create a guest username for the application with a random prefix
2128
- * @returns A guest email in the format "randomstring_guest@explorins.com"
2129
- */
2130
- function generateGuestUsername() {
2131
- const randomPart = Math.random().toString(36).substring(2, 8); // 6 character random string
2132
- return `${randomPart}_guest@explorins.com`;
2133
- }
2134
-
2135
- /**
2136
- * Utility functions for tenant-specific operations
2137
- */
2138
- /**
2139
- * Initialize tenant-specific analytics
2140
- * @param tenantId The tenant ID
2141
- * @param getTenantConfig Function to retrieve tenant configuration
2142
- */
2143
- const initTenantAnalytics = (tenantId, getTenantConfig) => {
2144
- const tenant = getTenantConfig(tenantId);
2145
- if (!tenant) {
2146
- console.warn(`Tenant ${tenantId} not found`);
2147
- return;
2148
- }
2149
- if (tenant.analytics && tenant.analytics.enabled && tenant.analytics.trackingId) ;
2150
- };
2151
2018
 
2152
2019
  /**
2153
2020
  * Browser-specific WebAuthn provider using DFNS browser SDK
@@ -2202,20 +2069,6 @@ async function getBrowserWebAuthnProvider() {
2202
2069
  * Includes only browser-compatible dependencies
2203
2070
  */
2204
2071
  // Re-export all shared functionality
2205
- /**
2206
- * Create a new AuthenticationService instance with browser WebAuthn provider
2207
- */
2208
- async function createAuthenticationService() {
2209
- const webAuthnProvider = await getBrowserWebAuthnProvider();
2210
- return new AuthenticationService(webAuthnProvider);
2211
- }
2212
- /**
2213
- * Create a new SigningService instance with browser WebAuthn provider
2214
- */
2215
- async function createSigningService() {
2216
- const webAuthnProvider = await getBrowserWebAuthnProvider();
2217
- return new SigningService(webAuthnProvider);
2218
- }
2219
2072
  /**
2220
2073
  * Create a new PersSignerSDK instance with browser WebAuthn provider
2221
2074
  * @param config - SDK configuration (ethersProviderUrl is required)
@@ -11463,27 +11316,12 @@ exports.TransactionValidator = TransactionValidator;
11463
11316
  exports.WalletService = WalletService;
11464
11317
  exports.WebAuthnCoordinator = WebAuthnCoordinator;
11465
11318
  exports.WebConfigProvider = WebConfigProvider;
11466
- exports.createAuthenticationService = createAuthenticationService;
11467
11319
  exports.createPersSignerSDK = createPersSignerSDK;
11468
- exports.createSearchParamLogger = createSearchParamLogger;
11469
- exports.createSearchString = createSearchString;
11470
- exports.createSigningService = createSigningService;
11471
- exports.createUrlWithSearchParams = createUrlWithSearchParams;
11472
- exports.generateAnonymousUsername = generateAnonymousUsername;
11473
- exports.generateGuestUsername = generateGuestUsername;
11474
- exports.generateUUID = generateUUID;
11475
- exports.getAllSearchParams = getAllSearchParams;
11476
11320
  exports.getBrowserWebAuthnProvider = getBrowserWebAuthnProvider;
11477
11321
  exports.getConfigProvider = getConfigProvider;
11478
11322
  exports.getHttpClient = getHttpClient;
11479
- exports.getSearchParam = getSearchParam;
11480
11323
  exports.getServiceConfig = getServiceConfig;
11481
11324
  exports.getWebAuthnProvider = getBrowserWebAuthnProvider;
11482
- exports.initTenantAnalytics = initTenantAnalytics;
11483
- exports.logSearchParams = logSearchParams;
11484
- exports.mergeSearchParams = mergeSearchParams;
11485
- exports.removeSearchParam = removeSearchParam;
11486
11325
  exports.setConfigProvider = setConfigProvider;
11487
11326
  exports.setHttpClient = setHttpClient;
11488
- exports.updateSearchParam = updateSearchParam;
11489
11327
  //# sourceMappingURL=browser.cjs.js.map