@explorins/pers-sdk-react-native 1.5.25 → 1.5.27
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/README.md +84 -73
- package/dist/hooks/useAnalytics.d.ts +1 -65
- package/dist/hooks/useAnalytics.d.ts.map +1 -1
- package/dist/hooks/useTransactions.d.ts +2 -3
- package/dist/hooks/useTransactions.d.ts.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +735 -161
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +11 -0
- package/dist/providers/react-native-auth-provider.d.ts.map +1 -1
- package/dist/providers/react-native-auth-provider.js +2 -2
- package/dist/providers/rn-dpop-provider.d.ts +19 -0
- package/dist/providers/rn-dpop-provider.d.ts.map +1 -0
- package/dist/providers/rn-dpop-provider.js +76 -0
- package/dist/storage/rn-secure-storage.d.ts +18 -0
- package/dist/storage/rn-secure-storage.d.ts.map +1 -0
- package/dist/storage/rn-secure-storage.js +102 -0
- package/package.json +6 -5
- package/src/hooks/useAnalytics.ts +4 -76
- package/src/hooks/useTransactions.ts +3 -3
- package/src/index.ts +24 -0
- package/src/providers/PersSDKProvider.tsx +13 -1
- package/src/providers/react-native-auth-provider.ts +2 -1
- package/src/providers/rn-dpop-provider.ts +88 -0
- package/src/storage/rn-secure-storage.ts +110 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAAuB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAAuB,MAAM,OAAO,CAAC;AAEnG,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAsB,MAAM,0BAA0B,CAAC;AAIxG,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtC,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAGlC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAGjC,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3H,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAMD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;CACrB,CA4HA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useState, useCallback, useRef } from 'react';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
3
4
|
import { PersSDK } from '@explorins/pers-sdk/core';
|
|
4
5
|
import { ReactNativeHttpClient } from './react-native-http-client';
|
|
5
6
|
import { createReactNativeAuthProvider } from './react-native-auth-provider';
|
|
7
|
+
import { ReactNativeDPoPProvider } from './rn-dpop-provider';
|
|
6
8
|
// Create the context
|
|
7
9
|
const SDKContext = createContext(null);
|
|
8
10
|
// Provider component
|
|
@@ -39,6 +41,15 @@ export const PersSDKProvider = ({ children }) => {
|
|
|
39
41
|
...config,
|
|
40
42
|
authProvider
|
|
41
43
|
};
|
|
44
|
+
// Inject Native DPoP Provider (crypto-based) for mobile platforms
|
|
45
|
+
// Web platforms use built-in WebCrypto provider by default
|
|
46
|
+
if (Platform.OS !== 'web' && (!config.dpop?.cryptoProvider)) {
|
|
47
|
+
sdkConfig.dpop = {
|
|
48
|
+
...(config.dpop || {}),
|
|
49
|
+
enabled: config.dpop?.enabled ?? true,
|
|
50
|
+
cryptoProvider: new ReactNativeDPoPProvider()
|
|
51
|
+
};
|
|
52
|
+
}
|
|
42
53
|
// Initialize PersSDK with platform-aware auth provider
|
|
43
54
|
const sdkInstance = new PersSDK(httpClient, sdkConfig);
|
|
44
55
|
setAuthProvider(authProvider);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-native-auth-provider.d.ts","sourceRoot":"","sources":["../../src/providers/react-native-auth-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,mBAAmB,EAAW,QAAQ,EAA4B,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"react-native-auth-provider.d.ts","sourceRoot":"","sources":["../../src/providers/react-native-auth-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,mBAAmB,EAAW,QAAQ,EAA4B,MAAM,0BAA0B,CAAC;AAG5G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAK7D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,qBAA0B,GACjC,mBAAmB,CAyBrB"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Platform } from 'react-native';
|
|
9
9
|
import { DefaultAuthProvider, LocalStorageTokenStorage } from '@explorins/pers-sdk/core';
|
|
10
|
-
import {
|
|
10
|
+
import { ReactNativeSecureStorage } from '../storage/rn-secure-storage';
|
|
11
11
|
// Use React Native's built-in platform detection
|
|
12
12
|
const isWebPlatform = Platform.OS === 'web';
|
|
13
13
|
/**
|
|
@@ -29,7 +29,7 @@ export function createReactNativeAuthProvider(projectKey, config = {}) {
|
|
|
29
29
|
// Platform-specific storage selection
|
|
30
30
|
const tokenStorage = customStorage || (isWebPlatform
|
|
31
31
|
? new LocalStorageTokenStorage()
|
|
32
|
-
: new
|
|
32
|
+
: new ReactNativeSecureStorage(keyPrefix));
|
|
33
33
|
// Return DefaultAuthProvider configured with platform-appropriate storage
|
|
34
34
|
return new DefaultAuthProvider({
|
|
35
35
|
authType,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DPoPCryptoProvider, DPoPKeyPair } from '@explorins/pers-sdk/core';
|
|
2
|
+
export declare class ReactNativeDPoPProvider implements DPoPCryptoProvider {
|
|
3
|
+
/**
|
|
4
|
+
* Generates a new key pair (ES256 recommended)
|
|
5
|
+
*
|
|
6
|
+
* Note: options.extractable is ignored because for React Native Keychain storage,
|
|
7
|
+
* we MUST export the keys as JWK/strings. We rely on the Keychain encryption
|
|
8
|
+
* for security at rest (High Security), making the key "Extractable" in memory
|
|
9
|
+
* but protected by hardware encryption when stored.
|
|
10
|
+
*/
|
|
11
|
+
generateKeyPair(options?: {
|
|
12
|
+
extractable?: boolean;
|
|
13
|
+
}): Promise<DPoPKeyPair>;
|
|
14
|
+
signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string>;
|
|
15
|
+
hashAccessToken(accessToken: string): Promise<string>;
|
|
16
|
+
private base64Url;
|
|
17
|
+
private base64UrlBuffer;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=rn-dpop-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rn-dpop-provider.d.ts","sourceRoot":"","sources":["../../src/providers/rn-dpop-provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE3E,qBAAa,uBAAwB,YAAW,kBAAkB;IAChE;;;;;;;OAOG;IACG,eAAe,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAoB1E,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC9E,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3D,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,eAAe;CAQxB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import crypto from 'react-native-quick-crypto';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
export class ReactNativeDPoPProvider {
|
|
4
|
+
/**
|
|
5
|
+
* Generates a new key pair (ES256 recommended)
|
|
6
|
+
*
|
|
7
|
+
* Note: options.extractable is ignored because for React Native Keychain storage,
|
|
8
|
+
* we MUST export the keys as JWK/strings. We rely on the Keychain encryption
|
|
9
|
+
* for security at rest (High Security), making the key "Extractable" in memory
|
|
10
|
+
* but protected by hardware encryption when stored.
|
|
11
|
+
*/
|
|
12
|
+
async generateKeyPair(options) {
|
|
13
|
+
// Generate P-256 Key Pair
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
|
|
16
|
+
if (err)
|
|
17
|
+
return reject(err);
|
|
18
|
+
// Export to JWK for SDK Compatibility
|
|
19
|
+
// We use 'export' because supportsObjects=false in our storage forces the SDK
|
|
20
|
+
// to expect serializable keys.
|
|
21
|
+
const pubJwk = publicKey.export({ format: 'jwk' });
|
|
22
|
+
const privJwk = privateKey.export({ format: 'jwk' });
|
|
23
|
+
resolve({
|
|
24
|
+
publicKey: pubJwk,
|
|
25
|
+
privateKey: privJwk
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async signProof(payload, keyPair) {
|
|
31
|
+
// 1. Construct Header
|
|
32
|
+
const header = {
|
|
33
|
+
typ: 'dpop+jwt',
|
|
34
|
+
alg: 'ES256',
|
|
35
|
+
jwk: keyPair.publicKey
|
|
36
|
+
};
|
|
37
|
+
// 2. Add Claims (iat/jti)
|
|
38
|
+
const finalPayload = {
|
|
39
|
+
...payload,
|
|
40
|
+
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
41
|
+
jti: payload.jti || crypto.randomUUID()
|
|
42
|
+
};
|
|
43
|
+
// 3. Encode
|
|
44
|
+
const b64Header = this.base64Url(JSON.stringify(header));
|
|
45
|
+
const b64Payload = this.base64Url(JSON.stringify(finalPayload));
|
|
46
|
+
const signingInput = `${b64Header}.${b64Payload}`;
|
|
47
|
+
// 4. Sign
|
|
48
|
+
const sign = crypto.createSign('SHA256');
|
|
49
|
+
sign.update(signingInput);
|
|
50
|
+
// sign.end() is not required/available in quick-crypto as it doesn't strictly follow stream interface
|
|
51
|
+
// Import private key back from JWK to sign
|
|
52
|
+
// The keyPair.privateKey is a JsonWebKey object because we exported it earlier.
|
|
53
|
+
// quick-crypto createPrivateKey expects jwk object if format is jwk
|
|
54
|
+
const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
|
|
55
|
+
const signature = sign.sign(privateKeyObj);
|
|
56
|
+
// signature is a Buffer in quick-crypto
|
|
57
|
+
return `${signingInput}.${this.base64UrlBuffer(signature)}`;
|
|
58
|
+
}
|
|
59
|
+
async hashAccessToken(accessToken) {
|
|
60
|
+
const hash = crypto.createHash('sha256').update(accessToken).digest();
|
|
61
|
+
// digest returns Buffer in quick-crypto
|
|
62
|
+
return this.base64UrlBuffer(hash);
|
|
63
|
+
}
|
|
64
|
+
// --- Helpers ---
|
|
65
|
+
base64Url(str) {
|
|
66
|
+
return this.base64UrlBuffer(Buffer.from(str));
|
|
67
|
+
}
|
|
68
|
+
base64UrlBuffer(buf) {
|
|
69
|
+
// Ensure we have a Buffer
|
|
70
|
+
const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
71
|
+
return buffer.toString('base64')
|
|
72
|
+
.replace(/=/g, '')
|
|
73
|
+
.replace(/\+/g, '-')
|
|
74
|
+
.replace(/\//g, '_');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TokenStorage } from '@explorins/pers-sdk/core';
|
|
2
|
+
/**
|
|
3
|
+
* Secure Storage implementation for React Native
|
|
4
|
+
* Uses Keychain/Keystore for sensitive data and AsyncStorage for non-sensitive data.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ReactNativeSecureStorage implements TokenStorage {
|
|
7
|
+
private keyPrefix;
|
|
8
|
+
readonly supportsObjects = false;
|
|
9
|
+
private readonly SECURE_KEYS;
|
|
10
|
+
constructor(keyPrefix?: string);
|
|
11
|
+
set(key: string, value: unknown): Promise<void>;
|
|
12
|
+
get(key: string): Promise<unknown | null>;
|
|
13
|
+
remove(key: string): Promise<void>;
|
|
14
|
+
clear(): Promise<void>;
|
|
15
|
+
private getKeyName;
|
|
16
|
+
private tryParse;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=rn-secure-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rn-secure-storage.d.ts","sourceRoot":"","sources":["../../src/storage/rn-secure-storage.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EAGb,MAAM,0BAA0B,CAAC;AAElC;;;GAGG;AACH,qBAAa,wBAAyB,YAAW,YAAY;IAY/C,OAAO,CAAC,SAAS;IAV7B,QAAQ,CAAC,eAAe,SAAS;IAGjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAKzB;gBAEiB,SAAS,GAAE,MAAuB;IAEhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAyBzC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,QAAQ;CAOjB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as Keychain from 'react-native-keychain';
|
|
2
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
3
|
+
import { DPOP_STORAGE_KEYS, AUTH_STORAGE_KEYS } from '@explorins/pers-sdk/core';
|
|
4
|
+
/**
|
|
5
|
+
* Secure Storage implementation for React Native
|
|
6
|
+
* Uses Keychain/Keystore for sensitive data and AsyncStorage for non-sensitive data.
|
|
7
|
+
*/
|
|
8
|
+
export class ReactNativeSecureStorage {
|
|
9
|
+
constructor(keyPrefix = 'pers_tokens_') {
|
|
10
|
+
this.keyPrefix = keyPrefix;
|
|
11
|
+
// Set to false because Keychain stores strings, so we need extractable (serializable) keys
|
|
12
|
+
this.supportsObjects = false;
|
|
13
|
+
// Define which keys MUST go to secure hardware storage
|
|
14
|
+
this.SECURE_KEYS = new Set([
|
|
15
|
+
DPOP_STORAGE_KEYS.PRIVATE,
|
|
16
|
+
AUTH_STORAGE_KEYS.ACCESS_TOKEN,
|
|
17
|
+
AUTH_STORAGE_KEYS.REFRESH_TOKEN,
|
|
18
|
+
AUTH_STORAGE_KEYS.PROVIDER_TOKEN
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
async set(key, value) {
|
|
22
|
+
const prefixedKey = this.getKeyName(key);
|
|
23
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
24
|
+
// Cast key to any to bypass strict literal type checking of the Set.has method
|
|
25
|
+
// In runtime, 'key' is just a string, so this is safe.
|
|
26
|
+
if (this.SECURE_KEYS.has(key)) {
|
|
27
|
+
// Store in Keychain/Keystore
|
|
28
|
+
// Service name acts as the key identifier in simple usage
|
|
29
|
+
await Keychain.setGenericPassword(prefixedKey, stringValue, { service: prefixedKey });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Store standard config/metadata in AsyncStorage
|
|
33
|
+
await AsyncStorage.setItem(prefixedKey, stringValue);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async get(key) {
|
|
37
|
+
const prefixedKey = this.getKeyName(key);
|
|
38
|
+
if (this.SECURE_KEYS.has(key)) {
|
|
39
|
+
try {
|
|
40
|
+
const credentials = await Keychain.getGenericPassword({ service: prefixedKey });
|
|
41
|
+
if (credentials) {
|
|
42
|
+
return this.tryParse(credentials.password);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
console.warn(`[ReactNativeSecureStorage] Failed to access keychain for ${key}`, e);
|
|
48
|
+
return null; // Key not found or access denied
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
try {
|
|
53
|
+
const val = await AsyncStorage.getItem(prefixedKey);
|
|
54
|
+
return val ? this.tryParse(val) : null;
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
console.warn(`[ReactNativeSecureStorage] Failed to access AsyncStorage for ${key}`, e);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async remove(key) {
|
|
63
|
+
const prefixedKey = this.getKeyName(key);
|
|
64
|
+
if (this.SECURE_KEYS.has(key)) {
|
|
65
|
+
await Keychain.resetGenericPassword({ service: prefixedKey });
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
await AsyncStorage.removeItem(prefixedKey);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async clear() {
|
|
72
|
+
// Clear all known secure keys
|
|
73
|
+
for (const key of this.SECURE_KEYS) {
|
|
74
|
+
await Keychain.resetGenericPassword({ service: this.getKeyName(key) });
|
|
75
|
+
}
|
|
76
|
+
// Clear AsyncStorage keys related to PERS
|
|
77
|
+
try {
|
|
78
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
79
|
+
const ourKeys = allKeys.filter(k => k.startsWith(this.keyPrefix));
|
|
80
|
+
if (ourKeys.length > 0) {
|
|
81
|
+
await AsyncStorage.multiRemove(ourKeys);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
console.warn('[ReactNativeSecureStorage] Failed to clear AsyncStorage', e);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
getKeyName(key) {
|
|
89
|
+
// For Keychain, we might want to avoid prefixes if we want to share across apps,
|
|
90
|
+
// but for simple isolation, prefix is fine to avoid collisions if multiple instances exist.
|
|
91
|
+
// However, Keychain services are global to the app bundle ID usually.
|
|
92
|
+
return `${this.keyPrefix}${key}`;
|
|
93
|
+
}
|
|
94
|
+
tryParse(val) {
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(val);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return val;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@explorins/pers-sdk-react-native",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.27",
|
|
4
4
|
"description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -37,15 +37,16 @@
|
|
|
37
37
|
"author": "eXplorins",
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@explorins/pers-sdk": "
|
|
41
|
-
"@explorins/pers-shared": "^2.1.
|
|
42
|
-
"@explorins/pers-signer": "^1.0.
|
|
40
|
+
"@explorins/pers-sdk": "file:.yalc/@explorins/pers-sdk",
|
|
41
|
+
"@explorins/pers-shared": "^2.1.52",
|
|
42
|
+
"@explorins/pers-signer": "^1.0.32",
|
|
43
43
|
"@explorins/web3-ts": "^0.3.75",
|
|
44
44
|
"buffer": "^6.0.3",
|
|
45
45
|
"ethers": "^6.15.0",
|
|
46
46
|
"react-native-get-random-values": "^2.0.0",
|
|
47
47
|
"react-native-keychain": "^10.0.0",
|
|
48
|
-
"react-native-passkey": "^3.3.2"
|
|
48
|
+
"react-native-passkey": "^3.3.2",
|
|
49
|
+
"react-native-quick-crypto": "^1.0.6"
|
|
49
50
|
},
|
|
50
51
|
"browser": {
|
|
51
52
|
"crypto": false,
|
|
@@ -1,81 +1,9 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { usePersSDK } from '../providers/PersSDKProvider';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
tokenType?: string;
|
|
8
|
-
triggerProcessType?: string;
|
|
9
|
-
tenantId?: string;
|
|
10
|
-
chainId?: number;
|
|
11
|
-
type?: string;
|
|
12
|
-
[key: string]: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type TransactionAnalyticsGroupBy =
|
|
16
|
-
| 'month' | 'week' | 'day' | 'year' | 'quarter'
|
|
17
|
-
| 'tokenType' | 'status' | 'chainId' | 'triggerProcessType' | 'type'
|
|
18
|
-
| 'senderAddress' | 'recipientAddress' | 'senderId' | 'recipientId' | 'senderOwnerType' | 'recipientOwnerType' | 'createdAt';
|
|
19
|
-
|
|
20
|
-
type TransactionAnalyticsMetric =
|
|
21
|
-
| 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
22
|
-
|
|
23
|
-
interface TransactionAnalyticsGroupByExpression {
|
|
24
|
-
expression: string;
|
|
25
|
-
alias: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Request DTO for transaction analytics
|
|
30
|
-
*
|
|
31
|
-
* @interface TransactionAnalyticsRequestDTO
|
|
32
|
-
*/
|
|
33
|
-
export interface TransactionAnalyticsRequestDTO {
|
|
34
|
-
filters?: TransactionAnalyticsFilters;
|
|
35
|
-
groupBy?: TransactionAnalyticsGroupBy[];
|
|
36
|
-
groupByExpressions?: TransactionAnalyticsGroupByExpression[];
|
|
37
|
-
metrics?: TransactionAnalyticsMetric[];
|
|
38
|
-
orderBy?: string;
|
|
39
|
-
orderDirection?: 'ASC' | 'DESC';
|
|
40
|
-
limit?: number;
|
|
41
|
-
startDate?: string;
|
|
42
|
-
endDate?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface TransactionAnalyticsResultItem {
|
|
46
|
-
[key: string]: string | number | Date | undefined;
|
|
47
|
-
count?: string;
|
|
48
|
-
sum?: number;
|
|
49
|
-
avg?: number;
|
|
50
|
-
min?: number;
|
|
51
|
-
max?: number;
|
|
52
|
-
tokentype?: string;
|
|
53
|
-
tokenType?: string;
|
|
54
|
-
year?: string;
|
|
55
|
-
month?: string;
|
|
56
|
-
type?: string;
|
|
57
|
-
senderaddress?: string;
|
|
58
|
-
senderAddress?: string;
|
|
59
|
-
recipientaddress?: string;
|
|
60
|
-
recipientAddress?: string;
|
|
61
|
-
triggerprocesstype?: string;
|
|
62
|
-
triggerProcessType?: string;
|
|
63
|
-
userid?: string;
|
|
64
|
-
userId?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Response DTO for transaction analytics
|
|
69
|
-
*
|
|
70
|
-
* @interface TransactionAnalyticsResponseDTO
|
|
71
|
-
*/
|
|
72
|
-
export interface TransactionAnalyticsResponseDTO {
|
|
73
|
-
results: TransactionAnalyticsResultItem[];
|
|
74
|
-
totalGroups: number;
|
|
75
|
-
metadata: {
|
|
76
|
-
executionTime: string;
|
|
77
|
-
};
|
|
78
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
TransactionAnalyticsRequestDTO,
|
|
5
|
+
TransactionAnalyticsResponseDTO
|
|
6
|
+
} from '@explorins/pers-shared';
|
|
79
7
|
|
|
80
8
|
/**
|
|
81
9
|
* React hook for analytics operations in the PERS SDK
|
|
@@ -5,9 +5,9 @@ import type {
|
|
|
5
5
|
TransactionRequestDTO,
|
|
6
6
|
TransactionRequestResponseDTO,
|
|
7
7
|
TransactionDTO,
|
|
8
|
-
TransactionRole
|
|
8
|
+
TransactionRole,
|
|
9
|
+
TransactionPaginationRequestDTO
|
|
9
10
|
} from '@explorins/pers-shared';
|
|
10
|
-
import type { TransactionPaginationParams } from '@explorins/pers-sdk/transaction';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* React hook for transaction operations in the PERS SDK
|
|
@@ -196,7 +196,7 @@ export const useTransactions = () => {
|
|
|
196
196
|
}
|
|
197
197
|
}, [sdk, isInitialized]);
|
|
198
198
|
|
|
199
|
-
const getPaginatedTransactions = useCallback(async (params:
|
|
199
|
+
const getPaginatedTransactions = useCallback(async (params: TransactionPaginationRequestDTO): Promise<any> => {
|
|
200
200
|
if (!isInitialized || !sdk) {
|
|
201
201
|
throw new Error('SDK not initialized. Call initialize() first.');
|
|
202
202
|
}
|
package/src/index.ts
CHANGED
|
@@ -68,6 +68,19 @@ export {
|
|
|
68
68
|
// SECURE STORAGE
|
|
69
69
|
// ==============================================================================
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Secure Token Storage with Keychain Integration
|
|
73
|
+
*
|
|
74
|
+
* Implements the TokenStorage interface using React Native Keychain for sensitive data
|
|
75
|
+
* (Private Keys, Access Tokens) and AsyncStorage for configuration/public data.
|
|
76
|
+
*
|
|
77
|
+
* - Encryption: Hardware-backed keystore
|
|
78
|
+
* - Persistence: Survives app uninstall (on iOS usually) or clears on uninstall (Android) if configured
|
|
79
|
+
*
|
|
80
|
+
* @see {@link ReactNativeSecureStorage} - The implementation class
|
|
81
|
+
*/
|
|
82
|
+
export { ReactNativeSecureStorage } from './storage/rn-secure-storage';
|
|
83
|
+
|
|
71
84
|
/**
|
|
72
85
|
* Secure token storage using React Native AsyncStorage
|
|
73
86
|
*
|
|
@@ -75,11 +88,22 @@ export {
|
|
|
75
88
|
* Integrates with React Native Keychain for enhanced security on supported devices.
|
|
76
89
|
*
|
|
77
90
|
* @see {@link AsyncStorageTokenStorage} - Secure storage implementation
|
|
91
|
+
* @deprecated Use ReactNativeSecureStorage for better security (Keychain integration)
|
|
78
92
|
*/
|
|
79
93
|
export {
|
|
80
94
|
AsyncStorageTokenStorage,
|
|
81
95
|
} from './storage/async-storage-token-storage';
|
|
82
96
|
|
|
97
|
+
// ==============================================================================
|
|
98
|
+
// DPoP PROVIDER
|
|
99
|
+
// ==============================================================================
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* React Native DPoP Crypto Provider
|
|
103
|
+
* implements DPoPCryptoProvider using react-native-quick-crypto
|
|
104
|
+
*/
|
|
105
|
+
export { ReactNativeDPoPProvider } from './providers/rn-dpop-provider';
|
|
106
|
+
|
|
83
107
|
// ==============================================================================
|
|
84
108
|
// CORE PROVIDER & CONTEXT
|
|
85
109
|
// ==============================================================================
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { createContext, useContext, useState, ReactNode, useCallback, useRef } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import { PersSDK, PersConfig, DefaultAuthProvider, DPoPCryptoProvider } from '@explorins/pers-sdk/core';
|
|
3
4
|
import { ReactNativeHttpClient } from './react-native-http-client';
|
|
4
5
|
import { createReactNativeAuthProvider } from './react-native-auth-provider';
|
|
6
|
+
import { ReactNativeDPoPProvider } from './rn-dpop-provider';
|
|
5
7
|
import { UserDTO, AdminDTO } from '@explorins/pers-shared';
|
|
6
8
|
|
|
7
9
|
// Import manager types for TypeScript
|
|
@@ -102,6 +104,16 @@ export const PersSDKProvider: React.FC<{
|
|
|
102
104
|
authProvider
|
|
103
105
|
};
|
|
104
106
|
|
|
107
|
+
// Inject Native DPoP Provider (crypto-based) for mobile platforms
|
|
108
|
+
// Web platforms use built-in WebCrypto provider by default
|
|
109
|
+
if (Platform.OS !== 'web' && (!config.dpop?.cryptoProvider)) {
|
|
110
|
+
sdkConfig.dpop = {
|
|
111
|
+
...(config.dpop || {}),
|
|
112
|
+
enabled: config.dpop?.enabled ?? true,
|
|
113
|
+
cryptoProvider: new ReactNativeDPoPProvider()
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
105
117
|
// Initialize PersSDK with platform-aware auth provider
|
|
106
118
|
const sdkInstance = new PersSDK(httpClient, sdkConfig);
|
|
107
119
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { Platform } from 'react-native';
|
|
10
10
|
import { DefaultAuthProvider, AuthApi, AuthType, LocalStorageTokenStorage } from '@explorins/pers-sdk/core';
|
|
11
11
|
import { AsyncStorageTokenStorage } from '../storage/async-storage-token-storage';
|
|
12
|
+
import { ReactNativeSecureStorage } from '../storage/rn-secure-storage';
|
|
12
13
|
import type { TokenStorage } from '@explorins/pers-sdk/core';
|
|
13
14
|
|
|
14
15
|
// Use React Native's built-in platform detection
|
|
@@ -58,7 +59,7 @@ export function createReactNativeAuthProvider(
|
|
|
58
59
|
const tokenStorage = customStorage || (
|
|
59
60
|
isWebPlatform
|
|
60
61
|
? new LocalStorageTokenStorage()
|
|
61
|
-
: new
|
|
62
|
+
: new ReactNativeSecureStorage(keyPrefix)
|
|
62
63
|
);
|
|
63
64
|
|
|
64
65
|
// Return DefaultAuthProvider configured with platform-appropriate storage
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import crypto from 'react-native-quick-crypto';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
import { DPoPCryptoProvider, DPoPKeyPair } from '@explorins/pers-sdk/core';
|
|
4
|
+
|
|
5
|
+
export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
|
|
6
|
+
/**
|
|
7
|
+
* Generates a new key pair (ES256 recommended)
|
|
8
|
+
*
|
|
9
|
+
* Note: options.extractable is ignored because for React Native Keychain storage,
|
|
10
|
+
* we MUST export the keys as JWK/strings. We rely on the Keychain encryption
|
|
11
|
+
* for security at rest (High Security), making the key "Extractable" in memory
|
|
12
|
+
* but protected by hardware encryption when stored.
|
|
13
|
+
*/
|
|
14
|
+
async generateKeyPair(options?: { extractable?: boolean }): Promise<DPoPKeyPair> {
|
|
15
|
+
// Generate P-256 Key Pair
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err: any, publicKey: any, privateKey: any) => {
|
|
18
|
+
if (err) return reject(err);
|
|
19
|
+
|
|
20
|
+
// Export to JWK for SDK Compatibility
|
|
21
|
+
// We use 'export' because supportsObjects=false in our storage forces the SDK
|
|
22
|
+
// to expect serializable keys.
|
|
23
|
+
const pubJwk = publicKey.export({ format: 'jwk' });
|
|
24
|
+
const privJwk = privateKey.export({ format: 'jwk' });
|
|
25
|
+
|
|
26
|
+
resolve({
|
|
27
|
+
publicKey: pubJwk,
|
|
28
|
+
privateKey: privJwk
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string> {
|
|
35
|
+
// 1. Construct Header
|
|
36
|
+
const header = {
|
|
37
|
+
typ: 'dpop+jwt',
|
|
38
|
+
alg: 'ES256',
|
|
39
|
+
jwk: keyPair.publicKey
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// 2. Add Claims (iat/jti)
|
|
43
|
+
const finalPayload = {
|
|
44
|
+
...payload,
|
|
45
|
+
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
46
|
+
jti: payload.jti || crypto.randomUUID()
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 3. Encode
|
|
50
|
+
const b64Header = this.base64Url(JSON.stringify(header));
|
|
51
|
+
const b64Payload = this.base64Url(JSON.stringify(finalPayload));
|
|
52
|
+
const signingInput = `${b64Header}.${b64Payload}`;
|
|
53
|
+
|
|
54
|
+
// 4. Sign
|
|
55
|
+
const sign = crypto.createSign('SHA256');
|
|
56
|
+
sign.update(signingInput);
|
|
57
|
+
// sign.end() is not required/available in quick-crypto as it doesn't strictly follow stream interface
|
|
58
|
+
|
|
59
|
+
// Import private key back from JWK to sign
|
|
60
|
+
// The keyPair.privateKey is a JsonWebKey object because we exported it earlier.
|
|
61
|
+
// quick-crypto createPrivateKey expects jwk object if format is jwk
|
|
62
|
+
const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey as any, format: 'jwk' });
|
|
63
|
+
const signature = sign.sign(privateKeyObj);
|
|
64
|
+
// signature is a Buffer in quick-crypto
|
|
65
|
+
|
|
66
|
+
return `${signingInput}.${this.base64UrlBuffer(signature)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async hashAccessToken(accessToken: string): Promise<string> {
|
|
70
|
+
const hash = crypto.createHash('sha256').update(accessToken).digest();
|
|
71
|
+
// digest returns Buffer in quick-crypto
|
|
72
|
+
return this.base64UrlBuffer(hash);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --- Helpers ---
|
|
76
|
+
private base64Url(str: string): string {
|
|
77
|
+
return this.base64UrlBuffer(Buffer.from(str));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private base64UrlBuffer(buf: Buffer | Uint8Array): string {
|
|
81
|
+
// Ensure we have a Buffer
|
|
82
|
+
const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
83
|
+
return buffer.toString('base64')
|
|
84
|
+
.replace(/=/g, '')
|
|
85
|
+
.replace(/\+/g, '-')
|
|
86
|
+
.replace(/\//g, '_');
|
|
87
|
+
}
|
|
88
|
+
}
|