@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.
@@ -135,13 +135,11 @@ const SIGNER_CONFIG = {
135
135
  * Used as fallback when production signer API is unavailable
136
136
  */
137
137
  STAGING_SIGNER_API_URL: 'https://signer-api-staging.pers.ninja/v1',
138
- // STAGING_SIGNER_API_URL: 'http://localhost:8080/v1',
139
138
  /**
140
139
  * PERS Platform API URLs
141
140
  */
142
141
  DEFAULT_PERS_API_URL: 'https://api.pers.ninja/v2',
143
142
  STAGING_PERS_API_URL: 'https://dev.api.pers.ninja/v2',
144
- // STAGING_PERS_API_URL: 'https://explorins-loyalty.ngrok.io',
145
143
  /**
146
144
  * Default relying party name for WebAuthn operations
147
145
  * This appears in the browser's authentication prompts
@@ -153,8 +151,31 @@ const SIGNER_CONFIG = {
153
151
  */
154
152
  // Default fallback provider for Sepolia testnet (no API key required)
155
153
  const DEFAULT_FALLBACK_PROVIDER = 'https://ethereum-sepolia.publicnode.com';
154
+ /**
155
+ * WebAuthn configuration constants
156
+ */
157
+ const WEBAUTHN_CONFIG = {
158
+ /**
159
+ * Timeout for WebAuthn operations (in milliseconds)
160
+ */
161
+ TIMEOUT: 60000,
162
+ /**
163
+ * User verification requirement
164
+ * 'preferred' - ask for verification if available
165
+ * 'required' - require verification
166
+ * 'discouraged' - don't ask for verification
167
+ */
168
+ USER_VERIFICATION: 'preferred',
169
+ /**
170
+ * Resident key requirement (Passkeys)
171
+ */
172
+ RESIDENT_KEY: 'required',
173
+ /**
174
+ * Authenticator attachment preference
175
+ */
176
+ AUTHENTICATOR_ATTACHMENT: 'platform',
177
+ };
156
178
 
157
- // import serviceState from '../core/ServiceState';
158
179
  /**
159
180
  * Detects the environment (Staging vs Production) based on the JWT token issuer.
160
181
  * Updates the global service state accordingly.
@@ -168,16 +189,8 @@ function detectStagingEnvironmentFromToken(token) {
168
189
  // Check if issuer matches staging URL
169
190
  if (payload.iss === SIGNER_CONFIG.STAGING_PERS_API_URL
170
191
  || SIGNER_CONFIG.STAGING_PERS_API_URL.includes(payload.iss)
171
- || SIGNER_CONFIG.STAGING_SIGNER_API_URL.includes(payload.iss)
172
- // Temporary additional checks for known staging issuers
173
- // || 'https://signer-api-staging.pers.ninja'.includes(payload.iss)
174
- // || 'https://dev.api.pers.ninja/v2'.includes(payload.iss)
175
- ) {
192
+ || SIGNER_CONFIG.STAGING_SIGNER_API_URL.includes(payload.iss)) {
176
193
  return true;
177
- /* } else if (payload.iss === SIGNER_CONFIG.DEFAULT_PERS_API_URL ||
178
- SIGNER_CONFIG.DEFAULT_PERS_API_URL.includes(payload.iss)) {
179
- // Explicitly production
180
- return false; */
181
194
  }
182
195
  }
183
196
  return false;
