@explorins/pers-signer 1.0.12 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -50,7 +50,17 @@ class FetchHttpClient {
50
50
  setTimeout(() => controller.abort(), options.timeout);
51
51
  }
52
52
  const response = await fetch(url, fetchOptions);
53
- const data = await response.json();
53
+ // Check if response is actually JSON before parsing
54
+ const responseText = await response.text();
55
+ let data;
56
+ try {
57
+ data = JSON.parse(responseText);
58
+ }
59
+ catch (parseError) {
60
+ console.error('[HttpClient] JSON parse error:', parseError);
61
+ console.error('[HttpClient] Failed to parse response text:', responseText);
62
+ throw new Error(`Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
63
+ }
54
64
  return {
55
65
  data,
56
66
  status: response.status,
@@ -161,187 +171,280 @@ function getServiceConfig() {
161
171
  /**
162
172
  * Authentication service for user login and registration
163
173
  * Uses constructor-based dependency injection for WebAuthn provider
174
+ * Updated for new v1 API endpoints
164
175
  */
165
176
  class AuthenticationService {
166
- constructor(webAuthnProvider) {
167
- this.webAuthnProvider = webAuthnProvider;
177
+ constructor(config) {
178
+ this.signerToken = null;
179
+ this.config = config;
180
+ this.webAuthnProvider = config.webAuthnProvider;
168
181
  }
169
182
  /**
170
- * Authenticate user with username
171
- * @param username - User identifier
172
- * @returns Promise resolving to authentication token
183
+ * Login with PERS token to get signer JWT
184
+ * @param persToken - PERS JWT from PERS authentication
185
+ * @returns Promise resolving to login response or provider challenge data
173
186
  */
174
- async login(username) {
187
+ async loginWithPersToken(persToken) {
175
188
  const httpClient = getHttpClient();
176
189
  const config = getServiceConfig();
177
190
  try {
178
- const response = await httpClient.post(`${config.apiUrl}/login`, {
179
- username,
180
- appId: config.appId,
181
- });
182
- // Check if the response contains an error
183
- if (response.data.status && response.data.status >= 400) {
184
- throw new Error(response.data.message || 'Login failed');
185
- }
186
- if (!response.data.token) {
187
- throw new Error(response.data.message || 'No token received from login');
191
+ const requestBody = {
192
+ authToken: persToken
193
+ };
194
+ const response = await httpClient.post(`${config.apiUrl}/auth/login`, requestBody
195
+ // Note: Login endpoint doesn't require Authorization header (public endpoint)
196
+ );
197
+ const backendResponse = response.data;
198
+ if (backendResponse && backendResponse.success) {
199
+ // Check if it's JWT response or wrapped provider response
200
+ if ('access_token' in backendResponse) {
201
+ // JWT response - set token and return
202
+ this.signerToken = backendResponse.access_token;
203
+ return backendResponse;
204
+ }
205
+ else {
206
+ // Wrapped provider response - return the data
207
+ return backendResponse.data;
208
+ }
188
209
  }
189
- console.info(`[WebAuthn] User login successful: ${username}`);
190
- return response.data.token;
210
+ console.error('[AuthenticationService] Invalid response - no success or success=false:', backendResponse);
211
+ throw new Error('Login failed: Invalid response format');
191
212
  }
192
213
  catch (error) {
193
- console.error(`[AuthenticationService] Login failed for ${username}:`, error);
214
+ // console.error(`[AuthenticationService] PERS token login failed:`, error);
194
215
  throw error;
195
216
  }
196
217
  }
197
218
  /**
198
- * Register new user with WebAuthn credential
199
- * @param username - User identifier
200
- * @returns Promise resolving to registration result
219
+ * Verify signer token validity
220
+ * @param token - Signer JWT to verify
221
+ * @returns Promise resolving to verification result
201
222
  */
202
- async register(username) {
223
+ async verifyToken(token) {
203
224
  const httpClient = getHttpClient();
204
225
  const config = getServiceConfig();
205
226
  try {
206
- // Step 1: Initialize registration and get challenge
207
- const initResponse = await httpClient.post(`${config.apiUrl}/register/init`, {
208
- username,
209
- appId: config.appId,
210
- });
211
- const challenge = initResponse.data;
212
- // Check if the registration init failed
213
- if (challenge.status && challenge.status >= 400) {
214
- throw new Error(challenge.message || 'Registration initialization failed');
215
- }
216
- // Step 2: Create WebAuthn credential using the challenge
217
- const attestation = await this.webAuthnProvider.create(challenge);
218
- console.info(`[WebAuthn] Credential created successfully for user: ${username}`);
219
- // Step 3: Complete registration with signed challenge
220
- const completeResponse = await httpClient.post(`${config.apiUrl}/register/complete`, {
221
- signedChallenge: { firstFactorCredential: attestation },
222
- temporaryAuthenticationToken: challenge.temporaryAuthenticationToken,
223
- appId: config.appId,
224
- });
225
- const result = completeResponse.data;
226
- // Check if the registration completion failed
227
- if (result.status && result.status >= 400) {
228
- throw new Error(result.message || 'Registration completion failed');
227
+ const requestBody = {
228
+ token
229
+ };
230
+ const response = await httpClient.post(`${config.apiUrl}/auth/verify`, requestBody, { Authorization: `Bearer ${token}` });
231
+ const verifyData = response.data;
232
+ if (!verifyData.valid) {
233
+ throw new Error('Token verification failed');
229
234
  }
230
- console.info(`[AuthenticationService] Registration successful for: ${username}`);
231
- return result;
235
+ return verifyData;
232
236
  }
233
237
  catch (error) {
234
- console.error(`[AuthenticationService] Registration failed for ${username}:`, error);
238
+ console.error(`[AuthenticationService] Token verification failed:`, error);
235
239
  throw error;
236
240
  }
237
241
  }
238
242
  /**
239
- * Validate authentication token (when backend supports it)
240
- * @param authToken - Token to validate
241
- * @returns Promise resolving to validation result
243
+ * Initialize user registration
244
+ * @param persToken - PERS JWT token (registration is public)
245
+ * @returns Promise resolving to registration challenge
242
246
  */
243
- async validateToken(authToken) {
247
+ async initializeRegistration(persToken) {
244
248
  const httpClient = getHttpClient();
245
249
  const config = getServiceConfig();
246
250
  try {
247
- const response = await httpClient.post(`${config.apiUrl}/validate-token`, {
248
- authToken,
249
- appId: config.appId,
250
- });
251
- return response.data.valid === true;
251
+ const requestBody = {
252
+ authToken: persToken
253
+ };
254
+ const response = await httpClient.post(`${config.apiUrl}/auth/register/init`, requestBody);
255
+ const backendResponse = response.data;
256
+ if (backendResponse.success && backendResponse.data) {
257
+ return backendResponse.data;
258
+ }
259
+ throw new Error('Registration initialization failed: No data returned');
252
260
  }
253
261
  catch (error) {
254
- console.error('[AuthenticationService] Error validating token:', error);
255
- return false;
262
+ // console.error(`[AuthenticationService] Registration initialization failed:`, error);
263
+ throw error;
256
264
  }
257
265
  }
258
266
  /**
259
- * Get user information (when backend supports it)
260
- * @param authToken - Authentication token
261
- * @returns Promise resolving to user info or null
267
+ * Get current signer token
268
+ * @returns The current signer JWT token
262
269
  */
263
- async getUser(authToken) {
264
- const httpClient = getHttpClient();
265
- try {
266
- const response = await httpClient.get(`${getServiceConfig().apiUrl}/user`, {
267
- 'Authorization': `Bearer ${authToken}`
268
- });
269
- return response.data;
270
- }
271
- catch (error) {
272
- console.error('[AuthenticationService] Error getting user:', error);
273
- return null;
274
- }
270
+ getSignerToken() {
271
+ return this.signerToken;
275
272
  }
276
- }
277
-
278
- /**
279
- * Wallet management service
280
- * Handles wallet listing and management operations
281
- */
282
- class WalletService {
283
273
  /**
284
- * List all wallets for authenticated user
285
- * @param authToken - Authentication token
286
- * @returns Promise resolving to wallet list
274
+ * Set signer token (for external token management)
275
+ * @param token - Signer JWT token
276
+ */
277
+ setSignerToken(token) {
278
+ this.signerToken = token;
279
+ }
280
+ /**
281
+ * Complete registration with WebAuthn challenge data (v1 API format)
282
+ * @param tmpAuthToken - Temporary auth token from init registration (temporaryAuthenticationToken)
283
+ * @param signedChallenge - WebAuthn credential response (will be restructured for backend)
284
+ * @param persToken - PERS JWT token (authToken)
285
+ * @returns Promise resolving to registration result
287
286
  */
288
- static async listWallets(authToken) {
287
+ async completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persToken) {
289
288
  const httpClient = getHttpClient();
290
289
  const config = getServiceConfig();
291
290
  try {
292
- const response = await httpClient.post(`${config.apiUrl}/wallets/list`, {
293
- authToken,
294
- appId: config.appId,
295
- });
296
- console.log('[WalletService] Raw API response:', response.data);
297
- return response.data;
291
+ const requestBody = {
292
+ temporaryAuthenticationToken: tmpAuthToken,
293
+ signedChallenge: {
294
+ firstFactorCredential: {
295
+ credentialKind: signedChallenge.credentialKind || "Fido2",
296
+ credentialInfo: {
297
+ credId: signedChallenge.credentialInfo?.credId || "",
298
+ clientData: signedChallenge.credentialInfo?.clientData || "",
299
+ attestationData: signedChallenge.credentialInfo?.attestationData || ""
300
+ }
301
+ }
302
+ },
303
+ authToken: persToken
304
+ };
305
+ const response = await httpClient.post(`${config.apiUrl}/auth/register/complete`, requestBody);
306
+ const backendResponse = response.data;
307
+ if (backendResponse && backendResponse.success) {
308
+ // Check if it's JWT response (has access_token at root level) or wrapped provider response
309
+ if ('access_token' in backendResponse) {
310
+ // JWT response - set token and return
311
+ this.signerToken = backendResponse.access_token;
312
+ return backendResponse;
313
+ }
314
+ else if ('data' in backendResponse) {
315
+ // Wrapped provider response - registration is not complete yet, needs more steps
316
+ return backendResponse.data;
317
+ }
318
+ else {
319
+ // Wrapped provider response - return the data
320
+ return backendResponse.data;
321
+ }
322
+ }
323
+ console.error('[AuthenticationService] Registration with challenge failed - invalid response:', backendResponse);
324
+ throw new Error('Registration completion with challenge failed');
298
325
  }
299
326
  catch (error) {
300
- console.error('[WalletService] Error listing wallets:', error);
327
+ console.error(`[AuthenticationService] Registration completion with challenge failed:`, error);
301
328
  throw error;
302
329
  }
303
330
  }
304
331
  /**
305
- * Get wallet by ID (when backend supports it)
306
- * @param authToken - Authentication token
307
- * @param walletId - Wallet identifier
308
- * @returns Promise resolving to wallet details
332
+ * Combined authentication flow - handles both login and registration
333
+ * @param identifier - User identifier (email/userId)
334
+ * @param persAccessToken - PERS JWT token for authentication
335
+ * @param webAuthnProvider - WebAuthn provider for credential creation
336
+ * @param relyingPartyConfig - Configuration for WebAuthn relying party
337
+ * @returns Promise resolving to authenticated user with signer token
309
338
  */
310
- static async getWallet(authToken, walletId) {
311
- const httpClient = getHttpClient();
312
- const config = getServiceConfig();
339
+ async combinedAuthentication(identifier, persAccessToken) {
313
340
  try {
314
- const response = await httpClient.post(`${config.apiUrl}/wallets/get`, {
315
- authToken,
316
- walletId,
317
- appId: config.appId,
318
- });
319
- return response.data;
341
+ // Step 1: Try to login with PERS token first (this will get signer JWT if user exists)
342
+ let signerToken;
343
+ try {
344
+ const loginResult = await this.loginWithPersToken(persAccessToken);
345
+ // Extract token from login result
346
+ if (loginResult && typeof loginResult === 'object' && 'access_token' in loginResult) {
347
+ signerToken = loginResult.access_token;
348
+ }
349
+ else {
350
+ throw new Error('Invalid login response format');
351
+ }
352
+ }
353
+ catch (loginError) {
354
+ // Step 2: User doesn't exist - register with v1 API
355
+ // 2a. Initialize registration
356
+ const initResult = await this.initializeRegistration(persAccessToken);
357
+ let signedChallenge = null;
358
+ let tmpAuthToken = null;
359
+ // 2b. Create WebAuthn credential using the challenge (if available)
360
+ if (initResult && typeof initResult === 'object') {
361
+ // Extract challenge and tmpAuthToken from init result with better type safety
362
+ const initData = initResult;
363
+ const challenge = initData.challenge;
364
+ tmpAuthToken = initData.temporaryAuthenticationToken || initData.tmpAuthToken || null;
365
+ if (challenge && initData.user) {
366
+ // Construct proper WebAuthn credential creation options
367
+ const credentialCreationOptions = {
368
+ challenge: challenge,
369
+ rp: {
370
+ name: this.config.relyingPartyName || 'PERS Signer',
371
+ id: typeof window !== 'undefined' ? window.location.hostname : 'localhost'
372
+ },
373
+ user: {
374
+ id: initData.user.id,
375
+ name: initData.user.name,
376
+ displayName: initData.user.displayName
377
+ },
378
+ pubKeyCredParams: initData.pubKeyCredParams || [
379
+ { alg: -7, type: "public-key" },
380
+ { alg: -257, type: "public-key" }
381
+ ],
382
+ authenticatorSelection: initData.authenticatorSelection || {},
383
+ attestation: initData.attestation || "direct",
384
+ excludeCredentials: initData.excludeCredentials || []
385
+ };
386
+ signedChallenge = await this.webAuthnProvider.create(credentialCreationOptions);
387
+ }
388
+ else {
389
+ console.warn(`[PersSignerSDK] Missing challenge or user data in init result`);
390
+ }
391
+ }
392
+ // 2c. Complete registration with proper challenge data - call AuthenticationService directly
393
+ const completeResult = await this.completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persAccessToken);
394
+ // Extract token from registration result
395
+ if (completeResult && typeof completeResult === 'object') {
396
+ // Check if this is a complete JWT response with access_token
397
+ if ('access_token' in completeResult) {
398
+ signerToken = completeResult.access_token;
399
+ }
400
+ else {
401
+ // This is a wrapped provider response - registration is not complete
402
+ // We have user created and wallet created, but need to get JWT token
403
+ // For now, throw an error indicating we need additional backend integration steps
404
+ throw new Error('Registration created user and wallet but JWT token not provided. Backend integration incomplete.');
405
+ }
406
+ }
407
+ else {
408
+ throw new Error('Registration completed but no response received');
409
+ }
410
+ }
411
+ return {
412
+ identifier,
413
+ signerAuthToken: signerToken,
414
+ persAccessToken
415
+ };
320
416
  }
321
417
  catch (error) {
322
- console.error(`[WalletService] Error getting wallet ${walletId}:`, error);
323
- throw error;
418
+ console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, error);
419
+ throw new Error(`Combined authentication failed: ${error}`);
324
420
  }
325
421
  }
422
+ }
423
+
424
+ /**
425
+ * Wallet management service
426
+ * Updated for v1 API endpoints per migration reference
427
+ */
428
+ class WalletService {
326
429
  /**
327
- * Create new wallet (when backend supports it)
328
- * @param authToken - Authentication token
329
- * @param walletConfig - Wallet configuration
330
- * @returns Promise resolving to wallet creation result
430
+ * List all wallets for authenticated user
431
+ * @param signerToken - Signer JWT token
432
+ * @returns Promise resolving to wallet data
331
433
  */
332
- static async createWallet(authToken, walletConfig) {
434
+ static async listWallets(signerToken) {
333
435
  const httpClient = getHttpClient();
334
436
  const config = getServiceConfig();
335
437
  try {
336
- const response = await httpClient.post(`${config.apiUrl}/wallets/create`, {
337
- authToken,
338
- ...walletConfig,
339
- appId: config.appId,
340
- });
341
- return response.data;
438
+ const requestBody = {};
439
+ const response = await httpClient.post(`${config.apiUrl}/wallets/list`, requestBody, { Authorization: `Bearer ${signerToken}` });
440
+ const backendResponse = response.data;
441
+ if (backendResponse.success && backendResponse.data) {
442
+ return backendResponse.data;
443
+ }
444
+ throw new Error('Failed to list wallets: Invalid response');
342
445
  }
343
446
  catch (error) {
344
- console.error('[WalletService] Error creating wallet:', error);
447
+ console.error('[WalletService] Error listing wallets:', error);
345
448
  throw error;
346
449
  }
347
450
  }
@@ -401,27 +504,36 @@ class SigningService {
401
504
  const config = getServiceConfig();
402
505
  try {
403
506
  // Step 1: Initialize signing and get challenge
404
- const initResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/init`, {
405
- authToken,
507
+ const initRequest = {
406
508
  walletId,
407
- request,
408
- appId: config.appId,
409
- });
410
- const { requestBody, challenge } = initResponse.data;
509
+ request
510
+ };
511
+ const initResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/init`, initRequest, { Authorization: `Bearer ${authToken}` });
512
+ const backendInitResponse = initResponse.data;
513
+ if (!backendInitResponse.success || !backendInitResponse.data) {
514
+ console.error('[SigningService] Init response invalid:', backendInitResponse);
515
+ throw new Error('Signature initialization failed');
516
+ }
517
+ const { requestBody, challenge } = backendInitResponse.data;
411
518
  // Step 2: Sign the challenge using WebAuthn
412
519
  const assertion = await this.webAuthnProvider.sign(challenge);
413
520
  // Step 3: Complete signing with signed challenge
414
- const completeResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/complete`, {
415
- authToken,
521
+ const completeRequest = {
416
522
  walletId,
417
523
  requestBody,
418
524
  signedChallenge: {
419
- challengeIdentifier: challenge.challengeIdentifier,
525
+ challengeIdentifier: challenge?.challengeIdentifier,
420
526
  firstFactor: assertion,
421
- },
422
- appId: config.appId,
423
- });
424
- return completeResponse.data;
527
+ }
528
+ };
529
+ const completeResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/complete`, completeRequest, { Authorization: `Bearer ${authToken}` });
530
+ const backendCompleteResponse = completeResponse.data;
531
+ if (!backendCompleteResponse.success || !backendCompleteResponse.data) {
532
+ console.error('[SigningService] Complete response invalid:', backendCompleteResponse);
533
+ throw new Error('Signature completion failed');
534
+ }
535
+ // Return the complete signature data from backend response
536
+ return backendCompleteResponse.data;
425
537
  }
426
538
  catch (error) {
427
539
  console.error(`[SigningService] Signing failed for wallet ${walletId}:`, error);
@@ -522,10 +634,10 @@ class PersService {
522
634
  * @returns Promise with tenant public information
523
635
  */
524
636
  static async getTenantById(tenantId, authToken) {
525
- // Check memory cache first
637
+ // Check memory cache first with expiration
526
638
  const cached = this.tenantCache.get(tenantId);
527
- if (cached) {
528
- return cached;
639
+ if (cached && Date.now() < cached.expiresAt) {
640
+ return cached.tenant;
529
641
  }
530
642
  try {
531
643
  const headers = {
@@ -563,10 +675,16 @@ class PersService {
563
675
  }
564
676
  }
565
677
  const tenantData = await response.json();
566
- // Cache the tenant data in memory only
567
- this.tenantCache.set(tenantId, tenantData);
678
+ // Cache the tenant data with expiration
679
+ const now = Date.now();
680
+ this.tenantCache.set(tenantId, {
681
+ tenant: tenantData,
682
+ cachedAt: now,
683
+ expiresAt: now + this.TENANT_CACHE_TTL
684
+ });
568
685
  // Update current project key (check multiple possible property names)
569
686
  this.currentProjectKey = tenantData.projectKey || tenantData.projectApiKey || tenantData.apiKey;
687
+ this.currentTenantId = tenantId;
570
688
  return tenantData;
571
689
  }
572
690
  catch (error) {
@@ -586,14 +704,35 @@ class PersService {
586
704
  const projectKey = tenantData.projectApiKey;
587
705
  if (projectKey) {
588
706
  this.currentProjectKey = projectKey;
707
+ this.currentTenantId = tenantId;
589
708
  }
590
709
  return tenantData;
591
710
  }
711
+ /**
712
+ * Ensure tenant is initialized and current, with automatic revalidation
713
+ * @param tenantId - The tenant ID to ensure is initialized
714
+ * @param authToken - Optional auth token for authentication
715
+ * @returns Promise with tenant information
716
+ */
717
+ static async ensureTenantInitialized(tenantId, authToken) {
718
+ // Check if we already have the right tenant initialized and it's still valid
719
+ const cached = this.tenantCache.get(tenantId);
720
+ if (this.currentTenantId === tenantId && cached && Date.now() < cached.expiresAt) {
721
+ return cached.tenant;
722
+ }
723
+ // Initialize or refresh tenant data
724
+ return await this.initializeTenant(tenantId, authToken);
725
+ }
592
726
  /**
593
727
  * Get the current project key (either from tenant or fallback)
728
+ * @param tenantId - Optional tenant ID to ensure is initialized
594
729
  * @returns The project key to use for API calls
595
730
  */
596
- static getProjectKey() {
731
+ static async getProjectKey(tenantId) {
732
+ // If tenantId provided and different from current, initialize it
733
+ if (tenantId && this.currentTenantId !== tenantId) {
734
+ await this.ensureTenantInitialized(tenantId);
735
+ }
597
736
  if (this.currentProjectKey) {
598
737
  return this.currentProjectKey;
599
738
  }
@@ -609,17 +748,19 @@ class PersService {
609
748
  static clearTenantCache() {
610
749
  this.tenantCache.clear();
611
750
  this.currentProjectKey = null;
751
+ this.currentTenantId = null;
612
752
  }
613
753
  /**
614
754
  * Authenticates a user with the PERS backend using their auth token
615
755
  *
616
756
  * @param authToken - The authentication token received from DFNS after login/registration
757
+ * @param tenantId - Optional tenant ID for automatic initialization
617
758
  * @returns A promise that resolves to the authentication response
618
759
  * @throws If the request fails
619
760
  */
620
- static async authenticateUser(authToken) {
761
+ static async authenticateUser(authToken, tenantId) {
621
762
  try {
622
- const projectKey = this.getProjectKey();
763
+ const projectKey = await this.getProjectKey(tenantId);
623
764
  const headers = {
624
765
  'accept': 'application/json',
625
766
  'x-project-key': projectKey,
@@ -669,30 +810,6 @@ class PersService {
669
810
  };
670
811
  }
671
812
  }
672
- /**
673
- * Prepares a transaction by calling the backend endpoint
674
- *
675
- * @param form - The transaction details
676
- * @param persAccessToken - The PERS access token for authentication (Bearer)
677
- * @returns A promise that resolves to the transaction preparation response
678
- * @throws If the request fails
679
- */
680
- static async prepareTransaction(form, persAccessToken) {
681
- // ✅ UPDATED: /transaction/auth/prepare-signing → /transactions/prepare
682
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions`, {
683
- method: "POST",
684
- headers: {
685
- "Content-Type": "application/json",
686
- 'x-project-key': this.getProjectKey(),
687
- Authorization: `Bearer ${persAccessToken}`,
688
- },
689
- body: JSON.stringify(form),
690
- });
691
- if (!res.ok)
692
- throw new Error("Failed to prepare transaction");
693
- const response = await res.json();
694
- return response;
695
- }
696
813
  /**
697
814
  * Submits a transaction by calling the backend endpoint
698
815
  *
@@ -700,208 +817,86 @@ class PersService {
700
817
  * @param signedTransactionOrSignature - The signed transaction data or EIP-712 signature
701
818
  * @param persAccessToken - The PERS access token for authentication (Bearer)
702
819
  * @param submissionType - The transaction format type
820
+ * @param tenantId - Optional tenant ID for automatic initialization
703
821
  * @returns A promise that resolves to the transaction submission response
704
822
  * @throws If the request fails
705
823
  */
