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