@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.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
|
-
|
|
420
|
-
|
|
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:
|
|
1744
|
-
|
|
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 ||
|
|
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 ||
|
|
1795
|
-
timeout:
|
|
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:
|
|
1902
|
-
userVerification: challenge.authenticatorSelection?.userVerification ||
|
|
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 ||
|
|
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 ||
|
|
1964
|
-
timeout: challenge.timeout ||
|
|
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
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
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) {
|