706
- static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType) {
707
- // Map TransactionFormat to the backend's expected submission type
708
- const dto = {
709
- transactionId,
710
- type: submissionType,
711
- ...(submissionType === browser.TRANSACTION_FORMATS.EIP_712
712
- ? { signature: signedTransactionOrSignature }
713
- : { signedTransaction: signedTransactionOrSignature })
714
- };
715
- // ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
716
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
717
- method: "POST",
718
- headers: {
719
- "Content-Type": "application/json",
720
- 'x-project-key': this.getProjectKey(),
721
- Authorization: `Bearer ${persAccessToken}`,
722
- },
723
- body: JSON.stringify(dto),
724
- });
725
- if (!res.ok)
726
- throw new Error("Failed to submit transaction");
727
- return await res.json();
728
- }
729
- /**
730
- * Fetches a prepared transaction for signing by transactionId
731
- * @param transactionId - The transaction ID to fetch
732
- * @param persAccessToken - The PERS access token for authentication (Bearer)
733
- * @returns The prepared transaction data
734
- * @throws If the request fails
735
- */
736
- static async fetchPreparedTransaction(transactionId, persAccessToken) {
737
- // ✅ UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
738
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
739
- headers: {
740
- "Content-Type": "application/json",
741
- "x-project-key": this.getProjectKey(),
742
- Authorization: `Bearer ${persAccessToken}`,
743
- },
744
- });
745
- const response = await res.json();
746
- if (!res.ok) {
747
- // Throw structured error with the backend response
748
- throw {
749
- status: res.status,
750
- message: response.message || "Failed to fetch prepared transaction",
751
- error: response
752
- };
753
- }
754
- return response;
755
- }
756
- /**
757
- * Claims a reward from the PERS blockchain system
758
- * @param rewardId - The ID of the reward to claim
759
- * @param pointsCost - The points cost for the reward
760
- * @param persAccessToken - The PERS access token for authentication (Bearer)
761
- * @returns The reward claim response including reward image URL
762
- * @throws If the request fails
763
- */
764
- static async claimReward(rewardId, pointsCost, persAccessToken) {
765
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/rewards/auth/claim`, {
766
- method: "POST",
767
- headers: {
768
- "Content-Type": "application/json",
769
- 'x-project-key': this.getProjectKey(),
770
- Authorization: `Bearer ${persAccessToken}`,
771
- },
772
- body: JSON.stringify({
773
- rewardId,
774
- pointsCost
775
- }),
776
- });
777
- if (!res.ok) {
778
- const error = await res.text();
779
- throw new Error(`Failed to claim reward: ${error}`);
780
- }
781
- return await res.json();
782
- }
783
- /**
784
- * Get all active campaigns
785
- * @returns Promise with list of active campaigns
786
- */
787
- static async getActiveCampaigns() {
788
- try {
789
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign`, {
790
- method: 'GET',
791
- headers: {
792
- 'accept': 'application/json',
793
- 'x-project-key': this.getProjectKey()
794
- }
795
- });
796
- if (!response.ok) {
797
- const errorData = await response.json().catch(() => ({}));
798
- throw {
799
- status: response.status,
800
- message: errorData.message || 'Failed to fetch active campaigns',
801
- error: errorData
802
- };
803
- }
804
- const data = await response.json();
805
- return data;
806
- }
807
- catch (error) {
808
- throw error;
809
- }
810
- }
811
- /**
812
- * Claims a campaign for a user
813
- * @param campaignId - The ID of the campaign to claim
814
- * @param persAccessToken - The PERS access token for authentication (Bearer)
815
- * @returns Promise with the campaign claim response
816
- * @throws If the request fails
817
- */
818
- static async claimCampaign(campaignId, persAccessToken) {
824
+ static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType, tenantId) {
819
825
  try {
820
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign/auth/claim`, {
821
- method: 'POST',
826
+ // Map TransactionFormat to the backend's expected submission type
827
+ const dto = {
828
+ transactionId,
829
+ type: submissionType,
830
+ ...(submissionType === browser.TRANSACTION_FORMATS.EIP_712
831
+ ? { signature: signedTransactionOrSignature }
832
+ : { signedTransaction: signedTransactionOrSignature })
833
+ };
834
+ // ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
835
+ const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
836
+ method: "POST",
822
837
  headers: {
823
- 'accept': 'application/json',
824
- 'x-project-key': this.getProjectKey(),
825
- 'Authorization': `Bearer ${persAccessToken}`,
826
- 'Content-Type': 'application/json'
838
+ "Content-Type": "application/json",
839
+ 'x-project-key': await this.getProjectKey(tenantId),
840
+ Authorization: `Bearer ${persAccessToken}`,
827
841
  },
828
- body: JSON.stringify({
829
- campaignId
830
- })
842
+ body: JSON.stringify(dto),
831
843
  });
832
- if (!response.ok) {
833
- const errorData = await response.json().catch(() => ({}));
844
+ const response = await res.json();
845
+ if (!res.ok) {
846
+ // Throw structured error with the backend response
834
847
  throw {
835
- status: response.status,
836
- message: errorData.message || 'Failed to claim campaign',
837
- error: errorData
848
+ status: res.status,
849
+ message: response.message || "Failed to submit transaction",
850
+ error: response
838
851
  };
839
852
  }
840
- const data = await response.json();
841
- return data;
853
+ return response;
842
854
  }
843
855
  catch (error) {
844
- throw error;
856
+ // Rethrow a structured error
857
+ throw {
858
+ status: error.status || 500,
859
+ message: error.message || 'Failed to submit transaction to PERS backend',
860
+ error
861
+ };
845
862
  }
846
863
  }
847
864
  /**
848
- * Gets all campaign claims for the authenticated user
865
+ * Fetches a prepared transaction for signing by transactionId
866
+ * @param transactionId - The transaction ID to fetch
849
867
  * @param persAccessToken - The PERS access token for authentication (Bearer)
850
- * @returns Promise with list of user's campaign claims
851
- * @throws If the request fails
852
- */
853
- static async getUserCampaignClaims(persAccessToken) {
854
- try {
855
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign/auth/claim`, {
856
- method: 'GET',
857
- headers: {
858
- 'accept': 'application/json',
859
- 'x-project-key': this.getProjectKey(),
860
- 'Authorization': `Bearer ${persAccessToken}`
861
- }
862
- });
863
- if (!response.ok) {
864
- const errorData = await response.json().catch(() => ({}));
865
- throw {
866
- status: response.status,
867
- message: errorData.message || 'Failed to fetch user campaign claims',
868
- error: errorData
869
- };
870
- }
871
- const data = await response.json();
872
- return data;
873
- }
874
- catch (error) {
875
- throw error;
876
- }
877
- }
878
- /**
879
- * Gets all available rewards for redemption
880
- * @returns Promise with list of available rewards that can be exchanged for points
868
+ * @param tenantId - Optional tenant ID for automatic initialization
869
+ * @returns The prepared transaction data
881
870
  * @throws If the request fails
882
871
  */
