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