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