883
- static async getAvailableRedemptions() {
872
+ static async fetchPreparedTransaction(transactionId, persAccessToken, tenantId) {
884
873
  try {
885
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/redemption`, {
886
- method: 'GET',
874
+ // UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
875
+ const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
887
876
  headers: {
888
- 'accept': 'application/json',
889
- 'x-project-key': this.getProjectKey()
890
- }
877
+ "Content-Type": "application/json",
878
+ "x-project-key": await this.getProjectKey(tenantId),
879
+ Authorization: `Bearer ${persAccessToken}`,
880
+ },
891
881
  });
892
- if (!response.ok) {
893
- const errorData = await response.json().catch(() => ({}));
882
+ const response = await res.json();
883
+ if (!res.ok) {
884
+ // Throw structured error with the backend response
894
885
  throw {
895
- status: response.status,
896
- message: errorData.message || 'Failed to fetch available redemptions',
897
- error: errorData
886
+ status: res.status,
887
+ message: response.message || "Failed to fetch prepared transaction",
888
+ error: response
898
889
  };
899
890
  }
900
- const data = await response.json();
901
- return data;
891
+ return response;
902
892
  }
903
893
  catch (error) {
904
- throw error;
894
+ // Rethrow a structured error
895
+ throw {
896
+ status: error.status || 500,
897
+ message: error.message || 'Failed to fetch prepared transaction from PERS backend',
898
+ error
899
+ };
905
900
  }
906
901
  }
907
902
  /**
@@ -919,7 +914,7 @@ class PersService {
919
914
  * @throws If transaction data is not available
920
915
  */
921
916
  static getTransactionDataForSigning(transactionResponse) {
922
- if (!this.isTransactionReadyForSigning(transactionResponse)) {
917
+ if (!transactionResponse.signingData) {
923
918
  throw new Error('Transaction data is not ready for signing');
924
919
  }
925
920
  return transactionResponse.signingData;
@@ -928,8 +923,79 @@ class PersService {
928
923
  PersService.config = DEFAULT_PERS_CONFIG;
929
924
  PersService.tenantCache = new Map();
930
925
  PersService.currentProjectKey = null;
926
+ PersService.currentTenantId = null;
931
927
  PersService.useStaging = false;
928
+ PersService.TENANT_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours - tenant configs are essentially static
929
+
930
+ /**
931
+ * Health check service for signer API
932
+ * Handles health check operations for the new v1 API
933
+ */
934
+ class HealthService {
935
+ /**
936
+ * Perform health check on the signer API
937
+ * @returns Promise resolving to health check result
938
+ */
939
+ static async checkHealth() {
940
+ const httpClient = getHttpClient();
941
+ const config = getServiceConfig();
942
+ try {
943
+ const response = await httpClient.get(`${config.apiUrl}/health`);
944
+ return response.data;
945
+ }
946
+ catch (error) {
947
+ console.error('[HealthService] Health check failed:', error);
948
+ // Return failed health check instead of throwing
949
+ return {
950
+ success: false
951
+ };
952
+ }
953
+ }
954
+ }
932
955
 
956
+ // Adapter function to convert backend signature response to legacy format
957
+ const adaptSignatureResponse = (newResponse) => {
958
+ if (!newResponse.signature) {
959
+ console.error('[KeyWallet] No signature in response:', newResponse);
960
+ throw new Error('Signature missing from response');
961
+ }
962
+ // Handle structured signature format (typically from EIP-712 signing)
963
+ if (typeof newResponse.signature === 'object' && 'r' in newResponse.signature) {
964
+ const { r, s, recid } = newResponse.signature;
965
+ return {
966
+ status: 'Signed',
967
+ signature: {
968
+ r,
969
+ s,
970
+ recid // Use recid directly from backend
971
+ }
972
+ };
973
+ }
974
+ // Handle string signature format (potentially from hash signing)
975
+ if (typeof newResponse.signature === 'string') {
976
+ // Parse string signature into components
977
+ // Assuming it's a hex string that can be split into r, s, v components
978
+ const signature = newResponse.signature;
979
+ if (signature.length === 132) { // 0x + 32 bytes r + 32 bytes s + 1 byte v
980
+ const r = '0x' + signature.slice(2, 66);
981
+ const s = '0x' + signature.slice(66, 130);
982
+ const v = parseInt(signature.slice(130, 132), 16);
983
+ const recid = v >= 27 ? v - 27 : v; // Convert v to recid
984
+ return {
985
+ status: 'Signed',
986
+ signature: {
987
+ r,
988
+ s,
989
+ recid
990
+ }
991
+ };
992
+ }
993
+ console.error('[KeyWallet] Invalid string signature length:', signature.length);
994
+ throw new Error(`Invalid string signature format - expected 132 characters, got ${signature.length}`);
995
+ }
996
+ console.error('[KeyWallet] Invalid signature format - expected structured object or string:', typeof newResponse.signature);
997
+ throw new Error('Invalid signature format in response - expected structured object or string from backend');
998
+ };
933
999
  let DfnsError$1 = class DfnsError extends Error {
934
1000
  constructor(code, message, details) {
935
1001
  super(message);
@@ -948,14 +1014,16 @@ const assertSigned = (res) => {
948
1014
  };
949
1015
  const combineSignature = (res) => {
950
1016
  if (!res.signature) {
1017
+ console.error('[KeyWallet] No signature in response for combining:', res);
951
1018
  throw new DfnsError$1(-1, "signature missing", res);
952
1019
  }
953
1020
  const { r, s, recid } = res.signature;
954
- return ethers.Signature.from({
1021
+ const ethersSignature = ethers.Signature.from({
955
1022
  r,
956
1023
  s,
957
1024
  v: recid ? 0x1c : 0x1b, // Assuming 0x1c for chain_id > 0, 0x1b otherwise. DFNS usually provides recid.
958
- }).serialized;
1025
+ });
1026
+ return ethersSignature.serialized;
959
1027
  };
960
1028
  class KeyWallet extends ethers.AbstractSigner {
961
1029
  constructor(options, provider) {
@@ -981,9 +1049,11 @@ class KeyWallet extends ethers.AbstractSigner {
981
1049
  return this.address;
982
1050
  }
983
1051
  async signHash(hash) {
984
- const res = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
1052
+ const rawRes = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
1053
+ const res = adaptSignatureResponse(rawRes);
985
1054
  assertSigned(res);
986
- return combineSignature(res);
1055
+ const combinedSignature = combineSignature(res);
1056
+ return combinedSignature;
987
1057
  }
988
1058
  async signTransaction(tx) {
989
1059
  // Resolve any addresses
@@ -1020,9 +1090,11 @@ class KeyWallet extends ethers.AbstractSigner {
1020
1090
  async signTypedData(domain, types, value) {
1021
1091
  try {
1022
1092
  // Use DFNS native EIP-712 support with correct structure
1023
- const res = await this.signingService.signTypedData(this.authToken, this.metadata.id, domain, types, value);
1093
+ const rawRes = await this.signingService.signTypedData(this.authToken, this.metadata.id, domain, types, value);
1094
+ const res = adaptSignatureResponse(rawRes);
1024
1095
  assertSigned(res);
1025
- return combineSignature(res);
1096
+ const combinedSignature = combineSignature(res);
1097
+ return combinedSignature;
1026
1098
  }
1027
1099
  catch (error) {
1028
1100
  console.error('[KeyWallet] EIP-712 signing failed:', error);
@@ -1270,8 +1342,8 @@ class TransactionValidator {
1270
1342
  if (!params.authTokens) {
1271
1343
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Authentication tokens are required', params.transactionId);
1272
1344
  }
1273
- if (!params.authTokens.backendAuthToken || !params.authTokens.persAccessToken) {
1274
- throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both backend and PERS authentication tokens are required', params.transactionId);
1345
+ if (!params.authTokens.signerAuthToken || !params.authTokens.persAccessToken) {
1346
+ throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both signer and PERS authentication tokens are required', params.transactionId);
1275
1347
  }
1276
1348
  if (!params.ethersProviderUrl || typeof params.ethersProviderUrl !== 'string') {
1277
1349
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Ethers provider URL is required', params.transactionId);
@@ -1283,84 +1355,21 @@ class TransactionValidator {
1283
1355
  * @returns true if valid, throws error if invalid
1284
1356
  */
1285
1357
  static validateAuthTokens(authTokens) {
1286
- if (!authTokens.persAccessToken || !authTokens.backendAuthToken) {
1287
- throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and backend auth token are required');
1358
+ if (!authTokens.persAccessToken || !authTokens.signerAuthToken) {
1359
+ throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and signer auth token are required');
1288
1360
  }
1289
1361
  return true;
1290
1362
  }
1291
1363
  }
1292
1364
 
1293
1365
  /**
1294
- * Handles transaction submission, success flows, and redirect logic
1366
+ * Utility class for coordinating WebAuthn operations to prevent conflicts
1367
+ * Manages global state flags to ensure only one WebAuthn operation runs at a time
1295
1368
  */
1296
- class TransactionSubmissionHandler {
1369
+ class WebAuthnCoordinator {
1297
1370
  /**
1298
- * Create redirect URL with transaction parameters
1299
- * @param returnUrl - Base return URL
1300
- * @param transactionHash - Transaction hash to include
1301
- * @param metadata - Optional metadata to include as parameters
1302
- * @returns Promise resolving to the complete redirect URL
1303
- */
1304
- static async createRedirectUrl(returnUrl, transactionHash, metadata) {
1305
- const additionalParams = {
1306
- txHash: transactionHash,
1307
- success: 'true'
1308
- };
1309
- // Add metadata if available
1310
- if (metadata) {
1311
- Object.entries(metadata).forEach(([key, value]) => {
1312
- // metadata is already constrained to string values by TransactionMetadata interface
1313
- additionalParams[key] = value;
1314
- });
1315
- }
1316
- // Dynamic import to avoid build issues
1317
- const { createUrlWithSearchParams } = await Promise.resolve().then(function () { return searchParams; });
1318
- return createUrlWithSearchParams(returnUrl, ['transactionId', 'returnUrl', 'jwt', 'token'], additionalParams);
1319
- }
1320
- /**
1321
- * Submit transaction and handle success flow
1322
- * @param preparedTransaction - The prepared transaction
1323
- * @param signature - The transaction signature
1324
- * @param signingData - The signing data used
1325
- * @param authTokens - Authentication tokens
1326
- * @param transactionId - Transaction ID for tracking
1327
- * @param returnUrl - Optional return URL for redirect
1328
- * @param metadata - Optional metadata for redirect parameters
1329
- * @returns Promise resolving to submission result
1330
- */
1331
- static async handleTransactionSubmission(preparedTransaction, signature, signingData, authTokens, returnUrl, metadata) {
1332
- // Submit signed transaction
1333
- const submitResult = await PersService.submitTransaction(preparedTransaction.transaction.id, signature, authTokens.persAccessToken, signingData.format);
1334
- // Get transaction hash for return URL or response
1335
- const transactionHash = submitResult.transaction?.transactionHash || '';
1336
- // Handle success - check for return URL
1337
- if (returnUrl) {
1338
- const redirectUrl = await this.createRedirectUrl(returnUrl, transactionHash, metadata);
1339
- return { submitResult, shouldRedirect: true, redirectUrl };
1340
- }
1341
- else {
1342
- return { submitResult, shouldRedirect: false };
1343
- }
1344
- }
1345
- }
1346
-
1347
- /**
1348
- * Utility class for coordinating WebAuthn operations to prevent conflicts
1349
- * Manages global state flags to ensure only one WebAuthn operation runs at a time
1350
- */
1351
- class WebAuthnCoordinator {
1352
- /**
1353
- * Clear the landing authentication flag
1354
- * Used when starting a new transaction signing flow
1355
- */
1356
- static clearLandingAuthentication() {
1357
- if (typeof window !== 'undefined') {
1358
- window.landingAuthenticationInProgress = false;
1359
- }
1360
- }
1361
- /**
1362
- * Check if a WebAuthn operation is currently in progress
1363
- * @returns True if an operation is in progress, false otherwise
1371
+ * Check if a WebAuthn operation is currently in progress
1372
+ * @returns True if an operation is in progress, false otherwise
1364
1373
  */
1365
1374
  static checkConcurrentOperations() {
1366
1375
  if (typeof window !== 'undefined') {
@@ -1390,16 +1399,16 @@ const SIGNABLE_STATUSES = [browser.TransactionStatus.PENDING_SIGNATURE, browser.
1390
1399
  * Uses constructor-based dependency injection for WebAuthn provider
1391
1400
  */
1392
1401
  class TransactionSigningService {
1393
- constructor(webAuthnProvider) {
1394
- this.webAuthnProvider = webAuthnProvider;
1402
+ constructor(config) {
1403
+ this.webAuthnProvider = config.webAuthnProvider;
1395
1404
  }
1396
1405
  /**
1397
1406
  * Prepare transaction for signing - fetch and validate
1398
1407
  */
1399
- async prepareTransaction(transactionId, authTokens) {
1408
+ async prepareTransaction(transactionId, authTokens, tenantId) {
1400
1409
  let preparedTransaction;
1401
1410
  try {
1402
- preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken);
1411
+ preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken, tenantId);
1403
1412
  }
1404
1413
  catch (err) {
1405
1414
  // Use TransactionErrorHandler to process PERS API errors
@@ -1420,7 +1429,7 @@ class TransactionSigningService {
1420
1429
  // Authenticate with PERS using backend signer token to set up signing account
1421
1430
  let updatedPersAccessToken = authTokens.persAccessToken;
1422
1431
  try {
1423
- const persSignerAuth = await PersService.authenticateUser(authTokens.backendAuthToken);
1432
+ const persSignerAuth = await PersService.authenticateUser(authTokens.signerAuthToken, tenantId);
1424
1433
  // Update PERS access token with the new one that has signing account linked
1425
1434
  const newPersAccessToken = persSignerAuth.accessToken;
1426
1435
  if (newPersAccessToken) {
@@ -1434,7 +1443,7 @@ class TransactionSigningService {
1434
1443
  }
1435
1444
  // If transaction is 'created' status but doesn't have signingData, fetch again with wallet-enabled token
1436
1445
  if (transactionStatus === browser.TransactionStatus.CREATED && !PersService.isTransactionReadyForSigning(preparedTransaction)) {
1437
- preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken);
1446
+ preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken, tenantId);
1438
1447
  }
1439
1448
  if (!PersService.isTransactionReadyForSigning(preparedTransaction)) {
1440
1449
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.TRANSACTION_NOT_READY, 'Transaction is not ready for signing', transactionId);
@@ -1449,19 +1458,21 @@ class TransactionSigningService {
1449
1458
  */
1450
1459
  async prepareWallet(authTokens, ethersProviderUrl) {
1451
1460
  // Wallet validation will be handled through API responses - no localStorage needed
1452
- let walletData;
1461
+ let walletListResult;
1453
1462
  try {
1454
- walletData = await WalletService.listWallets(authTokens.backendAuthToken);
1455
- console.log('[TransactionSigningService] Wallet list response:', walletData);
1463
+ // Use new WalletService API with signer token
1464
+ walletListResult = await WalletService.listWallets(authTokens.signerAuthToken);
1456
1465
  }
1457
1466
  catch (error) {
1458
1467
  console.error('[TransactionSigningService] Wallet list API failed:', error);
1459
1468
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'Failed to retrieve wallet information. Please refresh the page and try again.', undefined, error);
1460
1469
  }
1461
- // Check both possible response structures for compatibility
1462
- const wallets = walletData?.wallets || walletData?.items || [];
1470
+ // Handle multiple response formats for compatibility
1471
+ const wallets = walletListResult?.items ||
1472
+ walletListResult?.data?.wallets ||
1473
+ walletListResult?.wallets || [];
1463
1474
  if (!wallets?.length) {
1464
- console.error('[TransactionSigningService] No wallets found in response:', walletData);
1475
+ console.error('[TransactionSigningService] No wallets found in response:', walletListResult);
1465
1476
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'No wallet found for transaction signing. Please refresh the page and complete account setup.');
1466
1477
  }
1467
1478
  // Create SigningService with injected WebAuthn provider
@@ -1469,7 +1480,7 @@ class TransactionSigningService {
1469
1480
  // Create wallet instance with provider - use unknown type for flexibility
1470
1481
  const provider = new ethers.JsonRpcProvider(ethersProviderUrl);
1471
1482
  const wallet = new KeyWallet({
1472
- authToken: authTokens.backendAuthToken,
1483
+ authToken: authTokens.signerAuthToken,
1473
1484
  wallet: wallets[0],
1474
1485
  signingService: signingService,
1475
1486
  }).connect(provider);
@@ -1478,7 +1489,9 @@ class TransactionSigningService {
1478
1489
  /**
1479
1490
  * Execute the transaction signing with WebAuthn coordination
1480
1491
  */
1481
- async executeTransactionSigning(wallet, preparedTransaction) {
1492
+ async executeTransactionSigning(wallet,
1493
+ // preparedTransaction: TransactionRequestResponseDTO,
1494
+ signingData) {
1482
1495
  // Check for concurrent WebAuthn operations
1483
1496
  if (WebAuthnCoordinator.checkConcurrentOperations()) {
1484
1497
  throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WEBAUTHN_OPERATION_IN_PROGRESS, 'Another WebAuthn operation is in progress. Please wait and try again.');
@@ -1486,7 +1499,7 @@ class TransactionSigningService {
1486
1499
  // Set WebAuthn operation flag for transaction signing
1487
1500
  WebAuthnCoordinator.setOperationInProgress(true);
1488
1501
  try {
1489
- const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1502
+ // const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1490
1503
  const signature = await wallet.signPersTransaction(signingData);
1491
1504
  return signature;
1492
1505
  }
@@ -1505,6 +1518,13 @@ class TransactionSigningService {
1505
1518
  WebAuthnCoordinator.setOperationInProgress(false);
1506
1519
  }
1507
1520
  }
1521
+ async getPersSigningData(data) {
1522
+ // Step 1: Prepare transaction for signing
1523
+ const { preparedTransaction } = await this.prepareTransaction(data.transactionId, data.authTokens, data.tenantId);
1524
+ // const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1525
+ const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1526
+ return signingData;
1527
+ }
1508
1528
  /**
1509
1529
  * Main transaction signing orchestration method
1510
1530
  * Handles the complete flow from preparation to submission
@@ -1512,31 +1532,23 @@ class TransactionSigningService {
1512
1532
  * @returns Promise resolving to transaction signing result
1513
1533
  * @throws TransactionSigningError for validation and operation failures
1514
1534
  */
1515
- async signTransaction(params) {
1535
+ async signTransaction(params, signingData) {
1516
1536
  // Validate input parameters first using TransactionValidator
1517
1537
  TransactionValidator.validateSigningParams(params);
1518
- const { transactionId, authTokens, ethersProviderUrl, returnUrl, metadata } = params;
1538
+ const { transactionId, authTokens, ethersProviderUrl } = params;
1519
1539
  console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
1520
1540
  try {
1521
- // Clear previous landing authentication state
1522
- WebAuthnCoordinator.clearLandingAuthentication();
1523
- // Step 1: Prepare transaction for signing
1524
- const { preparedTransaction, updatedTokens } = await this.prepareTransaction(transactionId, authTokens);
1525
1541
  // Step 2: Prepare wallet for signing
1526
- const wallet = await this.prepareWallet(updatedTokens, ethersProviderUrl);
1542
+ const wallet = await this.prepareWallet(authTokens, ethersProviderUrl);
1527
1543
  // Step 3: Execute transaction signing
1528
- const signature = await this.executeTransactionSigning(wallet, preparedTransaction);
1529
- const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1530
- // Step 4: Submit transaction and handle success using TransactionSubmissionHandler
1531
- const { submitResult, shouldRedirect, redirectUrl } = await TransactionSubmissionHandler.handleTransactionSubmission(preparedTransaction, signature, signingData, updatedTokens, returnUrl, metadata);
1532
- console.info(`[TransactionSigningService] Completed successfully: ${transactionId}`);
1544
+ const signature = await this.executeTransactionSigning(wallet, signingData);
1545
+ console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
1533
1546
  return {
1534
1547
  success: true,
1535
1548
  transactionId,
1536
- transactionHash: submitResult.transaction?.transactionHash ?? undefined,
1549
+ signingData,
1550
+ // transactionHash: submitResult.transaction?.transactionHash ?? undefined,
1537
1551
  signature,
1538
- shouldRedirect,
1539
- redirectUrl
1540
1552
  };
1541
1553
  }
1542
1554
  catch (error) {
@@ -1562,6 +1574,41 @@ class TransactionSigningService {
1562
1574
  }
1563
1575
  }
1564
1576
 
1577
+ /**
1578
+ * Handles transaction submission, success flows, and redirect logic
1579
+ */
1580
+ class TransactionSubmissionHandler {
1581
+ /**
1582
+ * Create redirect URL with transaction parameters
1583
+ * @param returnUrl - Base return URL
1584
+ * @param transactionHash - Transaction hash to include
1585
+ * @param metadata - Optional metadata to include as parameters
1586
+ * @returns Promise resolving to the complete redirect URL
1587
+ */
1588
+ static async createRedirectUrl(returnUrl, transactionHash) {
1589
+ const additionalParams = {
1590
+ txHash: transactionHash,
1591
+ success: 'true'
1592
+ };
1593
+ // Build URL with required params
1594
+ const paramKeys = ['transactionId', 'returnUrl', 'jwt', 'token'];
1595
+ const url = new URL(returnUrl, window.location.origin);
1596
+ // Add required params if present in additionalParams
1597
+ paramKeys.forEach(key => {
1598
+ if (additionalParams[key]) {
1599
+ url.searchParams.set(key, additionalParams[key]);
1600
+ }
1601
+ });
1602
+ // Add all other params
1603
+ Object.entries(additionalParams).forEach(([key, value]) => {
1604
+ if (!paramKeys.includes(key)) {
1605
+ url.searchParams.set(key, value);
1606
+ }
1607
+ });
1608
+ return url.toString();
1609
+ }
1610
+ }
1611
+
1565
1612
  /**
1566
1613
  * Browser-specific WebAuthn provider using DFNS browser SDK
1567
1614
  */
@@ -1682,609 +1729,478 @@ async function getReactNativeWebAuthnProvider() {
1682
1729
  }
1683
1730
 
1684
1731
  /**
1685
- * PERS Blockchain Signer SDK - Simple Orchestrator
1732
+ * JWT Utility Functions
1686
1733
  *
1687
- * Lightweight SDK that orchestrates existing services.
1688
- * Uses environment configuration and existing infrastructure.
1734
+ * Browser-compatible JWT handling without external dependencies
1689
1735
  */
1690
1736
  /**
1691
- * Main PERS Signer SDK class
1692
- *
1693
- * Simple orchestrator that uses existing services and environment configuration.
1694
- * No complex initialization needed - services are already configured.
1737
+ * Extract and validate JWT token payload
1738
+ * Uses a browser-compatible approach without external dependencies
1695
1739
  */
1696
- class PersSignerSDK {
1697
- constructor(config) {
1698
- this.config = config;
1699
- this.webAuthnProvider = config.webAuthnProvider;
1700
- // Set up the configuration provider with stable defaults from constants
1701
- const serviceConfig = {
1702
- apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
1703
- relyingParty: {
1704
- id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
1705
- name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1706
- origin: typeof window !== 'undefined' ? window.location.origin : undefined,
1707
- },
1708
- };
1709
- setConfigProvider(new WebConfigProvider(serviceConfig));
1710
- // Initialize services with the WebAuthn provider
1711
- this.authService = new AuthenticationService(this.webAuthnProvider);
1712
- this.signingService = new SigningService(this.webAuthnProvider);
1713
- this.transactionSigningService = new TransactionSigningService(this.webAuthnProvider);
1714
- }
1715
- /**
1716
- * Re-export TransactionSigningService with WebAuthn provider already injected
1717
- * This provides the same interface as the original service but with automatic provider injection
1718
- */
1719
- get TransactionSigningService() {
1720
- return this.transactionSigningService;
1740
+ function extractJWTFromToken(jwtToken) {
1741
+ if (!jwtToken) {
1742
+ return { payload: null, isExpired: false };
1721
1743
  }
1722
- // ========================================================================
1723
- // HIGH-LEVEL METHODS (Same as Web Project)
1724
- // ========================================================================
1725
- /**
1726
- * Complete user onboarding flow - same as web project
1727
- * Uses existing AuthenticationService and PersService
1728
- */
1729
- async authenticateUser(userInfo) {
1730
- const identifier = userInfo.identifier;
1731
- try {
1732
- // Step 1: Try login first (existing user)
1733
- let signerAuthToken;
1734
- try {
1735
- signerAuthToken = await this.authService.login(identifier);
1736
- }
1737
- catch (loginError) {
1738
- // User doesn't exist - register new user
1739
- await this.authService.register(identifier);
1740
- signerAuthToken = await this.authService.login(identifier);
1741
- }
1742
- // Step 2: Authenticate with PERS backend
1743
- let persAccessToken = '';
1744
- try {
1745
- const persResponse = await PersService.authenticateUser(signerAuthToken);
1746
- persAccessToken = persResponse?.accessToken || '';
1747
- }
1748
- catch (persError) {
1749
- console.warn(`PERS authentication failed:`, persError);
1750
- // Continue without PERS but provide empty token
1751
- persAccessToken = '';
1752
- }
1753
- return {
1754
- identifier,
1755
- signerAuthToken,
1756
- persAccessToken
1757
- };
1758
- }
1759
- catch (error) {
1760
- throw new Error(`User authentication failed: ${error}`);
1761
- }
1744
+ try {
1745
+ // Use a more compatible JWT decoding approach
1746
+ // Split JWT into parts
1747
+ const parts = jwtToken.split('.');
1748
+ if (parts.length !== 3) {
1749
+ throw new Error('Invalid JWT format');
1750
+ }
1751
+ // Decode the payload (middle part)
1752
+ const payload = JSON.parse(atob(parts[1]));
1753
+ // Check expiration
1754
+ const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
1755
+ return { payload, isExpired };
1756
+ }
1757
+ catch (error) {
1758
+ console.warn('JWT decode failed:', error);
1759
+ return { payload: null, isExpired: false };
1762
1760
  }
1761
+ }
1762
+ /**
1763
+ * Check if a JWT token is expired
1764
+ */
1765
+ function isJWTExpired(jwtToken) {
1766
+ const { isExpired } = extractJWTFromToken(jwtToken);
1767
+ return isExpired;
1768
+ }
1769
+ /**
1770
+ * Get JWT payload without validation
1771
+ */
1772
+ function getJWTPayload(jwtToken) {
1773
+ const { payload } = extractJWTFromToken(jwtToken);
1774
+ return payload;
1775
+ }
1776
+
1777
+ /**
1778
+ * In-memory User Cache
1779
+ *
1780
+ * Provides secure, storage-free user authentication caching
1781
+ * Uses memory-only storage with TTL expiration for security
1782
+ */
1783
+ /**
1784
+ * In-memory user cache with TTL expiration
1785
+ * Security-first approach - no persistent storage
1786
+ */
1787
+ class UserCache {
1763
1788
  /**
1764
- * Complete PERS transaction signing flow - exactly like web project
1765
- * Uses existing TransactionSigningService with injected WebAuthn provider
1789
+ * Get cached user by identifier
1790
+ * @param identifier - User identifier (email/userId)
1791
+ * @returns Cached user or null if not found/expired
1766
1792
  */
1767
- async signPersTransaction(user, transactionId) {
1768
- if (!user.signerAuthToken) {
1769
- throw new Error('Signer authentication token required');
1770
- }
1771
- if (!user.persAccessToken) {
1772
- throw new Error('PERS access token required for transaction signing');
1773
- }
1774
- try {
1775
- // Use the existing high-level service with injected WebAuthn provider
1776
- const result = await this.transactionSigningService.signTransaction({
1777
- transactionId,
1778
- authTokens: {
1779
- backendAuthToken: user.signerAuthToken,
1780
- persAccessToken: user.persAccessToken
1781
- },
1782
- ethersProviderUrl: this.config.ethersProviderUrl || 'https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e'
1783
- });
1784
- return {
1785
- success: result.success,
1786
- transactionHash: result.transactionHash,
1787
- error: result.error
1788
- };
1793
+ static get(identifier) {
1794
+ const cached = this.cache.get(identifier);
1795
+ if (!cached) {
1796
+ return null;
1789
1797
  }
1790
- catch (error) {
1791
- return {
1792
- success: false,
1793
- error: `Transaction signing failed: ${error}`
1794
- };
1798
+ // Check if expired
1799
+ if (Date.now() > cached.expiresAt) {
1800
+ this.cache.delete(identifier);
1801
+ return null;
1795
1802
  }
1803
+ return cached.user;
1796
1804
  }
1797
- // ========================================================================
1798
- // GRANULAR METHODS (For Custom Integrations)
1799
- // ========================================================================
1800
1805
  /**
1801
- * Register new user - uses existing AuthenticationService
1806
+ * Cache user with TTL
1807
+ * @param identifier - User identifier
1808
+ * @param user - Authenticated user data
1809
+ * @param ttlMs - Time to live in milliseconds (default: 5 minutes)
1802
1810
  */
1803
- async registerUser(identifier) {
1804
- try {
1805
- await this.authService.register(identifier);
1806
- const authToken = await this.authService.login(identifier);
1807
- return { authToken };
1808
- }
1809
- catch (error) {
1810
- throw new Error(`User registration failed: ${error}`);
1811
- }
1811
+ static set(identifier, user, ttlMs = this.DEFAULT_TTL_MS) {
1812
+ this.cache.set(identifier, {
1813
+ user,
1814
+ expiresAt: Date.now() + ttlMs
1815
+ });
1812
1816
  }
1813
1817
  /**
1814
- * Login existing user - uses existing AuthenticationService
1818
+ * Remove user from cache
1819
+ * @param identifier - User identifier
1815
1820
  */
1816
- async loginUser(identifier) {
1817
- try {
1818
- return await this.authService.login(identifier);
1819
- }
1820
- catch (error) {
1821
- throw new Error(`User login failed: ${error}`);
1822
- }
1821
+ static delete(identifier) {
1822
+ return this.cache.delete(identifier);
1823
1823
  }
1824
1824
  /**
1825
- * Add wallet to PERS user - uses existing PersService
1825
+ * Clear all cached users
1826
1826
  */
1827
- async addWalletToPersUser(signerAuthToken) {
1828
- try {
1829
- const persResponse = await PersService.authenticateUser(signerAuthToken);
1830
- if (!persResponse?.accessToken) {
1831
- throw new Error('Failed to get PERS access token');
1832
- }
1833
- return persResponse.accessToken;
1834
- }
1835
- catch (error) {
1836
- throw new Error(`Failed to add wallet to PERS user: ${error}`);
1837
- }
1827
+ static clear() {
1828
+ this.cache.clear();
1838
1829
  }
1839
1830
  /**
1840
- * Retrieve transaction data from PERS - uses existing PersService
1831
+ * Get cache size (for debugging)
1841
1832
  */
1842
- async retrieveTransactionData(transactionId, persAccessToken) {
1843
- try {
1844
- return await PersService.fetchPreparedTransaction(transactionId, persAccessToken);
1845
- }
1846
- catch (error) {
1847
- // Preserve original error structure for proper error handling
1848
- throw error;
1849
- }
1833
+ static size() {
1834
+ return this.cache.size;
1850
1835
  }
1851
1836
  /**
1852
- * Sign transaction data - uses existing SigningService
1853
- * Follows same patterns as KeyWallet.signPersTransaction
1837
+ * Clean up expired entries
1854
1838
  */
1855
- async signTransactionData(signerAuthToken, signingData) {
1856
- try {
1857
- const wallets = await WalletService.listWallets(signerAuthToken);
1858
- if (!wallets.items || wallets.items.length === 0) {
1859
- throw new Error('No wallet found for user');
1860
- }
1861
- const walletId = wallets.items[0].id;
1862
- // Sign based on transaction format - same pattern as KeyWallet
1863
- switch (signingData.format) {
1864
- case browser.TRANSACTION_FORMATS.EIP_712: {
1865
- const eip712Data = signingData;
1866
- const typedData = eip712Data.typedData;
1867
- // Sign using same pattern as KeyWallet
1868
- const signResponse = await this.signingService.signTypedData(signerAuthToken, walletId, typedData.domain, { Transaction: typedData.types.Transaction }, typedData.message);
1869
- return `${signResponse.signature?.r}${signResponse.signature?.s}`;
1870
- }
1871
- default: {
1872
- // For legacy and other formats, use raw signing data
1873
- const signResponse = await this.signingService.signHash(signerAuthToken, walletId, JSON.stringify(signingData));
1874
- return `${signResponse.signature?.r}${signResponse.signature?.s}`;
1875
- }
1839
+ static cleanup() {
1840
+ const now = Date.now();
1841
+ let cleaned = 0;
1842
+ for (const [identifier, cached] of this.cache.entries()) {
1843
+ if (now > cached.expiresAt) {
1844
+ this.cache.delete(identifier);
1845
+ cleaned++;
1876
1846
  }
1877
1847
  }
1878
- catch (error) {
1879
- throw new Error(`Transaction signing failed: ${error}`);
1880
- }
1848
+ return cleaned;
1881
1849
  }
1850
+ }
1851
+ UserCache.cache = new Map();
1852
+ UserCache.DEFAULT_TTL_MS = 300000; // 5 minutes
1853
+
1854
+ /**
1855
+ * PERS Blockchain Signer SDK
1856
+ *
1857
+ * A lightweight blockchain transaction signing SDK with WebAuthn authentication.
1858
+ * Provides 5 focused methods for complete transaction lifecycle management:
1859
+ *
1860
+ * 1. loginUser(jwtToken) - Authenticate user with 5-minute caching
1861
+ * 2. signTransaction(signingData, jwtToken) - Sign transactions with auto-login
1862
+ * 3. submitTransaction(signedTx, jwtToken) - Submit signed transactions to blockchain
1863
+ * 4. signPersTransaction(jwtToken) - Legacy one-liner for backward compatibility
1864
+ * 5. signAndSubmitPersTransaction(jwtToken) - Complete sign + submit flow
1865
+ *
1866
+ * @example
1867
+ * ```typescript
1868
+ * import { createPersSignerSDK } from '@explorins/pers-signer';
1869
+ *
1870
+ * const sdk = createPersSignerSDK({
1871
+ * webAuthnProvider: myWebAuthnProvider,
1872
+ * ethersProviderUrl: 'https://ethereum-rpc.com'
1873
+ * });
1874
+ *
1875
+ * // Quick sign and submit
1876
+ * const result = await sdk.signAndSubmitPersTransaction(jwtToken);
1877
+ * if (result.success) {
1878
+ * console.log('Transaction submitted:', result.transactionHash);
1879
+ * }
1880
+ * ```
1881
+ */
1882
+ /**
1883
+ * PERS Blockchain Signer SDK Class
1884
+ *
1885
+ * Main SDK class providing blockchain transaction signing capabilities with WebAuthn authentication.
1886
+ * Implements a clean 5-method API for complete transaction lifecycle management.
1887
+ *
1888
+ * Features:
1889
+ * - WebAuthn-based secure authentication
1890
+ * - 5-minute user session caching
1891
+ * - Automatic transaction data fetching
1892
+ * - Blockchain transaction signing and submission
1893
+ * - Multi-tenant support
1894
+ *
1895
+ * @class PersSignerSDK
1896
+ */
1897
+ class PersSignerSDK {
1882
1898
  /**
1883
- * Submit signed transaction to PERS - uses existing PersService
1899
+ * Initialize the PERS Signer SDK
1900
+ *
1901
+ * @param {ExtendedPersSignerConfig} config - SDK configuration object
1902
+ * @throws {Error} If required configuration is missing
1884
1903
  */
1885
- async submitTransaction(transactionId, signature, persAccessToken, transactionFormat = browser.TRANSACTION_FORMATS.EIP_712) {
1886
- try {
1887
- const submitResult = await PersService.submitTransaction(transactionId, signature, persAccessToken, transactionFormat);
1888
- return {
1889
- transactionHash: submitResult.transaction?.transactionHash || undefined,
1890
- success: true
1891
- };
1892
- }
1893
- catch (error) {
1894
- throw new Error(`Transaction submission failed: ${error}`);
1895
- }
1904
+ constructor(config) {
1905
+ this.config = config;
1906
+ setConfigProvider(new WebConfigProvider({
1907
+ apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
1908
+ relyingParty: {
1909
+ id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
1910
+ name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1911
+ origin: typeof window !== 'undefined' ? window.location.origin : undefined,
1912
+ },
1913
+ }));
1914
+ this.authenticationService = new AuthenticationService(this.config);
1915
+ this.transactionSigningService = new TransactionSigningService(config);
1896
1916
  }
1897
1917
  /**
1898
- * Check if user exists - uses existing AuthenticationService
1918
+ * Authenticate user and cache session for 5 minutes
1919
+ *
1920
+ * Validates JWT token, authenticates with both signer and PERS backends,
1921
+ * and caches the authenticated user session to avoid repeated authentication.
1922
+ *
1923
+ * @param {string} jwtToken - JWT token containing user identifier and tenant info
1924
+ * @returns {Promise<AuthenticatedUser>} Authenticated user with access tokens
1925
+ * @throws {Error} If JWT is invalid, expired, or authentication fails
1926
+ *
1927
+ * @example
1928
+ * ```typescript
1929
+ * try {
1930
+ * const user = await sdk.loginUser(jwtToken);
1931
+ * console.log('Authenticated:', user.identifier);
1932
+ * } catch (error) {
1933
+ * console.error('Authentication failed:', error.message);
1934
+ * }
1935
+ * ```
1899
1936
  */
1900
- async checkUserExists(identifier) {
1901
- try {
1902
- await this.authService.login(identifier);
1903
- return true;
1937
+ async loginUser(jwtToken) {
1938
+ if (!jwtToken || typeof jwtToken !== 'string') {
1939
+ throw new Error('JWT token is required and must be a string');
1904
1940
  }
1905
- catch {
1906
- return false;
1941
+ const { payload, isExpired } = extractJWTFromToken(jwtToken);
1942
+ if (!payload || isExpired) {
1943
+ throw new Error('Invalid or expired JWT token');
1907
1944
  }
1908
- }
1909
- /**
1910
- * Get user wallets - uses existing WalletService
1911
- */
1912
- async getUserWallets(signerAuthToken) {
1913
- try {
1914
- return await WalletService.listWallets(signerAuthToken);
1945
+ const identifier = payload.identifierEmail || payload.email || payload.userId;
1946
+ const tenantId = payload.tenantId || '';
1947
+ if (!identifier) {
1948
+ throw new Error('JWT token missing user identifier (identifierEmail, email, or userId)');
1915
1949
  }
1916
- catch (error) {
1917
- throw new Error(`Failed to get user wallets: ${error}`);
1950
+ // Check cache first
1951
+ const cachedUser = UserCache.get(identifier);
1952
+ if (cachedUser && cachedUser.tenantId === tenantId && Date.now() < cachedUser.expiresAt) {
1953
+ return cachedUser;
1918
1954
  }
1919
- }
1920
- /**
1921
- * Get transaction status - uses existing PersService
1922
- */
1923
- async getTransactionStatus(transactionId, persAccessToken) {
1924
1955
  try {
1925
- const transaction = await PersService.fetchPreparedTransaction(transactionId, persAccessToken);
1926
- return {
1927
- status: transaction.transactionStatus,
1928
- transactionHash: transaction.transaction?.transactionHash || undefined
1956
+ // Authenticate and cache
1957
+ const authResult = await this.authenticationService.combinedAuthentication(identifier, jwtToken);
1958
+ const user = {
1959
+ identifier: authResult.identifier,
1960
+ signerAuthToken: authResult.signerAuthToken,
1961
+ persAccessToken: authResult.persAccessToken,
1962
+ tenantId,
1963
+ expiresAt: Date.now() + 300000 // 5 minutes
1929
1964
  };
1965
+ UserCache.set(identifier, user);
1966
+ return user;
1930
1967
  }
1931
1968
  catch (error) {
1932
- throw new Error(`Failed to get transaction status: ${error}`);
1969
+ throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1933
1970
  }
1934
1971
  }
1935
1972
  /**
1936
- * Initialize tenant - uses existing PersService
1937
- */
1938
- async initializeTenant(tenantId) {
1939
- await PersService.initializeTenant(tenantId);
1940
- }
1941
- // ========================================================================
1942
- // JWT AUTHENTICATION METHODS (For Legacy PERS Flow)
1943
- // ========================================================================
1944
- /**
1945
- * Extract and validate JWT token from URL search parameters
1946
- * Uses a simpler approach compatible with browser environments
1973
+ * Sign a PERS transaction (legacy compatibility method)
1974
+ *
1975
+ * Automatically handles user authentication, transaction data fetching,
1976
+ * and transaction signing in a single call. This is the legacy method
1977
+ * maintained for backward compatibility.
1978
+ *
1979
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
1980
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
1981
+ * @throws {Error} If authentication fails, transaction ID missing, or signing fails
1982
+ *
1983
+ * @example
1984
+ * ```typescript
1985
+ * try {
1986
+ * const result = await sdk.signPersTransaction(jwtToken);
1987
+ * if (result.success) {
1988
+ * console.log('Transaction signed:', result.signature);
1989
+ * }
1990
+ * } catch (error) {
1991
+ * console.error('Signing failed:', error.message);
1992
+ * }
1993
+ * ```
1947
1994
  */
1948
- extractJWTFromURL(searchParams) {
1949
- const jwtToken = searchParams.get('jwt') || searchParams.get('token');
1950
- if (!jwtToken) {
1951
- return { payload: null, isExpired: false };
1952
- }
1995
+ async signPersTransaction(jwtToken) {
1996
+ if (!jwtToken || typeof jwtToken !== 'string') {
1997
+ throw new Error('JWT token is required and must be a string');
1998
+ }
1999
+ const user = await this.loginUser(jwtToken);
2000
+ const { payload } = extractJWTFromToken(jwtToken);
2001
+ if (!payload?.transactionId) {
2002
+ throw new Error('JWT token missing transactionId in payload');
2003
+ }
2004
+ const authTokens = {
2005
+ signerAuthToken: user.signerAuthToken,
2006
+ persAccessToken: user.persAccessToken
2007
+ };
1953
2008
  try {
1954
- // Use a more compatible JWT decoding approach
1955
- // Split JWT into parts
1956
- const parts = jwtToken.split('.');
1957
- if (parts.length !== 3) {
1958
- throw new Error('Invalid JWT format');
2009
+ const persSigningData = await this.transactionSigningService.getPersSigningData({
2010
+ transactionId: payload.transactionId,
2011
+ authTokens,
2012
+ tenantId: user.tenantId
2013
+ });
2014
+ const result = await this.signTransaction(persSigningData, jwtToken);
2015
+ if (!result.success) {
2016
+ throw new Error(result.error || 'Transaction signing failed');
1959
2017
  }
1960
- // Decode the payload (middle part)
1961
- const payload = JSON.parse(atob(parts[1]));
1962
- // Check expiration
1963
- const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
1964
- return { payload, isExpired };
2018
+ return result;
1965
2019
  }
1966
2020
  catch (error) {
1967
- console.warn('JWT decode failed:', error);
1968
- return { payload: null, isExpired: false };
2021
+ throw new Error(`PERS transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1969
2022
  }
1970
2023
  }
1971
2024
  /**
1972
- * Initialize tenant from JWT payload using existing PersService
2025
+ * Sign a transaction with provided signing data
2026
+ *
2027
+ * Low-level method to sign transactions when you already have the signing data.
2028
+ * Automatically handles user authentication and applies the blockchain signature.
2029
+ *
2030
+ * @param {CounterfactualWalletTransactionResponse | LegacyTransaction} signingData - Transaction data to sign
2031
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
2032
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
2033
+ * @throws {Error} If authentication fails, transaction ID missing, or signing fails
2034
+ *
2035
+ * @example
2036
+ * ```typescript
2037
+ * const signingData = await getTransactionData(transactionId);
2038
+ * const result = await sdk.signTransaction(signingData, jwtToken);
2039
+ * console.log('Signed transaction:', result.signature);
2040
+ * ```
1973
2041
  */
1974
- async initializeTenantFromJWT(payload) {
1975
- if (!payload.tenantId) {
1976
- return 'default';
2042
+ async signTransaction(signingData, jwtToken) {
2043
+ if (!signingData) {
2044
+ throw new Error('Signing data is required');
2045
+ }
2046
+ if (!jwtToken || typeof jwtToken !== 'string') {
2047
+ throw new Error('JWT token is required and must be a string');
2048
+ }
2049
+ const user = await this.loginUser(jwtToken);
2050
+ const { payload } = extractJWTFromToken(jwtToken);
2051
+ if (!payload?.transactionId) {
2052
+ throw new Error('JWT token missing transactionId in payload');
1977
2053
  }
2054
+ const authTokens = {
2055
+ signerAuthToken: user.signerAuthToken,
2056
+ persAccessToken: user.persAccessToken
2057
+ };
1978
2058
  try {
1979
- // Use existing PersService.initializeTenant method
1980
- const tenantData = await PersService.initializeTenant(payload.tenantId);
1981
- // Extract project key from tenant data
1982
- const projectKey = tenantData.projectKey ||
1983
- tenantData.projectApiKey ||
1984
- tenantData.apiKey ||
1985
- payload.tenantId;
1986
- return projectKey;
2059
+ const result = await this.transactionSigningService.signTransaction({
2060
+ transactionId: payload.transactionId,
2061
+ tenantId: user.tenantId,
2062
+ authTokens,
2063
+ ethersProviderUrl: this.config.ethersProviderUrl || ''
2064
+ }, signingData);
2065
+ return result;
1987
2066
  }
1988
2067
  catch (error) {
1989
- console.warn('Tenant initialization failed:', error);
1990
- return payload.tenantId;
2068
+ throw new Error(`Transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1991
2069
  }
1992
2070
  }
1993
2071
  /**
1994
- * Authenticate user with PERS API using existing PersService pattern
1995
- */
1996
- /**
1997
- * Authenticate user with PERS API using existing PersService
2072
+ * Complete transaction flow: sign and submit in one call
2073
+ *
2074
+ * Convenience method that combines signing and submission into a single operation.
2075
+ * This is the recommended method for most use cases as it handles the complete
2076
+ * transaction lifecycle automatically.
2077
+ *
2078
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
2079
+ * @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
2080
+ * @throws {Error} If authentication, signing, or submission fails
2081
+ *
2082
+ * @example
2083
+ * ```typescript
2084
+ * try {
2085
+ * const result = await sdk.signAndSubmitPersTransaction(jwtToken);
2086
+ * if (result.success) {
2087
+ * console.log('Transaction completed:', result.transactionHash);
2088
+ * if (result.shouldRedirect) {
2089
+ * window.location.href = result.redirectUrl;
2090
+ * }
2091
+ * }
2092
+ * } catch (error) {
2093
+ * console.error('Transaction failed:', error.message);
2094
+ * }
2095
+ * ```
1998
2096
  */
1999
- async authenticatePersUser(jwtToken, projectKey) {
2097
+ async signAndSubmitPersTransaction(jwtToken) {
2098
+ if (!jwtToken || typeof jwtToken !== 'string') {
2099
+ throw new Error('JWT token is required and must be a string');
2100
+ }
2000
2101
  try {
2001
- // Configure PersService with the project key from tenant initialization
2002
- PersService.configure({ projectKey });
2003
- // Use the existing PersService method which handles all the complexity
2004
- const result = await PersService.authenticateUser(jwtToken);
2005
- // Convert to expected PersAuthResult format
2006
- return {
2007
- user: {
2008
- email: result.user?.email || undefined,
2009
- id: result.user?.id || undefined
2010
- },
2011
- accessToken: result.accessToken
2012
- };
2102
+ const signedTx = await this.signPersTransaction(jwtToken);
2103
+ const submittedTx = await this.submitTransaction(signedTx, jwtToken);
2104
+ return submittedTx;
2013
2105
  }
2014
2106
  catch (error) {
2015
- throw new Error(`PERS authentication failed: ${error}`);
2107
+ throw new Error(`Sign and submit transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
2016
2108
  }
2017
2109
  }
2018
2110
  /**
2019
- * Combined PERS + DFNS authentication flow
2111
+ * Submit a signed transaction to the blockchain
2112
+ *
2113
+ * Takes a signed transaction result and submits it to the blockchain network.
2114
+ * Returns detailed submission results including transaction hash and any
2115
+ * redirect information for UI flows.
2116
+ *
2117
+ * @param {TransactionSigningResult} signingResult - Result from a successful transaction signing
2118
+ * @param {string} jwtToken - JWT token containing tenant and user info
2119
+ * @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
2120
+ * @throws {Error} If signing result is invalid, JWT is missing tenantId, or submission fails
2121
+ *
2122
+ * @example
2123
+ * ```typescript
2124
+ * const signedTx = await sdk.signPersTransaction(jwtToken);
2125
+ * const result = await sdk.submitTransaction(signedTx, jwtToken);
2126
+ * console.log('Transaction submitted:', result.transactionHash);
2127
+ * ```
2020
2128
  */
2021
- async combinedAuthentication(identifier, persAccessToken) {
2129
+ async submitTransaction(signingResult, jwtToken) {
2130
+ if (!signingResult) {
2131
+ throw new Error('Signing result is required');
2132
+ }
2133
+ if (!jwtToken || typeof jwtToken !== 'string') {
2134
+ throw new Error('JWT token is required and must be a string');
2135
+ }
2136
+ if (!signingResult.success || !signingResult.signature || !signingResult.signingData) {
2137
+ throw new Error('Invalid signing result: must be successful with signature and signing data');
2138
+ }
2139
+ const { payload } = extractJWTFromToken(jwtToken);
2140
+ if (!payload?.tenantId) {
2141
+ throw new Error('JWT token missing tenantId in payload');
2142
+ }
2143
+ const user = await this.loginUser(jwtToken);
2022
2144
  try {
2023
- // Authenticate with DFNS - try login first, register if user doesn't exist
2024
- let signerAuthToken;
2025
- try {
2026
- signerAuthToken = await this.loginUser(identifier);
2027
- }
2028
- catch (loginError) {
2029
- console.log(`[PersSignerSDK] User not found, registering new user: ${identifier}`);
2030
- // User doesn't exist - register new user (registerUser already includes login)
2031
- const registrationResult = await this.registerUser(identifier);
2032
- signerAuthToken = registrationResult.authToken;
2033
- }
2145
+ const submitResult = await PersService.submitTransaction(signingResult.transactionId, signingResult.signature, user.persAccessToken, signingResult.signingData.format, payload.tenantId);
2146
+ // Transform response to SubmissionResult
2034
2147
  return {
2035
- identifier,
2036
- signerAuthToken,
2037
- persAccessToken
2148
+ success: true,
2149
+ transactionHash: submitResult.transaction.transactionHash,
2150
+ shouldRedirect: false, // Can be configured based on business requirements
2151
+ redirectUrl: undefined, // Can be set based on business logic
2152
+ error: undefined,
2153
+ submitResult: submitResult
2038
2154
  };
2039
2155
  }
2040
2156
  catch (error) {
2041
- throw new Error(`Combined authentication failed: ${error}`);
2157
+ throw new Error(`Transaction submission failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
2042
2158
  }
2043
2159
  }
2044
2160
  /**
2045
- * Complete JWT-based authentication flow (legacy PERS flow)
2161
+ * Clear user authentication cache
2162
+ *
2163
+ * Removes all cached user sessions, forcing fresh authentication
2164
+ * on the next method call. Useful for logout scenarios or when
2165
+ * switching between different user contexts.
2166
+ *
2167
+ * @example
2168
+ * ```typescript
2169
+ * // Clear cache on user logout
2170
+ * sdk.clearCache();
2171
+ * console.log('User cache cleared');
2172
+ * ```
2046
2173
  */
2047
- async authenticateWithJWT(searchParams) {
2048
- // 1. Extract JWT from URL
2049
- const { payload, isExpired } = this.extractJWTFromURL(searchParams);
2050
- if (!payload) {
2051
- // Return null instead of throwing error to allow graceful handling
2052
- return null;
2053
- }
2054
- if (isExpired) {
2055
- return {
2056
- user: {
2057
- identifier: payload.email || payload.userId || 'unknown',
2058
- signerAuthToken: '',
2059
- persAccessToken: ''
2060
- },
2061
- isExpired: true
2062
- };
2063
- }
2064
- // 2. Initialize tenant and get project key
2065
- const projectKey = await this.initializeTenantFromJWT(payload);
2066
- // 3. Get JWT token from URL
2067
- const jwtToken = searchParams.get('jwt') || searchParams.get('token');
2068
- if (!jwtToken) {
2069
- throw new Error('JWT token not found in URL parameters');
2070
- }
2071
- // 4. Authenticate with PERS
2072
- const persResult = await this.authenticatePersUser(jwtToken, projectKey);
2073
- // 5. Get identifier for DFNS authentication
2074
- const identifier = persResult.user.email || persResult.user.id;
2075
- if (!identifier) {
2076
- throw new Error('No identifier found in PERS response');
2077
- }
2078
- // 6. Combined authentication
2079
- const user = await this.combinedAuthentication(identifier, persResult.accessToken);
2080
- return {
2081
- user,
2082
- isExpired: false
2083
- };
2174
+ clearCache() {
2175
+ UserCache.clear();
2084
2176
  }
2085
2177
  }
2086
2178
  /**
2087
- * Factory function to create SDK
2088
- * Requires WebAuthnProvider from platform-specific entry point
2089
- */
2090
- function createPersSignerSDK(config) {
2091
- return new PersSignerSDK(config);
2092
- }
2093
-
2094
- /**
2095
- * Utility functions for handling URL search parameters in a consistent way across applications
2096
- */
2097
- /**
2098
- * Creates a URL search string that preserves all current parameters
2099
- * @param paramsToExclude Array of parameter names to exclude from the result
2100
- * @param paramsToAdd Object with additional parameters to add or override
2101
- * @param baseParams Optional URLSearchParams object to use instead of window.location.search
2102
- * @returns A search string with '?' prefix if there are parameters, or empty string if no parameters
2103
- */
2104
- function createSearchString(paramsToExclude = [], paramsToAdd = {}, baseParams) {
2105
- // Get base search params from the provided value or window location
2106
- const currentParams = new URLSearchParams(baseParams instanceof URLSearchParams
2107
- ? baseParams
2108
- : baseParams !== undefined
2109
- ? baseParams
2110
- : (typeof window !== 'undefined' ? window.location.search : ''));
2111
- // Remove excluded parameters
2112
- paramsToExclude.forEach(param => {
2113
- if (currentParams.has(param)) {
2114
- currentParams.delete(param);
2115
- }
2116
- });
2117
- // Add or update new parameters
2118
- Object.entries(paramsToAdd).forEach(([key, value]) => {
2119
- if (value !== undefined && value !== null) {
2120
- currentParams.set(key, value);
2121
- }
2122
- });
2123
- const searchString = currentParams.toString();
2124
- return searchString ? `?${searchString}` : '';
2125
- }
2126
- /**
2127
- * Creates a URL path with search parameters
2128
- * @param basePath The base path without search parameters
2129
- * @param paramsToExclude Array of parameter names to exclude from the result
2130
- * @param paramsToAdd Object with additional parameters to add or override
2131
- * @param baseParams Optional URLSearchParams object to use instead of window.location.search
2132
- * @returns A full path with search parameters
2133
- */
2134
- function createUrlWithSearchParams(basePath, paramsToExclude = [], paramsToAdd = {}, baseParams) {
2135
- const searchString = createSearchString(paramsToExclude, paramsToAdd, baseParams);
2136
- return `${basePath}${searchString}`;
2137
- }
2138
- /**
2139
- * Combines the current search parameters with new ones
2140
- * @param searchParams The current URLSearchParams object
2141
- * @param additionalParams Object with parameters to add or update
2142
- * @returns A new URLSearchParams object
2143
- */
2144
- function mergeSearchParams(searchParams, additionalParams = {}) {
2145
- const merged = new URLSearchParams(searchParams.toString());
2146
- Object.entries(additionalParams).forEach(([key, value]) => {
2147
- if (value !== undefined && value !== null) {
2148
- merged.set(key, value);
2149
- }
2150
- });
2151
- return merged;
2152
- }
2153
- /**
2154
- * Gets a specific search parameter value
2155
- * @param key The parameter name to get
2156
- * @param search Optional search string to parse (defaults to window.location.search)
2157
- * @returns The parameter value or null if not found
2158
- */
2159
- function getSearchParam(key, search) {
2160
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2161
- return params.get(key);
2162
- }
2163
- /**
2164
- * Gets all search parameters as a URLSearchParams object
2165
- * @param search Optional search string to parse (defaults to window.location.search)
2166
- * @returns A URLSearchParams object
2167
- */
2168
- function getAllSearchParams(search) {
2169
- return new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2170
- }
2171
- /**
2172
- * Removes a specific search parameter
2173
- * @param key The parameter name to remove
2174
- * @param search Optional search string to parse (defaults to window.location.search)
2175
- * @returns A new search string without the specified parameter
2176
- */
2177
- function removeSearchParam(key, search) {
2178
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2179
- params.delete(key);
2180
- const searchString = params.toString();
2181
- return searchString ? `?${searchString}` : '';
2182
- }
2183
- /**
2184
- * Updates or adds a specific search parameter
2185
- * @param key The parameter name to update
2186
- * @param value The new value for the parameter
2187
- * @param search Optional search string to parse (defaults to window.location.search)
2188
- * @returns A new search string with the updated parameter
2189
- */
2190
- function updateSearchParam(key, value, search) {
2191
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2192
- params.set(key, value);
2193
- const searchString = params.toString();
2194
- return searchString ? `?${searchString}` : '';
2195
- }
2196
-
2197
- var searchParams = /*#__PURE__*/Object.freeze({
2198
- __proto__: null,
2199
- createSearchString: createSearchString,
2200
- createUrlWithSearchParams: createUrlWithSearchParams,
2201
- getAllSearchParams: getAllSearchParams,
2202
- getSearchParam: getSearchParam,
2203
- mergeSearchParams: mergeSearchParams,
2204
- removeSearchParam: removeSearchParam,
2205
- updateSearchParam: updateSearchParam
2206
- });
2207
-
2208
- /**
2209
- * Utility for debugging search parameter handling
2210
- */
2211
- /**
2212
- * Logs the current search parameters with a custom message
2213
- * This is useful for debugging search parameter preservation across navigation
2179
+ * Create a new PERS Signer SDK instance
2214
2180
  *
2215
- * @param message - A descriptive message to identify where the logging happens
2216
- * @param additionalData - Optional additional data to log
2217
- */
2218
- function logSearchParams(message, additionalData) {
2219
- if (process.env.NODE_ENV !== 'production') {
2220
- // Check if window exists (for SSR compatibility)
2221
- if (typeof window === 'undefined') {
2222
- return;
2223
- }
2224
- const searchParams = new URLSearchParams(window.location.search);
2225
- searchParams.forEach((value, key) => {
2226
- });
2227
- console.group(`🔍 Search Params: ${message}`);
2228
- console.groupEnd();
2229
- }
2230
- }
2231
- /**
2232
- * Creates a search parameter tracking hook that can be used in React components
2233
- * Logs search parameter changes when the component mounts/updates
2181
+ * Factory function to create and configure a new SDK instance with the provided
2182
+ * configuration. This is the recommended way to initialize the SDK.
2234
2183
  *
2235
- * @param componentName - The name of the component (for logging)
2236
- */
2237
- function createSearchParamLogger(componentName) {
2238
- return () => {
2239
- logSearchParams(`${componentName} rendered`);
2240
- };
2241
- }
2242
-
2243
- /**
2244
- * Generates a RFC4122 version 4 compliant UUID
2245
- * @returns A randomly generated UUID
2246
- */
2247
- function generateUUID() {
2248
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
2249
- const r = Math.random() * 16 | 0;
2250
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
2251
- return v.toString(16);
2252
- });
2253
- }
2254
- /**
2255
- * Create a semi-anonymous username for users
2256
- * @returns A username in the format "user_[random string]"
2257
- */
2258
- function generateAnonymousUsername() {
2259
- const randomPart = Math.random().toString(36).substring(2, 10);
2260
- return `user_${randomPart}`;
2261
- }
2262
- /**
2263
- * Create a guest username for the application with a random prefix
2264
- * @returns A guest email in the format "randomstring_guest@explorins.com"
2184
+ * @param {PersSignerConfig} config - SDK configuration object
2185
+ * @returns {PersSignerSDK} Configured SDK instance ready for use
2186
+ * @throws {Error} If required configuration is missing or invalid
2187
+ *
2188
+ * @example
2189
+ * ```typescript
2190
+ * import { createPersSignerSDK, getWebAuthnProvider } from '@explorins/pers-signer';
2191
+ *
2192
+ * const webAuthnProvider = await getWebAuthnProvider();
2193
+ * const sdk = createPersSignerSDK({
2194
+ * webAuthnProvider,
2195
+ * ethersProviderUrl: 'https://mainnet.infura.io/v3/YOUR_KEY',
2196
+ * tenantId: 'your-tenant-id'
2197
+ * });
2198
+ * ```
2265
2199
  */
2266
- function generateGuestUsername() {
2267
- const randomPart = Math.random().toString(36).substring(2, 8); // 6 character random string
2268
- return `${randomPart}_guest@explorins.com`;
2200
+ function createPersSignerSDK(config) {
2201
+ return new PersSignerSDK(config);
2269
2202
  }
2270
2203
 
2271
- /**
2272
- * Utility functions for tenant-specific operations
2273
- */
2274
- /**
2275
- * Initialize tenant-specific analytics
2276
- * @param tenantId The tenant ID
2277
- * @param getTenantConfig Function to retrieve tenant configuration
2278
- */
2279
- const initTenantAnalytics = (tenantId, getTenantConfig) => {
2280
- const tenant = getTenantConfig(tenantId);
2281
- if (!tenant) {
2282
- console.warn(`Tenant ${tenantId} not found`);
2283
- return;
2284
- }
2285
- if (tenant.analytics && tenant.analytics.enabled && tenant.analytics.trackingId) ;
2286
- };
2287
-
2288
2204
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2289
2205
 
2290
2206
  function getAugmentedNamespace(n) {
@@ -11965,6 +11881,7 @@ Object.defineProperty(exports, "TransactionStatus", {
11965
11881
  });
11966
11882
  exports.AuthenticationService = AuthenticationService;
11967
11883
  exports.FetchHttpClient = FetchHttpClient;
11884
+ exports.HealthService = HealthService;
11968
11885
  exports.KeyWallet = KeyWallet;
11969
11886
  exports.PersService = PersService;
11970
11887
  exports.PersSignerSDK = PersSignerSDK;
@@ -11976,28 +11893,19 @@ exports.TransactionErrorHandler = TransactionErrorHandler;
11976
11893
  exports.TransactionSigningService = TransactionSigningService;
11977
11894
  exports.TransactionSubmissionHandler = TransactionSubmissionHandler;
11978
11895
  exports.TransactionValidator = TransactionValidator;
11896
+ exports.UserCache = UserCache;
11979
11897
  exports.WalletService = WalletService;
11980
11898
  exports.WebAuthnCoordinator = WebAuthnCoordinator;
11981
11899
  exports.WebConfigProvider = WebConfigProvider;
11982
11900
  exports.createPersSignerSDK = createPersSignerSDK;
11983
- exports.createSearchParamLogger = createSearchParamLogger;
11984
- exports.createSearchString = createSearchString;
11985
- exports.createUrlWithSearchParams = createUrlWithSearchParams;
11986
- exports.generateAnonymousUsername = generateAnonymousUsername;
11987
- exports.generateGuestUsername = generateGuestUsername;
11988
- exports.generateUUID = generateUUID;
11989
- exports.getAllSearchParams = getAllSearchParams;
11901
+ exports.extractJWTFromToken = extractJWTFromToken;
11990
11902
  exports.getBrowserWebAuthnProvider = getBrowserWebAuthnProvider;
11991
11903
  exports.getConfigProvider = getConfigProvider;
11992
11904
  exports.getHttpClient = getHttpClient;
11905
+ exports.getJWTPayload = getJWTPayload;
11993
11906
  exports.getReactNativeWebAuthnProvider = getReactNativeWebAuthnProvider;
11994
- exports.getSearchParam = getSearchParam;
11995
11907
  exports.getServiceConfig = getServiceConfig;
11996
- exports.initTenantAnalytics = initTenantAnalytics;
11997
- exports.logSearchParams = logSearchParams;
11998
- exports.mergeSearchParams = mergeSearchParams;
11999
- exports.removeSearchParam = removeSearchParam;
11908
+ exports.isJWTExpired = isJWTExpired;
12000
11909
  exports.setConfigProvider = setConfigProvider;
12001
11910
  exports.setHttpClient = setHttpClient;
12002
- exports.updateSearchParam = updateSearchParam;
12003
11911
  //# sourceMappingURL=index.cjs.js.map