@explorins/pers-signer 1.0.11 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.cjs.js +819 -981
- package/dist/browser.cjs.js.map +1 -1
- package/dist/browser.d.ts +334 -469
- package/dist/browser.esm.js +820 -967
- package/dist/browser.esm.js.map +1 -1
- package/dist/index.cjs.js +890 -973
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +597 -576
- package/dist/index.esm.js +886 -961
- package/dist/index.esm.js.map +1 -1
- package/dist/react-native.cjs.js +820 -982
- package/dist/react-native.cjs.js.map +1 -1
- package/dist/react-native.d.ts +334 -469
- package/dist/react-native.esm.js +821 -968
- package/dist/react-native.esm.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
167
|
-
this.
|
|
177
|
+
constructor(config) {
|
|
178
|
+
this.signerToken = null;
|
|
179
|
+
this.config = config;
|
|
180
|
+
this.webAuthnProvider = config.webAuthnProvider;
|
|
168
181
|
}
|
|
169
182
|
/**
|
|
170
|
-
*
|
|
171
|
-
* @param
|
|
172
|
-
* @returns Promise resolving to
|
|
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
|
|
187
|
+
async loginWithPersToken(persToken) {
|
|
175
188
|
const httpClient = getHttpClient();
|
|
176
189
|
const config = getServiceConfig();
|
|
177
190
|
try {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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.
|
|
190
|
-
|
|
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]
|
|
214
|
+
// console.error(`[AuthenticationService] PERS token login failed:`, error);
|
|
194
215
|
throw error;
|
|
195
216
|
}
|
|
196
217
|
}
|
|
197
218
|
/**
|
|
198
|
-
*
|
|
199
|
-
* @param
|
|
200
|
-
* @returns Promise resolving to
|
|
219
|
+
* Verify signer token validity
|
|
220
|
+
* @param token - Signer JWT to verify
|
|
221
|
+
* @returns Promise resolving to verification result
|
|
201
222
|
*/
|
|
202
|
-
async
|
|
223
|
+
async verifyToken(token) {
|
|
203
224
|
const httpClient = getHttpClient();
|
|
204
225
|
const config = getServiceConfig();
|
|
205
226
|
try {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
231
|
-
return result;
|
|
235
|
+
return verifyData;
|
|
232
236
|
}
|
|
233
237
|
catch (error) {
|
|
234
|
-
console.error(`[AuthenticationService]
|
|
238
|
+
console.error(`[AuthenticationService] Token verification failed:`, error);
|
|
235
239
|
throw error;
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
242
|
/**
|
|
239
|
-
*
|
|
240
|
-
* @param
|
|
241
|
-
* @returns Promise resolving to
|
|
243
|
+
* Initialize user registration
|
|
244
|
+
* @param persToken - PERS JWT token (registration is public)
|
|
245
|
+
* @returns Promise resolving to registration challenge
|
|
242
246
|
*/
|
|
243
|
-
async
|
|
247
|
+
async initializeRegistration(persToken) {
|
|
244
248
|
const httpClient = getHttpClient();
|
|
245
249
|
const config = getServiceConfig();
|
|
246
250
|
try {
|
|
247
|
-
const
|
|
248
|
-
authToken
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
|
|
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(
|
|
255
|
-
|
|
262
|
+
// console.error(`[AuthenticationService] Registration initialization failed:`, error);
|
|
263
|
+
throw error;
|
|
256
264
|
}
|
|
257
265
|
}
|
|
258
266
|
/**
|
|
259
|
-
* Get
|
|
260
|
-
* @
|
|
261
|
-
* @returns Promise resolving to user info or null
|
|
267
|
+
* Get current signer token
|
|
268
|
+
* @returns The current signer JWT token
|
|
262
269
|
*/
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
*
|
|
285
|
-
* @param
|
|
286
|
-
|
|
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
|
-
|
|
287
|
+
async completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persToken) {
|
|
289
288
|
const httpClient = getHttpClient();
|
|
290
289
|
const config = getServiceConfig();
|
|
291
290
|
try {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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(
|
|
327
|
+
console.error(`[AuthenticationService] Registration completion with challenge failed:`, error);
|
|
301
328
|
throw error;
|
|
302
329
|
}
|
|
303
330
|
}
|
|
304
331
|
/**
|
|
305
|
-
*
|
|
306
|
-
* @param
|
|
307
|
-
* @param
|
|
308
|
-
* @
|
|
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
|
-
|
|
311
|
-
const httpClient = getHttpClient();
|
|
312
|
-
const config = getServiceConfig();
|
|
339
|
+
async combinedAuthentication(identifier, persAccessToken) {
|
|
313
340
|
try {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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(`[
|
|
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
|
-
*
|
|
328
|
-
* @param
|
|
329
|
-
* @
|
|
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
|
|
434
|
+
static async listWallets(signerToken) {
|
|
333
435
|
const httpClient = getHttpClient();
|
|
334
436
|
const config = getServiceConfig();
|
|
335
437
|
try {
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
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
|
|
405
|
-
authToken,
|
|
507
|
+
const initRequest = {
|
|
406
508
|
walletId,
|
|
407
|
-
request
|
|
408
|
-
|
|
409
|
-
});
|
|
410
|
-
const
|
|
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
|
|
415
|
-
authToken,
|
|
521
|
+
const completeRequest = {
|
|
416
522
|
walletId,
|
|
417
523
|
requestBody,
|
|
418
524
|
signedChallenge: {
|
|
419
|
-
challengeIdentifier: challenge
|
|
525
|
+
challengeIdentifier: challenge?.challengeIdentifier,
|
|
420
526
|
firstFactor: assertion,
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
});
|
|
424
|
-
|
|
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
|
|
567
|
-
|
|
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
|
-
|
|
821
|
-
|
|
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
|
-
|
|
824
|
-
'x-project-key': this.getProjectKey(),
|
|
825
|
-
|
|
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
|
-
|
|
833
|
-
|
|
844
|
+
const response = await res.json();
|
|
845
|
+
if (!res.ok) {
|
|
846
|
+
// Throw structured error with the backend response
|
|
834
847
|
throw {
|
|
835
|
-
status:
|
|
836
|
-
message:
|
|
837
|
-
error:
|
|
848
|
+
status: res.status,
|
|
849
|
+
message: response.message || "Failed to submit transaction",
|
|
850
|
+
error: response
|
|
838
851
|
};
|
|
839
852
|
}
|
|
840
|
-
|
|
841
|
-
return data;
|
|
853
|
+
return response;
|
|
842
854
|
}
|
|
843
855
|
catch (error) {
|
|
844
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
851
|
-
* @
|
|
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
|
|
872
|
+
static async fetchPreparedTransaction(transactionId, persAccessToken, tenantId) {
|
|
884
873
|
try {
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
877
|
+
"Content-Type": "application/json",
|
|
878
|
+
"x-project-key": await this.getProjectKey(tenantId),
|
|
879
|
+
Authorization: `Bearer ${persAccessToken}`,
|
|
880
|
+
},
|
|
891
881
|
});
|
|
892
|
-
|
|
893
|
-
|
|
882
|
+
const response = await res.json();
|
|
883
|
+
if (!res.ok) {
|
|
884
|
+
// Throw structured error with the backend response
|
|
894
885
|
throw {
|
|
895
|
-
status:
|
|
896
|
-
message:
|
|
897
|
-
error:
|
|
886
|
+
status: res.status,
|
|
887
|
+
message: response.message || "Failed to fetch prepared transaction",
|
|
888
|
+
error: response
|
|
898
889
|
};
|
|
899
890
|
}
|
|
900
|
-
|
|
901
|
-
return data;
|
|
891
|
+
return response;
|
|
902
892
|
}
|
|
903
893
|
catch (error) {
|
|
904
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
|
1052
|
+
const rawRes = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
|
|
1053
|
+
const res = adaptSignatureResponse(rawRes);
|
|
985
1054
|
assertSigned(res);
|
|
986
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
1274
|
-
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both
|
|
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.
|
|
1287
|
-
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and
|
|
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
|
-
*
|
|
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
|
|
1369
|
+
class WebAuthnCoordinator {
|
|
1297
1370
|
/**
|
|
1298
|
-
*
|
|
1299
|
-
* @
|
|
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(
|
|
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.
|
|
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
|
|
1461
|
+
let walletListResult;
|
|
1453
1462
|
try {
|
|
1454
|
-
|
|
1455
|
-
|
|
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
|
-
//
|
|
1462
|
-
const wallets =
|
|
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:',
|
|
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.
|
|
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,
|
|
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
|
|
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(
|
|
1542
|
+
const wallet = await this.prepareWallet(authTokens, ethersProviderUrl);
|
|
1527
1543
|
// Step 3: Execute transaction signing
|
|
1528
|
-
const signature = await this.executeTransactionSigning(wallet,
|
|
1529
|
-
|
|
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
|
-
|
|
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,600 +1729,478 @@ async function getReactNativeWebAuthnProvider() {
|
|
|
1682
1729
|
}
|
|
1683
1730
|
|
|
1684
1731
|
/**
|
|
1685
|
-
*
|
|
1732
|
+
* JWT Utility Functions
|
|
1686
1733
|
*
|
|
1687
|
-
*
|
|
1688
|
-
* Uses environment configuration and existing infrastructure.
|
|
1734
|
+
* Browser-compatible JWT handling without external dependencies
|
|
1689
1735
|
*/
|
|
1690
1736
|
/**
|
|
1691
|
-
*
|
|
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
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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);
|
|
1740
|
+
function extractJWTFromToken(jwtToken) {
|
|
1741
|
+
if (!jwtToken) {
|
|
1742
|
+
return { payload: null, isExpired: false };
|
|
1714
1743
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
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
|
-
*
|
|
1765
|
-
*
|
|
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
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
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
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
-
*
|
|
1818
|
+
* Remove user from cache
|
|
1819
|
+
* @param identifier - User identifier
|
|
1815
1820
|
*/
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
-
*
|
|
1825
|
+
* Clear all cached users
|
|
1826
1826
|
*/
|
|
1827
|
-
|
|
1828
|
-
|
|
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
|
-
*
|
|
1831
|
+
* Get cache size (for debugging)
|
|
1841
1832
|
*/
|
|
1842
|
-
|
|
1843
|
-
|
|
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
|
-
*
|
|
1853
|
-
* Follows same patterns as KeyWallet.signPersTransaction
|
|
1837
|
+
* Clean up expired entries
|
|
1854
1838
|
*/
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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++;
|
|
1860
1846
|
}
|
|
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
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
catch (error) {
|
|
1879
|
-
throw new Error(`Transaction signing failed: ${error}`);
|
|
1880
1847
|
}
|
|
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
|
-
*
|
|
1899
|
+
* Initialize the PERS Signer SDK
|
|
1900
|
+
*
|
|
1901
|
+
* @param {PersSignerConfig} config - SDK configuration object
|
|
1902
|
+
* @throws {Error} If required configuration is missing
|
|
1884
1903
|
*/
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
-
*
|
|
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
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
-
|
|
1906
|
-
|
|
1941
|
+
const { payload, isExpired } = extractJWTFromToken(jwtToken);
|
|
1942
|
+
if (!payload || isExpired) {
|
|
1943
|
+
throw new Error('Invalid or expired JWT token');
|
|
1907
1944
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
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 || this.config.tenantId || '';
|
|
1947
|
+
if (!identifier) {
|
|
1948
|
+
throw new Error('JWT token missing user identifier (identifierEmail, email, or userId)');
|
|
1915
1949
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
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
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
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(`
|
|
1969
|
+
throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1933
1970
|
}
|
|
1934
1971
|
}
|
|
1935
1972
|
/**
|
|
1936
|
-
*
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
*
|
|
1946
|
-
*
|
|
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
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
1975
|
-
if (!
|
|
1976
|
-
|
|
2042
|
+
async signTransaction(signingData, jwtToken) {
|
|
2043
|
+
if (!signingData) {
|
|
2044
|
+
throw new Error('Signing data is required');
|
|
1977
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');
|
|
2053
|
+
}
|
|
2054
|
+
const authTokens = {
|
|
2055
|
+
signerAuthToken: user.signerAuthToken,
|
|
2056
|
+
persAccessToken: user.persAccessToken
|
|
2057
|
+
};
|
|
1978
2058
|
try {
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
*
|
|
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
|
|
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
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
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(`
|
|
2107
|
+
throw new Error(`Sign and submit transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2016
2108
|
}
|
|
2017
2109
|
}
|
|
2018
2110
|
/**
|
|
2019
|
-
*
|
|
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
|
|
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
|
-
|
|
2024
|
-
|
|
2145
|
+
const submitResult = await PersService.submitTransaction(signingResult.transactionId, signingResult.signature, user.persAccessToken, signingResult.signingData.format, payload.tenantId);
|
|
2146
|
+
// Transform response to SubmissionResult
|
|
2025
2147
|
return {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
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
|
|
2029
2154
|
};
|
|
2030
2155
|
}
|
|
2031
2156
|
catch (error) {
|
|
2032
|
-
throw new Error(`
|
|
2157
|
+
throw new Error(`Transaction submission failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2033
2158
|
}
|
|
2034
2159
|
}
|
|
2035
2160
|
/**
|
|
2036
|
-
*
|
|
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
|
+
* ```
|
|
2037
2173
|
*/
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
const { payload, isExpired } = this.extractJWTFromURL(searchParams);
|
|
2041
|
-
if (!payload) {
|
|
2042
|
-
// Return null instead of throwing error to allow graceful handling
|
|
2043
|
-
return null;
|
|
2044
|
-
}
|
|
2045
|
-
if (isExpired) {
|
|
2046
|
-
return {
|
|
2047
|
-
user: {
|
|
2048
|
-
identifier: payload.email || payload.userId || 'unknown',
|
|
2049
|
-
signerAuthToken: '',
|
|
2050
|
-
persAccessToken: ''
|
|
2051
|
-
},
|
|
2052
|
-
isExpired: true
|
|
2053
|
-
};
|
|
2054
|
-
}
|
|
2055
|
-
// 2. Initialize tenant and get project key
|
|
2056
|
-
const projectKey = await this.initializeTenantFromJWT(payload);
|
|
2057
|
-
// 3. Get JWT token from URL
|
|
2058
|
-
const jwtToken = searchParams.get('jwt') || searchParams.get('token');
|
|
2059
|
-
if (!jwtToken) {
|
|
2060
|
-
throw new Error('JWT token not found in URL parameters');
|
|
2061
|
-
}
|
|
2062
|
-
// 4. Authenticate with PERS
|
|
2063
|
-
const persResult = await this.authenticatePersUser(jwtToken, projectKey);
|
|
2064
|
-
// 5. Get identifier for DFNS authentication
|
|
2065
|
-
const identifier = persResult.user.email || persResult.user.id;
|
|
2066
|
-
if (!identifier) {
|
|
2067
|
-
throw new Error('No identifier found in PERS response');
|
|
2068
|
-
}
|
|
2069
|
-
// 6. Combined authentication
|
|
2070
|
-
const user = await this.combinedAuthentication(identifier, persResult.accessToken);
|
|
2071
|
-
return {
|
|
2072
|
-
user,
|
|
2073
|
-
isExpired: false
|
|
2074
|
-
};
|
|
2174
|
+
clearCache() {
|
|
2175
|
+
UserCache.clear();
|
|
2075
2176
|
}
|
|
2076
2177
|
}
|
|
2077
2178
|
/**
|
|
2078
|
-
*
|
|
2079
|
-
* Requires WebAuthnProvider from platform-specific entry point
|
|
2080
|
-
*/
|
|
2081
|
-
function createPersSignerSDK(config) {
|
|
2082
|
-
return new PersSignerSDK(config);
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
/**
|
|
2086
|
-
* Utility functions for handling URL search parameters in a consistent way across applications
|
|
2087
|
-
*/
|
|
2088
|
-
/**
|
|
2089
|
-
* Creates a URL search string that preserves all current parameters
|
|
2090
|
-
* @param paramsToExclude Array of parameter names to exclude from the result
|
|
2091
|
-
* @param paramsToAdd Object with additional parameters to add or override
|
|
2092
|
-
* @param baseParams Optional URLSearchParams object to use instead of window.location.search
|
|
2093
|
-
* @returns A search string with '?' prefix if there are parameters, or empty string if no parameters
|
|
2094
|
-
*/
|
|
2095
|
-
function createSearchString(paramsToExclude = [], paramsToAdd = {}, baseParams) {
|
|
2096
|
-
// Get base search params from the provided value or window location
|
|
2097
|
-
const currentParams = new URLSearchParams(baseParams instanceof URLSearchParams
|
|
2098
|
-
? baseParams
|
|
2099
|
-
: baseParams !== undefined
|
|
2100
|
-
? baseParams
|
|
2101
|
-
: (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2102
|
-
// Remove excluded parameters
|
|
2103
|
-
paramsToExclude.forEach(param => {
|
|
2104
|
-
if (currentParams.has(param)) {
|
|
2105
|
-
currentParams.delete(param);
|
|
2106
|
-
}
|
|
2107
|
-
});
|
|
2108
|
-
// Add or update new parameters
|
|
2109
|
-
Object.entries(paramsToAdd).forEach(([key, value]) => {
|
|
2110
|
-
if (value !== undefined && value !== null) {
|
|
2111
|
-
currentParams.set(key, value);
|
|
2112
|
-
}
|
|
2113
|
-
});
|
|
2114
|
-
const searchString = currentParams.toString();
|
|
2115
|
-
return searchString ? `?${searchString}` : '';
|
|
2116
|
-
}
|
|
2117
|
-
/**
|
|
2118
|
-
* Creates a URL path with search parameters
|
|
2119
|
-
* @param basePath The base path without search parameters
|
|
2120
|
-
* @param paramsToExclude Array of parameter names to exclude from the result
|
|
2121
|
-
* @param paramsToAdd Object with additional parameters to add or override
|
|
2122
|
-
* @param baseParams Optional URLSearchParams object to use instead of window.location.search
|
|
2123
|
-
* @returns A full path with search parameters
|
|
2124
|
-
*/
|
|
2125
|
-
function createUrlWithSearchParams(basePath, paramsToExclude = [], paramsToAdd = {}, baseParams) {
|
|
2126
|
-
const searchString = createSearchString(paramsToExclude, paramsToAdd, baseParams);
|
|
2127
|
-
return `${basePath}${searchString}`;
|
|
2128
|
-
}
|
|
2129
|
-
/**
|
|
2130
|
-
* Combines the current search parameters with new ones
|
|
2131
|
-
* @param searchParams The current URLSearchParams object
|
|
2132
|
-
* @param additionalParams Object with parameters to add or update
|
|
2133
|
-
* @returns A new URLSearchParams object
|
|
2134
|
-
*/
|
|
2135
|
-
function mergeSearchParams(searchParams, additionalParams = {}) {
|
|
2136
|
-
const merged = new URLSearchParams(searchParams.toString());
|
|
2137
|
-
Object.entries(additionalParams).forEach(([key, value]) => {
|
|
2138
|
-
if (value !== undefined && value !== null) {
|
|
2139
|
-
merged.set(key, value);
|
|
2140
|
-
}
|
|
2141
|
-
});
|
|
2142
|
-
return merged;
|
|
2143
|
-
}
|
|
2144
|
-
/**
|
|
2145
|
-
* Gets a specific search parameter value
|
|
2146
|
-
* @param key The parameter name to get
|
|
2147
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2148
|
-
* @returns The parameter value or null if not found
|
|
2149
|
-
*/
|
|
2150
|
-
function getSearchParam(key, search) {
|
|
2151
|
-
const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2152
|
-
return params.get(key);
|
|
2153
|
-
}
|
|
2154
|
-
/**
|
|
2155
|
-
* Gets all search parameters as a URLSearchParams object
|
|
2156
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2157
|
-
* @returns A URLSearchParams object
|
|
2158
|
-
*/
|
|
2159
|
-
function getAllSearchParams(search) {
|
|
2160
|
-
return new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2161
|
-
}
|
|
2162
|
-
/**
|
|
2163
|
-
* Removes a specific search parameter
|
|
2164
|
-
* @param key The parameter name to remove
|
|
2165
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2166
|
-
* @returns A new search string without the specified parameter
|
|
2167
|
-
*/
|
|
2168
|
-
function removeSearchParam(key, search) {
|
|
2169
|
-
const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2170
|
-
params.delete(key);
|
|
2171
|
-
const searchString = params.toString();
|
|
2172
|
-
return searchString ? `?${searchString}` : '';
|
|
2173
|
-
}
|
|
2174
|
-
/**
|
|
2175
|
-
* Updates or adds a specific search parameter
|
|
2176
|
-
* @param key The parameter name to update
|
|
2177
|
-
* @param value The new value for the parameter
|
|
2178
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2179
|
-
* @returns A new search string with the updated parameter
|
|
2180
|
-
*/
|
|
2181
|
-
function updateSearchParam(key, value, search) {
|
|
2182
|
-
const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2183
|
-
params.set(key, value);
|
|
2184
|
-
const searchString = params.toString();
|
|
2185
|
-
return searchString ? `?${searchString}` : '';
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
var searchParams = /*#__PURE__*/Object.freeze({
|
|
2189
|
-
__proto__: null,
|
|
2190
|
-
createSearchString: createSearchString,
|
|
2191
|
-
createUrlWithSearchParams: createUrlWithSearchParams,
|
|
2192
|
-
getAllSearchParams: getAllSearchParams,
|
|
2193
|
-
getSearchParam: getSearchParam,
|
|
2194
|
-
mergeSearchParams: mergeSearchParams,
|
|
2195
|
-
removeSearchParam: removeSearchParam,
|
|
2196
|
-
updateSearchParam: updateSearchParam
|
|
2197
|
-
});
|
|
2198
|
-
|
|
2199
|
-
/**
|
|
2200
|
-
* Utility for debugging search parameter handling
|
|
2201
|
-
*/
|
|
2202
|
-
/**
|
|
2203
|
-
* Logs the current search parameters with a custom message
|
|
2204
|
-
* This is useful for debugging search parameter preservation across navigation
|
|
2179
|
+
* Create a new PERS Signer SDK instance
|
|
2205
2180
|
*
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*/
|
|
2209
|
-
function logSearchParams(message, additionalData) {
|
|
2210
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
2211
|
-
// Check if window exists (for SSR compatibility)
|
|
2212
|
-
if (typeof window === 'undefined') {
|
|
2213
|
-
return;
|
|
2214
|
-
}
|
|
2215
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
2216
|
-
searchParams.forEach((value, key) => {
|
|
2217
|
-
});
|
|
2218
|
-
console.group(`🔍 Search Params: ${message}`);
|
|
2219
|
-
console.groupEnd();
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
|
-
/**
|
|
2223
|
-
* Creates a search parameter tracking hook that can be used in React components
|
|
2224
|
-
* 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.
|
|
2225
2183
|
*
|
|
2226
|
-
* @param
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
*
|
|
2236
|
-
*
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
2242
|
-
return v.toString(16);
|
|
2243
|
-
});
|
|
2244
|
-
}
|
|
2245
|
-
/**
|
|
2246
|
-
* Create a semi-anonymous username for users
|
|
2247
|
-
* @returns A username in the format "user_[random string]"
|
|
2248
|
-
*/
|
|
2249
|
-
function generateAnonymousUsername() {
|
|
2250
|
-
const randomPart = Math.random().toString(36).substring(2, 10);
|
|
2251
|
-
return `user_${randomPart}`;
|
|
2252
|
-
}
|
|
2253
|
-
/**
|
|
2254
|
-
* Create a guest username for the application with a random prefix
|
|
2255
|
-
* @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
|
+
* ```
|
|
2256
2199
|
*/
|
|
2257
|
-
function
|
|
2258
|
-
|
|
2259
|
-
return `${randomPart}_guest@explorins.com`;
|
|
2200
|
+
function createPersSignerSDK(config) {
|
|
2201
|
+
return new PersSignerSDK(config);
|
|
2260
2202
|
}
|
|
2261
2203
|
|
|
2262
|
-
/**
|
|
2263
|
-
* Utility functions for tenant-specific operations
|
|
2264
|
-
*/
|
|
2265
|
-
/**
|
|
2266
|
-
* Initialize tenant-specific analytics
|
|
2267
|
-
* @param tenantId The tenant ID
|
|
2268
|
-
* @param getTenantConfig Function to retrieve tenant configuration
|
|
2269
|
-
*/
|
|
2270
|
-
const initTenantAnalytics = (tenantId, getTenantConfig) => {
|
|
2271
|
-
const tenant = getTenantConfig(tenantId);
|
|
2272
|
-
if (!tenant) {
|
|
2273
|
-
console.warn(`Tenant ${tenantId} not found`);
|
|
2274
|
-
return;
|
|
2275
|
-
}
|
|
2276
|
-
if (tenant.analytics && tenant.analytics.enabled && tenant.analytics.trackingId) ;
|
|
2277
|
-
};
|
|
2278
|
-
|
|
2279
2204
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
2280
2205
|
|
|
2281
2206
|
function getAugmentedNamespace(n) {
|
|
@@ -11956,6 +11881,7 @@ Object.defineProperty(exports, "TransactionStatus", {
|
|
|
11956
11881
|
});
|
|
11957
11882
|
exports.AuthenticationService = AuthenticationService;
|
|
11958
11883
|
exports.FetchHttpClient = FetchHttpClient;
|
|
11884
|
+
exports.HealthService = HealthService;
|
|
11959
11885
|
exports.KeyWallet = KeyWallet;
|
|
11960
11886
|
exports.PersService = PersService;
|
|
11961
11887
|
exports.PersSignerSDK = PersSignerSDK;
|
|
@@ -11967,28 +11893,19 @@ exports.TransactionErrorHandler = TransactionErrorHandler;
|
|
|
11967
11893
|
exports.TransactionSigningService = TransactionSigningService;
|
|
11968
11894
|
exports.TransactionSubmissionHandler = TransactionSubmissionHandler;
|
|
11969
11895
|
exports.TransactionValidator = TransactionValidator;
|
|
11896
|
+
exports.UserCache = UserCache;
|
|
11970
11897
|
exports.WalletService = WalletService;
|
|
11971
11898
|
exports.WebAuthnCoordinator = WebAuthnCoordinator;
|
|
11972
11899
|
exports.WebConfigProvider = WebConfigProvider;
|
|
11973
11900
|
exports.createPersSignerSDK = createPersSignerSDK;
|
|
11974
|
-
exports.
|
|
11975
|
-
exports.createSearchString = createSearchString;
|
|
11976
|
-
exports.createUrlWithSearchParams = createUrlWithSearchParams;
|
|
11977
|
-
exports.generateAnonymousUsername = generateAnonymousUsername;
|
|
11978
|
-
exports.generateGuestUsername = generateGuestUsername;
|
|
11979
|
-
exports.generateUUID = generateUUID;
|
|
11980
|
-
exports.getAllSearchParams = getAllSearchParams;
|
|
11901
|
+
exports.extractJWTFromToken = extractJWTFromToken;
|
|
11981
11902
|
exports.getBrowserWebAuthnProvider = getBrowserWebAuthnProvider;
|
|
11982
11903
|
exports.getConfigProvider = getConfigProvider;
|
|
11983
11904
|
exports.getHttpClient = getHttpClient;
|
|
11905
|
+
exports.getJWTPayload = getJWTPayload;
|
|
11984
11906
|
exports.getReactNativeWebAuthnProvider = getReactNativeWebAuthnProvider;
|
|
11985
|
-
exports.getSearchParam = getSearchParam;
|
|
11986
11907
|
exports.getServiceConfig = getServiceConfig;
|
|
11987
|
-
exports.
|
|
11988
|
-
exports.logSearchParams = logSearchParams;
|
|
11989
|
-
exports.mergeSearchParams = mergeSearchParams;
|
|
11990
|
-
exports.removeSearchParam = removeSearchParam;
|
|
11908
|
+
exports.isJWTExpired = isJWTExpired;
|
|
11991
11909
|
exports.setConfigProvider = setConfigProvider;
|
|
11992
11910
|
exports.setHttpClient = setHttpClient;
|
|
11993
|
-
exports.updateSearchParam = updateSearchParam;
|
|
11994
11911
|
//# sourceMappingURL=index.cjs.js.map
|