@explorins/pers-signer 1.0.27 → 1.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -67,13 +67,11 @@ const SIGNER_CONFIG = {
67
67
  * Used as fallback when production signer API is unavailable
68
68
  */
69
69
  STAGING_SIGNER_API_URL: 'https://signer-api-staging.pers.ninja/v1',
70
- // STAGING_SIGNER_API_URL: 'http://localhost:8080/v1',
71
70
  /**
72
71
  * PERS Platform API URLs
73
72
  */
74
73
  DEFAULT_PERS_API_URL: 'https://api.pers.ninja/v2',
75
74
  STAGING_PERS_API_URL: 'https://dev.api.pers.ninja/v2',
76
- // STAGING_PERS_API_URL: 'https://explorins-loyalty.ngrok.io',
77
75
  /**
78
76
  * Default relying party name for WebAuthn operations
79
77
  * This appears in the browser's authentication prompts
@@ -85,8 +83,31 @@ const SIGNER_CONFIG = {
85
83
  */
86
84
  // Default fallback provider for Sepolia testnet (no API key required)
87
85
  const DEFAULT_FALLBACK_PROVIDER = 'https://ethereum-sepolia.publicnode.com';
86
+ /**
87
+ * WebAuthn configuration constants
88
+ */
89
+ const WEBAUTHN_CONFIG = {
90
+ /**
91
+ * Timeout for WebAuthn operations (in milliseconds)
92
+ */
93
+ TIMEOUT: 60000,
94
+ /**
95
+ * User verification requirement
96
+ * 'preferred' - ask for verification if available
97
+ * 'required' - require verification
98
+ * 'discouraged' - don't ask for verification
99
+ */
100
+ USER_VERIFICATION: 'preferred',
101
+ /**
102
+ * Resident key requirement (Passkeys)
103
+ */
104
+ RESIDENT_KEY: 'required',
105
+ /**
106
+ * Authenticator attachment preference
107
+ */
108
+ AUTHENTICATOR_ATTACHMENT: 'platform',
109
+ };
88
110
 
89
- // import serviceState from '../core/ServiceState';
90
111
  /**
91
112
  * Detects the environment (Staging vs Production) based on the JWT token issuer.
92
113
  * Updates the global service state accordingly.
@@ -100,16 +121,8 @@ function detectStagingEnvironmentFromToken(token) {
100
121
  // Check if issuer matches staging URL
101
122
  if (payload.iss === SIGNER_CONFIG.STAGING_PERS_API_URL
102
123
  || SIGNER_CONFIG.STAGING_PERS_API_URL.includes(payload.iss)
103
- || SIGNER_CONFIG.STAGING_SIGNER_API_URL.includes(payload.iss)
104
- // Temporary additional checks for known staging issuers
105
- // || 'https://signer-api-staging.pers.ninja'.includes(payload.iss)
106
- // || 'https://dev.api.pers.ninja/v2'.includes(payload.iss)
107
- ) {
124
+ || SIGNER_CONFIG.STAGING_SIGNER_API_URL.includes(payload.iss)) {
108
125
  return true;
109
- /* } else if (payload.iss === SIGNER_CONFIG.DEFAULT_PERS_API_URL ||
110
- SIGNER_CONFIG.DEFAULT_PERS_API_URL.includes(payload.iss)) {
111
- // Explicitly production
112
- return false; */
113
126
  }
114
127
  }
115
128
  return false;
