@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/browser.cjs.js +77 -41
- package/dist/browser.cjs.js.map +1 -1
- package/dist/browser.esm.js +77 -41
- package/dist/browser.esm.js.map +1 -1
- package/dist/index.cjs.js +88 -57
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +88 -57
- package/dist/index.esm.js.map +1 -1
- package/dist/react-native.cjs.js +88 -57
- package/dist/react-native.cjs.js.map +1 -1
- package/dist/react-native.esm.js +88 -57
- package/dist/react-native.esm.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
419
|
-
|
|
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:
|
|
1743
|
-
|
|
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 ||
|
|
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 ||
|
|
1794
|
-
timeout:
|
|
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:
|
|
1901
|
-
userVerification: challenge.authenticatorSelection?.userVerification ||
|
|
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 ||
|
|
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 ||
|
|
1963
|
-
timeout: challenge.timeout ||
|
|
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
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
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) {
|