@@ -1204,11 +1217,11 @@ class TransactionSigningService {
1204
1217
  let provider;
1205
1218
  try {
1206
1219
  // Try primary provider first
1207
- console.info(`[TransactionSigningService] Attempting to connect to primary provider: ${ethersProviderUrl}`);
1220
+ //console.info(`[TransactionSigningService] Attempting to connect to primary provider: ${ethersProviderUrl}`);
1208
1221
  provider = new JsonRpcProvider(ethersProviderUrl);
1209
1222
  // Test the connection with a simple call
1210
1223
  await provider.getNetwork();
1211
- console.info(`[TransactionSigningService] Successfully connected to primary provider`);
1224
+ //console.info(`[TransactionSigningService] Successfully connected to primary provider`);
1212
1225
  }
1213
1226
  catch (primaryError) {
1214
1227
  console.warn(`[TransactionSigningService] Primary provider failed, falling back to: ${DEFAULT_FALLBACK_PROVIDER}`, primaryError);
@@ -1216,7 +1229,7 @@ class TransactionSigningService {
1216
1229
  provider = new JsonRpcProvider(DEFAULT_FALLBACK_PROVIDER);
1217
1230
  // Test the fallback connection
1218
1231
  await provider.getNetwork();
1219
- console.info(`[TransactionSigningService] Successfully connected to fallback provider`);
1232
+ //console.info(`[TransactionSigningService] Successfully connected to fallback provider`);
1220
1233
  }
1221
1234
  catch (fallbackError) {
1222
1235
  console.error(`[TransactionSigningService] Both primary and fallback providers failed`, { primaryError, fallbackError });
@@ -1280,13 +1293,13 @@ class TransactionSigningService {
1280
1293
  // Validate input parameters first using TransactionValidator
1281
1294
  TransactionValidator.validateSigningParams(params);
1282
1295
  const { transactionId, authTokens, ethersProviderUrl } = params;
1283
- console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
1296
+ // console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
1284
1297
  try {
1285
1298
  // Step 2: Prepare wallet for signing
1286
1299
  const wallet = await this.prepareWallet(authTokens, ethersProviderUrl);
1287
1300
  // Step 3: Execute transaction signing
1288
1301
  const signature = await this.executeTransactionSigning(wallet, signingData);
1289
- console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
1302
+ // console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
1290
1303
  return {
1291
1304
  success: true,
1292
1305
  transactionId,
@@ -1585,7 +1598,7 @@ class AuthenticationService {
1585
1598
  async combinedAuthentication(identifier, persAccessToken) {
1586
1599
  try {
1587
1600
  // Step 1: Try to login with PERS token first (this will get signer JWT if user exists)
1588
- let signerToken;
1601
+ let signerToken = null;
1589
1602
  try {
1590
1603
  const loginResult = await this.loginWithPersToken(persAccessToken);
1591
1604
  // Extract token from login result
@@ -1599,6 +1612,11 @@ class AuthenticationService {
1599
1612
  catch (loginError) {
1600
1613
  // Step 2: User doesn't exist - register with v1 API (Unified Flow)
1601
1614
  try {
1615
+ // Only proceed to registration if the error is explicitly 'user_not_found'
1616
+ const errorMessage = loginError instanceof Error ? loginError.message : String(loginError);
1617
+ if (errorMessage !== 'user_not_found') {
1618
+ throw loginError;
1619
+ }
1602
1620
  const registerResult = await this.registerUser(persAccessToken);
1603
1621
  if (registerResult && registerResult.access_token) {
1604
1622
  signerToken = registerResult.access_token;
@@ -1609,9 +1627,13 @@ class AuthenticationService {
1609
1627
  }
1610
1628
  catch (registerError) {
1611
1629
  console.error(`[AuthenticationService] Registration failed for ${identifier}:`, registerError);
1630
+ // Explicitly rethrow to abort the entire authentication process
1612
1631
  throw registerError;
1613
1632
  }
1614
1633
  }
1634
+ if (!signerToken) {
1635
+ throw new Error('Authentication failed: Unable to obtain signer token');
1636
+ }
1615
1637
  const user = {
1616
1638
  identifier: identifier,
1617
1639
  signerAuthToken: signerToken,
@@ -1621,8 +1643,9 @@ class AuthenticationService {
1621
1643
  return user;
1622
1644
  }
1623
1645
  catch (error) {
1624
- console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, error);
1625
- throw new Error(`Combined authentication failed: ${error}`);
1646
+ const errorMessage = error instanceof Error ? error.message : String(error);
1647
+ console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, errorMessage);
1648
+ throw new Error(`Combined authentication failed: ${errorMessage}`);
1626
1649
  }
1627
1650
  }
1628
1651
  }
@@ -1755,7 +1778,6 @@ class PersSignerSDK {
1755
1778
  * @throws {Error} If required configuration is missing
1756
1779
  */
1757
1780
  constructor(config) {
1758
- // console.log('DEBUG: v1.2.0')
1759
1781
  this.config = config;
1760
1782
  setConfigProvider(new WebConfigProvider({
1761
1783
  apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
@@ -1964,10 +1986,12 @@ class PersSignerSDK {
1964
1986
  authTokens,
1965
1987
  ethersProviderUrl: this.config.ethersProviderUrl ?? DEFAULT_FALLBACK_PROVIDER
1966
1988
  }, signingData);
1967
- this.triggerStatusUpdate(SigningStatus.COMPLETED, 'Transaction signed successfully', {
1968
- transactionId: payload.transactionId,
1969
- signature: result.signature
1970
- }, statusCallback);
1989
+ if (result.success) {
1990
+ this.triggerStatusUpdate(SigningStatus.COMPLETED, 'Transaction signed successfully', {
1991
+ transactionId: payload.transactionId,
1992
+ signature: result.signature
1993
+ }, statusCallback);
1994
+ }
1971
1995
  return result;
1972
1996
  }
1973
1997
  catch (error) {
@@ -2111,6 +2135,28 @@ class PersSignerSDK {
2111
2135
  }
2112
2136
  }
2113
2137
 
2138
+ /**
2139
+ * Get the WebAuthn configuration based on the current environment.
2140
+ * This logic is shared between browser and React Native implementations
2141
+ * to ensure consistent behavior while respecting platform differences.
2142
+ */
2143
+ function getWebAuthnConfig() {
2144
+ // Check if running in a browser environment with window.location available
2145
+ // We use a safe check that won't crash in React Native
2146
+ const isBrowser = typeof window !== 'undefined' && !!window.location && !!window.location.hostname;
2147
+ return {
2148
+ relyingParty: {
2149
+ // In browser, prefer the actual hostname to avoid RP ID mismatch errors
2150
+ // In React Native, use the configured default RP ID
2151
+ id: isBrowser ? window.location.hostname : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
2152
+ name: SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
2153
+ // In browser, use the actual origin
2154
+ // In React Native, fallback to the default RP ID (or a configured origin if added later)
2155
+ origin: isBrowser ? window.location.origin : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
2156
+ }
2157
+ };
2158
+ }
2159
+
2114
2160
  /**
2115
2161
  * Base64URL encoding/decoding utilities
2116
2162
  * Used for WebAuthn credential ID and challenge handling
@@ -2174,9 +2220,8 @@ class BrowserWebAuthnProvider {
2174
2220
  // 1. Prepare options for navigator.credentials.create
2175
2221
  // We construct authenticatorSelection manually to ensure no conflicting legacy properties are present
2176
2222
  const authenticatorSelection = {
2177
- residentKey: 'required', // Force Passkey (discoverable credential)
2178
- // requireResidentKey: true, // REMOVED: Legacy property that can cause conflicts with 1Password
2179
- userVerification: challenge.authenticatorSelection?.userVerification || 'preferred',
2223
+ residentKey: WEBAUTHN_CONFIG.RESIDENT_KEY, // Force Passkey (discoverable credential)
2224
+ userVerification: challenge.authenticatorSelection?.userVerification || WEBAUTHN_CONFIG.USER_VERIFICATION,
2180
2225
  };
2181
2226
  const publicKey = {
2182
2227
  challenge: base64UrlToBuffer(challenge.challenge),
@@ -2192,16 +2237,13 @@ class BrowserWebAuthnProvider {
2192
2237
  pubKeyCredParams: challenge.pubKeyCredParams,
2193
2238
  authenticatorSelection,
2194
2239
  attestation: 'none',
2195
- timeout: challenge.timeout || 60000,
2240
+ timeout: challenge.timeout || WEBAUTHN_CONFIG.TIMEOUT,
2196
2241
  excludeCredentials: challenge.excludeCredentials?.map((cred) => ({
2197
2242
  id: base64UrlToBuffer(cred.id),
2198
2243
  type: 'public-key',
2199
2244
  transports: cred.transports,
2200
2245
  })),
2201
2246
  };
2202
- // console.log('WebAuthn create options:', publicKey);
2203
- // console.log('WebAuthn create challenge:', challenge);
2204
- // console.log('this.config', this.config)
2205
2247
  // 2. Call the browser's native WebAuthn API
2206
2248
  const credential = await navigator.credentials.create({ publicKey });
2207
2249
  if (!credential)
@@ -2225,8 +2267,8 @@ class BrowserWebAuthnProvider {
2225
2267
  const publicKey = {
2226
2268
  challenge: base64UrlToBuffer(challenge.challenge),
2227
2269
  rpId: challenge.rpId || this.config.relyingParty.id,
2228
- userVerification: challenge.userVerification || 'preferred',
2229
- timeout: 60000,
2270
+ userVerification: challenge.userVerification || WEBAUTHN_CONFIG.USER_VERIFICATION,
2271
+ timeout: WEBAUTHN_CONFIG.TIMEOUT,
2230
2272
  allowCredentials: challenge.allowCredentials?.webauthn?.map((cred) => ({
2231
2273
  id: base64UrlToBuffer(cred.id),
2232
2274
  type: 'public-key',
@@ -2265,13 +2307,7 @@ class BrowserWebAuthnProvider {
2265
2307
  * Get WebAuthn provider for browser environments
2266
2308
  */
2267
2309
  async function getBrowserWebAuthnProvider() {
2268
- const config = {
2269
- relyingParty: {
2270
- id: typeof window !== 'undefined' ? window.location.hostname : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
2271
- name: SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
2272
- origin: typeof window !== 'undefined' ? window.location.origin : SIGNER_CONFIG.DEFAULT_RELYING_PARTY_ID,
2273
- }
2274
- };
2310
+ const config = getWebAuthnConfig();
2275
2311
  return new BrowserWebAuthnProvider(config);
2276
2312
  }
2277
2313