@@ -379,7 +392,7 @@ class AuthenticationService {
379
392
  async combinedAuthentication(identifier, persAccessToken) {
380
393
  try {
381
394
  // Step 1: Try to login with PERS token first (this will get signer JWT if user exists)
382
- let signerToken;
395
+ let signerToken = null;
383
396
  try {
384
397
  const loginResult = await this.loginWithPersToken(persAccessToken);
385
398
  // Extract token from login result
@@ -393,6 +406,11 @@ class AuthenticationService {
393
406
  catch (loginError) {
394
407
  // Step 2: User doesn't exist - register with v1 API (Unified Flow)
395
408
  try {
409
+ // Only proceed to registration if the error is explicitly 'user_not_found'
410
+ const errorMessage = loginError instanceof Error ? loginError.message : String(loginError);
411
+ if (errorMessage !== 'user_not_found') {
412
+ throw loginError;
413
+ }
396
414
  const registerResult = await this.registerUser(persAccessToken);
397
415
  if (registerResult && registerResult.access_token) {
398
416
  signerToken = registerResult.access_token;
@@ -403,9 +421,13 @@ class AuthenticationService {
403
421
  }
404
422
  catch (registerError) {
405
423
  console.error(`[AuthenticationService] Registration failed for ${identifier}:`, registerError);
424
+ // Explicitly rethrow to abort the entire authentication process
406
425
  throw registerError;
407
426
  }
408
427
  }
428
+ if (!signerToken) {
429
+ throw new Error('Authentication failed: Unable to obtain signer token');
430
+ }
409
431
  const user = {
410
432
  identifier: identifier,
411
433
  signerAuthToken: signerToken,
@@ -415,8 +437,9 @@ class AuthenticationService {
415
437
  return user;
416
438
  }
417
439
  catch (error) {
418
- console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, error);
419
- throw new Error(`Combined authentication failed: ${error}`);
440
+ const errorMessage = error instanceof Error ? error.message : String(error);
441
+ console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, errorMessage);
442
+ throw new Error(`Combined authentication failed: ${errorMessage}`);
420
443
  }
421
444
  }
422
445
  }
@@ -1452,11 +1475,11 @@ class TransactionSigningService {
1452
1475
  let provider;
1453
1476
  try {
1454
1477
  // Try primary provider first
1455
- console.info(`[TransactionSigningService] Attempting to connect to primary provider: ${ethersProviderUrl}`);
1478
+ //console.info(`[TransactionSigningService] Attempting to connect to primary provider: ${ethersProviderUrl}`);
1456
1479
  provider = new JsonRpcProvider(ethersProviderUrl);
1457
1480
  // Test the connection with a simple call
1458
1481
  await provider.getNetwork();
1459
- console.info(`[TransactionSigningService] Successfully connected to primary provider`);
1482
+ //console.info(`[TransactionSigningService] Successfully connected to primary provider`);
1460
1483
  }
1461
1484
  catch (primaryError) {
1462
1485
  console.warn(`[TransactionSigningService] Primary provider failed, falling back to: ${DEFAULT_FALLBACK_PROVIDER}`, primaryError);
@@ -1464,7 +1487,7 @@ class TransactionSigningService {
1464
1487
  provider = new JsonRpcProvider(DEFAULT_FALLBACK_PROVIDER);
1465
1488
  // Test the fallback connection
1466
1489
  await provider.getNetwork();
1467
- console.info(`[TransactionSigningService] Successfully connected to fallback provider`);
1490
+ //console.info(`[TransactionSigningService] Successfully connected to fallback provider`);
1468
1491
  }
1469
1492
  catch (fallbackError) {
1470
1493
  console.error(`[TransactionSigningService] Both primary and fallback providers failed`, { primaryError, fallbackError });
@@ -1528,13 +1551,13 @@ class TransactionSigningService {
1528
1551
  // Validate input parameters first using TransactionValidator
1529
1552
  TransactionValidator.validateSigningParams(params);
1530
1553
  const { transactionId, authTokens, ethersProviderUrl } = params;
1531
- console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
1554
+ // console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
1532
1555
  try {
1533
1556
  // Step 2: Prepare wallet for signing
1534
1557
  const wallet = await this.prepareWallet(authTokens, ethersProviderUrl);
1535
1558
  // Step 3: Execute transaction signing
1536
1559
  const signature = await this.executeTransactionSigning(wallet, signingData);
1537
- console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
1560
+ // console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
1538
1561
  return {
1539
1562
  success: true,
1540
1563
  transactionId,
@@ -1667,6 +1690,28 @@ function getServiceConfig() {
1667
1690
  return getConfigProvider().getServiceConfig();
1668
1691
  }
1669
1692
 
1693
+ /**
1694
+ * Get the WebAuthn configuration based on the current environment.
1695
+ * This logic is shared between browser and React Native implementations
1696
+ * to ensure consistent behavior while respecting platform differences.
1697
+ */
1698
+ function getWebAuthnConfig() {
1699
+ // Check if running in a browser environment with window.location available
1700
+ // We use a safe check that won't crash in React Native
1701
+ const isBrowser = typeof window !== 'undefined' && !!window.location && !!window.location.hostname;
1702
+ return {
1703
+ relyingParty: {
1704
+ // In browser, prefer the actual hostname to avoid RP ID mismatch errors
1705
+ // In React Native, use the configured default RP ID
1706
+ id: isBrowser ? window.location.hostname : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
1707
+ name: SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1708
+ // In browser, use the actual origin
1709
+ // In React Native, fallback to the default RP ID (or a configured origin if added later)
1710
+ origin: isBrowser ? window.location.origin : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
1711
+ }
1712
+ };
1713
+ }
1714
+
1670
1715
  /**
1671
1716
  * Base64URL encoding/decoding utilities
1672
1717
  * Used for WebAuthn credential ID and challenge handling
@@ -1739,9 +1784,8 @@ class BrowserWebAuthnProvider {
1739
1784
  // 1. Prepare options for navigator.credentials.create
1740
1785
  // We construct authenticatorSelection manually to ensure no conflicting legacy properties are present
1741
1786
  const authenticatorSelection = {
1742
- residentKey: 'required', // Force Passkey (discoverable credential)
1743
- // requireResidentKey: true, // REMOVED: Legacy property that can cause conflicts with 1Password
1744
- userVerification: challenge.authenticatorSelection?.userVerification || 'preferred',
1787
+ residentKey: WEBAUTHN_CONFIG.RESIDENT_KEY, // Force Passkey (discoverable credential)
1788
+ userVerification: challenge.authenticatorSelection?.userVerification || WEBAUTHN_CONFIG.USER_VERIFICATION,
1745
1789
  };
1746
1790
  const publicKey = {
1747
1791
  challenge: base64UrlToBuffer(challenge.challenge),
@@ -1757,16 +1801,13 @@ class BrowserWebAuthnProvider {
1757
1801
  pubKeyCredParams: challenge.pubKeyCredParams,
1758
1802
  authenticatorSelection,
1759
1803
  attestation: 'none',
1760
- timeout: challenge.timeout || 60000,
1804
+ timeout: challenge.timeout || WEBAUTHN_CONFIG.TIMEOUT,
1761
1805
  excludeCredentials: challenge.excludeCredentials?.map((cred) => ({
1762
1806
  id: base64UrlToBuffer(cred.id),
1763
1807
  type: 'public-key',
1764
1808
  transports: cred.transports,
1765
1809
  })),
1766
1810
  };
1767
- // console.log('WebAuthn create options:', publicKey);
1768
- // console.log('WebAuthn create challenge:', challenge);
1769
- // console.log('this.config', this.config)
1770
1811
  // 2. Call the browser's native WebAuthn API
1771
1812
  const credential = await navigator.credentials.create({ publicKey });
1772
1813
  if (!credential)
@@ -1790,8 +1831,8 @@ class BrowserWebAuthnProvider {
1790
1831
  const publicKey = {
1791
1832
  challenge: base64UrlToBuffer(challenge.challenge),
1792
1833
  rpId: challenge.rpId || this.config.relyingParty.id,
1793
- userVerification: challenge.userVerification || 'preferred',
1794
- timeout: 60000,
1834
+ userVerification: challenge.userVerification || WEBAUTHN_CONFIG.USER_VERIFICATION,
1835
+ timeout: WEBAUTHN_CONFIG.TIMEOUT,
1795
1836
  allowCredentials: challenge.allowCredentials?.webauthn?.map((cred) => ({
1796
1837
  id: base64UrlToBuffer(cred.id),
1797
1838
  type: 'public-key',
@@ -1830,13 +1871,7 @@ class BrowserWebAuthnProvider {
1830
1871
  * Get WebAuthn provider for browser environments
1831
1872
  */
1832
1873
  async function getBrowserWebAuthnProvider() {
1833
- const config = {
1834
- relyingParty: {
1835
- id: typeof window !== 'undefined' ? window.location.hostname : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
1836
- name: SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1837
- origin: typeof window !== 'undefined' ? window.location.origin : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
1838
- }
1839
- };
1874
+ const config = getWebAuthnConfig();
1840
1875
  return new BrowserWebAuthnProvider(config);
1841
1876
  }
1842
1877
 
@@ -1852,13 +1887,18 @@ var WebAuthnProvider_browser = /*#__PURE__*/Object.freeze({
1852
1887
  */
1853
1888
  class ReactNativeWebAuthnProvider {
1854
1889
  constructor(config) {
1890
+ this.passkeyLibrary = null;
1855
1891
  this.config = config;
1856
1892
  }
1857
1893
  async getPasskeyLibrary() {
1894
+ if (this.passkeyLibrary) {
1895
+ return this.passkeyLibrary;
1896
+ }
1858
1897
  try {
1859
1898
  // Dynamic import to avoid hard dependency
1860
1899
  // @ts-ignore - Optional dependency
1861
1900
  const { Passkey } = await import('react-native-passkey');
1901
+ this.passkeyLibrary = Passkey;
1862
1902
  return Passkey;
1863
1903
  }
1864
1904
  catch (error) {
@@ -1874,7 +1914,6 @@ class ReactNativeWebAuthnProvider {
1874
1914
  }
1875
1915
  }
1876
1916
  async handleWebFallback(operation, challenge) {
1877
- console.log(`Falling back to browser WebAuthn for React Native Web (${operation})...`);
1878
1917
  try {
1879
1918
  // Import and use browser WebAuthn provider
1880
1919
  const { getBrowserWebAuthnProvider } = await Promise.resolve().then(function () { return WebAuthnProvider_browser; });
@@ -1897,8 +1936,8 @@ class ReactNativeWebAuthnProvider {
1897
1936
  // If it's a very long string, it might be an issue.
1898
1937
  // But let's focus on aligning the authenticatorSelection first.
1899
1938
  const authenticatorSelection = {
1900
- residentKey: 'required', // Force Passkey (discoverable credential)
1901
- userVerification: challenge.authenticatorSelection?.userVerification || 'preferred',
1939
+ residentKey: WEBAUTHN_CONFIG.RESIDENT_KEY, // Force Passkey (discoverable credential)
1940
+ userVerification: challenge.authenticatorSelection?.userVerification || WEBAUTHN_CONFIG.USER_VERIFICATION,
1902
1941
  // Explicitly undefined to avoid issues, though JS objects don't usually include undefined keys in JSON
1903
1942
  // But react-native-passkey might handle it differently.
1904
1943
  };
@@ -1927,11 +1966,9 @@ class ReactNativeWebAuthnProvider {
1927
1966
  transports: v.transports
1928
1967
  })), // Cast to any to avoid strict type mismatch with react-native-passkey types
1929
1968
  authenticatorSelection,
1930
- timeout: challenge.timeout || 60000,
1969
+ timeout: challenge.timeout || WEBAUTHN_CONFIG.TIMEOUT,
1931
1970
  };
1932
- console.log('[RNWebAuthn] Calling Passkey.create with:', JSON.stringify(request, null, 2));
1933
1971
  const result = await Passkey.create(request);
1934
- console.log('[RNWebAuthn] Passkey.create success:', JSON.stringify(result, null, 2));
1935
1972
  return {
1936
1973
  id: result.id,
1937
1974
  clientDataJSON: result.response.clientDataJSON,
@@ -1959,12 +1996,10 @@ class ReactNativeWebAuthnProvider {
1959
1996
  // transports: cred.transports,
1960
1997
  })),
1961
1998
  rpId: challenge.rpId || this.config.relyingParty.id,
1962
- userVerification: challenge.userVerification || 'preferred',
1963
- timeout: challenge.timeout || 60000,
1999
+ userVerification: challenge.userVerification || WEBAUTHN_CONFIG.USER_VERIFICATION,
2000
+ timeout: challenge.timeout || WEBAUTHN_CONFIG.TIMEOUT,
1964
2001
  };
1965
- console.log('[RNWebAuthn] Calling Passkey.get (sign) with:', JSON.stringify(request, null, 2));
1966
2002
  const credential = await Passkey.get(request);
1967
- console.log('[RNWebAuthn] Passkey.get success:', JSON.stringify(credential, null, 2));
1968
2003
  return {
1969
2004
  kind: 'Fido2',
1970
2005
  credentialAssertion: {
@@ -1989,12 +2024,7 @@ class ReactNativeWebAuthnProvider {
1989
2024
  * Get WebAuthn provider for React Native environments
1990
2025
  */
1991
2026
  async function getReactNativeWebAuthnProvider() {
1992
- const config = {
1993
- relyingParty: {
1994
- id: SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
1995
- name: SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1996
- }
1997
- };
2027
+ const config = getWebAuthnConfig();
1998
2028
  return new ReactNativeWebAuthnProvider(config);
1999
2029
  }
2000
2030
 
@@ -2126,7 +2156,6 @@ class PersSignerSDK {
2126
2156
  * @throws {Error} If required configuration is missing
2127
2157
  */
2128
2158
  constructor(config) {
2129
- // console.log('DEBUG: v1.2.0')
2130
2159
  this.config = config;
2131
2160
  setConfigProvider(new WebConfigProvider({
2132
2161
  apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
@@ -2335,10 +2364,12 @@ class PersSignerSDK {
2335
2364
  authTokens,
2336
2365
  ethersProviderUrl: this.config.ethersProviderUrl ?? DEFAULT_FALLBACK_PROVIDER
2337
2366
  }, signingData);
2338
- this.triggerStatusUpdate(SigningStatus.COMPLETED, 'Transaction signed successfully', {
2339
- transactionId: payload.transactionId,
2340
- signature: result.signature
2341
- }, statusCallback);
2367
+ if (result.success) {
2368
+ this.triggerStatusUpdate(SigningStatus.COMPLETED, 'Transaction signed successfully', {
2369
+ transactionId: payload.transactionId,
2370
+ signature: result.signature
2371
+ }, statusCallback);
2372
+ }
2342
2373
  return result;
2343
2374
  }
2344
2375
  catch (error) {