partner_react_native_sdk 0.1.4 → 0.1.5
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/lib/module/helpers/analytics/analytics_logger.js +4 -3
- package/lib/module/helpers/analytics/analytics_logger.js.map +1 -1
- package/lib/module/helpers/network/APICall.js +96 -4
- package/lib/module/helpers/network/APICall.js.map +1 -1
- package/lib/module/helpers/network/Encryption.js +241 -0
- package/lib/module/helpers/network/Encryption.js.map +1 -0
- package/lib/module/helpers/partner_library_react_native.js +52 -54
- package/lib/module/helpers/partner_library_react_native.js.map +1 -1
- package/lib/module/helpers/webview.js +30 -10
- package/lib/module/helpers/webview.js.map +1 -1
- package/lib/typescript/src/helpers/analytics/analytics_logger.d.ts.map +1 -1
- package/lib/typescript/src/helpers/network/APICall.d.ts.map +1 -1
- package/lib/typescript/src/helpers/network/Encryption.d.ts +1 -0
- package/lib/typescript/src/helpers/network/Encryption.d.ts.map +1 -0
- package/lib/typescript/src/helpers/partner_library_react_native.d.ts.map +1 -1
- package/lib/typescript/src/helpers/webview.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/helpers/analytics/analytics_logger.tsx +73 -72
- package/src/helpers/network/APICall.tsx +139 -54
- package/src/helpers/network/Encryption.tsx +239 -0
- package/src/helpers/partner_library_react_native.tsx +279 -273
- package/src/helpers/webview.tsx +39 -12
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// // Encryption.tsx
|
|
2
|
+
// // Tested with RN 0.73+.
|
|
3
|
+
|
|
4
|
+
// import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
5
|
+
// import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
// import * as base64js from 'base64-js';
|
|
7
|
+
// import { ServiceNames } from '../ServiceNames';
|
|
8
|
+
|
|
9
|
+
// type WebsiteKey = {
|
|
10
|
+
// kid: string;
|
|
11
|
+
// public: string; // PEM
|
|
12
|
+
// expiry: string; // ISO8601
|
|
13
|
+
// };
|
|
14
|
+
|
|
15
|
+
// const KEY_WEB = 'KEY_WEB';
|
|
16
|
+
// const KEY_WEB_EXPIRY = 'KEY_WEB_EXPIRY';
|
|
17
|
+
|
|
18
|
+
// const GCM_NONCE_LEN = 12; // 12-byte IV
|
|
19
|
+
// const GCM_TAG_LEN = 16; // 16-byte auth tag
|
|
20
|
+
// const AES_KEY_LEN = 256; // bits
|
|
21
|
+
|
|
22
|
+
// // ---------- Base64 helpers ----------
|
|
23
|
+
// const bytesToB64 = (bytes: Uint8Array): string => base64js.fromByteArray(bytes);
|
|
24
|
+
|
|
25
|
+
// const b64ToBytes = (b64: string): Uint8Array => base64js.toByteArray(b64);
|
|
26
|
+
|
|
27
|
+
// // ---------- UTF-8 helpers ----------
|
|
28
|
+
// const utf8Encode = (s: string): Uint8Array => new TextEncoder().encode(s);
|
|
29
|
+
|
|
30
|
+
// const utf8Decode = (bytes: Uint8Array): string =>
|
|
31
|
+
// new TextDecoder().decode(bytes);
|
|
32
|
+
|
|
33
|
+
// // ---------- AES-256-GCM ----------
|
|
34
|
+
|
|
35
|
+
// /** Generate a random AES-256-GCM CryptoKey (extractable for raw export). */
|
|
36
|
+
// export async function generateAESKey(): Promise<CryptoKey> {
|
|
37
|
+
// return await crypto.subtle.generateKey(
|
|
38
|
+
// { name: 'AES-GCM', length: AES_KEY_LEN },
|
|
39
|
+
// true, // extractable to raw for Base64 export
|
|
40
|
+
// ['encrypt', 'decrypt']
|
|
41
|
+
// );
|
|
42
|
+
// }
|
|
43
|
+
|
|
44
|
+
// /** Export AES key bytes → Base64 (to match your RSA input). */
|
|
45
|
+
// export async function exportAESKeyBase64(key: CryptoKey): Promise<string> {
|
|
46
|
+
// const raw = new Uint8Array(await crypto.subtle.exportKey('raw', key));
|
|
47
|
+
// return bytesToB64(raw);
|
|
48
|
+
// }
|
|
49
|
+
|
|
50
|
+
// /**
|
|
51
|
+
// * Encrypt plaintext with AES-GCM and return Base64( IV || ciphertext || tag )
|
|
52
|
+
// * Exactly matches your Flutter packing:
|
|
53
|
+
// * - IV: 12 bytes
|
|
54
|
+
// * - SubtleCrypto returns ciphertext||tag; we prepend IV and then Base64 the whole blob.
|
|
55
|
+
// */
|
|
56
|
+
// export async function encryptAES(
|
|
57
|
+
// plaintext: string,
|
|
58
|
+
// key: CryptoKey
|
|
59
|
+
// ): Promise<string> {
|
|
60
|
+
// const iv = crypto.getRandomValues(new Uint8Array(GCM_NONCE_LEN));
|
|
61
|
+
// const ptBytes = utf8Encode(plaintext);
|
|
62
|
+
|
|
63
|
+
// const ctWithTag = new Uint8Array(
|
|
64
|
+
// await crypto.subtle.encrypt(
|
|
65
|
+
// { name: 'AES-GCM', iv }, // 96-bit nonce
|
|
66
|
+
// key,
|
|
67
|
+
// ptBytes
|
|
68
|
+
// )
|
|
69
|
+
// );
|
|
70
|
+
// // Pack IV || (ciphertext||tag)
|
|
71
|
+
// const out = new Uint8Array(iv.length + ctWithTag.length);
|
|
72
|
+
// out.set(iv, 0);
|
|
73
|
+
// out.set(ctWithTag, iv.length);
|
|
74
|
+
// return bytesToB64(out);
|
|
75
|
+
// }
|
|
76
|
+
|
|
77
|
+
// /**
|
|
78
|
+
// * Decrypt Base64( IV || ciphertext || tag ) to plaintext.
|
|
79
|
+
// * Splits IV (12), tag (16) and feeds ciphertext||tag to AES-GCM.
|
|
80
|
+
// */
|
|
81
|
+
// export async function decryptAES(
|
|
82
|
+
// b64Data: string,
|
|
83
|
+
// key: CryptoKey
|
|
84
|
+
// ): Promise<string> {
|
|
85
|
+
// const data = b64ToBytes(b64Data);
|
|
86
|
+
// if (data.length < GCM_NONCE_LEN + GCM_TAG_LEN + 1) {
|
|
87
|
+
// throw new Error('Invalid GCM payload length');
|
|
88
|
+
// }
|
|
89
|
+
// const iv = data.slice(0, GCM_NONCE_LEN);
|
|
90
|
+
// const ctPlusTag = data.slice(GCM_NONCE_LEN); // subtle needs ciphertext||tag
|
|
91
|
+
// const pt = new Uint8Array(
|
|
92
|
+
// await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ctPlusTag)
|
|
93
|
+
// );
|
|
94
|
+
// return utf8Decode(pt);
|
|
95
|
+
// }
|
|
96
|
+
|
|
97
|
+
// // ---------- RSA-OAEP (SHA-256) ----------
|
|
98
|
+
|
|
99
|
+
// /** Parse PEM public key and import as CryptoKey (RSA-OAEP with SHA-256). */
|
|
100
|
+
// async function importRsaPublicKeyFromPem(pem: string): Promise<CryptoKey> {
|
|
101
|
+
// // Tolerate PEMs with/without headers
|
|
102
|
+
// const clean = pem
|
|
103
|
+
// .replace(/-----BEGIN PUBLIC KEY-----/g, '')
|
|
104
|
+
// .replace(/-----END PUBLIC KEY-----/g, '')
|
|
105
|
+
// .replace(/\s+/g, '');
|
|
106
|
+
// const der = b64ToBytes(clean);
|
|
107
|
+
// return await crypto.subtle.importKey(
|
|
108
|
+
// 'spki',
|
|
109
|
+
// der,
|
|
110
|
+
// { name: 'RSA-OAEP', hash: 'SHA-256' },
|
|
111
|
+
// false,
|
|
112
|
+
// ['encrypt']
|
|
113
|
+
// );
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
// /**
|
|
117
|
+
// * RSA-OAEP(SHA-256) encrypts the UTF-8 bytes of the **Base64(AES key)** string.
|
|
118
|
+
// * Returns Base64 ciphertext.
|
|
119
|
+
// */
|
|
120
|
+
// export async function encryptAESKeyWithRSA(
|
|
121
|
+
// aesKeyBase64: string,
|
|
122
|
+
// publicKeyPem: string
|
|
123
|
+
// ): Promise<string> {
|
|
124
|
+
// const pub = await importRsaPublicKeyFromPem(publicKeyPem);
|
|
125
|
+
// const data = utf8Encode(aesKeyBase64);
|
|
126
|
+
// const enc = new Uint8Array(
|
|
127
|
+
// await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, pub, data)
|
|
128
|
+
// );
|
|
129
|
+
// return bytesToB64(enc);
|
|
130
|
+
// }
|
|
131
|
+
|
|
132
|
+
// // ---------- Website key fetch / cache ----------
|
|
133
|
+
|
|
134
|
+
// /**
|
|
135
|
+
// * Fetches & caches the server public key.
|
|
136
|
+
// * Accepts two shapes (same as your Flutter):
|
|
137
|
+
// * A) { "<kid>": { kid, public, expiry } }
|
|
138
|
+
// * B) { kid, public, expiry }
|
|
139
|
+
// */
|
|
140
|
+
// export async function getWebsiteKey(): Promise<WebsiteKey> {
|
|
141
|
+
// const cachedJson = await AsyncStorage.getItem(KEY_WEB);
|
|
142
|
+
// const cachedExpiry = await AsyncStorage.getItem(KEY_WEB_EXPIRY);
|
|
143
|
+
|
|
144
|
+
// if (cachedJson && cachedExpiry) {
|
|
145
|
+
// try {
|
|
146
|
+
// const expiry = new Date(cachedExpiry);
|
|
147
|
+
// if (new Date() < expiry) {
|
|
148
|
+
// return JSON.parse(cachedJson) as WebsiteKey;
|
|
149
|
+
// }
|
|
150
|
+
// } catch {
|
|
151
|
+
// // ignore and refetch
|
|
152
|
+
// }
|
|
153
|
+
// }
|
|
154
|
+
// const txnId = uuidv4();
|
|
155
|
+
// const resp = await fetch(ServiceNames.WEBSITE_KEYS, {
|
|
156
|
+
// method: 'GET',
|
|
157
|
+
// headers: {
|
|
158
|
+
// 'X-Txn-ID': txnId,
|
|
159
|
+
// },
|
|
160
|
+
// });
|
|
161
|
+
|
|
162
|
+
// if (!resp.ok) {
|
|
163
|
+
// throw new Error(`Website keys fetch failed: ${resp.status}`);
|
|
164
|
+
// }
|
|
165
|
+
// const raw = await resp.json();
|
|
166
|
+
// console.log('Fetched website key', { txnId, raw });
|
|
167
|
+
|
|
168
|
+
// let obj: any;
|
|
169
|
+
// if (
|
|
170
|
+
// raw &&
|
|
171
|
+
// typeof raw === 'object' &&
|
|
172
|
+
// !(
|
|
173
|
+
// Object.prototype.hasOwnProperty.call(raw, 'kid') &&
|
|
174
|
+
// (Object.prototype.hasOwnProperty.call(raw, 'public') ||
|
|
175
|
+
// Object.prototype.hasOwnProperty.call(raw, 'public_key'))
|
|
176
|
+
// )
|
|
177
|
+
// ) {
|
|
178
|
+
// // Assume single-entry map keyed by KID
|
|
179
|
+
// const values = Object.values(raw);
|
|
180
|
+
// if (!values.length || typeof values[0] !== 'object') {
|
|
181
|
+
// throw new Error('Unexpected keyset response shape');
|
|
182
|
+
// }
|
|
183
|
+
// obj = values[0];
|
|
184
|
+
// } else {
|
|
185
|
+
// obj = raw;
|
|
186
|
+
// }
|
|
187
|
+
|
|
188
|
+
// const kid: string | undefined = obj.kid ?? obj.KID;
|
|
189
|
+
// const publicKey: string | undefined = obj.public ?? obj.public_key;
|
|
190
|
+
// const expiry: string | undefined = obj.expiry ?? obj.exp ?? obj.expiresAt;
|
|
191
|
+
|
|
192
|
+
// if (!kid || !publicKey || !expiry) {
|
|
193
|
+
// throw new Error('Missing kid/public/expiry in website key response');
|
|
194
|
+
// }
|
|
195
|
+
|
|
196
|
+
// const normalized: WebsiteKey = { kid, public: publicKey, expiry };
|
|
197
|
+
|
|
198
|
+
// await AsyncStorage.setItem(KEY_WEB, JSON.stringify(normalized));
|
|
199
|
+
// await AsyncStorage.setItem(KEY_WEB_EXPIRY, expiry);
|
|
200
|
+
|
|
201
|
+
// return normalized;
|
|
202
|
+
// }
|
|
203
|
+
|
|
204
|
+
// // ---------- Optional: Hybrid helpers (one-liners) ----------
|
|
205
|
+
|
|
206
|
+
// export type HybridEnvelope = {
|
|
207
|
+
// kid: string;
|
|
208
|
+
// rsaWrappedAESKey: string; // Base64 RSA(OAEP) of Base64(AES key)
|
|
209
|
+
// cipherData: string; // Base64( IV || ciphertext || tag )
|
|
210
|
+
// };
|
|
211
|
+
|
|
212
|
+
// export async function encryptHybrid(
|
|
213
|
+
// plaintext: string
|
|
214
|
+
// ): Promise<HybridEnvelope> {
|
|
215
|
+
// // 1) fetch server key
|
|
216
|
+
// const { kid, public: publicPem } = await getWebsiteKey();
|
|
217
|
+
|
|
218
|
+
// // 2) generate AES key & encrypt data
|
|
219
|
+
// const aesKey = await generateAESKey();
|
|
220
|
+
// const cipherData = await encryptAES(plaintext, aesKey);
|
|
221
|
+
|
|
222
|
+
// // 3) export AES key to Base64 and RSA-wrap it
|
|
223
|
+
// const aesKeyB64 = await exportAESKeyBase64(aesKey);
|
|
224
|
+
// const rsaWrappedAESKey = await encryptAESKeyWithRSA(aesKeyB64, publicPem);
|
|
225
|
+
|
|
226
|
+
// return { kid, rsaWrappedAESKey, cipherData };
|
|
227
|
+
// }
|
|
228
|
+
|
|
229
|
+
// /**
|
|
230
|
+
// * Decrypt using an already-imported AES key (symmetric path).
|
|
231
|
+
// * Use this for local decrypts (e.g., testing) or when the server sends you encrypted data
|
|
232
|
+
// * and you already have the AES key.
|
|
233
|
+
// */
|
|
234
|
+
// export async function decryptHybridLocal(
|
|
235
|
+
// cipherData: string,
|
|
236
|
+
// aesKey: CryptoKey
|
|
237
|
+
// ): Promise<string> {
|
|
238
|
+
// return await decryptAES(cipherData, aesKey);
|
|
239
|
+
// }
|