banking_dcb_sdk_react_native 0.1.9

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.
Files changed (100) hide show
  1. package/LICENSE +20 -0
  2. package/PartnerReactNativeSdk.podspec +31 -0
  3. package/README.md +14 -0
  4. package/android/build.gradle +100 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/AndroidManifestNew.xml +2 -0
  8. package/android/src/main/java/com/partnerreactnativesdk/PartnerReactNativeSdkModule.kt +23 -0
  9. package/android/src/main/java/com/partnerreactnativesdk/PartnerReactNativeSdkPackage.kt +33 -0
  10. package/ios/PartnerReactNativeSdk.h +6 -0
  11. package/ios/PartnerReactNativeSdk.mm +29 -0
  12. package/lib/module/helpers/ServiceNames.js +135 -0
  13. package/lib/module/helpers/ServiceNames.js.map +1 -0
  14. package/lib/module/helpers/analytics/analytics_event_model.js +37 -0
  15. package/lib/module/helpers/analytics/analytics_event_model.js.map +1 -0
  16. package/lib/module/helpers/analytics/analytics_logger.js +73 -0
  17. package/lib/module/helpers/analytics/analytics_logger.js.map +1 -0
  18. package/lib/module/helpers/analytics/event_storage.js +35 -0
  19. package/lib/module/helpers/analytics/event_storage.js.map +1 -0
  20. package/lib/module/helpers/banking_dcb_react_native.js +297 -0
  21. package/lib/module/helpers/banking_dcb_react_native.js.map +1 -0
  22. package/lib/module/helpers/helper.js +4 -0
  23. package/lib/module/helpers/helper.js.map +1 -0
  24. package/lib/module/helpers/network/APICall.js +111 -0
  25. package/lib/module/helpers/network/APICall.js.map +1 -0
  26. package/lib/module/helpers/network/Encryption.js +148 -0
  27. package/lib/module/helpers/network/Encryption.js.map +1 -0
  28. package/lib/module/helpers/network/network_manager.js +112 -0
  29. package/lib/module/helpers/network/network_manager.js.map +1 -0
  30. package/lib/module/helpers/utils/Constants.js +12 -0
  31. package/lib/module/helpers/utils/Constants.js.map +1 -0
  32. package/lib/module/helpers/utils/LibraryConstants.js +115 -0
  33. package/lib/module/helpers/utils/LibraryConstants.js.map +1 -0
  34. package/lib/module/helpers/utils/deviceInfoManager.js +39 -0
  35. package/lib/module/helpers/utils/deviceInfoManager.js.map +1 -0
  36. package/lib/module/helpers/utils/headerManager.js +21 -0
  37. package/lib/module/helpers/utils/headerManager.js.map +1 -0
  38. package/lib/module/helpers/utils/themeManager.js +40 -0
  39. package/lib/module/helpers/utils/themeManager.js.map +1 -0
  40. package/lib/module/helpers/utils/webviewCallback.js +20 -0
  41. package/lib/module/helpers/utils/webviewCallback.js.map +1 -0
  42. package/lib/module/helpers/webview.js +314 -0
  43. package/lib/module/helpers/webview.js.map +1 -0
  44. package/lib/module/index.js +5 -0
  45. package/lib/module/index.js.map +1 -0
  46. package/lib/module/package.json +1 -0
  47. package/lib/typescript/package.json +1 -0
  48. package/lib/typescript/src/helpers/ServiceNames.d.ts +51 -0
  49. package/lib/typescript/src/helpers/ServiceNames.d.ts.map +1 -0
  50. package/lib/typescript/src/helpers/analytics/analytics_event_model.d.ts +16 -0
  51. package/lib/typescript/src/helpers/analytics/analytics_event_model.d.ts.map +1 -0
  52. package/lib/typescript/src/helpers/analytics/analytics_logger.d.ts +17 -0
  53. package/lib/typescript/src/helpers/analytics/analytics_logger.d.ts.map +1 -0
  54. package/lib/typescript/src/helpers/analytics/event_storage.d.ts +8 -0
  55. package/lib/typescript/src/helpers/analytics/event_storage.d.ts.map +1 -0
  56. package/lib/typescript/src/helpers/banking_dcb_react_native.d.ts +33 -0
  57. package/lib/typescript/src/helpers/banking_dcb_react_native.d.ts.map +1 -0
  58. package/lib/typescript/src/helpers/helper.d.ts +1 -0
  59. package/lib/typescript/src/helpers/helper.d.ts.map +1 -0
  60. package/lib/typescript/src/helpers/network/APICall.d.ts +13 -0
  61. package/lib/typescript/src/helpers/network/APICall.d.ts.map +1 -0
  62. package/lib/typescript/src/helpers/network/Encryption.d.ts +24 -0
  63. package/lib/typescript/src/helpers/network/Encryption.d.ts.map +1 -0
  64. package/lib/typescript/src/helpers/network/network_manager.d.ts +21 -0
  65. package/lib/typescript/src/helpers/network/network_manager.d.ts.map +1 -0
  66. package/lib/typescript/src/helpers/utils/Constants.d.ts +10 -0
  67. package/lib/typescript/src/helpers/utils/Constants.d.ts.map +1 -0
  68. package/lib/typescript/src/helpers/utils/LibraryConstants.d.ts +24 -0
  69. package/lib/typescript/src/helpers/utils/LibraryConstants.d.ts.map +1 -0
  70. package/lib/typescript/src/helpers/utils/deviceInfoManager.d.ts +4 -0
  71. package/lib/typescript/src/helpers/utils/deviceInfoManager.d.ts.map +1 -0
  72. package/lib/typescript/src/helpers/utils/headerManager.d.ts +5 -0
  73. package/lib/typescript/src/helpers/utils/headerManager.d.ts.map +1 -0
  74. package/lib/typescript/src/helpers/utils/themeManager.d.ts +12 -0
  75. package/lib/typescript/src/helpers/utils/themeManager.d.ts.map +1 -0
  76. package/lib/typescript/src/helpers/utils/webviewCallback.d.ts +13 -0
  77. package/lib/typescript/src/helpers/utils/webviewCallback.d.ts.map +1 -0
  78. package/lib/typescript/src/helpers/webview.d.ts +21 -0
  79. package/lib/typescript/src/helpers/webview.d.ts.map +1 -0
  80. package/lib/typescript/src/index.d.ts +5 -0
  81. package/lib/typescript/src/index.d.ts.map +1 -0
  82. package/package.json +170 -0
  83. package/react-native.config.js +12 -0
  84. package/src/helpers/ServiceNames.tsx +170 -0
  85. package/src/helpers/analytics/analytics_event_model.tsx +47 -0
  86. package/src/helpers/analytics/analytics_logger.tsx +91 -0
  87. package/src/helpers/analytics/event_storage.tsx +44 -0
  88. package/src/helpers/banking_dcb_react_native.tsx +413 -0
  89. package/src/helpers/helper.tsx +1 -0
  90. package/src/helpers/network/APICall.tsx +154 -0
  91. package/src/helpers/network/Encryption.tsx +179 -0
  92. package/src/helpers/network/network_manager.tsx +122 -0
  93. package/src/helpers/utils/Constants.tsx +10 -0
  94. package/src/helpers/utils/LibraryConstants.tsx +133 -0
  95. package/src/helpers/utils/deviceInfoManager.tsx +37 -0
  96. package/src/helpers/utils/headerManager.tsx +22 -0
  97. package/src/helpers/utils/themeManager.tsx +51 -0
  98. package/src/helpers/utils/webviewCallback.tsx +25 -0
  99. package/src/helpers/webview.tsx +410 -0
  100. package/src/index.tsx +5 -0
@@ -0,0 +1,413 @@
1
+ import { APICall } from './network/APICall';
2
+ import { DeviceInfoManager } from './utils/deviceInfoManager';
3
+ import { LibraryConstants } from './utils/LibraryConstants';
4
+ import { type WebViewCallbackFunction } from './utils/webviewCallback';
5
+ import { ServiceNames } from './ServiceNames';
6
+ import { Constants } from './utils/Constants';
7
+ import { WebView } from './webview';
8
+ import { SafeAreaView } from 'react-native-safe-area-context';
9
+ import { StatusBar } from 'react-native';
10
+ export type { WebViewCallbackFunction } from './utils/webviewCallback';
11
+ import { AnalyticsLogger } from './analytics/analytics_logger';
12
+ import * as base64js from 'base64-js';
13
+
14
+ type InitOptions = {
15
+ whitelistedDomains: string[];
16
+ deviceBindingEnabled: boolean;
17
+ };
18
+
19
+ // Extend the global type to include base64FromArrayBuffer
20
+ declare global {
21
+ var base64FromArrayBuffer: ((arrayBuffer: ArrayBuffer) => string) | undefined;
22
+ }
23
+ type JSONMap = Record<string, any>;
24
+
25
+ export class BankingDcbReactNative {
26
+ private static instance: BankingDcbReactNative | null = null;
27
+ private static intialized = false;
28
+
29
+ private _apiCall = new APICall();
30
+ static webViewCallback: WebViewCallbackFunction;
31
+ private _analyticsLogger!: AnalyticsLogger;
32
+ private constructor() {}
33
+
34
+ static getInstance(): BankingDcbReactNative {
35
+ if (!BankingDcbReactNative.instance) {
36
+ BankingDcbReactNative.instance = new BankingDcbReactNative();
37
+ }
38
+ return BankingDcbReactNative.instance;
39
+ }
40
+
41
+ static async init(
42
+ hostName: string,
43
+ options: InitOptions = {
44
+ whitelistedDomains: [],
45
+ deviceBindingEnabled: false,
46
+ }
47
+ ): Promise<void> {
48
+ // console.log("PartnerLibrary.init - initialized:", PartnerLibrary.intialized);
49
+ if (BankingDcbReactNative.intialized) return;
50
+
51
+ try {
52
+ if (!global.base64FromArrayBuffer) {
53
+ global.base64FromArrayBuffer = (arrayBuffer: ArrayBuffer) => {
54
+ return base64js.fromByteArray(new Uint8Array(arrayBuffer));
55
+ };
56
+ }
57
+
58
+ // Ensure instance exists before setup
59
+ if (!BankingDcbReactNative.instance) {
60
+ // console.log("PartnerLibrary.init - creating new instance");
61
+ BankingDcbReactNative.instance = new BankingDcbReactNative();
62
+ }
63
+ await BankingDcbReactNative.instance._setup(
64
+ hostName,
65
+ options.whitelistedDomains,
66
+ options.deviceBindingEnabled
67
+ );
68
+ BankingDcbReactNative.intialized = true;
69
+ } catch (error) {
70
+ console.error('Initialization failed: ', error);
71
+ throw new Error('Initialization failed');
72
+ }
73
+ }
74
+
75
+ private async _setup(
76
+ hostName: string,
77
+ whitelistedDomains: string[],
78
+ deviceBindingEnabled: boolean
79
+ ): Promise<void> {
80
+ LibraryConstants.init({
81
+ host: hostName,
82
+ whitelistedDomains: whitelistedDomains,
83
+ deviceBinding: deviceBindingEnabled,
84
+ });
85
+ this._analyticsLogger = new AnalyticsLogger();
86
+ // this._analyticsLogger.logEvent({ event: 'REACT_NATIVE_INTIALIZED' });
87
+ }
88
+
89
+ async open(
90
+ module: string,
91
+ token: string,
92
+ WebViewCallbackFunction: WebViewCallbackFunction
93
+ ): Promise<React.ReactElement> {
94
+ // this._analyticsLogger.logEvent({ event: 'REACT_NATIVE_OPEN_FN_CALLED' });
95
+
96
+ if (!BankingDcbReactNative.intialized) {
97
+ throw new Error('PartnerLibrary not initialized. Call init() first.');
98
+ }
99
+ try {
100
+ let bank = '';
101
+
102
+ if (module.includes('banking/')) {
103
+ const startIndex = module.indexOf('banking/') + 'banking/'.length;
104
+ const temp = module.substring(startIndex);
105
+ const endIndex = temp.indexOf('/');
106
+ bank = endIndex === -1 ? temp : temp.substring(0, endIndex);
107
+ }
108
+
109
+ return this._loginAndNavigateToWebView(
110
+ module,
111
+ bank,
112
+ token,
113
+ WebViewCallbackFunction
114
+ );
115
+ } catch (e) {
116
+ console.error('Error opening webview:', e);
117
+ throw e;
118
+ }
119
+ }
120
+
121
+ private async _checkDeviceBinding(
122
+ bank: string,
123
+ url: string,
124
+ WebViewCallbackFunction: WebViewCallbackFunction
125
+ ): Promise<React.ReactElement> {
126
+ // this._analyticsLogger.logEvent({
127
+ // event: 'REACT_NATIVE_DEVICE_BINDING_CALLED',
128
+ // });
129
+
130
+ console.log('SDK:LOG: entered in check device binding');
131
+ if (LibraryConstants.deviceBindingEnabled) {
132
+ // TODO: Build the device binding flow
133
+ return Promise.resolve(<></>);
134
+ } else {
135
+ console.log('SDK:LOG: calling setup device session');
136
+
137
+ return this._setupDeviceSession(bank, url, WebViewCallbackFunction);
138
+ }
139
+ }
140
+
141
+ private async _loginAndNavigateToWebView(
142
+ module: string,
143
+ bank: string,
144
+ token: string,
145
+ WebViewCallbackFunction: WebViewCallbackFunction
146
+ ): Promise<React.ReactElement> {
147
+ // console.log("PartnerLibrary._loginAndNavigateToWebView - LibraryConstants.hostName:", LibraryConstants.hostName);
148
+ try {
149
+ // this._analyticsLogger.logEvent({ event: 'REACT_NATIVE_LOGIN_FN_CALLED' });
150
+ let tokenResponse: any;
151
+ const url = ServiceNames.LOGIN_URL;
152
+ console.log('SDK:LOG: ', url);
153
+ console.log('SDK:LOG: Token is : ', token);
154
+ tokenResponse = await this._apiCall.callAPI(
155
+ 'POST',
156
+ ServiceNames.LOGIN_URL,
157
+ {
158
+ body: {
159
+ token: token,
160
+ },
161
+ }
162
+ );
163
+ console.log('SDK:LOG Token response is : ', tokenResponse);
164
+ // this._analyticsLogger.logEvent({
165
+ // event: 'REACT_NATIVE_LOGIN_FN_RESPONSE :',
166
+ // response: tokenResponse,
167
+ // });
168
+
169
+ if (tokenResponse?.data?.code === 'USER_TOKEN_EXPIRED') {
170
+ console.log('SDK:LOG Token expired');
171
+ return Promise.resolve(<></>);
172
+ }
173
+
174
+ if (
175
+ tokenResponse &&
176
+ tokenResponse.data &&
177
+ typeof tokenResponse.data === 'object' &&
178
+ tokenResponse.data !== null &&
179
+ 'RATE_LIMIT_USER' in tokenResponse.data
180
+ ) {
181
+ console.log('SDK:LOG: rate limit user');
182
+ }
183
+
184
+ // console.log("Checking login success condition:", {
185
+ // statusCode: tokenResponse?.statusCode,
186
+ // code: tokenResponse?.data?.code
187
+ // });
188
+
189
+ if (tokenResponse?.data?.code === 'USER_LOGIN_SUCCESS') {
190
+ console.log('SDK:LOG: User login success');
191
+ this.fetchAndSetTheme();
192
+ return this._checkDeviceBinding(bank, module, WebViewCallbackFunction);
193
+ } else {
194
+ console.log(
195
+ 'SDK:LOG: Login not successful. Token Response:',
196
+ tokenResponse
197
+ );
198
+ return Promise.resolve(<></>);
199
+ }
200
+ } catch (e) {
201
+ console.error('SDK:LOG: Error during login:', e);
202
+ throw e;
203
+ }
204
+ }
205
+
206
+ private async _setupDeviceSession(
207
+ bank: string,
208
+ url: string,
209
+ WebViewCallbackFunction: WebViewCallbackFunction
210
+ ): Promise<React.ReactElement> {
211
+ try {
212
+ console.log('SDK:LOG: in setup device session');
213
+
214
+ const deviceInfo = await DeviceInfoManager.getDeviceInfo();
215
+ const appVersion = deviceInfo['app_version'] || 'unknown';
216
+
217
+ if (__DEV__) {
218
+ console.log('Device Info:', deviceInfo);
219
+ console.log('App Version:', appVersion);
220
+ }
221
+ console.log('SDK:LOG device uuid is ');
222
+
223
+ const response = await this._apiCall.callAPI(
224
+ 'POST',
225
+ ServiceNames.DEVICE_SESSION.params({ partner: bank }),
226
+ {
227
+ body: {
228
+ [Constants.MANUFACTURER]: deviceInfo[Constants.MANUFACTURER],
229
+ [Constants.MODEL]: deviceInfo[Constants.MODEL],
230
+ [Constants.DEVICE_UUID]: deviceInfo[Constants.DEVICE_UUID],
231
+ [Constants.OS]: deviceInfo[Constants.OS],
232
+ [Constants.OS_VERSION]: deviceInfo[Constants.OS_VERSION],
233
+ [Constants.APP_VERSION]: appVersion,
234
+ },
235
+ }
236
+ );
237
+ //console.log("response from setup device session is : ", response)
238
+ if (
239
+ response &&
240
+ response.data &&
241
+ typeof response.data === 'object' &&
242
+ response.data !== null &&
243
+ 'RATE_LIMIT_USER' in response.data
244
+ ) {
245
+ console.log('SDK:LOG: rate limit user');
246
+ }
247
+
248
+ if (response?.status === 200) {
249
+ return this._openWebView(url, WebViewCallbackFunction);
250
+ } else {
251
+ console.warn('SDK:LOG: Error setting up device session:', response);
252
+ return this._openWebView(url, WebViewCallbackFunction);
253
+ }
254
+ } catch (e) {
255
+ console.error('SDK:LOG: Error setting up device session:', e);
256
+ throw e;
257
+ }
258
+ }
259
+
260
+ async _openWebView(
261
+ url: string,
262
+ WebViewCallbackFunction: WebViewCallbackFunction
263
+ ): Promise<React.ReactElement> {
264
+ console.log('SDK:LOG: entered in open web view');
265
+ console.log('SDK:LOG: url is : ', LibraryConstants.hostName + url);
266
+ // this._analyticsLogger.logEvent({
267
+ // event: 'REACT_NATIVE_WEBVIEW_OPEN_CALLED',
268
+ // });
269
+
270
+ return (
271
+ <SafeAreaView style={{ flex: 1 }}>
272
+ <WebView
273
+ url={LibraryConstants.hostName + url}
274
+ onCallback={WebViewCallbackFunction}
275
+ whitelistedUrls={LibraryConstants.whitelistedDomains}
276
+ hostName={LibraryConstants.hostName}
277
+ onPageFinished={() =>
278
+ console.log('SDK:LOG: WebView page finished loading')
279
+ }
280
+ />
281
+ </SafeAreaView>
282
+ );
283
+ }
284
+
285
+ async fetchAndSetTheme(): Promise<void> {
286
+ try {
287
+ console.log('SDK:LOG: Fetching theme from API');
288
+
289
+ const themeResponse: any = await this._apiCall.callAPI(
290
+ 'GET',
291
+ ServiceNames.THEME_URL
292
+ );
293
+ const theme = themeResponse?.data;
294
+ console.log('SDK:LOG: Theme response:', theme);
295
+
296
+ if (theme && theme['--color-primary-500']) {
297
+ const rgbValues = theme['--color-primary-500'].split(' ').map(Number);
298
+ if (rgbValues.length === 3) {
299
+ const [r, g, b] = rgbValues;
300
+
301
+ // Update LibraryConstants color
302
+ LibraryConstants.setPrimaryColorFromRGB(r, g, b);
303
+
304
+ // Set status bar color
305
+
306
+ StatusBar.setBackgroundColor(LibraryConstants.primaryColor);
307
+ StatusBar.setBarStyle('light-content');
308
+
309
+ console.log(
310
+ 'SDK:LOG: Theme color set successfully:',
311
+ LibraryConstants.primaryColor
312
+ );
313
+ } else {
314
+ console.warn('Invalid RGB values in theme response');
315
+ }
316
+ } else {
317
+ console.warn('--color-primary-500 not found in theme response');
318
+ }
319
+ } catch (e) {
320
+ console.error('Error Fetching theme: ', e);
321
+ throw e;
322
+ }
323
+ }
324
+ async publicapi(
325
+ method: string,
326
+ apiUrl: string,
327
+ options?: {
328
+ body?: Record<string, any>;
329
+ headers?: Record<string, any>;
330
+ }
331
+ ): Promise<any> {
332
+ return this._apiCall.callAPI(method, apiUrl, options);
333
+ }
334
+
335
+ public async sdkLogin(token: string, partner: string): Promise<JSONMap> {
336
+ try {
337
+ // 1) LOGIN
338
+ console.log('SDK:LOG: ', ServiceNames.LOGIN_URL);
339
+ console.log('SDK:LOG: Token is: ', token);
340
+
341
+ const loginResp = await this._apiCall.callAPI(
342
+ 'POST',
343
+ ServiceNames.LOGIN_URL,
344
+ {
345
+ body: { token },
346
+ }
347
+ );
348
+
349
+ // Normalize to a JSON object
350
+ const loginData: JSONMap =
351
+ loginResp?.data && typeof loginResp.data === 'object'
352
+ ? { ...loginResp.data }
353
+ : {};
354
+
355
+ const isSuccess =
356
+ loginResp?.status === 200 && loginData?.code === 'USER_LOGIN_SUCCESS';
357
+
358
+ if (!isSuccess) {
359
+ // Not a successful login → return login JSON as-is
360
+ return loginData;
361
+ }
362
+
363
+ // 2) DEVICE SESSION
364
+ try {
365
+ console.log('SDK:LOG: in device-session');
366
+
367
+ const deviceInfo = await DeviceInfoManager.getDeviceInfo();
368
+ const appVersion = deviceInfo['app_version'] || 'unknown';
369
+
370
+ if (__DEV__) {
371
+ console.log('Device Info:', deviceInfo);
372
+ console.log('App Version:', appVersion);
373
+ }
374
+
375
+ const deviceResp = await this._apiCall.callAPI(
376
+ 'POST',
377
+ ServiceNames.DEVICE_SESSION.params({ partner }),
378
+ {
379
+ body: {
380
+ [Constants.MANUFACTURER]: deviceInfo[Constants.MANUFACTURER],
381
+ [Constants.MODEL]: deviceInfo[Constants.MODEL],
382
+ [Constants.DEVICE_UUID]: deviceInfo[Constants.DEVICE_UUID],
383
+ [Constants.OS]: deviceInfo[Constants.OS], // "Android"/"iOS" from your manager
384
+ [Constants.OS_VERSION]: deviceInfo[Constants.OS_VERSION],
385
+ [Constants.APP_VERSION]: appVersion,
386
+ },
387
+ }
388
+ );
389
+
390
+ // Nest device-session response into the same login JSON
391
+ loginData.device =
392
+ deviceResp?.data && typeof deviceResp.data === 'object'
393
+ ? deviceResp.data
394
+ : { success: false, message: 'device session null' };
395
+
396
+ return loginData;
397
+ } catch (err: any) {
398
+ // Keep shape stable if device session fails
399
+ loginData.device = {
400
+ success: false,
401
+ message: String(err?.message || err),
402
+ };
403
+ return loginData;
404
+ }
405
+ } catch (err: any) {
406
+ // Absolute fallback: minimal error shape
407
+ return {
408
+ success: false,
409
+ message: `sdkLogin failed: ${String(err?.message || err)}`,
410
+ };
411
+ }
412
+ }
413
+ }
@@ -0,0 +1 @@
1
+ console.log("Helper file loaded");
@@ -0,0 +1,154 @@
1
+ import NetworkManager from './network_manager';
2
+ import { HeaderManager } from '../utils/headerManager';
3
+
4
+ import {
5
+ generateAESKey,
6
+ exportAESKeyBase64,
7
+ encryptAESKeyWithRSA,
8
+ encryptAES,
9
+ decryptAES,
10
+ getWebsiteKey,
11
+ } from './Encryption';
12
+
13
+ export class APICall {
14
+ private networkManager: NetworkManager = new NetworkManager();
15
+ private headerManager: HeaderManager = new HeaderManager();
16
+
17
+ async callAPI(
18
+ method: string,
19
+ apiUrl: string,
20
+ options?: {
21
+ body?: Record<string, any>;
22
+ headers?: Record<string, any>;
23
+ encrypted?: boolean;
24
+ }
25
+ ): Promise<any> {
26
+ const { body, headers, encrypted = true } = options || {};
27
+ try {
28
+ const response = encrypted
29
+ ? await this._callAPIEncrypt(method, apiUrl, body, headers)
30
+ : await this._callAPIRaw(method, apiUrl, body, headers);
31
+
32
+ return response;
33
+ } catch (error) {
34
+ console.log('Error in callAPI:', error);
35
+ throw error;
36
+ }
37
+ }
38
+
39
+ private async _callAPIRaw(
40
+ method: string,
41
+ apiUrl: string,
42
+ body?: Record<string, any>,
43
+ headers?: Record<string, any>
44
+ ): Promise<any> {
45
+ const mergedHeaders = {
46
+ ...(await this.headerManager.generateHeaders()),
47
+ ...(headers || {}),
48
+ };
49
+
50
+ return this.networkManager.request({
51
+ url: apiUrl,
52
+ method: method,
53
+ body: body,
54
+ headers: mergedHeaders,
55
+ });
56
+ }
57
+ // ----------------- ENCRYPTED CALL (quick-crypto) -----------------
58
+ private async _callAPIEncrypt(
59
+ method: string,
60
+ apiUrl: string,
61
+ body?: Record<string, any>,
62
+ headers?: Record<string, any>
63
+ ): Promise<any> {
64
+ // 1) Generate ephemeral AES-256 key (Uint8Array)
65
+ const aesKey = generateAESKey(); // Uint8Array (32 bytes)
66
+
67
+ // 2) Export AES key bytes -> Base64 (for RSA input)
68
+ const aesKeyBase64 = exportAESKeyBase64(aesKey); // string
69
+
70
+ // 3) Fetch server public key (cached) and RSA-wrap the AES key
71
+ const websiteKey = await getWebsiteKey(); // { kid, public, expiry }
72
+ const rsaEncryptedKey = encryptAESKeyWithRSA(
73
+ aesKeyBase64,
74
+ websiteKey.public
75
+ ); // string
76
+
77
+ // 4) Encrypt payload with AES-GCM -> Base64(IV||CT||TAG)
78
+ const encryptedPayload = encryptAES(JSON.stringify(body ?? {}), aesKey); // string
79
+
80
+ // 5) Merge headers (stringify all values)
81
+ const baseHeaders = await this.headerManager.generateHeaders();
82
+ const mergedHeaders: Record<string, string> = {
83
+ ...Object.fromEntries(
84
+ Object.entries(baseHeaders).map(([k, v]) => [k, String(v)])
85
+ ),
86
+ ...(headers
87
+ ? Object.fromEntries(
88
+ Object.entries(headers).map(([k, v]) => [k, String(v)])
89
+ )
90
+ : {}),
91
+ };
92
+
93
+ // 6) Add encryption headers
94
+ const encryptedHeaders: Record<string, string> = {
95
+ 'Content-Type': 'application/json;charset=utf-8',
96
+ 'Accept': 'application/json',
97
+ ...mergedHeaders,
98
+ 'encryption_kid': websiteKey.kid ?? '',
99
+ 'key': rsaEncryptedKey ?? '',
100
+ };
101
+
102
+ // 7) Make the encrypted request with { encrypted: "<blob>" }
103
+ const response = await this.networkManager.request({
104
+ url: apiUrl,
105
+ method,
106
+ body: { encrypted: encryptedPayload },
107
+ headers: encryptedHeaders,
108
+ });
109
+
110
+ // 8) Decrypt response if it has { encrypted }
111
+ const decrypted = APICall.handleDecryptedResponse(response, aesKey);
112
+ if (decrypted != null) {
113
+ response!.data = decrypted;
114
+ }
115
+ return response;
116
+ }
117
+
118
+ // ----------------- RESPONSE DECRYPT (quick-crypto) -----------------
119
+ static handleDecryptedResponse(
120
+ response: any,
121
+ aesKey: Uint8Array
122
+ ): Record<string, any> | null {
123
+ try {
124
+ if (!response) return null;
125
+
126
+ const raw = response.data;
127
+ // Accept string or object
128
+ const data: Record<string, any> =
129
+ typeof raw === 'string' ? JSON.parse(raw) : (raw as any);
130
+
131
+ if (!data || typeof data !== 'object' || !('encrypted' in data)) {
132
+ // no encrypted field, return null to signal "not decrypted"
133
+ return null;
134
+ }
135
+
136
+ const encryptedPayload = String(data.encrypted ?? '');
137
+ if (!encryptedPayload) return null;
138
+
139
+ const decryptedJson = decryptAES(encryptedPayload, aesKey); // sync
140
+
141
+ try {
142
+ const obj = JSON.parse(decryptedJson);
143
+ return obj && typeof obj === 'object'
144
+ ? (obj as Record<string, any>)
145
+ : null;
146
+ } catch {
147
+ // decrypted payload wasn't valid JSON
148
+ return null;
149
+ }
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+ }