@a-cube-io/ereceipts-js-sdk 2.0.9 → 2.1.1
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 +1 -7
- package/dist/index.cjs.js +273 -3459
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +81 -1502
- package/dist/index.esm.js +295 -3403
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.js +295 -3403
- package/dist/index.native.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -1892,27 +1892,6 @@ function clearObject(input) {
|
|
|
1892
1892
|
}
|
|
1893
1893
|
return input;
|
|
1894
1894
|
}
|
|
1895
|
-
function clearObjectShallow(obj) {
|
|
1896
|
-
if (!obj || typeof obj !== 'object') {
|
|
1897
|
-
return {};
|
|
1898
|
-
}
|
|
1899
|
-
const cleaned = {};
|
|
1900
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1901
|
-
if (value !== null && value !== undefined && value !== '') {
|
|
1902
|
-
cleaned[key] = value;
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
return cleaned;
|
|
1906
|
-
}
|
|
1907
|
-
function isEmpty(value) {
|
|
1908
|
-
return value === null || value === undefined || value === '';
|
|
1909
|
-
}
|
|
1910
|
-
function hasNonEmptyValues(obj) {
|
|
1911
|
-
if (!obj || typeof obj !== 'object') {
|
|
1912
|
-
return false;
|
|
1913
|
-
}
|
|
1914
|
-
return Object.values(obj).some((value) => !isEmpty(value));
|
|
1915
|
-
}
|
|
1916
1895
|
|
|
1917
1896
|
/**
|
|
1918
1897
|
* Platform detection utilities
|
|
@@ -2135,7 +2114,7 @@ function formatDecimal(value, decimals = 2) {
|
|
|
2135
2114
|
return num.toFixed(decimals);
|
|
2136
2115
|
}
|
|
2137
2116
|
|
|
2138
|
-
const log$
|
|
2117
|
+
const log$b = createPrefixedLogger('AUTH-SERVICE');
|
|
2139
2118
|
class AuthenticationService {
|
|
2140
2119
|
get user$() {
|
|
2141
2120
|
return this.userSubject.asObservable();
|
|
@@ -2157,7 +2136,7 @@ class AuthenticationService {
|
|
|
2157
2136
|
}
|
|
2158
2137
|
async login(credentials) {
|
|
2159
2138
|
this.authStateSubject.next('authenticating');
|
|
2160
|
-
log$
|
|
2139
|
+
log$b.info('Login attempt', {
|
|
2161
2140
|
authUrl: this.config.authUrl,
|
|
2162
2141
|
email: credentials.email,
|
|
2163
2142
|
});
|
|
@@ -2168,7 +2147,7 @@ class AuthenticationService {
|
|
|
2168
2147
|
});
|
|
2169
2148
|
const jwtPayload = parseJwt(response.data.token);
|
|
2170
2149
|
const expiresAt = jwtPayload.exp * 1000;
|
|
2171
|
-
log$
|
|
2150
|
+
log$b.info('Login successful', {
|
|
2172
2151
|
authUrl: this.config.authUrl,
|
|
2173
2152
|
tokenPrefix: response.data.token.substring(0, 30) + '...',
|
|
2174
2153
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
@@ -2197,21 +2176,21 @@ class AuthenticationService {
|
|
|
2197
2176
|
const token = await this.tokenStorage.getAccessToken();
|
|
2198
2177
|
if (!token) {
|
|
2199
2178
|
// No token - clear any stale user state
|
|
2200
|
-
log$
|
|
2179
|
+
log$b.debug('getCurrentUser: No token in storage');
|
|
2201
2180
|
if (this.userSubject.value) {
|
|
2202
2181
|
this.userSubject.next(null);
|
|
2203
2182
|
this.authStateSubject.next('idle');
|
|
2204
2183
|
}
|
|
2205
2184
|
return null;
|
|
2206
2185
|
}
|
|
2207
|
-
log$
|
|
2186
|
+
log$b.debug('getCurrentUser: Token found', {
|
|
2208
2187
|
tokenPrefix: token.substring(0, 30) + '...',
|
|
2209
2188
|
tokenLength: token.length,
|
|
2210
2189
|
});
|
|
2211
2190
|
const jwtPayload = parseJwt(token);
|
|
2212
2191
|
if (isTokenExpired(jwtPayload)) {
|
|
2213
2192
|
// Token expired - clear everything
|
|
2214
|
-
log$
|
|
2193
|
+
log$b.warn('getCurrentUser: Token expired');
|
|
2215
2194
|
await this.tokenStorage.clearTokens();
|
|
2216
2195
|
this.userSubject.next(null);
|
|
2217
2196
|
this.authStateSubject.next('idle');
|
|
@@ -2221,7 +2200,7 @@ class AuthenticationService {
|
|
|
2221
2200
|
// Token is valid - return cached user if available
|
|
2222
2201
|
const currentUser = this.userSubject.value;
|
|
2223
2202
|
if (currentUser) {
|
|
2224
|
-
log$
|
|
2203
|
+
log$b.debug('getCurrentUser: Returning cached user', {
|
|
2225
2204
|
email: currentUser.email,
|
|
2226
2205
|
roles: currentUser.roles,
|
|
2227
2206
|
});
|
|
@@ -2244,12 +2223,12 @@ class AuthenticationService {
|
|
|
2244
2223
|
async isAuthenticated() {
|
|
2245
2224
|
const token = await this.tokenStorage.getAccessToken();
|
|
2246
2225
|
if (!token) {
|
|
2247
|
-
log$
|
|
2226
|
+
log$b.debug('isAuthenticated: No token in storage');
|
|
2248
2227
|
return false;
|
|
2249
2228
|
}
|
|
2250
2229
|
const jwtPayload = parseJwt(token);
|
|
2251
2230
|
const expired = isTokenExpired(jwtPayload);
|
|
2252
|
-
log$
|
|
2231
|
+
log$b.debug('isAuthenticated: Token check', {
|
|
2253
2232
|
hasToken: true,
|
|
2254
2233
|
expired,
|
|
2255
2234
|
expiresAt: new Date(jwtPayload.exp * 1000).toISOString(),
|
|
@@ -2469,7 +2448,7 @@ class ConfigManager {
|
|
|
2469
2448
|
}
|
|
2470
2449
|
}
|
|
2471
2450
|
|
|
2472
|
-
// Enum options arrays
|
|
2451
|
+
// Enum options arrays (used by consumers for dropdowns and form validation)
|
|
2473
2452
|
const VAT_RATE_CODE_OPTIONS = [
|
|
2474
2453
|
'4.00',
|
|
2475
2454
|
'5.00',
|
|
@@ -2496,103 +2475,10 @@ const VAT_RATE_CODE_OPTIONS = [
|
|
|
2496
2475
|
];
|
|
2497
2476
|
const GOOD_OR_SERVICE_OPTIONS = ['goods', 'service'];
|
|
2498
2477
|
const RECEIPT_PROOF_TYPE_OPTIONS = ['POS', 'VR', 'ND'];
|
|
2499
|
-
// Enum
|
|
2478
|
+
// Enum schemas (used by consumers as building blocks for their own form schemas)
|
|
2500
2479
|
const VatRateCodeSchema = z__namespace.enum(VAT_RATE_CODE_OPTIONS);
|
|
2501
2480
|
const GoodOrServiceSchema = z__namespace.enum(GOOD_OR_SERVICE_OPTIONS);
|
|
2502
2481
|
const ReceiptProofTypeSchema = z__namespace.enum(RECEIPT_PROOF_TYPE_OPTIONS);
|
|
2503
|
-
// Receipt Item Schema
|
|
2504
|
-
const ReceiptItemSchema = z__namespace.object({
|
|
2505
|
-
type: GoodOrServiceSchema.optional(),
|
|
2506
|
-
quantity: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2507
|
-
description: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2508
|
-
unit_price: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2509
|
-
vat_rate_code: VatRateCodeSchema.optional(),
|
|
2510
|
-
simplified_vat_allocation: z__namespace.boolean().optional(),
|
|
2511
|
-
discount: z__namespace.string().nullable().optional(),
|
|
2512
|
-
is_down_payment_or_voucher_redemption: z__namespace.boolean().optional(),
|
|
2513
|
-
complimentary: z__namespace.boolean().optional(),
|
|
2514
|
-
});
|
|
2515
|
-
// Main Receipt Input Schema
|
|
2516
|
-
const ReceiptInputSchema = z__namespace
|
|
2517
|
-
.object({
|
|
2518
|
-
items: z__namespace.array(ReceiptItemSchema).min(1, { error: 'arrayMin1' }),
|
|
2519
|
-
customer_tax_code: z__namespace.string().optional(),
|
|
2520
|
-
customer_lottery_code: z__namespace.string().optional(),
|
|
2521
|
-
discount: z__namespace.string().nullable().optional(),
|
|
2522
|
-
invoice_issuing: z__namespace.boolean().optional(),
|
|
2523
|
-
uncollected_dcr_to_ssn: z__namespace.boolean().optional(),
|
|
2524
|
-
services_uncollected_amount: z__namespace.string().nullable().optional(),
|
|
2525
|
-
goods_uncollected_amount: z__namespace.string().nullable().optional(),
|
|
2526
|
-
cash_payment_amount: z__namespace.string().nullable().optional(),
|
|
2527
|
-
electronic_payment_amount: z__namespace.string().nullable().optional(),
|
|
2528
|
-
ticket_restaurant_payment_amount: z__namespace.string().nullable().optional(),
|
|
2529
|
-
ticket_restaurant_quantity: z__namespace.number().optional(),
|
|
2530
|
-
})
|
|
2531
|
-
.refine((data) => {
|
|
2532
|
-
// At least one payment method should be provided
|
|
2533
|
-
const hasCashPayment = data.cash_payment_amount && parseFloat(data.cash_payment_amount) > 0;
|
|
2534
|
-
const hasElectronicPayment = data.electronic_payment_amount && parseFloat(data.electronic_payment_amount) > 0;
|
|
2535
|
-
const hasTicketPayment = data.ticket_restaurant_payment_amount &&
|
|
2536
|
-
parseFloat(data.ticket_restaurant_payment_amount) > 0;
|
|
2537
|
-
return hasCashPayment || hasElectronicPayment || hasTicketPayment;
|
|
2538
|
-
}, {
|
|
2539
|
-
error: 'At least one payment method is required',
|
|
2540
|
-
path: ['payment_methods'],
|
|
2541
|
-
})
|
|
2542
|
-
.refine((data) => {
|
|
2543
|
-
// only one between customer_tax_code and customer_lottery_code can be provided
|
|
2544
|
-
return !data.customer_tax_code || !data.customer_lottery_code;
|
|
2545
|
-
}, {
|
|
2546
|
-
error: 'Only one between customer_tax_code and customer_lottery_code can be provided',
|
|
2547
|
-
path: ['customer_tax_code', 'customer_lottery_code'],
|
|
2548
|
-
});
|
|
2549
|
-
// Receipt Return or Void via PEM Schema
|
|
2550
|
-
const ReceiptReturnOrVoidViaPEMInputSchema = z__namespace.object({
|
|
2551
|
-
device_id: z__namespace.string().optional(),
|
|
2552
|
-
items: z__namespace.array(ReceiptItemSchema).min(1, { error: 'arrayMin1' }),
|
|
2553
|
-
document_number: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2554
|
-
document_datetime: z__namespace.string().optional(),
|
|
2555
|
-
lottery_code: z__namespace.string().optional(),
|
|
2556
|
-
});
|
|
2557
|
-
// Receipt Return or Void with Proof Schema
|
|
2558
|
-
const ReceiptReturnOrVoidWithProofInputSchema = z__namespace.object({
|
|
2559
|
-
items: z__namespace.array(ReceiptItemSchema).min(1, { error: 'arrayMin1' }),
|
|
2560
|
-
proof: ReceiptProofTypeSchema,
|
|
2561
|
-
document_datetime: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2562
|
-
});
|
|
2563
|
-
// Void Receipt Schema
|
|
2564
|
-
const VoidReceiptInputSchema = z__namespace.object({
|
|
2565
|
-
document_number: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2566
|
-
});
|
|
2567
|
-
const ReceiptReturnItemSchema = z__namespace
|
|
2568
|
-
.array(z__namespace.object({
|
|
2569
|
-
id: z__namespace.number(),
|
|
2570
|
-
quantity: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2571
|
-
}))
|
|
2572
|
-
.min(1, { error: 'arrayMin1' });
|
|
2573
|
-
// Receipt Return Schema
|
|
2574
|
-
const ReceiptReturnInputSchema = z__namespace.object({
|
|
2575
|
-
items: z__namespace.array(ReceiptReturnItemSchema).min(1, { error: 'arrayMin1' }),
|
|
2576
|
-
document_number: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2577
|
-
});
|
|
2578
|
-
|
|
2579
|
-
// Cashier Create Input Schema (MF1)
|
|
2580
|
-
const CashierCreateInputSchema = z__namespace.object({
|
|
2581
|
-
email: z__namespace
|
|
2582
|
-
.string()
|
|
2583
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2584
|
-
.max(255, { error: 'emailMaxLength' })
|
|
2585
|
-
.email({ error: 'invalidEmail' }),
|
|
2586
|
-
password: z__namespace
|
|
2587
|
-
.string()
|
|
2588
|
-
.min(8, { error: 'passwordMinLength' })
|
|
2589
|
-
.max(40, { error: 'passwordMaxLength' }),
|
|
2590
|
-
name: z__namespace.string().min(1, { error: 'fieldIsRequired' }).max(255, { error: 'nameMaxLength' }),
|
|
2591
|
-
display_name: z__namespace
|
|
2592
|
-
.string()
|
|
2593
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2594
|
-
.max(255, { error: 'displayNameMaxLength' }),
|
|
2595
|
-
});
|
|
2596
2482
|
|
|
2597
2483
|
// Enum options arrays
|
|
2598
2484
|
const PEM_STATUS_OPTIONS = [
|
|
@@ -2603,312 +2489,12 @@ const PEM_STATUS_OPTIONS = [
|
|
|
2603
2489
|
'OFFLINE',
|
|
2604
2490
|
'DISCARDED',
|
|
2605
2491
|
];
|
|
2606
|
-
// Address Schema (reusable)
|
|
2607
|
-
const AddressSchema = z__namespace.object({
|
|
2608
|
-
street_address: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2609
|
-
street_number: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2610
|
-
zip_code: z__namespace
|
|
2611
|
-
.string()
|
|
2612
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2613
|
-
.regex(/^\d{5}$/, { error: 'invalidZipCode' }),
|
|
2614
|
-
city: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2615
|
-
province: z__namespace
|
|
2616
|
-
.string()
|
|
2617
|
-
.min(2, { error: 'provinceMinLength' })
|
|
2618
|
-
.max(2, { error: 'provinceMaxLength' })
|
|
2619
|
-
.toUpperCase(),
|
|
2620
|
-
});
|
|
2621
|
-
// PEM Status Schema
|
|
2622
|
-
const PEMStatusSchema = z__namespace.enum(PEM_STATUS_OPTIONS);
|
|
2623
|
-
// Activation Request Schema
|
|
2624
|
-
const ActivationRequestSchema = z__namespace.object({
|
|
2625
|
-
registration_key: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2626
|
-
});
|
|
2627
|
-
// PEM Status Offline Request Schema
|
|
2628
|
-
const PEMStatusOfflineRequestSchema = z__namespace.object({
|
|
2629
|
-
timestamp: z__namespace
|
|
2630
|
-
.string()
|
|
2631
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2632
|
-
.refine((val) => !isNaN(Date.parse(val)), {
|
|
2633
|
-
error: 'invalidDateFormat',
|
|
2634
|
-
}),
|
|
2635
|
-
reason: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2636
|
-
});
|
|
2637
|
-
|
|
2638
|
-
// Cash Register Create Schema
|
|
2639
|
-
const CashRegisterCreateSchema = z__namespace.object({
|
|
2640
|
-
pem_serial_number: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2641
|
-
name: z__namespace.string().min(1, { error: 'fieldIsRequired' }).max(100, { error: 'nameMaxLength' }),
|
|
2642
|
-
});
|
|
2643
|
-
|
|
2644
|
-
// VAT number validation regex (Partita IVA - 11 digits)
|
|
2645
|
-
const VAT_NUMBER_REGEX = /^\d{11}$/;
|
|
2646
|
-
// Fiscal code validation regex (Codice Fiscale - 11 digits only for merchants)
|
|
2647
|
-
const FISCAL_CODE_REGEX = /^\d{11}$/;
|
|
2648
|
-
// Password validation regex (from OpenAPI spec)
|
|
2649
|
-
const PASSWORD_REGEX = /^((?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{10,}).*)$/;
|
|
2650
|
-
// Merchant Create Input Schema
|
|
2651
|
-
const MerchantCreateInputSchema = z__namespace
|
|
2652
|
-
.object({
|
|
2653
|
-
vat_number: z__namespace
|
|
2654
|
-
.string()
|
|
2655
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2656
|
-
.regex(VAT_NUMBER_REGEX, { error: 'invalidVatNumber' }),
|
|
2657
|
-
fiscal_code: z__namespace.string().regex(FISCAL_CODE_REGEX, { error: 'invalidFiscalCode' }).optional(),
|
|
2658
|
-
business_name: z__namespace.string().max(200, { error: 'businessNameMaxLength' }).optional().nullable(),
|
|
2659
|
-
first_name: z__namespace.string().max(100, { error: 'firstNameMaxLength' }).optional().nullable(),
|
|
2660
|
-
last_name: z__namespace.string().max(100, { error: 'lastNameMaxLength' }).optional().nullable(),
|
|
2661
|
-
email: z__namespace.string().min(1, { error: 'fieldIsRequired' }).email({ error: 'invalidEmail' }),
|
|
2662
|
-
password: z__namespace
|
|
2663
|
-
.string()
|
|
2664
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2665
|
-
.regex(PASSWORD_REGEX, { error: 'passwordComplexity' }),
|
|
2666
|
-
address: AddressSchema.optional(),
|
|
2667
|
-
})
|
|
2668
|
-
.refine((data) => {
|
|
2669
|
-
const hasBusinessName = data.business_name && data.business_name.trim() !== '';
|
|
2670
|
-
const hasPersonalNames = (data.first_name && data.first_name.trim() !== '') ||
|
|
2671
|
-
(data.last_name && data.last_name.trim() !== '');
|
|
2672
|
-
// If business name is set, first/last name must not be provided
|
|
2673
|
-
if (hasBusinessName && hasPersonalNames) {
|
|
2674
|
-
return false;
|
|
2675
|
-
}
|
|
2676
|
-
// At least one naming method must be provided
|
|
2677
|
-
if (!hasBusinessName && !hasPersonalNames) {
|
|
2678
|
-
return false;
|
|
2679
|
-
}
|
|
2680
|
-
return true;
|
|
2681
|
-
}, {
|
|
2682
|
-
error: 'businessNameOrPersonalNamesRequired',
|
|
2683
|
-
path: ['business_name'],
|
|
2684
|
-
});
|
|
2685
|
-
// Merchant Update Input Schema
|
|
2686
|
-
const MerchantUpdateInputSchema = z__namespace.object({
|
|
2687
|
-
business_name: z__namespace.string().max(200, { error: 'businessNameMaxLength' }).optional().nullable(),
|
|
2688
|
-
first_name: z__namespace.string().max(100, { error: 'firstNameMaxLength' }).optional().nullable(),
|
|
2689
|
-
last_name: z__namespace.string().max(100, { error: 'lastNameMaxLength' }).optional().nullable(),
|
|
2690
|
-
address: AddressSchema.optional().nullable(),
|
|
2691
|
-
});
|
|
2692
2492
|
|
|
2693
2493
|
// Enum options arrays
|
|
2694
2494
|
const PEM_TYPE_OPTIONS = ['AP', 'SP', 'TM', 'PV'];
|
|
2695
|
-
// PEM Data Schema
|
|
2696
|
-
const PemDataSchema = z__namespace.object({
|
|
2697
|
-
version: z__namespace.string().min(1, { error: 'fieldIsRequired' }),
|
|
2698
|
-
type: z__namespace.enum(PEM_TYPE_OPTIONS, {
|
|
2699
|
-
error: 'invalidPemType',
|
|
2700
|
-
}),
|
|
2701
|
-
});
|
|
2702
|
-
// PEM Create Input Schema
|
|
2703
|
-
const PemCreateInputSchema = z__namespace.object({
|
|
2704
|
-
merchant_uuid: z__namespace.string().min(1, { error: 'fieldIsRequired' }).uuid({ error: 'invalidUuid' }),
|
|
2705
|
-
address: AddressSchema.optional(),
|
|
2706
|
-
/* external_pem_data: PemDataSchema.optional(), */
|
|
2707
|
-
});
|
|
2708
2495
|
|
|
2709
|
-
//
|
|
2710
|
-
const FISCAL_ID_REGEX = /^([A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]|[0-9]{11})$/;
|
|
2711
|
-
// Supplier Create Input Schema
|
|
2712
|
-
const SupplierCreateInputSchema = z__namespace.object({
|
|
2713
|
-
fiscal_id: z__namespace
|
|
2714
|
-
.string()
|
|
2715
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2716
|
-
.regex(FISCAL_ID_REGEX, { error: 'invalidFiscalId' })
|
|
2717
|
-
.toUpperCase(),
|
|
2718
|
-
name: z__namespace.string().min(1, { error: 'fieldIsRequired' }).max(200, { error: 'nameMaxLength' }),
|
|
2719
|
-
address: AddressSchema.optional(),
|
|
2720
|
-
});
|
|
2721
|
-
// Supplier Update Input Schema
|
|
2722
|
-
const SupplierUpdateInputSchema = z__namespace.object({
|
|
2723
|
-
name: z__namespace.string().min(1, { error: 'fieldIsRequired' }).max(200, { error: 'nameMaxLength' }),
|
|
2724
|
-
address: AddressSchema.optional(),
|
|
2725
|
-
});
|
|
2726
|
-
|
|
2727
|
-
// Journal Close Input Schema
|
|
2728
|
-
const JournalCloseInputSchema = z__namespace.object({
|
|
2729
|
-
closing_timestamp: z__namespace
|
|
2730
|
-
.string()
|
|
2731
|
-
.min(1, { error: 'fieldIsRequired' })
|
|
2732
|
-
.refine((val) => !isNaN(Date.parse(val)), {
|
|
2733
|
-
error: 'invalidDateFormat',
|
|
2734
|
-
}),
|
|
2735
|
-
reason: z__namespace.string().max(255, { error: 'reasonMaxLength' }).optional(),
|
|
2736
|
-
});
|
|
2737
|
-
|
|
2738
|
-
// Daily Report Status Options
|
|
2496
|
+
// Enum options arrays
|
|
2739
2497
|
const DAILY_REPORT_STATUS_OPTIONS = ['pending', 'sent', 'error'];
|
|
2740
|
-
// Daily Report Status Schema
|
|
2741
|
-
const DailyReportStatusSchema = z__namespace.enum(DAILY_REPORT_STATUS_OPTIONS, {
|
|
2742
|
-
error: 'invalidDailyReportStatus',
|
|
2743
|
-
});
|
|
2744
|
-
// Daily Reports List Parameters Schema
|
|
2745
|
-
const DailyReportsParamsSchema = z__namespace.object({
|
|
2746
|
-
pem_serial_number: z__namespace.string().min(1, { error: 'fieldIsRequired' }).optional(),
|
|
2747
|
-
date_from: z__namespace
|
|
2748
|
-
.string()
|
|
2749
|
-
.refine((val) => !isNaN(Date.parse(val)), {
|
|
2750
|
-
error: 'invalidDateFormat',
|
|
2751
|
-
})
|
|
2752
|
-
.optional(),
|
|
2753
|
-
date_to: z__namespace
|
|
2754
|
-
.string()
|
|
2755
|
-
.refine((val) => !isNaN(Date.parse(val)), {
|
|
2756
|
-
error: 'invalidDateFormat',
|
|
2757
|
-
})
|
|
2758
|
-
.optional(),
|
|
2759
|
-
status: DailyReportStatusSchema.optional(),
|
|
2760
|
-
page: z__namespace.number().min(1, { error: 'pageMinValue' }).optional(),
|
|
2761
|
-
});
|
|
2762
|
-
|
|
2763
|
-
const NotificationScopeSchema = z__namespace.object({
|
|
2764
|
-
type: z__namespace.literal('global'),
|
|
2765
|
-
});
|
|
2766
|
-
const PemStatusSchema = z__namespace.enum(['ONLINE', 'OFFLINE']);
|
|
2767
|
-
const NotificationDataBlockAtSchema = z__namespace.object({
|
|
2768
|
-
block_at: z__namespace.string(),
|
|
2769
|
-
});
|
|
2770
|
-
const NotificationDataPemStatusSchema = z__namespace.object({
|
|
2771
|
-
from: PemStatusSchema,
|
|
2772
|
-
to: PemStatusSchema,
|
|
2773
|
-
});
|
|
2774
|
-
const NotificationBaseSchema = z__namespace.object({
|
|
2775
|
-
uuid: z__namespace.string().uuid({ error: 'invalidUuid' }),
|
|
2776
|
-
scope: NotificationScopeSchema,
|
|
2777
|
-
source: z__namespace.enum(['system', 'Italian Tax Authority']),
|
|
2778
|
-
level: z__namespace.enum(['info', 'warning', 'error', 'critical']),
|
|
2779
|
-
created_at: z__namespace.string(),
|
|
2780
|
-
});
|
|
2781
|
-
const NotificationMf2UnreachableSchema = NotificationBaseSchema.extend({
|
|
2782
|
-
type: z__namespace.literal('INTERNAL_COMMUNICATION_FAILURE'),
|
|
2783
|
-
code: z__namespace.literal('SYS-W-01'),
|
|
2784
|
-
data: NotificationDataBlockAtSchema,
|
|
2785
|
-
});
|
|
2786
|
-
const NotificationPemsBlockedSchema = NotificationBaseSchema.extend({
|
|
2787
|
-
type: z__namespace.literal('PEM_STATUS_CHANGED'),
|
|
2788
|
-
code: z__namespace.literal('SYS-C-01'),
|
|
2789
|
-
data: NotificationDataPemStatusSchema,
|
|
2790
|
-
});
|
|
2791
|
-
const NotificationPemBackOnlineSchema = NotificationBaseSchema.extend({
|
|
2792
|
-
type: z__namespace.literal('PEM_STATUS_CHANGED'),
|
|
2793
|
-
code: z__namespace.literal('SYS-I-01'),
|
|
2794
|
-
data: NotificationDataPemStatusSchema,
|
|
2795
|
-
});
|
|
2796
|
-
const NotificationSchema = z__namespace.discriminatedUnion('code', [
|
|
2797
|
-
NotificationMf2UnreachableSchema,
|
|
2798
|
-
NotificationPemsBlockedSchema,
|
|
2799
|
-
NotificationPemBackOnlineSchema,
|
|
2800
|
-
]);
|
|
2801
|
-
const NotificationListResponseSchema = z__namespace.object({
|
|
2802
|
-
members: z__namespace.array(NotificationSchema),
|
|
2803
|
-
});
|
|
2804
|
-
|
|
2805
|
-
const TelemetryMerchantSchema = z__namespace.object({
|
|
2806
|
-
vat_number: z__namespace.string(),
|
|
2807
|
-
fiscal_code: z__namespace.string().nullable(),
|
|
2808
|
-
business_name: z__namespace.string(),
|
|
2809
|
-
});
|
|
2810
|
-
const TelemetrySupplierSchema = z__namespace.object({
|
|
2811
|
-
vat_number: z__namespace.string(),
|
|
2812
|
-
fiscal_code: z__namespace.string().nullable(),
|
|
2813
|
-
business_name: z__namespace.string(),
|
|
2814
|
-
});
|
|
2815
|
-
const TelemetrySoftwareVersionSchema = z__namespace.object({
|
|
2816
|
-
version: z__namespace.string(),
|
|
2817
|
-
swid: z__namespace.string(),
|
|
2818
|
-
installed_at: z__namespace.string(),
|
|
2819
|
-
status: z__namespace.enum(['active', 'inactive', 'archived']),
|
|
2820
|
-
});
|
|
2821
|
-
const TelemetrySoftwareSchema = z__namespace.object({
|
|
2822
|
-
code: z__namespace.string(),
|
|
2823
|
-
name: z__namespace.string(),
|
|
2824
|
-
approval_reference: z__namespace.string(),
|
|
2825
|
-
version_info: TelemetrySoftwareVersionSchema,
|
|
2826
|
-
});
|
|
2827
|
-
const PendingReceiptsSchema = z__namespace.object({
|
|
2828
|
-
count: z__namespace.number().int().nonnegative(),
|
|
2829
|
-
total_amount: z__namespace.string(),
|
|
2830
|
-
});
|
|
2831
|
-
const TransmissionSchema = z__namespace.object({
|
|
2832
|
-
attempted_at: z__namespace.string(),
|
|
2833
|
-
outcome: z__namespace.enum(['success', 'failed', 'pending']),
|
|
2834
|
-
});
|
|
2835
|
-
const MessageSchema = z__namespace.object({
|
|
2836
|
-
received_at: z__namespace.string(),
|
|
2837
|
-
content: z__namespace.string(),
|
|
2838
|
-
});
|
|
2839
|
-
const LotterySecretRequestSchema = z__namespace.object({
|
|
2840
|
-
requested_at: z__namespace.string(),
|
|
2841
|
-
outcome: z__namespace.enum(['success', 'failed', 'pending']),
|
|
2842
|
-
});
|
|
2843
|
-
const LotterySchema = z__namespace.object({
|
|
2844
|
-
last_transmission: TransmissionSchema,
|
|
2845
|
-
secret_request: LotterySecretRequestSchema,
|
|
2846
|
-
});
|
|
2847
|
-
const TelemetrySchema = z__namespace.object({
|
|
2848
|
-
pem_id: z__namespace.string(),
|
|
2849
|
-
pem_status: z__namespace.enum(['ONLINE', 'OFFLINE', 'ERROR']),
|
|
2850
|
-
pem_status_changed_at: z__namespace.string(),
|
|
2851
|
-
merchant: TelemetryMerchantSchema,
|
|
2852
|
-
supplier: TelemetrySupplierSchema,
|
|
2853
|
-
software: TelemetrySoftwareSchema,
|
|
2854
|
-
last_communication_at: z__namespace.string(),
|
|
2855
|
-
pending_receipts: PendingReceiptsSchema,
|
|
2856
|
-
last_receipt_transmission: TransmissionSchema,
|
|
2857
|
-
last_message_from_mf2: MessageSchema,
|
|
2858
|
-
ade_corrispettivi_transmission: TransmissionSchema,
|
|
2859
|
-
last_message_from_ade: MessageSchema,
|
|
2860
|
-
lottery: LotterySchema,
|
|
2861
|
-
});
|
|
2862
|
-
|
|
2863
|
-
// Receipt schemas and types
|
|
2864
|
-
// Common validation utilities
|
|
2865
|
-
const ValidationMessages = {
|
|
2866
|
-
fieldIsRequired: 'This field is required',
|
|
2867
|
-
arrayMin1: 'At least one item is required',
|
|
2868
|
-
paymentMethodRequired: 'At least one payment method is required',
|
|
2869
|
-
invalidEmail: 'Please enter a valid email address',
|
|
2870
|
-
passwordMinLength: 'Password must be at least 8 characters long',
|
|
2871
|
-
passwordComplexity: 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
|
|
2872
|
-
invalidZipCode: 'Please enter a valid 5-digit zip code',
|
|
2873
|
-
provinceMinLength: 'Province code must be 2 characters',
|
|
2874
|
-
provinceMaxLength: 'Province code must be 2 characters',
|
|
2875
|
-
invalidDateFormat: 'Please enter a valid date',
|
|
2876
|
-
nameMaxLength: 'Name is too long',
|
|
2877
|
-
invalidFiscalId: 'Please enter a valid Italian fiscal ID (Codice Fiscale or Partita IVA)',
|
|
2878
|
-
invalidVatNumber: 'Please enter a valid VAT number (11 digits)',
|
|
2879
|
-
invalidFiscalCode: 'Please enter a valid fiscal code (11 digits)',
|
|
2880
|
-
businessNameMaxLength: 'Business name is too long (max 200 characters)',
|
|
2881
|
-
businessNameOrPersonalNamesRequired: 'Please provide either a business name or first/last name, but not both',
|
|
2882
|
-
firstNameMaxLength: 'First name is too long (max 100 characters)',
|
|
2883
|
-
lastNameMaxLength: 'Last name is too long (max 100 characters)',
|
|
2884
|
-
invalidUuid: 'Please enter a valid UUID',
|
|
2885
|
-
invalidPemType: 'PEM type must be one of: AP, SP, TM, PV',
|
|
2886
|
-
reasonMaxLength: 'Reason is too long (max 255 characters)',
|
|
2887
|
-
pageMinValue: 'Page number must be at least 1',
|
|
2888
|
-
invalidDailyReportStatus: 'Daily report status must be one of: pending, sent, error',
|
|
2889
|
-
displayNameMaxLength: 'Display name is too long (max 255 characters)',
|
|
2890
|
-
};
|
|
2891
|
-
// Validation helper functions
|
|
2892
|
-
const validateInput = (schema, data) => {
|
|
2893
|
-
const result = schema.safeParse(data);
|
|
2894
|
-
if (!result.success) {
|
|
2895
|
-
const errors = result.error.issues.map((error) => ({
|
|
2896
|
-
field: error.path.join('.'),
|
|
2897
|
-
message: error.message,
|
|
2898
|
-
code: error.code,
|
|
2899
|
-
}));
|
|
2900
|
-
return {
|
|
2901
|
-
success: false,
|
|
2902
|
-
errors,
|
|
2903
|
-
data: null,
|
|
2904
|
-
};
|
|
2905
|
-
}
|
|
2906
|
-
return {
|
|
2907
|
-
success: true,
|
|
2908
|
-
errors: [],
|
|
2909
|
-
data: result.data,
|
|
2910
|
-
};
|
|
2911
|
-
};
|
|
2912
2498
|
|
|
2913
2499
|
class ACubeSDKError extends Error {
|
|
2914
2500
|
constructor(type, message, originalError, statusCode, violations) {
|
|
@@ -2921,7 +2507,7 @@ class ACubeSDKError extends Error {
|
|
|
2921
2507
|
}
|
|
2922
2508
|
}
|
|
2923
2509
|
|
|
2924
|
-
const log$
|
|
2510
|
+
const log$a = createPrefixedLogger('AUTH-STRATEGY');
|
|
2925
2511
|
class AuthStrategy {
|
|
2926
2512
|
constructor(jwtHandler, mtlsHandler, userProvider, mtlsAdapter) {
|
|
2927
2513
|
this.jwtHandler = jwtHandler;
|
|
@@ -2936,7 +2522,7 @@ class AuthStrategy {
|
|
|
2936
2522
|
const platform = this.detectPlatform();
|
|
2937
2523
|
const userRole = await this.getUserRole();
|
|
2938
2524
|
const isReceiptEndpoint = this.isReceiptEndpoint(url);
|
|
2939
|
-
log$
|
|
2525
|
+
log$a.debug('Determining auth config', {
|
|
2940
2526
|
url,
|
|
2941
2527
|
method,
|
|
2942
2528
|
platform,
|
|
@@ -3067,7 +2653,7 @@ class JwtAuthHandler {
|
|
|
3067
2653
|
}
|
|
3068
2654
|
}
|
|
3069
2655
|
|
|
3070
|
-
const log$
|
|
2656
|
+
const log$9 = createPrefixedLogger('MTLS-HANDLER');
|
|
3071
2657
|
class MtlsAuthHandler {
|
|
3072
2658
|
constructor(mtlsAdapter, certificatePort) {
|
|
3073
2659
|
this.mtlsAdapter = mtlsAdapter;
|
|
@@ -3109,7 +2695,7 @@ class MtlsAuthHandler {
|
|
|
3109
2695
|
async makeRequest(url, config, jwtToken) {
|
|
3110
2696
|
const requestKey = this.generateRequestKey(url, config, jwtToken);
|
|
3111
2697
|
if (this.pendingRequests.has(requestKey)) {
|
|
3112
|
-
log$
|
|
2698
|
+
log$9.debug('Deduplicating concurrent request:', url);
|
|
3113
2699
|
return this.pendingRequests.get(requestKey);
|
|
3114
2700
|
}
|
|
3115
2701
|
const requestPromise = this.executeRequest(url, config, jwtToken, false);
|
|
@@ -3133,10 +2719,10 @@ class MtlsAuthHandler {
|
|
|
3133
2719
|
};
|
|
3134
2720
|
if (jwtToken) {
|
|
3135
2721
|
headers['Authorization'] = `Bearer ${jwtToken}`;
|
|
3136
|
-
log$
|
|
2722
|
+
log$9.debug('JWT token present:', jwtToken.substring(0, 20) + '...');
|
|
3137
2723
|
}
|
|
3138
2724
|
else {
|
|
3139
|
-
log$
|
|
2725
|
+
log$9.warn('No JWT token provided for mTLS request');
|
|
3140
2726
|
}
|
|
3141
2727
|
const fullUrl = this.constructMtlsUrl(url);
|
|
3142
2728
|
const mtlsConfig = {
|
|
@@ -3147,25 +2733,25 @@ class MtlsAuthHandler {
|
|
|
3147
2733
|
timeout: config.timeout,
|
|
3148
2734
|
responseType: config.responseType,
|
|
3149
2735
|
};
|
|
3150
|
-
log$
|
|
3151
|
-
log$
|
|
2736
|
+
log$9.debug('header-mtls', headers);
|
|
2737
|
+
log$9.debug(`${config.method} ${fullUrl}`);
|
|
3152
2738
|
if (config.data) {
|
|
3153
|
-
log$
|
|
2739
|
+
log$9.debug('Request body:', config.data);
|
|
3154
2740
|
}
|
|
3155
2741
|
try {
|
|
3156
2742
|
const response = await this.mtlsAdapter.request(mtlsConfig);
|
|
3157
|
-
log$
|
|
2743
|
+
log$9.debug(`Response ${response.status} from ${fullUrl}`);
|
|
3158
2744
|
if (response.data) {
|
|
3159
|
-
log$
|
|
2745
|
+
log$9.debug('Response body:', response.data);
|
|
3160
2746
|
}
|
|
3161
2747
|
return response.data;
|
|
3162
2748
|
}
|
|
3163
2749
|
catch (error) {
|
|
3164
|
-
log$
|
|
2750
|
+
log$9.error(`Response error from ${fullUrl}:`, error);
|
|
3165
2751
|
if (error && typeof error === 'object' && 'response' in error) {
|
|
3166
2752
|
const axiosError = error;
|
|
3167
2753
|
if (axiosError.response?.data) {
|
|
3168
|
-
log$
|
|
2754
|
+
log$9.error('Response body:', axiosError.response.data);
|
|
3169
2755
|
}
|
|
3170
2756
|
}
|
|
3171
2757
|
if (isRetryAttempt) {
|
|
@@ -3175,7 +2761,7 @@ class MtlsAuthHandler {
|
|
|
3175
2761
|
if (!shouldRetry) {
|
|
3176
2762
|
throw error;
|
|
3177
2763
|
}
|
|
3178
|
-
log$
|
|
2764
|
+
log$9.debug('Request failed, reconfiguring certificate and retrying...');
|
|
3179
2765
|
try {
|
|
3180
2766
|
await this.configureCertificate(certificate);
|
|
3181
2767
|
return await this.executeRequest(url, config, jwtToken, true);
|
|
@@ -3190,2000 +2776,99 @@ class MtlsAuthHandler {
|
|
|
3190
2776
|
const message = error.message.toLowerCase();
|
|
3191
2777
|
return (message.includes('certificate') ||
|
|
3192
2778
|
message.includes('ssl') ||
|
|
3193
|
-
message.includes('tls') ||
|
|
3194
|
-
message.includes('handshake'));
|
|
3195
|
-
}
|
|
3196
|
-
return false;
|
|
3197
|
-
}
|
|
3198
|
-
async configureCertificate(certificate) {
|
|
3199
|
-
if (!this.mtlsAdapter) {
|
|
3200
|
-
throw new Error('mTLS adapter not available');
|
|
3201
|
-
}
|
|
3202
|
-
const certificateData = {
|
|
3203
|
-
certificate: certificate.certificate,
|
|
3204
|
-
privateKey: certificate.privateKey,
|
|
3205
|
-
format: certificate.format.toUpperCase(),
|
|
3206
|
-
};
|
|
3207
|
-
await this.mtlsAdapter.configureCertificate(certificateData);
|
|
3208
|
-
}
|
|
3209
|
-
async storeCertificate(certificate, privateKey, options = {}) {
|
|
3210
|
-
if (!this.certificatePort) {
|
|
3211
|
-
throw new Error('Certificate port not available');
|
|
3212
|
-
}
|
|
3213
|
-
if (this.mtlsAdapter) {
|
|
3214
|
-
try {
|
|
3215
|
-
await this.mtlsAdapter.removeCertificate();
|
|
3216
|
-
}
|
|
3217
|
-
catch {
|
|
3218
|
-
// No existing certificate to remove
|
|
3219
|
-
}
|
|
3220
|
-
}
|
|
3221
|
-
const format = (options.format || 'pem');
|
|
3222
|
-
await this.certificatePort.storeCertificate(certificate, privateKey, format);
|
|
3223
|
-
if (this.mtlsAdapter) {
|
|
3224
|
-
const certificateData = {
|
|
3225
|
-
certificate,
|
|
3226
|
-
privateKey,
|
|
3227
|
-
format: format.toUpperCase(),
|
|
3228
|
-
};
|
|
3229
|
-
await this.mtlsAdapter.configureCertificate(certificateData);
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
async clearCertificate() {
|
|
3233
|
-
if (this.mtlsAdapter) {
|
|
3234
|
-
try {
|
|
3235
|
-
await this.mtlsAdapter.removeCertificate();
|
|
3236
|
-
}
|
|
3237
|
-
catch {
|
|
3238
|
-
// No certificate to remove
|
|
3239
|
-
}
|
|
3240
|
-
}
|
|
3241
|
-
if (this.certificatePort) {
|
|
3242
|
-
await this.certificatePort.clearCertificate();
|
|
3243
|
-
}
|
|
3244
|
-
}
|
|
3245
|
-
async testConnection() {
|
|
3246
|
-
if (!this.mtlsAdapter) {
|
|
3247
|
-
return false;
|
|
3248
|
-
}
|
|
3249
|
-
return this.mtlsAdapter.testConnection();
|
|
3250
|
-
}
|
|
3251
|
-
getBaseUrl() {
|
|
3252
|
-
if (!this.mtlsAdapter) {
|
|
3253
|
-
return null;
|
|
3254
|
-
}
|
|
3255
|
-
return this.mtlsAdapter.getBaseUrl();
|
|
3256
|
-
}
|
|
3257
|
-
async getStatus() {
|
|
3258
|
-
const status = {
|
|
3259
|
-
adapterAvailable: !!this.mtlsAdapter,
|
|
3260
|
-
certificatePortAvailable: !!this.certificatePort,
|
|
3261
|
-
isReady: false,
|
|
3262
|
-
hasCertificate: false,
|
|
3263
|
-
certificateInfo: null,
|
|
3264
|
-
platformInfo: this.mtlsAdapter?.getPlatformInfo() || null,
|
|
3265
|
-
pendingRequestsCount: this.pendingRequests.size,
|
|
3266
|
-
};
|
|
3267
|
-
if (this.certificatePort) {
|
|
3268
|
-
try {
|
|
3269
|
-
status.hasCertificate = await this.certificatePort.hasCertificate();
|
|
3270
|
-
if (status.hasCertificate) {
|
|
3271
|
-
status.certificateInfo = await this.certificatePort.getCertificateInfo();
|
|
3272
|
-
}
|
|
3273
|
-
}
|
|
3274
|
-
catch {
|
|
3275
|
-
// Ignore errors
|
|
3276
|
-
}
|
|
3277
|
-
}
|
|
3278
|
-
status.isReady = await this.isMtlsReady();
|
|
3279
|
-
return status;
|
|
3280
|
-
}
|
|
3281
|
-
clearPendingRequests() {
|
|
3282
|
-
this.pendingRequests.clear();
|
|
3283
|
-
}
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3286
|
-
const DEFAULT_QUEUE_CONFIG = {
|
|
3287
|
-
maxRetries: 3,
|
|
3288
|
-
retryDelay: 1000,
|
|
3289
|
-
maxRetryDelay: 30000,
|
|
3290
|
-
backoffMultiplier: 2,
|
|
3291
|
-
maxQueueSize: 1000,
|
|
3292
|
-
batchSize: 10,
|
|
3293
|
-
syncInterval: 30000,
|
|
3294
|
-
};
|
|
3295
|
-
|
|
3296
|
-
class OperationQueue {
|
|
3297
|
-
constructor(storage, config = DEFAULT_QUEUE_CONFIG, events = {}) {
|
|
3298
|
-
this.storage = storage;
|
|
3299
|
-
this.config = config;
|
|
3300
|
-
this.events = events;
|
|
3301
|
-
this.queue = [];
|
|
3302
|
-
this.processing = false;
|
|
3303
|
-
this.config = { ...DEFAULT_QUEUE_CONFIG, ...config };
|
|
3304
|
-
this.loadQueue();
|
|
3305
|
-
if (this.config.syncInterval > 0) {
|
|
3306
|
-
this.startAutoSync();
|
|
3307
|
-
}
|
|
3308
|
-
}
|
|
3309
|
-
async addOperation(type, resource, endpoint, method, data, priority = 1) {
|
|
3310
|
-
if (this.queue.length >= this.config.maxQueueSize) {
|
|
3311
|
-
const lowPriorityIndex = this.queue.findIndex((op) => op.priority === 1);
|
|
3312
|
-
if (lowPriorityIndex !== -1) {
|
|
3313
|
-
this.queue.splice(lowPriorityIndex, 1);
|
|
3314
|
-
}
|
|
3315
|
-
else {
|
|
3316
|
-
throw new Error('Queue is full');
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
const operation = {
|
|
3320
|
-
id: this.generateId(),
|
|
3321
|
-
type,
|
|
3322
|
-
resource,
|
|
3323
|
-
endpoint,
|
|
3324
|
-
method,
|
|
3325
|
-
data,
|
|
3326
|
-
status: 'pending',
|
|
3327
|
-
createdAt: Date.now(),
|
|
3328
|
-
updatedAt: Date.now(),
|
|
3329
|
-
retryCount: 0,
|
|
3330
|
-
maxRetries: this.config.maxRetries,
|
|
3331
|
-
priority,
|
|
3332
|
-
};
|
|
3333
|
-
const insertIndex = this.queue.findIndex((op) => op.priority < priority);
|
|
3334
|
-
if (insertIndex === -1) {
|
|
3335
|
-
this.queue.push(operation);
|
|
3336
|
-
}
|
|
3337
|
-
else {
|
|
3338
|
-
this.queue.splice(insertIndex, 0, operation);
|
|
3339
|
-
}
|
|
3340
|
-
await this.saveQueue();
|
|
3341
|
-
this.events.onOperationAdded?.(operation);
|
|
3342
|
-
return operation.id;
|
|
3343
|
-
}
|
|
3344
|
-
getPendingOperations() {
|
|
3345
|
-
return this.queue.filter((op) => op.status === 'pending' || op.status === 'failed');
|
|
3346
|
-
}
|
|
3347
|
-
getOperation(id) {
|
|
3348
|
-
return this.queue.find((op) => op.id === id);
|
|
3349
|
-
}
|
|
3350
|
-
async removeOperation(id) {
|
|
3351
|
-
const index = this.queue.findIndex((op) => op.id === id);
|
|
3352
|
-
if (index === -1)
|
|
3353
|
-
return false;
|
|
3354
|
-
this.queue.splice(index, 1);
|
|
3355
|
-
await this.saveQueue();
|
|
3356
|
-
return true;
|
|
3357
|
-
}
|
|
3358
|
-
async updateOperation(id, updates) {
|
|
3359
|
-
const operation = this.queue.find((op) => op.id === id);
|
|
3360
|
-
if (!operation)
|
|
3361
|
-
return false;
|
|
3362
|
-
Object.assign(operation, { ...updates, updatedAt: Date.now() });
|
|
3363
|
-
await this.saveQueue();
|
|
3364
|
-
return true;
|
|
3365
|
-
}
|
|
3366
|
-
getStats() {
|
|
3367
|
-
return {
|
|
3368
|
-
total: this.queue.length,
|
|
3369
|
-
pending: this.queue.filter((op) => op.status === 'pending').length,
|
|
3370
|
-
processing: this.queue.filter((op) => op.status === 'processing').length,
|
|
3371
|
-
completed: this.queue.filter((op) => op.status === 'completed').length,
|
|
3372
|
-
failed: this.queue.filter((op) => op.status === 'failed').length,
|
|
3373
|
-
};
|
|
3374
|
-
}
|
|
3375
|
-
async clearQueue() {
|
|
3376
|
-
this.queue = [];
|
|
3377
|
-
await this.saveQueue();
|
|
3378
|
-
}
|
|
3379
|
-
async clearCompleted() {
|
|
3380
|
-
this.queue = this.queue.filter((op) => op.status !== 'completed');
|
|
3381
|
-
await this.saveQueue();
|
|
3382
|
-
}
|
|
3383
|
-
async clearFailed() {
|
|
3384
|
-
this.queue = this.queue.filter((op) => op.status !== 'failed');
|
|
3385
|
-
await this.saveQueue();
|
|
3386
|
-
}
|
|
3387
|
-
async retryFailed() {
|
|
3388
|
-
for (const operation of this.queue.filter((op) => op.status === 'failed')) {
|
|
3389
|
-
if (operation.retryCount < operation.maxRetries) {
|
|
3390
|
-
operation.status = 'pending';
|
|
3391
|
-
operation.retryCount++;
|
|
3392
|
-
operation.updatedAt = Date.now();
|
|
3393
|
-
delete operation.error;
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
await this.saveQueue();
|
|
3397
|
-
}
|
|
3398
|
-
getNextBatch() {
|
|
3399
|
-
return this.queue
|
|
3400
|
-
.filter((op) => op.status === 'pending')
|
|
3401
|
-
.sort((a, b) => b.priority - a.priority || a.createdAt - b.createdAt)
|
|
3402
|
-
.slice(0, this.config.batchSize);
|
|
3403
|
-
}
|
|
3404
|
-
isEmpty() {
|
|
3405
|
-
return this.getPendingOperations().length === 0;
|
|
3406
|
-
}
|
|
3407
|
-
startAutoSync() {
|
|
3408
|
-
if (this.syncIntervalId)
|
|
3409
|
-
return;
|
|
3410
|
-
this.syncIntervalId = setInterval(() => {
|
|
3411
|
-
if (!this.isEmpty() && !this.processing) {
|
|
3412
|
-
this.events.onQueueEmpty?.();
|
|
3413
|
-
}
|
|
3414
|
-
}, this.config.syncInterval);
|
|
3415
|
-
}
|
|
3416
|
-
stopAutoSync() {
|
|
3417
|
-
if (this.syncIntervalId) {
|
|
3418
|
-
clearInterval(this.syncIntervalId);
|
|
3419
|
-
this.syncIntervalId = undefined;
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
setProcessing(value) {
|
|
3423
|
-
this.processing = value;
|
|
3424
|
-
}
|
|
3425
|
-
isCurrentlyProcessing() {
|
|
3426
|
-
return this.processing;
|
|
3427
|
-
}
|
|
3428
|
-
async loadQueue() {
|
|
3429
|
-
try {
|
|
3430
|
-
const queueData = await this.storage.get(OperationQueue.QUEUE_KEY);
|
|
3431
|
-
if (queueData) {
|
|
3432
|
-
this.queue = JSON.parse(queueData);
|
|
3433
|
-
this.queue.forEach((op) => {
|
|
3434
|
-
if (op.status === 'processing') {
|
|
3435
|
-
op.status = 'pending';
|
|
3436
|
-
}
|
|
3437
|
-
});
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
catch {
|
|
3441
|
-
this.queue = [];
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
async saveQueue() {
|
|
3445
|
-
try {
|
|
3446
|
-
await this.storage.set(OperationQueue.QUEUE_KEY, JSON.stringify(this.queue));
|
|
3447
|
-
}
|
|
3448
|
-
catch (error) {
|
|
3449
|
-
this.events.onError?.(new Error(`Failed to save queue: ${error}`));
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
|
-
generateId() {
|
|
3453
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3454
|
-
}
|
|
3455
|
-
destroy() {
|
|
3456
|
-
this.stopAutoSync();
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
OperationQueue.QUEUE_KEY = 'acube_operation_queue';
|
|
3460
|
-
|
|
3461
|
-
class SyncManager {
|
|
3462
|
-
constructor(queue, httpPort, networkMonitor, config, events = {}) {
|
|
3463
|
-
this.queue = queue;
|
|
3464
|
-
this.httpPort = httpPort;
|
|
3465
|
-
this.networkMonitor = networkMonitor;
|
|
3466
|
-
this.config = config;
|
|
3467
|
-
this.events = events;
|
|
3468
|
-
this.isOnline = true;
|
|
3469
|
-
this.destroy$ = new Subject();
|
|
3470
|
-
this.setupNetworkMonitoring();
|
|
3471
|
-
}
|
|
3472
|
-
setupNetworkMonitoring() {
|
|
3473
|
-
// Subscribe to online$ to track current state
|
|
3474
|
-
this.networkSubscription = this.networkMonitor.online$
|
|
3475
|
-
.pipe(startWith(true), // Assume online initially
|
|
3476
|
-
pairwise(), filter(([wasOnline, isNowOnline]) => !wasOnline && isNowOnline), takeUntil(this.destroy$))
|
|
3477
|
-
.subscribe(() => {
|
|
3478
|
-
// Offline → Online transition detected
|
|
3479
|
-
this.syncPendingOperations();
|
|
3480
|
-
});
|
|
3481
|
-
// Track current online state
|
|
3482
|
-
this.networkMonitor.online$.pipe(takeUntil(this.destroy$)).subscribe((online) => {
|
|
3483
|
-
this.isOnline = online;
|
|
3484
|
-
});
|
|
3485
|
-
}
|
|
3486
|
-
async syncPendingOperations() {
|
|
3487
|
-
if (!this.isOnline) {
|
|
3488
|
-
throw new Error('Cannot sync while offline');
|
|
3489
|
-
}
|
|
3490
|
-
if (this.queue.isCurrentlyProcessing()) {
|
|
3491
|
-
throw new Error('Sync already in progress');
|
|
3492
|
-
}
|
|
3493
|
-
this.queue.setProcessing(true);
|
|
3494
|
-
try {
|
|
3495
|
-
const results = [];
|
|
3496
|
-
let successCount = 0;
|
|
3497
|
-
let failureCount = 0;
|
|
3498
|
-
while (!this.queue.isEmpty()) {
|
|
3499
|
-
const batch = this.queue.getNextBatch();
|
|
3500
|
-
if (batch.length === 0)
|
|
3501
|
-
break;
|
|
3502
|
-
const batchPromises = batch.map((operation) => this.processOperation(operation));
|
|
3503
|
-
const batchResults = await Promise.allSettled(batchPromises);
|
|
3504
|
-
batchResults.forEach((result, index) => {
|
|
3505
|
-
const operation = batch[index];
|
|
3506
|
-
if (!operation)
|
|
3507
|
-
return;
|
|
3508
|
-
if (result.status === 'fulfilled') {
|
|
3509
|
-
const syncResult = result.value;
|
|
3510
|
-
results.push(syncResult);
|
|
3511
|
-
if (syncResult.success) {
|
|
3512
|
-
successCount++;
|
|
3513
|
-
this.events.onOperationCompleted?.(syncResult);
|
|
3514
|
-
}
|
|
3515
|
-
else {
|
|
3516
|
-
failureCount++;
|
|
3517
|
-
this.events.onOperationFailed?.(syncResult);
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
else {
|
|
3521
|
-
const syncResult = {
|
|
3522
|
-
operation,
|
|
3523
|
-
success: false,
|
|
3524
|
-
error: result.reason?.message || 'Unknown error',
|
|
3525
|
-
};
|
|
3526
|
-
results.push(syncResult);
|
|
3527
|
-
failureCount++;
|
|
3528
|
-
this.events.onOperationFailed?.(syncResult);
|
|
3529
|
-
this.queue.updateOperation(operation.id, {
|
|
3530
|
-
status: 'failed',
|
|
3531
|
-
error: syncResult.error,
|
|
3532
|
-
});
|
|
3533
|
-
}
|
|
3534
|
-
});
|
|
3535
|
-
if (!this.queue.isEmpty()) {
|
|
3536
|
-
await this.delay(500);
|
|
3537
|
-
}
|
|
3538
|
-
}
|
|
3539
|
-
const batchResult = {
|
|
3540
|
-
totalOperations: results.length,
|
|
3541
|
-
successCount,
|
|
3542
|
-
failureCount,
|
|
3543
|
-
results,
|
|
3544
|
-
};
|
|
3545
|
-
this.events.onBatchSyncCompleted?.(batchResult);
|
|
3546
|
-
if (this.queue.isEmpty()) {
|
|
3547
|
-
this.events.onQueueEmpty?.();
|
|
3548
|
-
}
|
|
3549
|
-
return batchResult;
|
|
3550
|
-
}
|
|
3551
|
-
finally {
|
|
3552
|
-
this.queue.setProcessing(false);
|
|
3553
|
-
}
|
|
3554
|
-
}
|
|
3555
|
-
async processOperation(operation) {
|
|
3556
|
-
await this.queue.updateOperation(operation.id, { status: 'processing' });
|
|
3557
|
-
try {
|
|
3558
|
-
const response = await this.executeOperation(operation);
|
|
3559
|
-
await this.queue.updateOperation(operation.id, { status: 'completed' });
|
|
3560
|
-
return { operation, success: true, response };
|
|
3561
|
-
}
|
|
3562
|
-
catch (error) {
|
|
3563
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
3564
|
-
if (operation.retryCount < operation.maxRetries && this.isRetryableError(error)) {
|
|
3565
|
-
const delay = this.calculateRetryDelay(operation.retryCount);
|
|
3566
|
-
await this.queue.updateOperation(operation.id, {
|
|
3567
|
-
status: 'pending',
|
|
3568
|
-
retryCount: operation.retryCount + 1,
|
|
3569
|
-
error: errorMessage,
|
|
3570
|
-
});
|
|
3571
|
-
setTimeout(() => {
|
|
3572
|
-
if (this.isOnline && !this.queue.isCurrentlyProcessing()) {
|
|
3573
|
-
this.syncPendingOperations();
|
|
3574
|
-
}
|
|
3575
|
-
}, delay);
|
|
3576
|
-
return { operation, success: false, error: `Retrying: ${errorMessage}` };
|
|
3577
|
-
}
|
|
3578
|
-
else {
|
|
3579
|
-
await this.queue.updateOperation(operation.id, {
|
|
3580
|
-
status: 'failed',
|
|
3581
|
-
error: errorMessage,
|
|
3582
|
-
});
|
|
3583
|
-
return { operation, success: false, error: errorMessage };
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
async executeOperation(operation) {
|
|
3588
|
-
const { method, endpoint, data, headers } = operation;
|
|
3589
|
-
const config = headers ? { headers } : undefined;
|
|
3590
|
-
switch (method) {
|
|
3591
|
-
case 'GET':
|
|
3592
|
-
return (await this.httpPort.get(endpoint, config)).data;
|
|
3593
|
-
case 'POST':
|
|
3594
|
-
return (await this.httpPort.post(endpoint, data, config)).data;
|
|
3595
|
-
case 'PUT':
|
|
3596
|
-
return (await this.httpPort.put(endpoint, data, config)).data;
|
|
3597
|
-
case 'PATCH':
|
|
3598
|
-
return (await this.httpPort.patch(endpoint, data, config)).data;
|
|
3599
|
-
case 'DELETE':
|
|
3600
|
-
return (await this.httpPort.delete(endpoint, config)).data;
|
|
3601
|
-
default:
|
|
3602
|
-
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
isRetryableError(error) {
|
|
3606
|
-
const errorObj = error;
|
|
3607
|
-
if (errorObj.code === 'NETWORK_ERROR')
|
|
3608
|
-
return true;
|
|
3609
|
-
if (errorObj.statusCode && errorObj.statusCode >= 500)
|
|
3610
|
-
return true;
|
|
3611
|
-
if (errorObj.statusCode === 429)
|
|
3612
|
-
return true;
|
|
3613
|
-
const errorMessage = error?.message;
|
|
3614
|
-
if (errorObj.code === 'ECONNABORTED' || errorMessage?.includes('timeout'))
|
|
3615
|
-
return true;
|
|
3616
|
-
return false;
|
|
3617
|
-
}
|
|
3618
|
-
calculateRetryDelay(retryCount) {
|
|
3619
|
-
const delay = this.config.retryDelay * Math.pow(this.config.backoffMultiplier, retryCount);
|
|
3620
|
-
return Math.min(delay, this.config.maxRetryDelay);
|
|
3621
|
-
}
|
|
3622
|
-
delay(ms) {
|
|
3623
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3624
|
-
}
|
|
3625
|
-
isCurrentlyOnline() {
|
|
3626
|
-
return this.isOnline;
|
|
3627
|
-
}
|
|
3628
|
-
async triggerSync() {
|
|
3629
|
-
if (!this.isOnline)
|
|
3630
|
-
return null;
|
|
3631
|
-
if (this.queue.isEmpty()) {
|
|
3632
|
-
return { totalOperations: 0, successCount: 0, failureCount: 0, results: [] };
|
|
3633
|
-
}
|
|
3634
|
-
return await this.syncPendingOperations();
|
|
3635
|
-
}
|
|
3636
|
-
getSyncStatus() {
|
|
3637
|
-
return {
|
|
3638
|
-
isOnline: this.isOnline,
|
|
3639
|
-
isProcessing: this.queue.isCurrentlyProcessing(),
|
|
3640
|
-
queueStats: this.queue.getStats(),
|
|
3641
|
-
};
|
|
3642
|
-
}
|
|
3643
|
-
destroy() {
|
|
3644
|
-
this.destroy$.next();
|
|
3645
|
-
this.destroy$.complete();
|
|
3646
|
-
this.networkSubscription?.unsubscribe();
|
|
3647
|
-
}
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
class OfflineManager {
|
|
3651
|
-
get queue$() {
|
|
3652
|
-
return this.queueSubject.asObservable();
|
|
3653
|
-
}
|
|
3654
|
-
get syncStatus$() {
|
|
3655
|
-
return this.syncStatusSubject.asObservable();
|
|
3656
|
-
}
|
|
3657
|
-
constructor(storage, httpPort, networkMonitor, config = {}, events = {}, _cache) {
|
|
3658
|
-
this.queueSubject = new BehaviorSubject([]);
|
|
3659
|
-
this.syncStatusSubject = new BehaviorSubject({
|
|
3660
|
-
isOnline: true,
|
|
3661
|
-
isProcessing: false,
|
|
3662
|
-
queueStats: { total: 0, pending: 0, processing: 0, completed: 0, failed: 0 },
|
|
3663
|
-
});
|
|
3664
|
-
this.destroy$ = new Subject();
|
|
3665
|
-
const finalConfig = { ...DEFAULT_QUEUE_CONFIG, ...config };
|
|
3666
|
-
const wrappedEvents = {
|
|
3667
|
-
...events,
|
|
3668
|
-
onOperationAdded: (op) => {
|
|
3669
|
-
this.updateQueueState();
|
|
3670
|
-
events.onOperationAdded?.(op);
|
|
3671
|
-
},
|
|
3672
|
-
onOperationCompleted: (result) => {
|
|
3673
|
-
this.updateQueueState();
|
|
3674
|
-
events.onOperationCompleted?.(result);
|
|
3675
|
-
},
|
|
3676
|
-
onOperationFailed: (result) => {
|
|
3677
|
-
this.updateQueueState();
|
|
3678
|
-
events.onOperationFailed?.(result);
|
|
3679
|
-
},
|
|
3680
|
-
onBatchSyncCompleted: (result) => {
|
|
3681
|
-
this.updateQueueState();
|
|
3682
|
-
events.onBatchSyncCompleted?.(result);
|
|
3683
|
-
},
|
|
3684
|
-
};
|
|
3685
|
-
this.queue = new OperationQueue(storage, finalConfig, wrappedEvents);
|
|
3686
|
-
this.syncManager = new SyncManager(this.queue, httpPort, networkMonitor, finalConfig, wrappedEvents);
|
|
3687
|
-
this.updateQueueState();
|
|
3688
|
-
}
|
|
3689
|
-
updateQueueState() {
|
|
3690
|
-
this.queueSubject.next(this.queue.getPendingOperations());
|
|
3691
|
-
this.syncStatusSubject.next(this.syncManager.getSyncStatus());
|
|
3692
|
-
}
|
|
3693
|
-
async queueOperation(type, resource, endpoint, method, data, priority = 1) {
|
|
3694
|
-
const id = await this.queue.addOperation(type, resource, endpoint, method, data, priority);
|
|
3695
|
-
this.updateQueueState();
|
|
3696
|
-
return id;
|
|
3697
|
-
}
|
|
3698
|
-
async queueReceiptCreation(receiptData, priority = 2) {
|
|
3699
|
-
return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts', 'POST', receiptData, priority);
|
|
3700
|
-
}
|
|
3701
|
-
async queueReceiptVoid(voidData, priority = 3) {
|
|
3702
|
-
return await this.queueOperation('DELETE', 'receipt', '/mf1/receipts', 'DELETE', voidData, priority);
|
|
3703
|
-
}
|
|
3704
|
-
async queueReceiptReturn(returnData, priority = 3) {
|
|
3705
|
-
return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts/return', 'POST', returnData, priority);
|
|
3706
|
-
}
|
|
3707
|
-
async queueCashierCreation(cashierData, priority = 1) {
|
|
3708
|
-
return await this.queueOperation('CREATE', 'cashier', '/mf1/cashiers', 'POST', cashierData, priority);
|
|
3709
|
-
}
|
|
3710
|
-
isOnline() {
|
|
3711
|
-
return this.syncManager.isCurrentlyOnline();
|
|
3712
|
-
}
|
|
3713
|
-
getStatus() {
|
|
3714
|
-
return this.syncManager.getSyncStatus();
|
|
3715
|
-
}
|
|
3716
|
-
getPendingCount() {
|
|
3717
|
-
return this.queue.getPendingOperations().length;
|
|
3718
|
-
}
|
|
3719
|
-
isEmpty() {
|
|
3720
|
-
return this.queue.isEmpty();
|
|
3721
|
-
}
|
|
3722
|
-
async sync() {
|
|
3723
|
-
const result = await this.syncManager.triggerSync();
|
|
3724
|
-
this.updateQueueState();
|
|
3725
|
-
return result;
|
|
3726
|
-
}
|
|
3727
|
-
async retryFailed() {
|
|
3728
|
-
await this.queue.retryFailed();
|
|
3729
|
-
this.updateQueueState();
|
|
3730
|
-
if (this.isOnline()) {
|
|
3731
|
-
await this.sync();
|
|
3732
|
-
}
|
|
3733
|
-
}
|
|
3734
|
-
async clearCompleted() {
|
|
3735
|
-
await this.queue.clearCompleted();
|
|
3736
|
-
this.updateQueueState();
|
|
3737
|
-
}
|
|
3738
|
-
async clearFailed() {
|
|
3739
|
-
await this.queue.clearFailed();
|
|
3740
|
-
this.updateQueueState();
|
|
3741
|
-
}
|
|
3742
|
-
async clearAll() {
|
|
3743
|
-
await this.queue.clearQueue();
|
|
3744
|
-
this.updateQueueState();
|
|
3745
|
-
}
|
|
3746
|
-
getOperation(id) {
|
|
3747
|
-
return this.queue.getOperation(id);
|
|
3748
|
-
}
|
|
3749
|
-
async removeOperation(id) {
|
|
3750
|
-
const result = await this.queue.removeOperation(id);
|
|
3751
|
-
this.updateQueueState();
|
|
3752
|
-
return result;
|
|
3753
|
-
}
|
|
3754
|
-
getQueueStats() {
|
|
3755
|
-
return this.queue.getStats();
|
|
3756
|
-
}
|
|
3757
|
-
startAutoSync() {
|
|
3758
|
-
this.queue.startAutoSync();
|
|
3759
|
-
}
|
|
3760
|
-
stopAutoSync() {
|
|
3761
|
-
this.queue.stopAutoSync();
|
|
3762
|
-
}
|
|
3763
|
-
destroy() {
|
|
3764
|
-
this.destroy$.next();
|
|
3765
|
-
this.destroy$.complete();
|
|
3766
|
-
this.queue.destroy();
|
|
3767
|
-
this.syncManager.destroy();
|
|
3768
|
-
}
|
|
3769
|
-
}
|
|
3770
|
-
|
|
3771
|
-
class CompressionAdapter {
|
|
3772
|
-
compress(data, threshold = 1024) {
|
|
3773
|
-
const originalSize = data.length * 2;
|
|
3774
|
-
if (originalSize < threshold) {
|
|
3775
|
-
return {
|
|
3776
|
-
data,
|
|
3777
|
-
compressed: false,
|
|
3778
|
-
originalSize,
|
|
3779
|
-
compressedSize: originalSize,
|
|
3780
|
-
};
|
|
3781
|
-
}
|
|
3782
|
-
try {
|
|
3783
|
-
const compressed = this.compressString(data);
|
|
3784
|
-
const compressedSize = compressed.length * 2;
|
|
3785
|
-
if (compressedSize < originalSize) {
|
|
3786
|
-
return {
|
|
3787
|
-
data: compressed,
|
|
3788
|
-
compressed: true,
|
|
3789
|
-
originalSize,
|
|
3790
|
-
compressedSize,
|
|
3791
|
-
};
|
|
3792
|
-
}
|
|
3793
|
-
return {
|
|
3794
|
-
data,
|
|
3795
|
-
compressed: false,
|
|
3796
|
-
originalSize,
|
|
3797
|
-
compressedSize: originalSize,
|
|
3798
|
-
};
|
|
3799
|
-
}
|
|
3800
|
-
catch {
|
|
3801
|
-
return {
|
|
3802
|
-
data,
|
|
3803
|
-
compressed: false,
|
|
3804
|
-
originalSize,
|
|
3805
|
-
compressedSize: originalSize,
|
|
3806
|
-
};
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
decompress(data, compressed) {
|
|
3810
|
-
if (!compressed) {
|
|
3811
|
-
return { data, wasCompressed: false };
|
|
3812
|
-
}
|
|
3813
|
-
try {
|
|
3814
|
-
const decompressed = this.decompressString(data);
|
|
3815
|
-
return { data: decompressed, wasCompressed: true };
|
|
3816
|
-
}
|
|
3817
|
-
catch {
|
|
3818
|
-
return { data, wasCompressed: false };
|
|
3819
|
-
}
|
|
3820
|
-
}
|
|
3821
|
-
estimateSavings(data) {
|
|
3822
|
-
const repeated = data.match(/(.)\1{3,}/g);
|
|
3823
|
-
if (!repeated)
|
|
3824
|
-
return 0;
|
|
3825
|
-
let savings = 0;
|
|
3826
|
-
for (const match of repeated) {
|
|
3827
|
-
const originalBytes = match.length * 2;
|
|
3828
|
-
const compressedBytes = 6;
|
|
3829
|
-
if (originalBytes > compressedBytes) {
|
|
3830
|
-
savings += originalBytes - compressedBytes;
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
return Math.min(savings, data.length * 2 * 0.5);
|
|
3834
|
-
}
|
|
3835
|
-
compressString(input) {
|
|
3836
|
-
let compressed = '';
|
|
3837
|
-
let i = 0;
|
|
3838
|
-
while (i < input.length) {
|
|
3839
|
-
let count = 1;
|
|
3840
|
-
const char = input[i];
|
|
3841
|
-
while (i + count < input.length && input[i + count] === char && count < 255) {
|
|
3842
|
-
count++;
|
|
3843
|
-
}
|
|
3844
|
-
if (count > 3) {
|
|
3845
|
-
compressed += `~${count}${char}`;
|
|
3846
|
-
}
|
|
3847
|
-
else {
|
|
3848
|
-
for (let j = 0; j < count; j++) {
|
|
3849
|
-
compressed += char;
|
|
3850
|
-
}
|
|
3851
|
-
}
|
|
3852
|
-
i += count;
|
|
3853
|
-
}
|
|
3854
|
-
return `COMP:${btoa(compressed)}`;
|
|
3855
|
-
}
|
|
3856
|
-
decompressString(input) {
|
|
3857
|
-
if (!input.startsWith('COMP:')) {
|
|
3858
|
-
return input;
|
|
3859
|
-
}
|
|
3860
|
-
const encodedData = input.substring(5);
|
|
3861
|
-
if (!encodedData) {
|
|
3862
|
-
return input;
|
|
3863
|
-
}
|
|
3864
|
-
const compressed = atob(encodedData);
|
|
3865
|
-
let decompressed = '';
|
|
3866
|
-
let i = 0;
|
|
3867
|
-
while (i < compressed.length) {
|
|
3868
|
-
if (compressed[i] === '~' && i + 2 < compressed.length) {
|
|
3869
|
-
let countStr = '';
|
|
3870
|
-
i++;
|
|
3871
|
-
while (i < compressed.length) {
|
|
3872
|
-
const char = compressed[i];
|
|
3873
|
-
if (char && /\d/.test(char)) {
|
|
3874
|
-
countStr += char;
|
|
3875
|
-
i++;
|
|
3876
|
-
}
|
|
3877
|
-
else {
|
|
3878
|
-
break;
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
if (countStr && i < compressed.length) {
|
|
3882
|
-
const count = parseInt(countStr, 10);
|
|
3883
|
-
const char = compressed[i];
|
|
3884
|
-
for (let j = 0; j < count; j++) {
|
|
3885
|
-
decompressed += char;
|
|
3886
|
-
}
|
|
3887
|
-
i++;
|
|
3888
|
-
}
|
|
3889
|
-
}
|
|
3890
|
-
else {
|
|
3891
|
-
decompressed += compressed[i];
|
|
3892
|
-
i++;
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
return decompressed;
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3898
|
-
function compressData(data, threshold = 1024) {
|
|
3899
|
-
return new CompressionAdapter().compress(data, threshold);
|
|
3900
|
-
}
|
|
3901
|
-
function decompressData(data, compressed) {
|
|
3902
|
-
return new CompressionAdapter().decompress(data, compressed);
|
|
3903
|
-
}
|
|
3904
|
-
|
|
3905
|
-
const log$e = createPrefixedLogger('CACHE-RN');
|
|
3906
|
-
/**
|
|
3907
|
-
* React Native cache adapter using SQLite (Expo or react-native-sqlite-storage)
|
|
3908
|
-
* Cache never expires - data persists until explicitly invalidated
|
|
3909
|
-
*/
|
|
3910
|
-
class ReactNativeCacheAdapter {
|
|
3911
|
-
constructor(options = {}) {
|
|
3912
|
-
this.db = null;
|
|
3913
|
-
this.initPromise = null;
|
|
3914
|
-
this.isExpo = false;
|
|
3915
|
-
this.hasCompressedColumn = false;
|
|
3916
|
-
this.options = {
|
|
3917
|
-
maxSize: 50 * 1024 * 1024, // 50MB
|
|
3918
|
-
maxEntries: 10000,
|
|
3919
|
-
compression: false,
|
|
3920
|
-
compressionThreshold: 1024,
|
|
3921
|
-
...options,
|
|
3922
|
-
};
|
|
3923
|
-
this.initPromise = this.initialize();
|
|
3924
|
-
}
|
|
3925
|
-
normalizeResults(results) {
|
|
3926
|
-
if (this.isExpo) {
|
|
3927
|
-
const expoResults = results;
|
|
3928
|
-
if (Array.isArray(expoResults)) {
|
|
3929
|
-
return expoResults;
|
|
3930
|
-
}
|
|
3931
|
-
return expoResults.results || [];
|
|
3932
|
-
}
|
|
3933
|
-
else {
|
|
3934
|
-
const rnResults = results;
|
|
3935
|
-
const rows = rnResults.rows;
|
|
3936
|
-
if (!rows || rows.length === 0)
|
|
3937
|
-
return [];
|
|
3938
|
-
const normalizedRows = [];
|
|
3939
|
-
for (let i = 0; i < rows.length; i++) {
|
|
3940
|
-
normalizedRows.push(rows.item(i));
|
|
3941
|
-
}
|
|
3942
|
-
return normalizedRows;
|
|
3943
|
-
}
|
|
3944
|
-
}
|
|
3945
|
-
async initialize() {
|
|
3946
|
-
if (this.db)
|
|
3947
|
-
return;
|
|
3948
|
-
try {
|
|
3949
|
-
// Try Expo SQLite first
|
|
3950
|
-
const ExpoSQLite = require('expo-sqlite');
|
|
3951
|
-
this.db = await ExpoSQLite.openDatabaseAsync(ReactNativeCacheAdapter.DB_NAME);
|
|
3952
|
-
this.isExpo = true;
|
|
3953
|
-
await this.createTables();
|
|
3954
|
-
}
|
|
3955
|
-
catch (expoError) {
|
|
3956
|
-
try {
|
|
3957
|
-
// Fallback to react-native-sqlite-storage
|
|
3958
|
-
const SQLite = require('react-native-sqlite-storage');
|
|
3959
|
-
this.db = await new Promise((resolve, reject) => {
|
|
3960
|
-
SQLite.openDatabase({
|
|
3961
|
-
name: ReactNativeCacheAdapter.DB_NAME,
|
|
3962
|
-
location: 'default',
|
|
3963
|
-
}, resolve, reject);
|
|
3964
|
-
});
|
|
3965
|
-
this.isExpo = false;
|
|
3966
|
-
await this.createTables();
|
|
3967
|
-
}
|
|
3968
|
-
catch (rnError) {
|
|
3969
|
-
throw new Error(`Failed to initialize SQLite: Expo error: ${expoError}, RN error: ${rnError}`);
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
}
|
|
3973
|
-
async createTables() {
|
|
3974
|
-
// Create table with simplified schema (no TTL)
|
|
3975
|
-
const createTableSQL = `
|
|
3976
|
-
CREATE TABLE IF NOT EXISTS ${ReactNativeCacheAdapter.TABLE_NAME} (
|
|
3977
|
-
cache_key TEXT PRIMARY KEY,
|
|
3978
|
-
data TEXT NOT NULL,
|
|
3979
|
-
timestamp INTEGER NOT NULL
|
|
3980
|
-
);
|
|
3981
|
-
|
|
3982
|
-
CREATE INDEX IF NOT EXISTS idx_timestamp ON ${ReactNativeCacheAdapter.TABLE_NAME}(timestamp);
|
|
3983
|
-
`;
|
|
3984
|
-
await this.executeSql(createTableSQL);
|
|
3985
|
-
// Then, run migrations to add new columns if they don't exist
|
|
3986
|
-
await this.runMigrations();
|
|
3987
|
-
}
|
|
3988
|
-
async runMigrations() {
|
|
3989
|
-
log$e.debug('Running database migrations...');
|
|
3990
|
-
try {
|
|
3991
|
-
// Check if compressed column exists
|
|
3992
|
-
this.hasCompressedColumn = await this.checkColumnExists('compressed');
|
|
3993
|
-
if (!this.hasCompressedColumn) {
|
|
3994
|
-
log$e.debug('Adding compressed column to cache table');
|
|
3995
|
-
const addColumnSQL = `ALTER TABLE ${ReactNativeCacheAdapter.TABLE_NAME} ADD COLUMN compressed INTEGER DEFAULT 0`;
|
|
3996
|
-
await this.executeSql(addColumnSQL);
|
|
3997
|
-
this.hasCompressedColumn = true;
|
|
3998
|
-
log$e.debug('Successfully added compressed column');
|
|
3999
|
-
}
|
|
4000
|
-
else {
|
|
4001
|
-
log$e.debug('Compressed column already exists');
|
|
4002
|
-
}
|
|
4003
|
-
log$e.debug('Database migrations completed', {
|
|
4004
|
-
hasCompressedColumn: this.hasCompressedColumn,
|
|
4005
|
-
});
|
|
4006
|
-
}
|
|
4007
|
-
catch (error) {
|
|
4008
|
-
log$e.debug('Migration failed, disabling compression features', error);
|
|
4009
|
-
this.hasCompressedColumn = false;
|
|
4010
|
-
// Don't throw - allow the app to continue even if migration fails
|
|
4011
|
-
// The compressed feature will just be disabled
|
|
4012
|
-
}
|
|
4013
|
-
}
|
|
4014
|
-
async checkColumnExists(columnName) {
|
|
4015
|
-
try {
|
|
4016
|
-
const pragmaSQL = `PRAGMA table_info(${ReactNativeCacheAdapter.TABLE_NAME})`;
|
|
4017
|
-
const results = await this.executeSql(pragmaSQL);
|
|
4018
|
-
const columns = this.normalizeResults(results);
|
|
4019
|
-
log$e.debug('Table columns found', { columns: columns.map((c) => c.name) });
|
|
4020
|
-
return columns.some((column) => column.name === columnName);
|
|
4021
|
-
}
|
|
4022
|
-
catch (error) {
|
|
4023
|
-
log$e.debug('Error checking column existence', error);
|
|
4024
|
-
return false;
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
4027
|
-
async get(key) {
|
|
4028
|
-
await this.ensureInitialized();
|
|
4029
|
-
const sql = `SELECT * FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key = ?`;
|
|
4030
|
-
log$e.debug('Executing get query', { sql, key });
|
|
4031
|
-
const results = await this.executeSql(sql, [key]);
|
|
4032
|
-
log$e.debug('Get query results', { key, hasResults: !!results });
|
|
4033
|
-
// Normalize results from different SQLite implementations
|
|
4034
|
-
const rows = this.normalizeResults(results);
|
|
4035
|
-
if (!rows || rows.length === 0) {
|
|
4036
|
-
return null;
|
|
4037
|
-
}
|
|
4038
|
-
const row = rows[0];
|
|
4039
|
-
if (!row) {
|
|
4040
|
-
return null;
|
|
4041
|
-
}
|
|
4042
|
-
const isCompressed = this.hasCompressedColumn ? !!row.compressed : false;
|
|
4043
|
-
const rawData = isCompressed ? decompressData(row.data, true).data : row.data;
|
|
4044
|
-
return {
|
|
4045
|
-
data: JSON.parse(rawData),
|
|
4046
|
-
timestamp: row.timestamp,
|
|
4047
|
-
compressed: isCompressed,
|
|
4048
|
-
};
|
|
4049
|
-
}
|
|
4050
|
-
async set(key, data) {
|
|
4051
|
-
const item = {
|
|
4052
|
-
data,
|
|
4053
|
-
timestamp: Date.now(),
|
|
4054
|
-
};
|
|
4055
|
-
log$e.debug('Setting cache item', { key });
|
|
4056
|
-
return this.setItem(key, item);
|
|
4057
|
-
}
|
|
4058
|
-
async setItem(key, item) {
|
|
4059
|
-
await this.ensureInitialized();
|
|
4060
|
-
// Handle compression if enabled and compressed column is available
|
|
4061
|
-
const serializedData = JSON.stringify(item.data);
|
|
4062
|
-
let finalData = serializedData;
|
|
4063
|
-
let isCompressed = false;
|
|
4064
|
-
if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
|
|
4065
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4066
|
-
finalData = compressionResult.data;
|
|
4067
|
-
isCompressed = compressionResult.compressed;
|
|
4068
|
-
log$e.debug('Compression result', {
|
|
4069
|
-
key,
|
|
4070
|
-
originalSize: compressionResult.originalSize,
|
|
4071
|
-
compressedSize: compressionResult.compressedSize,
|
|
4072
|
-
compressed: isCompressed,
|
|
4073
|
-
savings: compressionResult.originalSize - compressionResult.compressedSize,
|
|
4074
|
-
});
|
|
4075
|
-
}
|
|
4076
|
-
log$e.debug('Setting item with metadata', {
|
|
4077
|
-
key,
|
|
4078
|
-
timestamp: item.timestamp,
|
|
4079
|
-
compressed: isCompressed,
|
|
4080
|
-
hasCompressedColumn: this.hasCompressedColumn,
|
|
4081
|
-
});
|
|
4082
|
-
// Build SQL and parameters based on available columns
|
|
4083
|
-
let sql;
|
|
4084
|
-
let params;
|
|
4085
|
-
if (this.hasCompressedColumn) {
|
|
4086
|
-
sql = `
|
|
4087
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4088
|
-
(cache_key, data, timestamp, compressed)
|
|
4089
|
-
VALUES (?, ?, ?, ?)
|
|
4090
|
-
`;
|
|
4091
|
-
params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
|
|
4092
|
-
}
|
|
4093
|
-
else {
|
|
4094
|
-
// Fallback for databases without compressed column
|
|
4095
|
-
sql = `
|
|
4096
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4097
|
-
(cache_key, data, timestamp)
|
|
4098
|
-
VALUES (?, ?, ?)
|
|
4099
|
-
`;
|
|
4100
|
-
params = [key, finalData, item.timestamp];
|
|
4101
|
-
}
|
|
4102
|
-
log$e.debug('Executing setItem SQL', { key, paramsCount: params.length });
|
|
4103
|
-
await this.executeSql(sql, params);
|
|
4104
|
-
}
|
|
4105
|
-
async setBatch(items) {
|
|
4106
|
-
if (items.length === 0)
|
|
4107
|
-
return;
|
|
4108
|
-
await this.ensureInitialized();
|
|
4109
|
-
log$e.debug('Batch setting items', { count: items.length });
|
|
4110
|
-
if (this.isExpo) {
|
|
4111
|
-
await this.db.withTransactionAsync(async () => {
|
|
4112
|
-
for (const [key, item] of items) {
|
|
4113
|
-
await this.setBatchItem(key, item);
|
|
4114
|
-
}
|
|
4115
|
-
});
|
|
4116
|
-
}
|
|
4117
|
-
else {
|
|
4118
|
-
return new Promise((resolve, reject) => {
|
|
4119
|
-
this.db.transaction((tx) => {
|
|
4120
|
-
const promises = items.map(([key, item]) => this.setBatchItemRN(tx, key, item));
|
|
4121
|
-
Promise.all(promises)
|
|
4122
|
-
.then(() => resolve())
|
|
4123
|
-
.catch(reject);
|
|
4124
|
-
}, reject, () => resolve());
|
|
4125
|
-
});
|
|
4126
|
-
}
|
|
4127
|
-
log$e.debug('Batch operation completed', { count: items.length });
|
|
4128
|
-
}
|
|
4129
|
-
async setBatchItem(key, item) {
|
|
4130
|
-
// Handle compression if enabled and compressed column is available
|
|
4131
|
-
const serializedData = JSON.stringify(item.data);
|
|
4132
|
-
let finalData = serializedData;
|
|
4133
|
-
let isCompressed = false;
|
|
4134
|
-
if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
|
|
4135
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4136
|
-
finalData = compressionResult.data;
|
|
4137
|
-
isCompressed = compressionResult.compressed;
|
|
4138
|
-
}
|
|
4139
|
-
// Build SQL and parameters based on available columns
|
|
4140
|
-
let sql;
|
|
4141
|
-
let params;
|
|
4142
|
-
if (this.hasCompressedColumn) {
|
|
4143
|
-
sql = `
|
|
4144
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4145
|
-
(cache_key, data, timestamp, compressed)
|
|
4146
|
-
VALUES (?, ?, ?, ?)
|
|
4147
|
-
`;
|
|
4148
|
-
params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
|
|
4149
|
-
}
|
|
4150
|
-
else {
|
|
4151
|
-
sql = `
|
|
4152
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4153
|
-
(cache_key, data, timestamp)
|
|
4154
|
-
VALUES (?, ?, ?)
|
|
4155
|
-
`;
|
|
4156
|
-
params = [key, finalData, item.timestamp];
|
|
4157
|
-
}
|
|
4158
|
-
await this.db.runAsync(sql, params);
|
|
4159
|
-
}
|
|
4160
|
-
async setBatchItemRN(tx, key, item) {
|
|
4161
|
-
// Handle compression if enabled and compressed column is available
|
|
4162
|
-
const serializedData = JSON.stringify(item.data);
|
|
4163
|
-
let finalData = serializedData;
|
|
4164
|
-
let isCompressed = false;
|
|
4165
|
-
if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
|
|
4166
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4167
|
-
finalData = compressionResult.data;
|
|
4168
|
-
isCompressed = compressionResult.compressed;
|
|
4169
|
-
}
|
|
4170
|
-
// Build SQL and parameters based on available columns
|
|
4171
|
-
let sql;
|
|
4172
|
-
let params;
|
|
4173
|
-
if (this.hasCompressedColumn) {
|
|
4174
|
-
sql = `
|
|
4175
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4176
|
-
(cache_key, data, timestamp, compressed)
|
|
4177
|
-
VALUES (?, ?, ?, ?)
|
|
4178
|
-
`;
|
|
4179
|
-
params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
|
|
4180
|
-
}
|
|
4181
|
-
else {
|
|
4182
|
-
sql = `
|
|
4183
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4184
|
-
(cache_key, data, timestamp)
|
|
4185
|
-
VALUES (?, ?, ?)
|
|
4186
|
-
`;
|
|
4187
|
-
params = [key, finalData, item.timestamp];
|
|
4188
|
-
}
|
|
4189
|
-
return new Promise((resolve, reject) => {
|
|
4190
|
-
tx.executeSql(sql, params, () => resolve(), (_, error) => {
|
|
4191
|
-
reject(error);
|
|
4192
|
-
return false;
|
|
4193
|
-
});
|
|
4194
|
-
});
|
|
4195
|
-
}
|
|
4196
|
-
async invalidate(pattern) {
|
|
4197
|
-
await this.ensureInitialized();
|
|
4198
|
-
const keys = await this.getKeys(pattern);
|
|
4199
|
-
if (keys.length === 0)
|
|
4200
|
-
return;
|
|
4201
|
-
const placeholders = keys.map(() => '?').join(',');
|
|
4202
|
-
const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key IN (${placeholders})`;
|
|
4203
|
-
await this.executeSql(sql, keys);
|
|
4204
|
-
}
|
|
4205
|
-
async clear() {
|
|
4206
|
-
await this.ensureInitialized();
|
|
4207
|
-
const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
|
|
4208
|
-
await this.executeSql(sql);
|
|
4209
|
-
}
|
|
4210
|
-
async getSize() {
|
|
4211
|
-
await this.ensureInitialized();
|
|
4212
|
-
const sql = `
|
|
4213
|
-
SELECT
|
|
4214
|
-
COUNT(*) as entries,
|
|
4215
|
-
SUM(LENGTH(data)) as bytes
|
|
4216
|
-
FROM ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4217
|
-
`;
|
|
4218
|
-
const results = await this.executeSql(sql);
|
|
4219
|
-
const rows = this.normalizeResults(results);
|
|
4220
|
-
const row = rows[0] || { entries: 0, bytes: 0 };
|
|
4221
|
-
return {
|
|
4222
|
-
entries: row.entries || 0,
|
|
4223
|
-
bytes: (row.bytes || 0) * 2,
|
|
4224
|
-
lastCleanup: Date.now(),
|
|
4225
|
-
};
|
|
4226
|
-
}
|
|
4227
|
-
async cleanup() {
|
|
4228
|
-
// No cleanup needed - cache never expires
|
|
4229
|
-
return 0;
|
|
4230
|
-
}
|
|
4231
|
-
async getKeys(pattern) {
|
|
4232
|
-
await this.ensureInitialized();
|
|
4233
|
-
let sql = `SELECT cache_key FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
|
|
4234
|
-
const params = [];
|
|
4235
|
-
if (pattern) {
|
|
4236
|
-
// Simple pattern matching with LIKE
|
|
4237
|
-
const likePattern = pattern.replace(/\*/g, '%').replace(/\?/g, '_');
|
|
4238
|
-
sql += ' WHERE cache_key LIKE ?';
|
|
4239
|
-
params.push(likePattern);
|
|
4240
|
-
}
|
|
4241
|
-
const results = await this.executeSql(sql, params);
|
|
4242
|
-
const keys = [];
|
|
4243
|
-
const rows = this.normalizeResults(results);
|
|
4244
|
-
for (const row of rows) {
|
|
4245
|
-
keys.push(row.cache_key);
|
|
4246
|
-
}
|
|
4247
|
-
return keys;
|
|
4248
|
-
}
|
|
4249
|
-
async executeSql(sql, params = []) {
|
|
4250
|
-
if (this.isExpo) {
|
|
4251
|
-
const expoDB = this.db;
|
|
4252
|
-
if (sql.toLowerCase().includes('select') || sql.toLowerCase().includes('pragma')) {
|
|
4253
|
-
const result = await expoDB.getAllAsync(sql, params);
|
|
4254
|
-
return Array.isArray(result) ? { results: result } : result;
|
|
4255
|
-
}
|
|
4256
|
-
else {
|
|
4257
|
-
return await expoDB.runAsync(sql, params);
|
|
4258
|
-
}
|
|
4259
|
-
}
|
|
4260
|
-
else {
|
|
4261
|
-
// react-native-sqlite-storage
|
|
4262
|
-
return new Promise((resolve, reject) => {
|
|
4263
|
-
this.db.transaction((tx) => {
|
|
4264
|
-
tx.executeSql(sql, params, (_, results) => resolve(results), (_, error) => {
|
|
4265
|
-
reject(error);
|
|
4266
|
-
return false;
|
|
4267
|
-
});
|
|
4268
|
-
});
|
|
4269
|
-
});
|
|
4270
|
-
}
|
|
4271
|
-
}
|
|
4272
|
-
async ensureInitialized() {
|
|
4273
|
-
if (!this.initPromise) {
|
|
4274
|
-
this.initPromise = this.initialize();
|
|
4275
|
-
}
|
|
4276
|
-
await this.initPromise;
|
|
4277
|
-
}
|
|
4278
|
-
}
|
|
4279
|
-
ReactNativeCacheAdapter.DB_NAME = 'acube_cache.db';
|
|
4280
|
-
ReactNativeCacheAdapter.TABLE_NAME = 'cache_entries';
|
|
4281
|
-
/**
|
|
4282
|
-
* Memory-based fallback cache adapter for environments without SQLite
|
|
4283
|
-
* Cache never expires - data persists until explicitly invalidated
|
|
4284
|
-
*/
|
|
4285
|
-
class MemoryCacheAdapter {
|
|
4286
|
-
constructor(options = {}) {
|
|
4287
|
-
this.cache = new Map();
|
|
4288
|
-
this.totalBytes = 0;
|
|
4289
|
-
this.options = {
|
|
4290
|
-
maxEntries: 1000,
|
|
4291
|
-
...options,
|
|
4292
|
-
};
|
|
4293
|
-
}
|
|
4294
|
-
calculateItemSize(key, item) {
|
|
4295
|
-
// Calculate rough size estimation for memory usage
|
|
4296
|
-
const keySize = key.length * 2; // UTF-16 estimation
|
|
4297
|
-
const itemSize = JSON.stringify(item).length * 2; // UTF-16 estimation
|
|
4298
|
-
return keySize + itemSize;
|
|
4299
|
-
}
|
|
4300
|
-
async get(key) {
|
|
4301
|
-
log$e.debug('Getting cache item', { key });
|
|
4302
|
-
const item = this.cache.get(key);
|
|
4303
|
-
if (!item) {
|
|
4304
|
-
log$e.debug('Cache miss', { key });
|
|
4305
|
-
return null;
|
|
4306
|
-
}
|
|
4307
|
-
// Handle decompression if needed
|
|
4308
|
-
const isCompressed = !!item.compressed;
|
|
4309
|
-
let finalData = item.data;
|
|
4310
|
-
if (isCompressed) {
|
|
4311
|
-
const decompressed = decompressData(item.data, true);
|
|
4312
|
-
finalData = JSON.parse(decompressed.data);
|
|
4313
|
-
}
|
|
4314
|
-
log$e.debug('Cache hit', { key, compressed: isCompressed });
|
|
4315
|
-
return {
|
|
4316
|
-
...item,
|
|
4317
|
-
data: finalData,
|
|
4318
|
-
compressed: isCompressed,
|
|
4319
|
-
};
|
|
4320
|
-
}
|
|
4321
|
-
async set(key, data) {
|
|
4322
|
-
log$e.debug('Setting cache item', { key });
|
|
4323
|
-
// Handle compression if enabled
|
|
4324
|
-
let finalData = data;
|
|
4325
|
-
let isCompressed = false;
|
|
4326
|
-
if (this.options.compression && this.options.compressionThreshold) {
|
|
4327
|
-
const serializedData = JSON.stringify(data);
|
|
4328
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4329
|
-
if (compressionResult.compressed) {
|
|
4330
|
-
finalData = compressionResult.data;
|
|
4331
|
-
isCompressed = true;
|
|
4332
|
-
log$e.debug('Compression result', {
|
|
4333
|
-
key,
|
|
4334
|
-
originalSize: compressionResult.originalSize,
|
|
4335
|
-
compressedSize: compressionResult.compressedSize,
|
|
4336
|
-
savings: compressionResult.originalSize - compressionResult.compressedSize,
|
|
4337
|
-
});
|
|
4338
|
-
}
|
|
4339
|
-
}
|
|
4340
|
-
const item = {
|
|
4341
|
-
data: finalData,
|
|
4342
|
-
timestamp: Date.now(),
|
|
4343
|
-
compressed: isCompressed,
|
|
4344
|
-
};
|
|
4345
|
-
return this.setItem(key, item);
|
|
4346
|
-
}
|
|
4347
|
-
async setItem(key, item) {
|
|
4348
|
-
// Calculate size of new item
|
|
4349
|
-
const newItemSize = this.calculateItemSize(key, item);
|
|
4350
|
-
// If item already exists, subtract old size
|
|
4351
|
-
if (this.cache.has(key)) {
|
|
4352
|
-
const oldItem = this.cache.get(key);
|
|
4353
|
-
const oldItemSize = this.calculateItemSize(key, oldItem);
|
|
4354
|
-
this.totalBytes -= oldItemSize;
|
|
4355
|
-
}
|
|
4356
|
-
// Enforce max entries limit
|
|
4357
|
-
if (this.cache.size >= (this.options.maxEntries || 1000) && !this.cache.has(key)) {
|
|
4358
|
-
const oldestKey = this.cache.keys().next().value;
|
|
4359
|
-
if (oldestKey) {
|
|
4360
|
-
const oldestItem = this.cache.get(oldestKey);
|
|
4361
|
-
const oldestItemSize = this.calculateItemSize(oldestKey, oldestItem);
|
|
4362
|
-
this.totalBytes -= oldestItemSize;
|
|
4363
|
-
this.cache.delete(oldestKey);
|
|
4364
|
-
log$e.debug('Removed oldest item for capacity', { oldestKey, freedBytes: oldestItemSize });
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4367
|
-
// Set new item and update total size
|
|
4368
|
-
this.cache.set(key, item);
|
|
4369
|
-
this.totalBytes += newItemSize;
|
|
4370
|
-
log$e.debug('Updated cache size', {
|
|
4371
|
-
entries: this.cache.size,
|
|
4372
|
-
totalBytes: this.totalBytes,
|
|
4373
|
-
newItemSize,
|
|
4374
|
-
});
|
|
4375
|
-
}
|
|
4376
|
-
async setBatch(items) {
|
|
4377
|
-
if (items.length === 0)
|
|
4378
|
-
return;
|
|
4379
|
-
log$e.debug('Batch setting items', { count: items.length });
|
|
4380
|
-
let totalNewBytes = 0;
|
|
4381
|
-
let totalOldBytes = 0;
|
|
4382
|
-
const itemsToRemove = [];
|
|
4383
|
-
// First pass: calculate size changes and identify capacity issues
|
|
4384
|
-
for (const [key, item] of items) {
|
|
4385
|
-
const newItemSize = this.calculateItemSize(key, item);
|
|
4386
|
-
totalNewBytes += newItemSize;
|
|
4387
|
-
// If item already exists, track old size for removal
|
|
4388
|
-
if (this.cache.has(key)) {
|
|
4389
|
-
const oldItem = this.cache.get(key);
|
|
4390
|
-
const oldItemSize = this.calculateItemSize(key, oldItem);
|
|
4391
|
-
totalOldBytes += oldItemSize;
|
|
4392
|
-
}
|
|
4393
|
-
}
|
|
4394
|
-
// Handle capacity limits - remove oldest items if needed
|
|
4395
|
-
const projectedEntries = this.cache.size + items.filter(([key]) => !this.cache.has(key)).length;
|
|
4396
|
-
const maxEntries = this.options.maxEntries || 1000;
|
|
4397
|
-
if (projectedEntries > maxEntries) {
|
|
4398
|
-
const entriesToRemove = projectedEntries - maxEntries;
|
|
4399
|
-
const oldestKeys = Array.from(this.cache.keys()).slice(0, entriesToRemove);
|
|
4400
|
-
for (const oldKey of oldestKeys) {
|
|
4401
|
-
const oldItem = this.cache.get(oldKey);
|
|
4402
|
-
const oldItemSize = this.calculateItemSize(oldKey, oldItem);
|
|
4403
|
-
this.totalBytes -= oldItemSize;
|
|
4404
|
-
this.cache.delete(oldKey);
|
|
4405
|
-
itemsToRemove.push(oldKey);
|
|
4406
|
-
}
|
|
4407
|
-
if (itemsToRemove.length > 0) {
|
|
4408
|
-
log$e.debug('Removed items for batch capacity', {
|
|
4409
|
-
removedCount: itemsToRemove.length,
|
|
4410
|
-
removedKeys: itemsToRemove,
|
|
4411
|
-
});
|
|
4412
|
-
}
|
|
4413
|
-
}
|
|
4414
|
-
// Update total bytes accounting
|
|
4415
|
-
this.totalBytes = this.totalBytes - totalOldBytes + totalNewBytes;
|
|
4416
|
-
// Second pass: set all items
|
|
4417
|
-
for (const [key, item] of items) {
|
|
4418
|
-
this.cache.set(key, item);
|
|
4419
|
-
}
|
|
4420
|
-
log$e.debug('Batch operation completed', {
|
|
4421
|
-
count: items.length,
|
|
4422
|
-
totalBytes: this.totalBytes,
|
|
4423
|
-
entries: this.cache.size,
|
|
4424
|
-
bytesAdded: totalNewBytes - totalOldBytes,
|
|
4425
|
-
});
|
|
4426
|
-
}
|
|
4427
|
-
async invalidate(pattern) {
|
|
4428
|
-
const regex = this.patternToRegex(pattern);
|
|
4429
|
-
let removed = 0;
|
|
4430
|
-
let bytesFreed = 0;
|
|
4431
|
-
for (const key of this.cache.keys()) {
|
|
4432
|
-
if (regex.test(key)) {
|
|
4433
|
-
const item = this.cache.get(key);
|
|
4434
|
-
const itemSize = this.calculateItemSize(key, item);
|
|
4435
|
-
this.cache.delete(key);
|
|
4436
|
-
this.totalBytes -= itemSize;
|
|
4437
|
-
bytesFreed += itemSize;
|
|
4438
|
-
removed++;
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
if (removed > 0) {
|
|
4442
|
-
log$e.debug('Invalidation completed', {
|
|
4443
|
-
pattern,
|
|
4444
|
-
entriesRemoved: removed,
|
|
4445
|
-
bytesFreed,
|
|
4446
|
-
remainingEntries: this.cache.size,
|
|
4447
|
-
remainingBytes: this.totalBytes,
|
|
4448
|
-
});
|
|
4449
|
-
}
|
|
4450
|
-
}
|
|
4451
|
-
async clear() {
|
|
4452
|
-
this.cache.clear();
|
|
4453
|
-
this.totalBytes = 0;
|
|
4454
|
-
log$e.debug('Cache cleared', { entries: 0, bytes: 0 });
|
|
4455
|
-
}
|
|
4456
|
-
async getSize() {
|
|
4457
|
-
return {
|
|
4458
|
-
entries: this.cache.size,
|
|
4459
|
-
bytes: this.totalBytes,
|
|
4460
|
-
lastCleanup: Date.now(),
|
|
4461
|
-
};
|
|
4462
|
-
}
|
|
4463
|
-
async cleanup() {
|
|
4464
|
-
// No cleanup needed - cache never expires
|
|
4465
|
-
return 0;
|
|
4466
|
-
}
|
|
4467
|
-
async getKeys(pattern) {
|
|
4468
|
-
const keys = Array.from(this.cache.keys());
|
|
4469
|
-
if (!pattern)
|
|
4470
|
-
return keys;
|
|
4471
|
-
const regex = this.patternToRegex(pattern);
|
|
4472
|
-
return keys.filter((key) => regex.test(key));
|
|
4473
|
-
}
|
|
4474
|
-
patternToRegex(pattern) {
|
|
4475
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
4476
|
-
const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
4477
|
-
return new RegExp(`^${regexPattern}$`);
|
|
4478
|
-
}
|
|
4479
|
-
}
|
|
4480
|
-
|
|
4481
|
-
const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
|
|
4482
|
-
|
|
4483
|
-
let idbProxyableTypes;
|
|
4484
|
-
let cursorAdvanceMethods;
|
|
4485
|
-
// This is a function to prevent it throwing up in node environments.
|
|
4486
|
-
function getIdbProxyableTypes() {
|
|
4487
|
-
return (idbProxyableTypes ||
|
|
4488
|
-
(idbProxyableTypes = [
|
|
4489
|
-
IDBDatabase,
|
|
4490
|
-
IDBObjectStore,
|
|
4491
|
-
IDBIndex,
|
|
4492
|
-
IDBCursor,
|
|
4493
|
-
IDBTransaction,
|
|
4494
|
-
]));
|
|
4495
|
-
}
|
|
4496
|
-
// This is a function to prevent it throwing up in node environments.
|
|
4497
|
-
function getCursorAdvanceMethods() {
|
|
4498
|
-
return (cursorAdvanceMethods ||
|
|
4499
|
-
(cursorAdvanceMethods = [
|
|
4500
|
-
IDBCursor.prototype.advance,
|
|
4501
|
-
IDBCursor.prototype.continue,
|
|
4502
|
-
IDBCursor.prototype.continuePrimaryKey,
|
|
4503
|
-
]));
|
|
4504
|
-
}
|
|
4505
|
-
const transactionDoneMap = new WeakMap();
|
|
4506
|
-
const transformCache = new WeakMap();
|
|
4507
|
-
const reverseTransformCache = new WeakMap();
|
|
4508
|
-
function promisifyRequest(request) {
|
|
4509
|
-
const promise = new Promise((resolve, reject) => {
|
|
4510
|
-
const unlisten = () => {
|
|
4511
|
-
request.removeEventListener('success', success);
|
|
4512
|
-
request.removeEventListener('error', error);
|
|
4513
|
-
};
|
|
4514
|
-
const success = () => {
|
|
4515
|
-
resolve(wrap(request.result));
|
|
4516
|
-
unlisten();
|
|
4517
|
-
};
|
|
4518
|
-
const error = () => {
|
|
4519
|
-
reject(request.error);
|
|
4520
|
-
unlisten();
|
|
4521
|
-
};
|
|
4522
|
-
request.addEventListener('success', success);
|
|
4523
|
-
request.addEventListener('error', error);
|
|
4524
|
-
});
|
|
4525
|
-
// This mapping exists in reverseTransformCache but doesn't exist in transformCache. This
|
|
4526
|
-
// is because we create many promises from a single IDBRequest.
|
|
4527
|
-
reverseTransformCache.set(promise, request);
|
|
4528
|
-
return promise;
|
|
4529
|
-
}
|
|
4530
|
-
function cacheDonePromiseForTransaction(tx) {
|
|
4531
|
-
// Early bail if we've already created a done promise for this transaction.
|
|
4532
|
-
if (transactionDoneMap.has(tx))
|
|
4533
|
-
return;
|
|
4534
|
-
const done = new Promise((resolve, reject) => {
|
|
4535
|
-
const unlisten = () => {
|
|
4536
|
-
tx.removeEventListener('complete', complete);
|
|
4537
|
-
tx.removeEventListener('error', error);
|
|
4538
|
-
tx.removeEventListener('abort', error);
|
|
4539
|
-
};
|
|
4540
|
-
const complete = () => {
|
|
4541
|
-
resolve();
|
|
4542
|
-
unlisten();
|
|
4543
|
-
};
|
|
4544
|
-
const error = () => {
|
|
4545
|
-
reject(tx.error || new DOMException('AbortError', 'AbortError'));
|
|
4546
|
-
unlisten();
|
|
4547
|
-
};
|
|
4548
|
-
tx.addEventListener('complete', complete);
|
|
4549
|
-
tx.addEventListener('error', error);
|
|
4550
|
-
tx.addEventListener('abort', error);
|
|
4551
|
-
});
|
|
4552
|
-
// Cache it for later retrieval.
|
|
4553
|
-
transactionDoneMap.set(tx, done);
|
|
4554
|
-
}
|
|
4555
|
-
let idbProxyTraps = {
|
|
4556
|
-
get(target, prop, receiver) {
|
|
4557
|
-
if (target instanceof IDBTransaction) {
|
|
4558
|
-
// Special handling for transaction.done.
|
|
4559
|
-
if (prop === 'done')
|
|
4560
|
-
return transactionDoneMap.get(target);
|
|
4561
|
-
// Make tx.store return the only store in the transaction, or undefined if there are many.
|
|
4562
|
-
if (prop === 'store') {
|
|
4563
|
-
return receiver.objectStoreNames[1]
|
|
4564
|
-
? undefined
|
|
4565
|
-
: receiver.objectStore(receiver.objectStoreNames[0]);
|
|
4566
|
-
}
|
|
4567
|
-
}
|
|
4568
|
-
// Else transform whatever we get back.
|
|
4569
|
-
return wrap(target[prop]);
|
|
4570
|
-
},
|
|
4571
|
-
set(target, prop, value) {
|
|
4572
|
-
target[prop] = value;
|
|
4573
|
-
return true;
|
|
4574
|
-
},
|
|
4575
|
-
has(target, prop) {
|
|
4576
|
-
if (target instanceof IDBTransaction &&
|
|
4577
|
-
(prop === 'done' || prop === 'store')) {
|
|
4578
|
-
return true;
|
|
4579
|
-
}
|
|
4580
|
-
return prop in target;
|
|
4581
|
-
},
|
|
4582
|
-
};
|
|
4583
|
-
function replaceTraps(callback) {
|
|
4584
|
-
idbProxyTraps = callback(idbProxyTraps);
|
|
4585
|
-
}
|
|
4586
|
-
function wrapFunction(func) {
|
|
4587
|
-
// Due to expected object equality (which is enforced by the caching in `wrap`), we
|
|
4588
|
-
// only create one new func per func.
|
|
4589
|
-
// Cursor methods are special, as the behaviour is a little more different to standard IDB. In
|
|
4590
|
-
// IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
|
|
4591
|
-
// cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
|
|
4592
|
-
// with real promises, so each advance methods returns a new promise for the cursor object, or
|
|
4593
|
-
// undefined if the end of the cursor has been reached.
|
|
4594
|
-
if (getCursorAdvanceMethods().includes(func)) {
|
|
4595
|
-
return function (...args) {
|
|
4596
|
-
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
|
|
4597
|
-
// the original object.
|
|
4598
|
-
func.apply(unwrap(this), args);
|
|
4599
|
-
return wrap(this.request);
|
|
4600
|
-
};
|
|
4601
|
-
}
|
|
4602
|
-
return function (...args) {
|
|
4603
|
-
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
|
|
4604
|
-
// the original object.
|
|
4605
|
-
return wrap(func.apply(unwrap(this), args));
|
|
4606
|
-
};
|
|
4607
|
-
}
|
|
4608
|
-
function transformCachableValue(value) {
|
|
4609
|
-
if (typeof value === 'function')
|
|
4610
|
-
return wrapFunction(value);
|
|
4611
|
-
// This doesn't return, it just creates a 'done' promise for the transaction,
|
|
4612
|
-
// which is later returned for transaction.done (see idbObjectHandler).
|
|
4613
|
-
if (value instanceof IDBTransaction)
|
|
4614
|
-
cacheDonePromiseForTransaction(value);
|
|
4615
|
-
if (instanceOfAny(value, getIdbProxyableTypes()))
|
|
4616
|
-
return new Proxy(value, idbProxyTraps);
|
|
4617
|
-
// Return the same value back if we're not going to transform it.
|
|
4618
|
-
return value;
|
|
4619
|
-
}
|
|
4620
|
-
function wrap(value) {
|
|
4621
|
-
// We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
|
|
4622
|
-
// IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
|
|
4623
|
-
if (value instanceof IDBRequest)
|
|
4624
|
-
return promisifyRequest(value);
|
|
4625
|
-
// If we've already transformed this value before, reuse the transformed value.
|
|
4626
|
-
// This is faster, but it also provides object equality.
|
|
4627
|
-
if (transformCache.has(value))
|
|
4628
|
-
return transformCache.get(value);
|
|
4629
|
-
const newValue = transformCachableValue(value);
|
|
4630
|
-
// Not all types are transformed.
|
|
4631
|
-
// These may be primitive types, so they can't be WeakMap keys.
|
|
4632
|
-
if (newValue !== value) {
|
|
4633
|
-
transformCache.set(value, newValue);
|
|
4634
|
-
reverseTransformCache.set(newValue, value);
|
|
4635
|
-
}
|
|
4636
|
-
return newValue;
|
|
4637
|
-
}
|
|
4638
|
-
const unwrap = (value) => reverseTransformCache.get(value);
|
|
4639
|
-
|
|
4640
|
-
/**
|
|
4641
|
-
* Open a database.
|
|
4642
|
-
*
|
|
4643
|
-
* @param name Name of the database.
|
|
4644
|
-
* @param version Schema version.
|
|
4645
|
-
* @param callbacks Additional callbacks.
|
|
4646
|
-
*/
|
|
4647
|
-
function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
|
|
4648
|
-
const request = indexedDB.open(name, version);
|
|
4649
|
-
const openPromise = wrap(request);
|
|
4650
|
-
if (upgrade) {
|
|
4651
|
-
request.addEventListener('upgradeneeded', (event) => {
|
|
4652
|
-
upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
|
|
4653
|
-
});
|
|
4654
|
-
}
|
|
4655
|
-
if (blocked) {
|
|
4656
|
-
request.addEventListener('blocked', (event) => blocked(
|
|
4657
|
-
// Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
|
|
4658
|
-
event.oldVersion, event.newVersion, event));
|
|
4659
|
-
}
|
|
4660
|
-
openPromise
|
|
4661
|
-
.then((db) => {
|
|
4662
|
-
if (terminated)
|
|
4663
|
-
db.addEventListener('close', () => terminated());
|
|
4664
|
-
if (blocking) {
|
|
4665
|
-
db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));
|
|
4666
|
-
}
|
|
4667
|
-
})
|
|
4668
|
-
.catch(() => { });
|
|
4669
|
-
return openPromise;
|
|
4670
|
-
}
|
|
4671
|
-
/**
|
|
4672
|
-
* Delete a database.
|
|
4673
|
-
*
|
|
4674
|
-
* @param name Name of the database.
|
|
4675
|
-
*/
|
|
4676
|
-
function deleteDB(name, { blocked } = {}) {
|
|
4677
|
-
const request = indexedDB.deleteDatabase(name);
|
|
4678
|
-
if (blocked) {
|
|
4679
|
-
request.addEventListener('blocked', (event) => blocked(
|
|
4680
|
-
// Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
|
|
4681
|
-
event.oldVersion, event));
|
|
4682
|
-
}
|
|
4683
|
-
return wrap(request).then(() => undefined);
|
|
4684
|
-
}
|
|
4685
|
-
|
|
4686
|
-
const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
|
|
4687
|
-
const writeMethods = ['put', 'add', 'delete', 'clear'];
|
|
4688
|
-
const cachedMethods = new Map();
|
|
4689
|
-
function getMethod(target, prop) {
|
|
4690
|
-
if (!(target instanceof IDBDatabase &&
|
|
4691
|
-
!(prop in target) &&
|
|
4692
|
-
typeof prop === 'string')) {
|
|
4693
|
-
return;
|
|
4694
|
-
}
|
|
4695
|
-
if (cachedMethods.get(prop))
|
|
4696
|
-
return cachedMethods.get(prop);
|
|
4697
|
-
const targetFuncName = prop.replace(/FromIndex$/, '');
|
|
4698
|
-
const useIndex = prop !== targetFuncName;
|
|
4699
|
-
const isWrite = writeMethods.includes(targetFuncName);
|
|
4700
|
-
if (
|
|
4701
|
-
// Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
|
|
4702
|
-
!(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
|
|
4703
|
-
!(isWrite || readMethods.includes(targetFuncName))) {
|
|
4704
|
-
return;
|
|
4705
|
-
}
|
|
4706
|
-
const method = async function (storeName, ...args) {
|
|
4707
|
-
// isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
|
|
4708
|
-
const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
|
|
4709
|
-
let target = tx.store;
|
|
4710
|
-
if (useIndex)
|
|
4711
|
-
target = target.index(args.shift());
|
|
4712
|
-
// Must reject if op rejects.
|
|
4713
|
-
// If it's a write operation, must reject if tx.done rejects.
|
|
4714
|
-
// Must reject with op rejection first.
|
|
4715
|
-
// Must resolve with op value.
|
|
4716
|
-
// Must handle both promises (no unhandled rejections)
|
|
4717
|
-
return (await Promise.all([
|
|
4718
|
-
target[targetFuncName](...args),
|
|
4719
|
-
isWrite && tx.done,
|
|
4720
|
-
]))[0];
|
|
4721
|
-
};
|
|
4722
|
-
cachedMethods.set(prop, method);
|
|
4723
|
-
return method;
|
|
4724
|
-
}
|
|
4725
|
-
replaceTraps((oldTraps) => ({
|
|
4726
|
-
...oldTraps,
|
|
4727
|
-
get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
|
|
4728
|
-
has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
|
|
4729
|
-
}));
|
|
4730
|
-
|
|
4731
|
-
const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
|
|
4732
|
-
const methodMap = {};
|
|
4733
|
-
const advanceResults = new WeakMap();
|
|
4734
|
-
const ittrProxiedCursorToOriginalProxy = new WeakMap();
|
|
4735
|
-
const cursorIteratorTraps = {
|
|
4736
|
-
get(target, prop) {
|
|
4737
|
-
if (!advanceMethodProps.includes(prop))
|
|
4738
|
-
return target[prop];
|
|
4739
|
-
let cachedFunc = methodMap[prop];
|
|
4740
|
-
if (!cachedFunc) {
|
|
4741
|
-
cachedFunc = methodMap[prop] = function (...args) {
|
|
4742
|
-
advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
|
|
4743
|
-
};
|
|
4744
|
-
}
|
|
4745
|
-
return cachedFunc;
|
|
4746
|
-
},
|
|
4747
|
-
};
|
|
4748
|
-
async function* iterate(...args) {
|
|
4749
|
-
// tslint:disable-next-line:no-this-assignment
|
|
4750
|
-
let cursor = this;
|
|
4751
|
-
if (!(cursor instanceof IDBCursor)) {
|
|
4752
|
-
cursor = await cursor.openCursor(...args);
|
|
4753
|
-
}
|
|
4754
|
-
if (!cursor)
|
|
4755
|
-
return;
|
|
4756
|
-
cursor = cursor;
|
|
4757
|
-
const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
|
|
4758
|
-
ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
|
|
4759
|
-
// Map this double-proxy back to the original, so other cursor methods work.
|
|
4760
|
-
reverseTransformCache.set(proxiedCursor, unwrap(cursor));
|
|
4761
|
-
while (cursor) {
|
|
4762
|
-
yield proxiedCursor;
|
|
4763
|
-
// If one of the advancing methods was not called, call continue().
|
|
4764
|
-
cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
|
|
4765
|
-
advanceResults.delete(proxiedCursor);
|
|
4766
|
-
}
|
|
4767
|
-
}
|
|
4768
|
-
function isIteratorProp(target, prop) {
|
|
4769
|
-
return ((prop === Symbol.asyncIterator &&
|
|
4770
|
-
instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||
|
|
4771
|
-
(prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));
|
|
4772
|
-
}
|
|
4773
|
-
replaceTraps((oldTraps) => ({
|
|
4774
|
-
...oldTraps,
|
|
4775
|
-
get(target, prop, receiver) {
|
|
4776
|
-
if (isIteratorProp(target, prop))
|
|
4777
|
-
return iterate;
|
|
4778
|
-
return oldTraps.get(target, prop, receiver);
|
|
4779
|
-
},
|
|
4780
|
-
has(target, prop) {
|
|
4781
|
-
return isIteratorProp(target, prop) || oldTraps.has(target, prop);
|
|
4782
|
-
},
|
|
4783
|
-
}));
|
|
4784
|
-
|
|
4785
|
-
const log$d = createPrefixedLogger('CACHE-WEB');
|
|
4786
|
-
/**
|
|
4787
|
-
* Web cache adapter using IndexedDB with automatic error recovery
|
|
4788
|
-
* Cache never expires - data persists until explicitly invalidated
|
|
4789
|
-
*/
|
|
4790
|
-
class WebCacheAdapter {
|
|
4791
|
-
constructor(options = {}) {
|
|
4792
|
-
this.db = null;
|
|
4793
|
-
this.initPromise = null;
|
|
4794
|
-
this.retryCount = 0;
|
|
4795
|
-
this.maxRetries = 3;
|
|
4796
|
-
this.options = {
|
|
4797
|
-
maxSize: 50 * 1024 * 1024, // 50MB
|
|
4798
|
-
maxEntries: 10000,
|
|
4799
|
-
compression: false,
|
|
4800
|
-
compressionThreshold: 1024,
|
|
4801
|
-
...options,
|
|
4802
|
-
};
|
|
4803
|
-
this.initPromise = this.initialize();
|
|
4804
|
-
}
|
|
4805
|
-
async initialize() {
|
|
4806
|
-
if (this.db)
|
|
4807
|
-
return;
|
|
4808
|
-
log$d.debug('Initializing IndexedDB cache', {
|
|
4809
|
-
dbName: WebCacheAdapter.DB_NAME,
|
|
4810
|
-
version: WebCacheAdapter.DB_VERSION,
|
|
4811
|
-
});
|
|
4812
|
-
try {
|
|
4813
|
-
this.db = await this.openDatabase();
|
|
4814
|
-
log$d.debug('IndexedDB cache initialized successfully');
|
|
4815
|
-
this.retryCount = 0; // Reset retry count on success
|
|
4816
|
-
}
|
|
4817
|
-
catch (error) {
|
|
4818
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
4819
|
-
log$d.debug('Failed to initialize IndexedDB', { error: errorMessage });
|
|
4820
|
-
// Check if this is a version conflict error
|
|
4821
|
-
if (this.isVersionConflictError(errorMessage)) {
|
|
4822
|
-
await this.handleVersionConflict();
|
|
4823
|
-
}
|
|
4824
|
-
else {
|
|
4825
|
-
throw new Error(`Failed to initialize IndexedDB: ${errorMessage}`);
|
|
4826
|
-
}
|
|
4827
|
-
}
|
|
4828
|
-
}
|
|
4829
|
-
async openDatabase() {
|
|
4830
|
-
return await openDB(WebCacheAdapter.DB_NAME, WebCacheAdapter.DB_VERSION, {
|
|
4831
|
-
upgrade: (db, oldVersion, newVersion, transaction) => {
|
|
4832
|
-
log$d.debug('Database upgrade needed', { oldVersion, newVersion });
|
|
4833
|
-
this.handleUpgrade(db, oldVersion, newVersion, transaction);
|
|
4834
|
-
},
|
|
4835
|
-
blocked: () => {
|
|
4836
|
-
log$d.debug('Database blocked - another tab may be open');
|
|
4837
|
-
},
|
|
4838
|
-
blocking: () => {
|
|
4839
|
-
log$d.debug('Database blocking - will close connection');
|
|
4840
|
-
if (this.db) {
|
|
4841
|
-
this.db.close();
|
|
4842
|
-
this.db = null;
|
|
4843
|
-
}
|
|
4844
|
-
},
|
|
4845
|
-
terminated: () => {
|
|
4846
|
-
log$d.debug('Database connection terminated unexpectedly');
|
|
4847
|
-
this.db = null;
|
|
4848
|
-
},
|
|
4849
|
-
});
|
|
4850
|
-
}
|
|
4851
|
-
handleUpgrade(db, oldVersion, newVersion, transaction) {
|
|
4852
|
-
log$d.debug('Handling database upgrade', { oldVersion, newVersion });
|
|
4853
|
-
// Create cache store if it doesn't exist (initial setup)
|
|
4854
|
-
if (!db.objectStoreNames.contains(WebCacheAdapter.STORE_NAME)) {
|
|
4855
|
-
const store = db.createObjectStore(WebCacheAdapter.STORE_NAME, { keyPath: 'key' });
|
|
4856
|
-
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
4857
|
-
log$d.debug('Created cache store and timestamp index');
|
|
4858
|
-
}
|
|
4859
|
-
// Handle migration from version 1 to 2
|
|
4860
|
-
if (oldVersion < 2) {
|
|
4861
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
4862
|
-
// Remove unused indexes from simplified cache structure
|
|
4863
|
-
const indexesToRemove = ['tags', 'source', 'syncStatus'];
|
|
4864
|
-
indexesToRemove.forEach((indexName) => {
|
|
4865
|
-
try {
|
|
4866
|
-
if (store.indexNames.contains(indexName)) {
|
|
4867
|
-
store.deleteIndex(indexName);
|
|
4868
|
-
log$d.debug(`Removed unused index: ${indexName}`);
|
|
4869
|
-
}
|
|
4870
|
-
}
|
|
4871
|
-
catch (error) {
|
|
4872
|
-
// Ignore errors if indexes don't exist
|
|
4873
|
-
log$d.debug(`Warning: Could not remove index ${indexName}`, error);
|
|
4874
|
-
}
|
|
4875
|
-
});
|
|
4876
|
-
}
|
|
4877
|
-
log$d.debug('Database upgrade completed');
|
|
4878
|
-
}
|
|
4879
|
-
isVersionConflictError(errorMessage) {
|
|
4880
|
-
return (errorMessage.includes('less than the existing version') ||
|
|
4881
|
-
errorMessage.includes('version conflict') ||
|
|
4882
|
-
errorMessage.includes('VersionError'));
|
|
4883
|
-
}
|
|
4884
|
-
async handleVersionConflict() {
|
|
4885
|
-
log$d.debug('Handling version conflict, attempting recovery...');
|
|
4886
|
-
if (this.retryCount >= this.maxRetries) {
|
|
4887
|
-
throw new Error('Failed to resolve IndexedDB version conflict after multiple attempts');
|
|
4888
|
-
}
|
|
4889
|
-
this.retryCount++;
|
|
4890
|
-
try {
|
|
4891
|
-
// Close any existing connection
|
|
4892
|
-
if (this.db) {
|
|
4893
|
-
this.db.close();
|
|
4894
|
-
this.db = null;
|
|
4895
|
-
}
|
|
4896
|
-
// Delete the problematic database
|
|
4897
|
-
log$d.debug('Deleting problematic database to resolve version conflict');
|
|
4898
|
-
await deleteDB(WebCacheAdapter.DB_NAME);
|
|
4899
|
-
// Wait a bit for the deletion to complete
|
|
4900
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
4901
|
-
// Try to open the database again
|
|
4902
|
-
log$d.debug(`Retrying database initialization (attempt ${this.retryCount}/${this.maxRetries})`);
|
|
4903
|
-
this.db = await this.openDatabase();
|
|
4904
|
-
log$d.debug('Successfully recovered from version conflict');
|
|
4905
|
-
this.retryCount = 0; // Reset retry count on success
|
|
4906
|
-
}
|
|
4907
|
-
catch (retryError) {
|
|
4908
|
-
const retryErrorMessage = retryError instanceof Error ? retryError.message : 'Unknown error';
|
|
4909
|
-
log$d.debug('Recovery attempt failed', { attempt: this.retryCount, error: retryErrorMessage });
|
|
4910
|
-
if (this.retryCount < this.maxRetries) {
|
|
4911
|
-
// Try again
|
|
4912
|
-
await this.handleVersionConflict();
|
|
4913
|
-
}
|
|
4914
|
-
else {
|
|
4915
|
-
throw new Error(`Failed to recover from IndexedDB version conflict: ${retryErrorMessage}`);
|
|
4916
|
-
}
|
|
4917
|
-
}
|
|
4918
|
-
}
|
|
4919
|
-
async get(key) {
|
|
4920
|
-
await this.ensureInitialized();
|
|
4921
|
-
log$d.debug('Getting cache item', { key });
|
|
4922
|
-
try {
|
|
4923
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
|
|
4924
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
4925
|
-
const result = await store.get(key);
|
|
4926
|
-
if (!result) {
|
|
4927
|
-
return null;
|
|
4928
|
-
}
|
|
4929
|
-
const item = result;
|
|
4930
|
-
// Handle decompression if needed
|
|
4931
|
-
const isCompressed = !!item.compressed;
|
|
4932
|
-
let finalData;
|
|
4933
|
-
if (isCompressed) {
|
|
4934
|
-
const decompressed = decompressData(item.data, true);
|
|
4935
|
-
finalData = JSON.parse(decompressed.data);
|
|
4936
|
-
}
|
|
4937
|
-
else {
|
|
4938
|
-
finalData = item.data;
|
|
4939
|
-
}
|
|
4940
|
-
return {
|
|
4941
|
-
data: finalData,
|
|
4942
|
-
timestamp: item.timestamp,
|
|
4943
|
-
compressed: isCompressed,
|
|
4944
|
-
};
|
|
4945
|
-
}
|
|
4946
|
-
catch (error) {
|
|
4947
|
-
log$d.debug('Error getting cache item', { key, error });
|
|
4948
|
-
return null; // Return null on error instead of throwing
|
|
4949
|
-
}
|
|
4950
|
-
}
|
|
4951
|
-
async set(key, data) {
|
|
4952
|
-
const item = {
|
|
4953
|
-
data,
|
|
4954
|
-
timestamp: Date.now(),
|
|
4955
|
-
};
|
|
4956
|
-
return this.setItem(key, item);
|
|
4957
|
-
}
|
|
4958
|
-
async setItem(key, item) {
|
|
4959
|
-
await this.ensureInitialized();
|
|
4960
|
-
// Handle compression if enabled
|
|
4961
|
-
let finalData = item.data;
|
|
4962
|
-
let isCompressed = false;
|
|
4963
|
-
if (this.options.compression && this.options.compressionThreshold) {
|
|
4964
|
-
const serializedData = JSON.stringify(item.data);
|
|
4965
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4966
|
-
if (compressionResult.compressed) {
|
|
4967
|
-
finalData = compressionResult.data;
|
|
4968
|
-
isCompressed = true;
|
|
4969
|
-
log$d.debug('Compression result', {
|
|
4970
|
-
key,
|
|
4971
|
-
originalSize: compressionResult.originalSize,
|
|
4972
|
-
compressedSize: compressionResult.compressedSize,
|
|
4973
|
-
compressed: isCompressed,
|
|
4974
|
-
savings: compressionResult.originalSize - compressionResult.compressedSize,
|
|
4975
|
-
});
|
|
4976
|
-
}
|
|
4977
|
-
}
|
|
4978
|
-
log$d.debug('Setting cache item', { key, timestamp: item.timestamp, compressed: isCompressed });
|
|
4979
|
-
const storedItem = {
|
|
4980
|
-
key,
|
|
4981
|
-
data: finalData,
|
|
4982
|
-
timestamp: item.timestamp,
|
|
4983
|
-
compressed: isCompressed,
|
|
4984
|
-
};
|
|
4985
|
-
try {
|
|
4986
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
4987
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
4988
|
-
await store.put(storedItem);
|
|
4989
|
-
}
|
|
4990
|
-
catch (error) {
|
|
4991
|
-
log$d.debug('Error setting cache item', { key, error });
|
|
4992
|
-
// Silently fail for cache writes
|
|
2779
|
+
message.includes('tls') ||
|
|
2780
|
+
message.includes('handshake'));
|
|
4993
2781
|
}
|
|
2782
|
+
return false;
|
|
4994
2783
|
}
|
|
4995
|
-
async
|
|
4996
|
-
if (
|
|
4997
|
-
|
|
4998
|
-
await this.ensureInitialized();
|
|
4999
|
-
log$d.debug('Batch setting items', { count: items.length });
|
|
5000
|
-
try {
|
|
5001
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
5002
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5003
|
-
// Process all items in the transaction
|
|
5004
|
-
const promises = items.map(([key, item]) => {
|
|
5005
|
-
const storedItem = this.prepareBatchItem(key, item);
|
|
5006
|
-
return store.put(storedItem);
|
|
5007
|
-
});
|
|
5008
|
-
await Promise.all(promises);
|
|
5009
|
-
log$d.debug('Batch operation completed', { count: items.length });
|
|
5010
|
-
}
|
|
5011
|
-
catch (error) {
|
|
5012
|
-
log$d.debug('Error in batch operation', { count: items.length, error });
|
|
5013
|
-
// Silently fail for batch writes
|
|
5014
|
-
}
|
|
5015
|
-
}
|
|
5016
|
-
prepareBatchItem(key, item) {
|
|
5017
|
-
// Handle compression if enabled (same logic as setItem)
|
|
5018
|
-
let finalData = item.data;
|
|
5019
|
-
let isCompressed = false;
|
|
5020
|
-
if (this.options.compression && this.options.compressionThreshold) {
|
|
5021
|
-
const serializedData = JSON.stringify(item.data);
|
|
5022
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
5023
|
-
if (compressionResult.compressed) {
|
|
5024
|
-
finalData = compressionResult.data;
|
|
5025
|
-
isCompressed = true;
|
|
5026
|
-
}
|
|
2784
|
+
async configureCertificate(certificate) {
|
|
2785
|
+
if (!this.mtlsAdapter) {
|
|
2786
|
+
throw new Error('mTLS adapter not available');
|
|
5027
2787
|
}
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
compressed: isCompressed,
|
|
2788
|
+
const certificateData = {
|
|
2789
|
+
certificate: certificate.certificate,
|
|
2790
|
+
privateKey: certificate.privateKey,
|
|
2791
|
+
format: certificate.format.toUpperCase(),
|
|
5033
2792
|
};
|
|
2793
|
+
await this.mtlsAdapter.configureCertificate(certificateData);
|
|
5034
2794
|
}
|
|
5035
|
-
async
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
const deletePromises = keys.map((key) => this.delete(key));
|
|
5039
|
-
await Promise.all(deletePromises);
|
|
5040
|
-
}
|
|
5041
|
-
async clear() {
|
|
5042
|
-
await this.ensureInitialized();
|
|
5043
|
-
try {
|
|
5044
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
5045
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5046
|
-
await store.clear();
|
|
5047
|
-
log$d.debug('Cache cleared successfully');
|
|
5048
|
-
}
|
|
5049
|
-
catch (error) {
|
|
5050
|
-
log$d.debug('Error clearing cache', error);
|
|
5051
|
-
// Silently fail for cache clear
|
|
2795
|
+
async storeCertificate(certificate, privateKey, options = {}) {
|
|
2796
|
+
if (!this.certificatePort) {
|
|
2797
|
+
throw new Error('Certificate port not available');
|
|
5052
2798
|
}
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
let entries = 0;
|
|
5060
|
-
let bytes = 0;
|
|
5061
|
-
// Use cursor for efficient iteration
|
|
5062
|
-
let cursor = await store.openCursor();
|
|
5063
|
-
while (cursor) {
|
|
5064
|
-
entries++;
|
|
5065
|
-
// Rough estimation of size
|
|
5066
|
-
bytes += JSON.stringify(cursor.value).length * 2; // UTF-16 encoding
|
|
5067
|
-
cursor = await cursor.continue();
|
|
2799
|
+
if (this.mtlsAdapter) {
|
|
2800
|
+
try {
|
|
2801
|
+
await this.mtlsAdapter.removeCertificate();
|
|
2802
|
+
}
|
|
2803
|
+
catch {
|
|
2804
|
+
// No existing certificate to remove
|
|
5068
2805
|
}
|
|
5069
|
-
return {
|
|
5070
|
-
entries,
|
|
5071
|
-
bytes,
|
|
5072
|
-
lastCleanup: Date.now(),
|
|
5073
|
-
};
|
|
5074
2806
|
}
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
2807
|
+
const format = (options.format || 'pem');
|
|
2808
|
+
await this.certificatePort.storeCertificate(certificate, privateKey, format);
|
|
2809
|
+
if (this.mtlsAdapter) {
|
|
2810
|
+
const certificateData = {
|
|
2811
|
+
certificate,
|
|
2812
|
+
privateKey,
|
|
2813
|
+
format: format.toUpperCase(),
|
|
5081
2814
|
};
|
|
2815
|
+
await this.mtlsAdapter.configureCertificate(certificateData);
|
|
5082
2816
|
}
|
|
5083
2817
|
}
|
|
5084
|
-
async
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
|
|
5092
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5093
|
-
const allKeys = (await store.getAllKeys());
|
|
5094
|
-
if (!pattern) {
|
|
5095
|
-
return allKeys;
|
|
2818
|
+
async clearCertificate() {
|
|
2819
|
+
if (this.mtlsAdapter) {
|
|
2820
|
+
try {
|
|
2821
|
+
await this.mtlsAdapter.removeCertificate();
|
|
2822
|
+
}
|
|
2823
|
+
catch {
|
|
2824
|
+
// No certificate to remove
|
|
5096
2825
|
}
|
|
5097
|
-
const regex = this.patternToRegex(pattern);
|
|
5098
|
-
return allKeys.filter((key) => regex.test(key));
|
|
5099
2826
|
}
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
return [];
|
|
2827
|
+
if (this.certificatePort) {
|
|
2828
|
+
await this.certificatePort.clearCertificate();
|
|
5103
2829
|
}
|
|
5104
2830
|
}
|
|
5105
|
-
async
|
|
5106
|
-
|
|
5107
|
-
try {
|
|
5108
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
5109
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5110
|
-
await store.delete(key);
|
|
5111
|
-
return true;
|
|
5112
|
-
}
|
|
5113
|
-
catch (error) {
|
|
5114
|
-
log$d.debug('Error deleting cache item', { key, error });
|
|
2831
|
+
async testConnection() {
|
|
2832
|
+
if (!this.mtlsAdapter) {
|
|
5115
2833
|
return false;
|
|
5116
2834
|
}
|
|
2835
|
+
return this.mtlsAdapter.testConnection();
|
|
5117
2836
|
}
|
|
5118
|
-
|
|
5119
|
-
if (!this.
|
|
5120
|
-
|
|
5121
|
-
}
|
|
5122
|
-
try {
|
|
5123
|
-
await this.initPromise;
|
|
5124
|
-
}
|
|
5125
|
-
catch (error) {
|
|
5126
|
-
log$d.debug('Failed to ensure initialization', error);
|
|
5127
|
-
// Reset and try once more
|
|
5128
|
-
this.initPromise = null;
|
|
5129
|
-
this.db = null;
|
|
5130
|
-
this.initPromise = this.initialize();
|
|
5131
|
-
await this.initPromise;
|
|
2837
|
+
getBaseUrl() {
|
|
2838
|
+
if (!this.mtlsAdapter) {
|
|
2839
|
+
return null;
|
|
5132
2840
|
}
|
|
2841
|
+
return this.mtlsAdapter.getBaseUrl();
|
|
5133
2842
|
}
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
case 'web':
|
|
5150
|
-
return new WebCacheAdapter({
|
|
5151
|
-
maxSize: 50 * 1024 * 1024,
|
|
5152
|
-
maxEntries: 10000,
|
|
5153
|
-
compression: false,
|
|
5154
|
-
});
|
|
5155
|
-
case 'react-native':
|
|
5156
|
-
try {
|
|
5157
|
-
return new ReactNativeCacheAdapter({
|
|
5158
|
-
maxSize: 100 * 1024 * 1024,
|
|
5159
|
-
maxEntries: 15000,
|
|
5160
|
-
});
|
|
5161
|
-
}
|
|
5162
|
-
catch {
|
|
5163
|
-
return new MemoryCacheAdapter({
|
|
5164
|
-
maxSize: 10 * 1024 * 1024,
|
|
5165
|
-
maxEntries: 5000,
|
|
5166
|
-
});
|
|
2843
|
+
async getStatus() {
|
|
2844
|
+
const status = {
|
|
2845
|
+
adapterAvailable: !!this.mtlsAdapter,
|
|
2846
|
+
certificatePortAvailable: !!this.certificatePort,
|
|
2847
|
+
isReady: false,
|
|
2848
|
+
hasCertificate: false,
|
|
2849
|
+
certificateInfo: null,
|
|
2850
|
+
platformInfo: this.mtlsAdapter?.getPlatformInfo() || null,
|
|
2851
|
+
pendingRequestsCount: this.pendingRequests.size,
|
|
2852
|
+
};
|
|
2853
|
+
if (this.certificatePort) {
|
|
2854
|
+
try {
|
|
2855
|
+
status.hasCertificate = await this.certificatePort.hasCertificate();
|
|
2856
|
+
if (status.hasCertificate) {
|
|
2857
|
+
status.certificateInfo = await this.certificatePort.getCertificateInfo();
|
|
5167
2858
|
}
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
maxEntries: 5000,
|
|
5173
|
-
});
|
|
2859
|
+
}
|
|
2860
|
+
catch {
|
|
2861
|
+
// Ignore errors
|
|
2862
|
+
}
|
|
5174
2863
|
}
|
|
2864
|
+
status.isReady = await this.isMtlsReady();
|
|
2865
|
+
return status;
|
|
5175
2866
|
}
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
return undefined;
|
|
2867
|
+
clearPendingRequests() {
|
|
2868
|
+
this.pendingRequests.clear();
|
|
5179
2869
|
}
|
|
5180
2870
|
}
|
|
5181
2871
|
|
|
5182
|
-
/**
|
|
5183
|
-
* Mixin that adds multiGet, multiSet, multiRemove to any storage adapter
|
|
5184
|
-
* Eliminates duplicate code across Node, Web, and React Native adapters
|
|
5185
|
-
*/
|
|
5186
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5187
2872
|
/**
|
|
5188
2873
|
* Abstract base class for standard storage adapters
|
|
5189
2874
|
* Implements multi-operations, subclasses implement core operations
|
|
@@ -5231,7 +2916,7 @@ class BaseSecureStorageAdapter {
|
|
|
5231
2916
|
}
|
|
5232
2917
|
}
|
|
5233
2918
|
|
|
5234
|
-
const log$
|
|
2919
|
+
const log$8 = createPrefixedLogger('NETWORK-BASE');
|
|
5235
2920
|
class NetworkBase {
|
|
5236
2921
|
constructor(initialOnline = true, debounceMs = 300) {
|
|
5237
2922
|
this.destroy$ = new Subject();
|
|
@@ -5251,14 +2936,14 @@ class NetworkBase {
|
|
|
5251
2936
|
const current = this.statusSubject.getValue();
|
|
5252
2937
|
if (current.online !== online) {
|
|
5253
2938
|
this.statusSubject.next({ online, timestamp: Date.now() });
|
|
5254
|
-
log$
|
|
2939
|
+
log$8.debug(`Network status changed: ${online ? 'online' : 'offline'}`);
|
|
5255
2940
|
}
|
|
5256
2941
|
}
|
|
5257
2942
|
destroy() {
|
|
5258
2943
|
this.destroy$.next();
|
|
5259
2944
|
this.destroy$.complete();
|
|
5260
2945
|
this.statusSubject.complete();
|
|
5261
|
-
log$
|
|
2946
|
+
log$8.debug('Network monitor destroyed');
|
|
5262
2947
|
}
|
|
5263
2948
|
}
|
|
5264
2949
|
|
|
@@ -5318,7 +3003,7 @@ class NodeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5318
3003
|
}
|
|
5319
3004
|
}
|
|
5320
3005
|
|
|
5321
|
-
const log$
|
|
3006
|
+
const log$7 = createPrefixedLogger('RN-STORAGE');
|
|
5322
3007
|
/**
|
|
5323
3008
|
* React Native storage adapter using AsyncStorage
|
|
5324
3009
|
* Note: Uses native batch operations for better performance (not base class)
|
|
@@ -5350,7 +3035,7 @@ class ReactNativeStorageAdapter {
|
|
|
5350
3035
|
return await this.AsyncStorage.getItem(key);
|
|
5351
3036
|
}
|
|
5352
3037
|
catch (error) {
|
|
5353
|
-
log$
|
|
3038
|
+
log$7.error('Failed to get item from AsyncStorage:', error);
|
|
5354
3039
|
return null;
|
|
5355
3040
|
}
|
|
5356
3041
|
}
|
|
@@ -5391,7 +3076,7 @@ class ReactNativeStorageAdapter {
|
|
|
5391
3076
|
return await this.AsyncStorage.getAllKeys();
|
|
5392
3077
|
}
|
|
5393
3078
|
catch (error) {
|
|
5394
|
-
log$
|
|
3079
|
+
log$7.error('Failed to get all keys:', error);
|
|
5395
3080
|
return [];
|
|
5396
3081
|
}
|
|
5397
3082
|
}
|
|
@@ -5408,7 +3093,7 @@ class ReactNativeStorageAdapter {
|
|
|
5408
3093
|
return result;
|
|
5409
3094
|
}
|
|
5410
3095
|
catch (error) {
|
|
5411
|
-
log$
|
|
3096
|
+
log$7.error('Failed to get multiple items:', error);
|
|
5412
3097
|
const result = {};
|
|
5413
3098
|
keys.forEach((key) => {
|
|
5414
3099
|
result[key] = null;
|
|
@@ -5458,7 +3143,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5458
3143
|
return;
|
|
5459
3144
|
}
|
|
5460
3145
|
catch {
|
|
5461
|
-
log$
|
|
3146
|
+
log$7.debug('expo-secure-store not available, trying react-native-keychain');
|
|
5462
3147
|
}
|
|
5463
3148
|
try {
|
|
5464
3149
|
const Keychain = require('react-native-keychain');
|
|
@@ -5467,7 +3152,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5467
3152
|
return;
|
|
5468
3153
|
}
|
|
5469
3154
|
catch {
|
|
5470
|
-
log$
|
|
3155
|
+
log$7.error('react-native-keychain not available');
|
|
5471
3156
|
}
|
|
5472
3157
|
throw new Error('No secure storage available. Please install expo-secure-store or react-native-keychain');
|
|
5473
3158
|
}
|
|
@@ -5485,7 +3170,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5485
3170
|
}
|
|
5486
3171
|
}
|
|
5487
3172
|
catch (error) {
|
|
5488
|
-
log$
|
|
3173
|
+
log$7.error('Failed to get secure item:', error);
|
|
5489
3174
|
}
|
|
5490
3175
|
return null;
|
|
5491
3176
|
}
|
|
@@ -5522,10 +3207,10 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5522
3207
|
}
|
|
5523
3208
|
}
|
|
5524
3209
|
async clear() {
|
|
5525
|
-
log$
|
|
3210
|
+
log$7.warn('Clear all secure items not fully implemented for React Native');
|
|
5526
3211
|
}
|
|
5527
3212
|
async getAllKeys() {
|
|
5528
|
-
log$
|
|
3213
|
+
log$7.warn('Get all secure keys not implemented for React Native');
|
|
5529
3214
|
return [];
|
|
5530
3215
|
}
|
|
5531
3216
|
async isAvailable() {
|
|
@@ -5743,7 +3428,7 @@ class NodeNetworkMonitor extends NetworkBase {
|
|
|
5743
3428
|
}
|
|
5744
3429
|
}
|
|
5745
3430
|
|
|
5746
|
-
const log$
|
|
3431
|
+
const log$6 = createPrefixedLogger('RN-NETWORK');
|
|
5747
3432
|
/**
|
|
5748
3433
|
* React Native network monitor using RxJS
|
|
5749
3434
|
* Supports both @react-native-community/netinfo and expo-network
|
|
@@ -5756,7 +3441,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5756
3441
|
this.moduleReady$ = new Subject();
|
|
5757
3442
|
this.isExpo = detectPlatform().isExpo;
|
|
5758
3443
|
this.init().catch((error) => {
|
|
5759
|
-
log$
|
|
3444
|
+
log$6.error('Network monitor initialization failed:', error);
|
|
5760
3445
|
});
|
|
5761
3446
|
}
|
|
5762
3447
|
async init() {
|
|
@@ -5775,10 +3460,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5775
3460
|
try {
|
|
5776
3461
|
const module = require('@react-native-community/netinfo');
|
|
5777
3462
|
this.netInfoModule = module.default || module;
|
|
5778
|
-
log$
|
|
3463
|
+
log$6.debug('Loaded @react-native-community/netinfo module');
|
|
5779
3464
|
}
|
|
5780
3465
|
catch (error) {
|
|
5781
|
-
log$
|
|
3466
|
+
log$6.error('Failed to load React Native NetInfo module:', error);
|
|
5782
3467
|
this.netInfoModule = null;
|
|
5783
3468
|
}
|
|
5784
3469
|
}
|
|
@@ -5786,10 +3471,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5786
3471
|
try {
|
|
5787
3472
|
const module = require('expo-network');
|
|
5788
3473
|
this.netInfoModule = module.default || module;
|
|
5789
|
-
log$
|
|
3474
|
+
log$6.debug('Loaded expo-network module');
|
|
5790
3475
|
}
|
|
5791
3476
|
catch (error) {
|
|
5792
|
-
log$
|
|
3477
|
+
log$6.error('Failed to load Expo Network module:', error);
|
|
5793
3478
|
this.netInfoModule = null;
|
|
5794
3479
|
}
|
|
5795
3480
|
}
|
|
@@ -5806,16 +3491,16 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5806
3491
|
}
|
|
5807
3492
|
const online = !!(state.isConnected && state.isInternetReachable !== false);
|
|
5808
3493
|
this.updateStatus(online);
|
|
5809
|
-
log$
|
|
3494
|
+
log$6.debug('Initial network state:', online ? 'online' : 'offline');
|
|
5810
3495
|
}
|
|
5811
3496
|
catch (error) {
|
|
5812
|
-
log$
|
|
3497
|
+
log$6.warn('Could not fetch initial network state:', error);
|
|
5813
3498
|
}
|
|
5814
3499
|
}
|
|
5815
3500
|
subscribeToStateChanges() {
|
|
5816
3501
|
if (!this.netInfoModule)
|
|
5817
3502
|
return;
|
|
5818
|
-
log$
|
|
3503
|
+
log$6.debug('Subscribing to network state changes');
|
|
5819
3504
|
const handleState = (state) => {
|
|
5820
3505
|
const online = !!(state.isConnected && (state.isInternetReachable ?? true));
|
|
5821
3506
|
this.updateStatus(online);
|
|
@@ -5857,7 +3542,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5857
3542
|
};
|
|
5858
3543
|
}
|
|
5859
3544
|
catch (error) {
|
|
5860
|
-
log$
|
|
3545
|
+
log$6.error('Failed to fetch detailed network info:', error);
|
|
5861
3546
|
return null;
|
|
5862
3547
|
}
|
|
5863
3548
|
}
|
|
@@ -5993,7 +3678,7 @@ class CertificateValidator {
|
|
|
5993
3678
|
}
|
|
5994
3679
|
}
|
|
5995
3680
|
|
|
5996
|
-
const log$
|
|
3681
|
+
const log$5 = createPrefixedLogger('RN-MTLS');
|
|
5997
3682
|
/**
|
|
5998
3683
|
* React Native mTLS Adapter using @a-cube-io/expo-mutual-tls
|
|
5999
3684
|
*/
|
|
@@ -6015,7 +3700,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6015
3700
|
this.expoMTLS = ExpoMutualTls;
|
|
6016
3701
|
// Set up debug logging with the correct event signature
|
|
6017
3702
|
const debugListener = ExpoMutualTls.onDebugLog((event) => {
|
|
6018
|
-
log$
|
|
3703
|
+
log$5.debug(`${event.type}: ${event.message}`, {
|
|
6019
3704
|
method: event.method,
|
|
6020
3705
|
url: event.url,
|
|
6021
3706
|
statusCode: event.statusCode,
|
|
@@ -6025,28 +3710,28 @@ class ReactNativeMTLSAdapter {
|
|
|
6025
3710
|
this.eventListeners.push(debugListener);
|
|
6026
3711
|
// Set up error logging with the correct event signature
|
|
6027
3712
|
const errorListener = ExpoMutualTls.onError((event) => {
|
|
6028
|
-
log$
|
|
3713
|
+
log$5.error(event.message, {
|
|
6029
3714
|
code: event.code,
|
|
6030
3715
|
});
|
|
6031
3716
|
});
|
|
6032
3717
|
this.eventListeners.push(errorListener);
|
|
6033
3718
|
// Set up certificate expiry monitoring with the correct event signature
|
|
6034
3719
|
const expiryListener = ExpoMutualTls.onCertificateExpiry((event) => {
|
|
6035
|
-
log$
|
|
3720
|
+
log$5.warn(`Certificate ${event.subject} expires at ${new Date(event.expiry)}`, {
|
|
6036
3721
|
alias: event.alias,
|
|
6037
3722
|
warning: event.warning,
|
|
6038
3723
|
});
|
|
6039
3724
|
});
|
|
6040
3725
|
this.eventListeners.push(expiryListener);
|
|
6041
|
-
log$
|
|
3726
|
+
log$5.debug('Expo mTLS module loaded successfully');
|
|
6042
3727
|
}
|
|
6043
3728
|
catch (error) {
|
|
6044
|
-
log$
|
|
3729
|
+
log$5.warn('@a-cube-io/expo-mutual-tls not available:', error);
|
|
6045
3730
|
}
|
|
6046
3731
|
}
|
|
6047
3732
|
async isMTLSSupported() {
|
|
6048
3733
|
const supported = this.expoMTLS !== null;
|
|
6049
|
-
log$
|
|
3734
|
+
log$5.debug('mTLS support check:', {
|
|
6050
3735
|
supported,
|
|
6051
3736
|
platform: this.getPlatformInfo().platform,
|
|
6052
3737
|
moduleAvailable: !!this.expoMTLS,
|
|
@@ -6058,7 +3743,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6058
3743
|
throw new MTLSError(exports.MTLSErrorType.NOT_SUPPORTED, 'Expo mTLS module not available');
|
|
6059
3744
|
}
|
|
6060
3745
|
this.config = config;
|
|
6061
|
-
log$
|
|
3746
|
+
log$5.debug('Initialized with config:', {
|
|
6062
3747
|
baseUrl: config.baseUrl,
|
|
6063
3748
|
port: config.port,
|
|
6064
3749
|
timeout: config.timeout,
|
|
@@ -6072,7 +3757,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6072
3757
|
if (!this.config) {
|
|
6073
3758
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Adapter not initialized. Call initialize() first.');
|
|
6074
3759
|
}
|
|
6075
|
-
log$
|
|
3760
|
+
log$5.debug('Configuring certificate:', {
|
|
6076
3761
|
format: certificateData.format,
|
|
6077
3762
|
hasPassword: !!certificateData.password,
|
|
6078
3763
|
certificateLength: certificateData.certificate.length,
|
|
@@ -6089,14 +3774,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6089
3774
|
'client-key-service', // keyService
|
|
6090
3775
|
true // enableLogging - let the native module handle its own debug logging
|
|
6091
3776
|
);
|
|
6092
|
-
log$
|
|
3777
|
+
log$5.debug('PEM services configured:', configResult);
|
|
6093
3778
|
if (!configResult.success) {
|
|
6094
3779
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, `PEM configuration failed: ${configResult.state}`);
|
|
6095
3780
|
}
|
|
6096
3781
|
// Step 2: Store the actual PEM certificate and private key
|
|
6097
3782
|
const storeResult = await this.expoMTLS.storePEM(certificateData.certificate, certificateData.privateKey, certificateData.password // passphrase (optional)
|
|
6098
3783
|
);
|
|
6099
|
-
log$
|
|
3784
|
+
log$5.debug('PEM certificate store result:', storeResult);
|
|
6100
3785
|
if (!storeResult) {
|
|
6101
3786
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, 'Failed to store PEM certificate');
|
|
6102
3787
|
}
|
|
@@ -6109,14 +3794,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6109
3794
|
const configResult = await this.expoMTLS.configureP12('client-p12-service', // keychainService
|
|
6110
3795
|
true // enableLogging - let the native module handle its own debug logging
|
|
6111
3796
|
);
|
|
6112
|
-
log$
|
|
3797
|
+
log$5.debug('P12 service configured:', configResult);
|
|
6113
3798
|
if (!configResult.success) {
|
|
6114
3799
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, `P12 configuration failed: ${configResult.state}`);
|
|
6115
3800
|
}
|
|
6116
3801
|
// Step 2: Store the P12 certificate data
|
|
6117
3802
|
const storeResult = await this.expoMTLS.storeP12(certificateData.certificate, // P12 data in certificate field
|
|
6118
3803
|
certificateData.password);
|
|
6119
|
-
log$
|
|
3804
|
+
log$5.debug('P12 certificate store result:', storeResult);
|
|
6120
3805
|
if (!storeResult) {
|
|
6121
3806
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, 'Failed to store P12 certificate');
|
|
6122
3807
|
}
|
|
@@ -6130,7 +3815,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6130
3815
|
if (error instanceof MTLSError) {
|
|
6131
3816
|
throw error;
|
|
6132
3817
|
}
|
|
6133
|
-
log$
|
|
3818
|
+
log$5.error('Certificate configuration failed:', error);
|
|
6134
3819
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Failed to configure certificate', error);
|
|
6135
3820
|
}
|
|
6136
3821
|
}
|
|
@@ -6141,38 +3826,38 @@ class ReactNativeMTLSAdapter {
|
|
|
6141
3826
|
try {
|
|
6142
3827
|
// Use static method call
|
|
6143
3828
|
const hasCert = await this.expoMTLS.hasCertificate();
|
|
6144
|
-
log$
|
|
3829
|
+
log$5.debug('Certificate availability check:', hasCert);
|
|
6145
3830
|
return hasCert;
|
|
6146
3831
|
}
|
|
6147
3832
|
catch (error) {
|
|
6148
|
-
log$
|
|
3833
|
+
log$5.error('Certificate check failed:', error);
|
|
6149
3834
|
return false;
|
|
6150
3835
|
}
|
|
6151
3836
|
}
|
|
6152
3837
|
async getCertificateInfo() {
|
|
6153
3838
|
if (!this.expoMTLS) {
|
|
6154
|
-
log$
|
|
3839
|
+
log$5.debug('Certificate info requested but module not available');
|
|
6155
3840
|
return null;
|
|
6156
3841
|
}
|
|
6157
3842
|
try {
|
|
6158
3843
|
const hasCert = await this.hasCertificate();
|
|
6159
3844
|
if (!hasCert) {
|
|
6160
|
-
log$
|
|
3845
|
+
log$5.debug('No certificate stored');
|
|
6161
3846
|
return null;
|
|
6162
3847
|
}
|
|
6163
3848
|
// Use getCertificatesInfo to retrieve information about stored certificates
|
|
6164
3849
|
const result = await this.expoMTLS.getCertificatesInfo();
|
|
6165
3850
|
if (!result || !result.certificates || result.certificates.length === 0) {
|
|
6166
|
-
log$
|
|
3851
|
+
log$5.debug('No certificate information available');
|
|
6167
3852
|
return null;
|
|
6168
3853
|
}
|
|
6169
3854
|
// Get the first certificate (primary client certificate)
|
|
6170
3855
|
const cert = result.certificates[0];
|
|
6171
3856
|
if (!cert) {
|
|
6172
|
-
log$
|
|
3857
|
+
log$5.debug('Certificate data is empty');
|
|
6173
3858
|
return null;
|
|
6174
3859
|
}
|
|
6175
|
-
log$
|
|
3860
|
+
log$5.debug('Retrieved certificate info:', {
|
|
6176
3861
|
subject: cert.subject.commonName,
|
|
6177
3862
|
issuer: cert.issuer.commonName,
|
|
6178
3863
|
validFrom: new Date(cert.validFrom),
|
|
@@ -6191,7 +3876,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6191
3876
|
};
|
|
6192
3877
|
}
|
|
6193
3878
|
catch (error) {
|
|
6194
|
-
log$
|
|
3879
|
+
log$5.error('Failed to get certificate info:', error);
|
|
6195
3880
|
return null;
|
|
6196
3881
|
}
|
|
6197
3882
|
}
|
|
@@ -6202,7 +3887,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6202
3887
|
*/
|
|
6203
3888
|
async parseCertificateData(certificateData) {
|
|
6204
3889
|
if (!this.expoMTLS) {
|
|
6205
|
-
log$
|
|
3890
|
+
log$5.debug('Parse certificate: Module not available');
|
|
6206
3891
|
return null;
|
|
6207
3892
|
}
|
|
6208
3893
|
try {
|
|
@@ -6219,14 +3904,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6219
3904
|
else {
|
|
6220
3905
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, `Unsupported certificate format: ${certificateData.format}`);
|
|
6221
3906
|
}
|
|
6222
|
-
log$
|
|
3907
|
+
log$5.debug('Certificate parsed successfully:', {
|
|
6223
3908
|
certificateCount: result.certificates.length,
|
|
6224
3909
|
subjects: result.certificates.map((cert) => cert.subject.commonName),
|
|
6225
3910
|
});
|
|
6226
3911
|
return result;
|
|
6227
3912
|
}
|
|
6228
3913
|
catch (error) {
|
|
6229
|
-
log$
|
|
3914
|
+
log$5.error('Failed to parse certificate:', error);
|
|
6230
3915
|
if (error instanceof MTLSError) {
|
|
6231
3916
|
throw error;
|
|
6232
3917
|
}
|
|
@@ -6241,7 +3926,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6241
3926
|
if (!hasCert) {
|
|
6242
3927
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_NOT_FOUND, 'No certificate configured');
|
|
6243
3928
|
}
|
|
6244
|
-
log$
|
|
3929
|
+
log$5.debug('Making mTLS request:', {
|
|
6245
3930
|
method: requestConfig.method || 'GET',
|
|
6246
3931
|
url: requestConfig.url,
|
|
6247
3932
|
headers: requestConfig.headers,
|
|
@@ -6249,7 +3934,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6249
3934
|
responseType: requestConfig.responseType,
|
|
6250
3935
|
});
|
|
6251
3936
|
if (requestConfig.data) {
|
|
6252
|
-
log$
|
|
3937
|
+
log$5.debug('mTLS request body:', requestConfig.data);
|
|
6253
3938
|
}
|
|
6254
3939
|
try {
|
|
6255
3940
|
const response = await this.expoMTLS.request(requestConfig.url, {
|
|
@@ -6258,7 +3943,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6258
3943
|
body: requestConfig.data ? JSON.stringify(requestConfig.data) : undefined,
|
|
6259
3944
|
responseType: requestConfig.responseType,
|
|
6260
3945
|
});
|
|
6261
|
-
log$
|
|
3946
|
+
log$5.debug('mTLS request successful:', response);
|
|
6262
3947
|
if (!response.success) {
|
|
6263
3948
|
throw new MTLSError(exports.MTLSErrorType.CONNECTION_FAILED, `mTLS request failed: ${response.statusMessage} (${response.statusCode})`, undefined, response.statusCode);
|
|
6264
3949
|
}
|
|
@@ -6270,7 +3955,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6270
3955
|
data = JSON.parse(response.body);
|
|
6271
3956
|
}
|
|
6272
3957
|
catch (parseError) {
|
|
6273
|
-
log$
|
|
3958
|
+
log$5.warn('Failed to parse JSON response:', parseError);
|
|
6274
3959
|
// If parsing fails, keep raw body
|
|
6275
3960
|
}
|
|
6276
3961
|
}
|
|
@@ -6287,7 +3972,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6287
3972
|
};
|
|
6288
3973
|
}
|
|
6289
3974
|
catch (error) {
|
|
6290
|
-
log$
|
|
3975
|
+
log$5.error('mTLS request failed:', error);
|
|
6291
3976
|
throw new MTLSError(exports.MTLSErrorType.CONNECTION_FAILED, 'mTLS request failed', error);
|
|
6292
3977
|
}
|
|
6293
3978
|
}
|
|
@@ -6300,18 +3985,18 @@ class ReactNativeMTLSAdapter {
|
|
|
6300
3985
|
*/
|
|
6301
3986
|
async testConnection() {
|
|
6302
3987
|
if (!this.expoMTLS || !this.config) {
|
|
6303
|
-
log$
|
|
3988
|
+
log$5.debug('Diagnostic test: No mTLS module or config available');
|
|
6304
3989
|
return false;
|
|
6305
3990
|
}
|
|
6306
3991
|
try {
|
|
6307
3992
|
const hasCert = await this.hasCertificate();
|
|
6308
3993
|
if (!hasCert) {
|
|
6309
|
-
log$
|
|
3994
|
+
log$5.debug('Diagnostic test: No certificate configured');
|
|
6310
3995
|
return false;
|
|
6311
3996
|
}
|
|
6312
|
-
log$
|
|
3997
|
+
log$5.debug('Running diagnostic test (may fail even if mTLS works):', this.config.baseUrl);
|
|
6313
3998
|
const result = await this.expoMTLS.testConnection(this.config.baseUrl);
|
|
6314
|
-
log$
|
|
3999
|
+
log$5.debug('Diagnostic test result (NOT validation):', {
|
|
6315
4000
|
success: result.success,
|
|
6316
4001
|
statusCode: result.statusCode,
|
|
6317
4002
|
statusMessage: result.statusMessage,
|
|
@@ -6322,13 +4007,13 @@ class ReactNativeMTLSAdapter {
|
|
|
6322
4007
|
return result.success;
|
|
6323
4008
|
}
|
|
6324
4009
|
catch (error) {
|
|
6325
|
-
log$
|
|
4010
|
+
log$5.warn('Diagnostic test failed (this is expected):', error);
|
|
6326
4011
|
return false;
|
|
6327
4012
|
}
|
|
6328
4013
|
}
|
|
6329
4014
|
async removeCertificate() {
|
|
6330
4015
|
if (!this.expoMTLS) {
|
|
6331
|
-
log$
|
|
4016
|
+
log$5.debug('Remove certificate: Module not available');
|
|
6332
4017
|
return;
|
|
6333
4018
|
}
|
|
6334
4019
|
try {
|
|
@@ -6337,10 +4022,10 @@ class ReactNativeMTLSAdapter {
|
|
|
6337
4022
|
this.isConfigured = false;
|
|
6338
4023
|
// Cleanup event listeners
|
|
6339
4024
|
this.cleanupEventListeners();
|
|
6340
|
-
log$
|
|
4025
|
+
log$5.debug('Certificate removed successfully');
|
|
6341
4026
|
}
|
|
6342
4027
|
catch (error) {
|
|
6343
|
-
log$
|
|
4028
|
+
log$5.error('Failed to remove certificate:', error);
|
|
6344
4029
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Failed to remove certificate', error);
|
|
6345
4030
|
}
|
|
6346
4031
|
}
|
|
@@ -6349,7 +4034,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6349
4034
|
*/
|
|
6350
4035
|
cleanupEventListeners() {
|
|
6351
4036
|
if (this.eventListeners.length > 0) {
|
|
6352
|
-
log$
|
|
4037
|
+
log$5.debug(`Cleaning up ${this.eventListeners.length} event listeners`);
|
|
6353
4038
|
}
|
|
6354
4039
|
// Remove individual listeners if they have remove methods
|
|
6355
4040
|
this.eventListeners.forEach((listener) => {
|
|
@@ -6386,7 +4071,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6386
4071
|
}
|
|
6387
4072
|
}
|
|
6388
4073
|
|
|
6389
|
-
const log$
|
|
4074
|
+
const log$4 = createPrefixedLogger('WEB-MTLS');
|
|
6390
4075
|
/**
|
|
6391
4076
|
* Web mTLS Adapter - Graceful fallback for web browsers
|
|
6392
4077
|
*
|
|
@@ -6400,13 +4085,13 @@ const log$7 = createPrefixedLogger('WEB-MTLS');
|
|
|
6400
4085
|
*/
|
|
6401
4086
|
class WebMTLSAdapter {
|
|
6402
4087
|
constructor() {
|
|
6403
|
-
log$
|
|
6404
|
-
log$
|
|
4088
|
+
log$4.warn('Web browsers do not support programmatic mTLS configuration');
|
|
4089
|
+
log$4.info('Use JWT authentication or configure client certificates in browser settings');
|
|
6405
4090
|
}
|
|
6406
4091
|
async isMTLSSupported() {
|
|
6407
4092
|
// mTLS is not supported programmatically in web browsers
|
|
6408
4093
|
const supported = false;
|
|
6409
|
-
log$
|
|
4094
|
+
log$4.debug('mTLS support check:', {
|
|
6410
4095
|
supported,
|
|
6411
4096
|
platform: this.getPlatformInfo().platform,
|
|
6412
4097
|
reason: 'Browser security model prevents programmatic certificate configuration',
|
|
@@ -6415,14 +4100,14 @@ class WebMTLSAdapter {
|
|
|
6415
4100
|
return supported;
|
|
6416
4101
|
}
|
|
6417
4102
|
async initialize(config) {
|
|
6418
|
-
log$
|
|
4103
|
+
log$4.warn('Initialized but mTLS not available in web browsers:', {
|
|
6419
4104
|
baseUrl: config.baseUrl,
|
|
6420
4105
|
port: config.port,
|
|
6421
4106
|
recommendation: 'Use standard HTTPS with JWT authentication',
|
|
6422
4107
|
});
|
|
6423
4108
|
}
|
|
6424
4109
|
async configureCertificate(certificateData) {
|
|
6425
|
-
log$
|
|
4110
|
+
log$4.error('Certificate configuration attempted:', {
|
|
6426
4111
|
format: certificateData.format,
|
|
6427
4112
|
reason: 'Not supported in web browsers',
|
|
6428
4113
|
alternatives: [
|
|
@@ -6437,15 +4122,15 @@ class WebMTLSAdapter {
|
|
|
6437
4122
|
}
|
|
6438
4123
|
async hasCertificate() {
|
|
6439
4124
|
// We cannot detect if the browser has certificates configured
|
|
6440
|
-
log$
|
|
4125
|
+
log$4.debug('Certificate availability check: Cannot detect browser certificates programmatically');
|
|
6441
4126
|
return false;
|
|
6442
4127
|
}
|
|
6443
4128
|
async getCertificateInfo() {
|
|
6444
|
-
log$
|
|
4129
|
+
log$4.debug('Certificate info requested: Not accessible in web browsers');
|
|
6445
4130
|
return null;
|
|
6446
4131
|
}
|
|
6447
4132
|
async request(requestConfig) {
|
|
6448
|
-
log$
|
|
4133
|
+
log$4.error('mTLS request attempted:', {
|
|
6449
4134
|
method: requestConfig.method,
|
|
6450
4135
|
url: requestConfig.url,
|
|
6451
4136
|
reason: 'Not supported in web browsers',
|
|
@@ -6460,11 +4145,11 @@ class WebMTLSAdapter {
|
|
|
6460
4145
|
'are properly configured in the browser certificate store.');
|
|
6461
4146
|
}
|
|
6462
4147
|
async testConnection() {
|
|
6463
|
-
log$
|
|
4148
|
+
log$4.debug('Connection test: mTLS not available in web browsers');
|
|
6464
4149
|
return false;
|
|
6465
4150
|
}
|
|
6466
4151
|
async removeCertificate() {
|
|
6467
|
-
log$
|
|
4152
|
+
log$4.debug('Remove certificate: No certificates to remove (not supported in web browsers)');
|
|
6468
4153
|
// No-op - cannot remove certificates programmatically in browsers
|
|
6469
4154
|
}
|
|
6470
4155
|
/**
|
|
@@ -6472,7 +4157,7 @@ class WebMTLSAdapter {
|
|
|
6472
4157
|
* Always returns null for web browsers as mTLS is not supported
|
|
6473
4158
|
*/
|
|
6474
4159
|
getBaseUrl() {
|
|
6475
|
-
log$
|
|
4160
|
+
log$4.debug('Base URL requested: Not supported in web browsers');
|
|
6476
4161
|
return null;
|
|
6477
4162
|
}
|
|
6478
4163
|
getPlatformInfo() {
|
|
@@ -6505,7 +4190,7 @@ class WebMTLSAdapter {
|
|
|
6505
4190
|
}
|
|
6506
4191
|
}
|
|
6507
4192
|
|
|
6508
|
-
const log$
|
|
4193
|
+
const log$3 = createPrefixedLogger('MTLS-LOADER');
|
|
6509
4194
|
function loadMTLSAdapter(platform, config) {
|
|
6510
4195
|
try {
|
|
6511
4196
|
let adapter;
|
|
@@ -6539,7 +4224,7 @@ function loadMTLSAdapter(platform, config) {
|
|
|
6539
4224
|
return adapter;
|
|
6540
4225
|
}
|
|
6541
4226
|
catch (error) {
|
|
6542
|
-
log$
|
|
4227
|
+
log$3.warn(`mTLS adapter not available for platform ${platform}:`, error);
|
|
6543
4228
|
return null;
|
|
6544
4229
|
}
|
|
6545
4230
|
}
|
|
@@ -6549,7 +4234,7 @@ async function initializeAdapterAsync(adapter, config) {
|
|
|
6549
4234
|
if (isSupported) {
|
|
6550
4235
|
await adapter.initialize(config);
|
|
6551
4236
|
const platformInfo = adapter.getPlatformInfo();
|
|
6552
|
-
log$
|
|
4237
|
+
log$3.debug('mTLS adapter initialized:', {
|
|
6553
4238
|
platform: platformInfo.platform,
|
|
6554
4239
|
mtlsSupported: platformInfo.mtlsSupported,
|
|
6555
4240
|
certificateStorage: platformInfo.certificateStorage,
|
|
@@ -6558,31 +4243,28 @@ async function initializeAdapterAsync(adapter, config) {
|
|
|
6558
4243
|
}
|
|
6559
4244
|
}
|
|
6560
4245
|
catch (error) {
|
|
6561
|
-
log$
|
|
4246
|
+
log$3.warn('Failed to initialize mTLS adapter:', error);
|
|
6562
4247
|
}
|
|
6563
4248
|
}
|
|
6564
4249
|
|
|
6565
|
-
const log$
|
|
4250
|
+
const log$2 = createPrefixedLogger('ADAPTER-LOADER');
|
|
6566
4251
|
function loadPlatformAdapters(options = {}) {
|
|
6567
4252
|
const { mtlsConfig } = options;
|
|
6568
4253
|
const { platform } = detectPlatform();
|
|
6569
|
-
log$
|
|
4254
|
+
log$2.debug('Loading adapters for platform:', platform);
|
|
6570
4255
|
const storageAdapters = loadStorageAdapters(platform);
|
|
6571
4256
|
const networkMonitor = loadNetworkMonitor(platform);
|
|
6572
|
-
const cache = loadCacheAdapter(platform);
|
|
6573
4257
|
const mtls = loadMTLSAdapter(platform, mtlsConfig);
|
|
6574
|
-
log$
|
|
4258
|
+
log$2.debug('Adapters loaded:', {
|
|
6575
4259
|
platform,
|
|
6576
4260
|
hasStorage: !!storageAdapters.storage,
|
|
6577
4261
|
hasSecureStorage: !!storageAdapters.secureStorage,
|
|
6578
4262
|
hasNetworkMonitor: !!networkMonitor,
|
|
6579
|
-
hasCache: !!cache,
|
|
6580
4263
|
hasMTLS: !!mtls,
|
|
6581
4264
|
});
|
|
6582
4265
|
return {
|
|
6583
4266
|
...storageAdapters,
|
|
6584
4267
|
networkMonitor,
|
|
6585
|
-
cache,
|
|
6586
4268
|
mtls: mtls || undefined,
|
|
6587
4269
|
};
|
|
6588
4270
|
}
|
|
@@ -6599,12 +4281,9 @@ function createACubeMTLSConfig(baseUrl, timeout, autoInitialize = true, forcePor
|
|
|
6599
4281
|
|
|
6600
4282
|
const DI_TOKENS = {
|
|
6601
4283
|
HTTP_PORT: Symbol('HTTP_PORT'),
|
|
6602
|
-
BASE_HTTP_PORT: Symbol('BASE_HTTP_PORT'),
|
|
6603
4284
|
STORAGE_PORT: Symbol('STORAGE_PORT'),
|
|
6604
4285
|
SECURE_STORAGE_PORT: Symbol('SECURE_STORAGE_PORT'),
|
|
6605
4286
|
NETWORK_PORT: Symbol('NETWORK_PORT'),
|
|
6606
|
-
CACHE_PORT: Symbol('CACHE_PORT'),
|
|
6607
|
-
CACHE_KEY_GENERATOR: Symbol('CACHE_KEY_GENERATOR'),
|
|
6608
4287
|
MTLS_PORT: Symbol('MTLS_PORT'),
|
|
6609
4288
|
TOKEN_STORAGE_PORT: Symbol('TOKEN_STORAGE_PORT'),
|
|
6610
4289
|
RECEIPT_REPOSITORY: Symbol('RECEIPT_REPOSITORY'),
|
|
@@ -6622,7 +4301,6 @@ const DI_TOKENS = {
|
|
|
6622
4301
|
AUTH_SERVICE: Symbol('AUTH_SERVICE'),
|
|
6623
4302
|
AUTHENTICATION_SERVICE: Symbol('AUTHENTICATION_SERVICE'),
|
|
6624
4303
|
CERTIFICATE_SERVICE: Symbol('CERTIFICATE_SERVICE'),
|
|
6625
|
-
OFFLINE_SERVICE: Symbol('OFFLINE_SERVICE'),
|
|
6626
4304
|
NOTIFICATION_SERVICE: Symbol('NOTIFICATION_SERVICE'),
|
|
6627
4305
|
TELEMETRY_SERVICE: Symbol('TELEMETRY_SERVICE'),
|
|
6628
4306
|
};
|
|
@@ -7515,547 +5193,85 @@ class TelemetryMapper {
|
|
|
7515
5193
|
adeCorrispettiviTransmission: output.ade_corrispettivi_transmission
|
|
7516
5194
|
? this.transmissionFromApi(output.ade_corrispettivi_transmission)
|
|
7517
5195
|
: null,
|
|
7518
|
-
lastMessageFromAde: output.last_message_from_ade
|
|
7519
|
-
? this.messageFromApi(output.last_message_from_ade)
|
|
7520
|
-
: null,
|
|
7521
|
-
lottery: this.lotteryFromApi(output.lottery),
|
|
7522
|
-
};
|
|
7523
|
-
}
|
|
7524
|
-
static merchantFromApi(output) {
|
|
7525
|
-
return {
|
|
7526
|
-
vatNumber: output.vat_number,
|
|
7527
|
-
fiscalCode: output.fiscal_code,
|
|
7528
|
-
businessName: output.business_name,
|
|
7529
|
-
};
|
|
7530
|
-
}
|
|
7531
|
-
static supplierFromApi(output) {
|
|
7532
|
-
return {
|
|
7533
|
-
vatNumber: output.vat_number,
|
|
7534
|
-
fiscalCode: output.fiscal_code,
|
|
7535
|
-
businessName: output.business_name,
|
|
7536
|
-
};
|
|
7537
|
-
}
|
|
7538
|
-
static softwareVersionFromApi(output) {
|
|
7539
|
-
return {
|
|
7540
|
-
version: output.version,
|
|
7541
|
-
swid: output.swid,
|
|
7542
|
-
installedAt: output.installed_at,
|
|
7543
|
-
status: output.status,
|
|
7544
|
-
};
|
|
7545
|
-
}
|
|
7546
|
-
static softwareFromApi(output) {
|
|
7547
|
-
return {
|
|
7548
|
-
code: output.code,
|
|
7549
|
-
name: output.name,
|
|
7550
|
-
approvalReference: output.approval_reference,
|
|
7551
|
-
versionInfo: output.version_info ? this.softwareVersionFromApi(output.version_info) : null,
|
|
7552
|
-
};
|
|
7553
|
-
}
|
|
7554
|
-
static pendingReceiptsFromApi(output) {
|
|
7555
|
-
return {
|
|
7556
|
-
count: output.count,
|
|
7557
|
-
totalAmount: output.total_amount,
|
|
7558
|
-
};
|
|
7559
|
-
}
|
|
7560
|
-
static transmissionFromApi(output) {
|
|
7561
|
-
return {
|
|
7562
|
-
attemptedAt: output.attempted_at,
|
|
7563
|
-
outcome: output.outcome,
|
|
7564
|
-
};
|
|
7565
|
-
}
|
|
7566
|
-
static messageFromApi(output) {
|
|
7567
|
-
return {
|
|
7568
|
-
receivedAt: output.received_at,
|
|
7569
|
-
content: output.content,
|
|
7570
|
-
};
|
|
7571
|
-
}
|
|
7572
|
-
static secretRequestFromApi(output) {
|
|
7573
|
-
return {
|
|
7574
|
-
requestedAt: output.requested_at,
|
|
7575
|
-
outcome: output.outcome,
|
|
7576
|
-
};
|
|
7577
|
-
}
|
|
7578
|
-
static lotteryFromApi(output) {
|
|
7579
|
-
return {
|
|
7580
|
-
lastTransmission: output.last_transmission
|
|
7581
|
-
? this.transmissionFromApi(output.last_transmission)
|
|
7582
|
-
: null,
|
|
7583
|
-
secretRequest: output.secret_request
|
|
7584
|
-
? this.secretRequestFromApi(output.secret_request)
|
|
7585
|
-
: null,
|
|
7586
|
-
};
|
|
7587
|
-
}
|
|
7588
|
-
}
|
|
7589
|
-
|
|
7590
|
-
class TelemetryRepositoryImpl {
|
|
7591
|
-
constructor(http) {
|
|
7592
|
-
this.http = http;
|
|
7593
|
-
}
|
|
7594
|
-
async getTelemetry(pemId) {
|
|
7595
|
-
const response = await this.http.get(`/mf1/pems/${pemId}/telemetry`);
|
|
7596
|
-
return TelemetryMapper.fromApiOutput(response.data);
|
|
7597
|
-
}
|
|
7598
|
-
}
|
|
7599
|
-
|
|
7600
|
-
const log$4 = createPrefixedLogger('CACHE-KEY');
|
|
7601
|
-
const URL_PATTERNS = [
|
|
7602
|
-
// Receipt (mf1) - specific patterns first
|
|
7603
|
-
{
|
|
7604
|
-
pattern: /^\/mf1\/receipts\/([^/]+)\/returnable-items$/,
|
|
7605
|
-
resource: 'receipt',
|
|
7606
|
-
action: 'returnable',
|
|
7607
|
-
},
|
|
7608
|
-
{
|
|
7609
|
-
pattern: /^\/mf1\/receipts\/([^/]+)\/details$/,
|
|
7610
|
-
resource: 'receipt',
|
|
7611
|
-
action: 'details',
|
|
7612
|
-
},
|
|
7613
|
-
{ pattern: /^\/mf1\/receipts\/([^/]+)$/, resource: 'receipt' },
|
|
7614
|
-
{
|
|
7615
|
-
pattern: /^\/mf1\/pems\/([^/]+)\/receipts$/,
|
|
7616
|
-
resource: 'receipt',
|
|
7617
|
-
parent: 'point-of-sale',
|
|
7618
|
-
isList: true,
|
|
7619
|
-
},
|
|
7620
|
-
{ pattern: /^\/mf1\/receipts$/, resource: 'receipt', isList: true },
|
|
7621
|
-
// Merchant (mf2)
|
|
7622
|
-
{ pattern: /^\/mf2\/merchants\/([^/]+)$/, resource: 'merchant' },
|
|
7623
|
-
{ pattern: /^\/mf2\/merchants$/, resource: 'merchant', isList: true },
|
|
7624
|
-
// Cashier (mf1)
|
|
7625
|
-
{ pattern: /^\/mf1\/cashiers\/me$/, resource: 'cashier', action: 'me' },
|
|
7626
|
-
{ pattern: /^\/mf1\/cashiers\/([^/]+)$/, resource: 'cashier' },
|
|
7627
|
-
{ pattern: /^\/mf1\/cashiers$/, resource: 'cashier', isList: true },
|
|
7628
|
-
// Cash Register (mf1)
|
|
7629
|
-
{ pattern: /^\/mf1\/cash-registers\/([^/]+)$/, resource: 'cash-register' },
|
|
7630
|
-
{ pattern: /^\/mf1\/cash-registers$/, resource: 'cash-register', isList: true },
|
|
7631
|
-
// Point of Sale (mf1)
|
|
7632
|
-
{ pattern: /^\/mf1\/pems\/([^/]+)$/, resource: 'point-of-sale' },
|
|
7633
|
-
{ pattern: /^\/mf1\/pems$/, resource: 'point-of-sale', isList: true },
|
|
7634
|
-
// Nested resources under merchant (mf2)
|
|
7635
|
-
{
|
|
7636
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers\/([^/]+)$/,
|
|
7637
|
-
resource: 'supplier',
|
|
7638
|
-
parent: 'merchant',
|
|
7639
|
-
},
|
|
7640
|
-
{
|
|
7641
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers$/,
|
|
7642
|
-
resource: 'supplier',
|
|
7643
|
-
parent: 'merchant',
|
|
7644
|
-
isList: true,
|
|
7645
|
-
},
|
|
7646
|
-
{
|
|
7647
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/daily-reports/,
|
|
7648
|
-
resource: 'daily-report',
|
|
7649
|
-
parent: 'merchant',
|
|
7650
|
-
isList: true,
|
|
7651
|
-
},
|
|
7652
|
-
{
|
|
7653
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/journals/,
|
|
7654
|
-
resource: 'journal',
|
|
7655
|
-
parent: 'merchant',
|
|
7656
|
-
isList: true,
|
|
7657
|
-
},
|
|
7658
|
-
// PEM (mf2)
|
|
7659
|
-
{
|
|
7660
|
-
pattern: /^\/mf2\/pems\/([^/]+)\/certificates$/,
|
|
7661
|
-
resource: 'pem',
|
|
7662
|
-
action: 'certificates',
|
|
7663
|
-
},
|
|
7664
|
-
{ pattern: /^\/mf2\/pems\/([^/]+)$/, resource: 'pem' },
|
|
7665
|
-
// Others
|
|
7666
|
-
{ pattern: /^\/mf1\/notifications/, resource: 'notification', isList: true },
|
|
7667
|
-
{
|
|
7668
|
-
pattern: /^\/mf1\/pems\/([^/]+)\/telemetry$/,
|
|
7669
|
-
resource: 'telemetry',
|
|
7670
|
-
},
|
|
7671
|
-
];
|
|
7672
|
-
const DEFAULT_TTL_CONFIG = {
|
|
7673
|
-
// Data that rarely changes - 30 min TTL for items only
|
|
7674
|
-
merchant: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7675
|
-
'point-of-sale': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7676
|
-
'cash-register': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7677
|
-
pem: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: false },
|
|
7678
|
-
// Data that changes moderately - 10 min TTL for items only
|
|
7679
|
-
cashier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7680
|
-
supplier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7681
|
-
// Data that can change - 5 min TTL for items only
|
|
7682
|
-
receipt: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7683
|
-
'daily-report': { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7684
|
-
journal: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7685
|
-
// Real-time data - 1 min TTL
|
|
7686
|
-
notification: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
|
|
7687
|
-
telemetry: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
|
|
7688
|
-
};
|
|
7689
|
-
const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
|
|
7690
|
-
class CacheKeyGenerator {
|
|
7691
|
-
constructor(customConfig) {
|
|
7692
|
-
this.config = { ...DEFAULT_TTL_CONFIG, ...customConfig };
|
|
7693
|
-
log$4.info('CacheKeyGenerator initialized with config:', {
|
|
7694
|
-
resources: Object.keys(this.config),
|
|
7695
|
-
});
|
|
7696
|
-
}
|
|
7697
|
-
generate(url, params) {
|
|
7698
|
-
const parsed = this.parseUrl(url);
|
|
7699
|
-
if (!parsed) {
|
|
7700
|
-
// Fallback: use URL as key
|
|
7701
|
-
const paramStr = params ? this.serializeParams(params) : '';
|
|
7702
|
-
const key = paramStr ? `${url}?${paramStr}` : url;
|
|
7703
|
-
log$4.debug('URL not matched, using fallback key:', { url, key });
|
|
7704
|
-
return key;
|
|
7705
|
-
}
|
|
7706
|
-
const { resource, ids, action, isList, parent } = parsed;
|
|
7707
|
-
if (isList) {
|
|
7708
|
-
const paramStr = params ? this.serializeParams(params) : '';
|
|
7709
|
-
const parentPart = parent && ids.length > 0 ? `${parent}=${ids[0]}&` : '';
|
|
7710
|
-
const key = `${resource}:list:${parentPart}${paramStr}`;
|
|
7711
|
-
log$4.debug('Generated list cache key:', { url, key, resource });
|
|
7712
|
-
return key;
|
|
7713
|
-
}
|
|
7714
|
-
// Single item
|
|
7715
|
-
if (ids.length === 0 && action) {
|
|
7716
|
-
// Special case for endpoints like /cashiers/me
|
|
7717
|
-
const key = `${resource}:${action}`;
|
|
7718
|
-
log$4.debug('Generated special action cache key:', { url, key, resource, action });
|
|
7719
|
-
return key;
|
|
7720
|
-
}
|
|
7721
|
-
let key = `${resource}:${ids.join(':')}`;
|
|
7722
|
-
if (action) {
|
|
7723
|
-
key += `:${action}`;
|
|
7724
|
-
}
|
|
7725
|
-
log$4.debug('Generated item cache key:', { url, key, resource, ids, action });
|
|
7726
|
-
return key;
|
|
7727
|
-
}
|
|
7728
|
-
parseResource(url) {
|
|
7729
|
-
const parsed = this.parseUrl(url);
|
|
7730
|
-
return parsed?.resource;
|
|
7731
|
-
}
|
|
7732
|
-
getTTL(url) {
|
|
7733
|
-
const resource = this.parseResource(url);
|
|
7734
|
-
if (!resource) {
|
|
7735
|
-
log$4.debug('No resource found for URL, using default TTL:', { url, ttl: DEFAULT_TTL });
|
|
7736
|
-
return DEFAULT_TTL;
|
|
7737
|
-
}
|
|
7738
|
-
const ttl = this.config[resource].ttlMs;
|
|
7739
|
-
log$4.debug('TTL for resource:', { url, resource, ttlMs: ttl, ttlMin: ttl / 60000 });
|
|
7740
|
-
return ttl;
|
|
7741
|
-
}
|
|
7742
|
-
shouldCache(url) {
|
|
7743
|
-
const parsed = this.parseUrl(url);
|
|
7744
|
-
if (!parsed) {
|
|
7745
|
-
log$4.debug('URL not recognized, should not cache:', { url });
|
|
7746
|
-
return false;
|
|
7747
|
-
}
|
|
7748
|
-
const { resource, isList } = parsed;
|
|
7749
|
-
const config = this.config[resource];
|
|
7750
|
-
if (isList) {
|
|
7751
|
-
log$4.debug('List endpoint cache decision:', {
|
|
7752
|
-
url,
|
|
7753
|
-
resource,
|
|
7754
|
-
isList: true,
|
|
7755
|
-
shouldCache: config.cacheList,
|
|
7756
|
-
});
|
|
7757
|
-
return config.cacheList;
|
|
7758
|
-
}
|
|
7759
|
-
log$4.debug('Item endpoint cache decision:', {
|
|
7760
|
-
url,
|
|
7761
|
-
resource,
|
|
7762
|
-
isList: false,
|
|
7763
|
-
shouldCache: config.cacheItem,
|
|
7764
|
-
});
|
|
7765
|
-
return config.cacheItem;
|
|
7766
|
-
}
|
|
7767
|
-
getInvalidationPatterns(url, method) {
|
|
7768
|
-
const parsed = this.parseUrl(url);
|
|
7769
|
-
if (!parsed) {
|
|
7770
|
-
log$4.debug('No patterns to invalidate for URL:', { url, method });
|
|
7771
|
-
return [];
|
|
7772
|
-
}
|
|
7773
|
-
const { resource, ids, parent } = parsed;
|
|
7774
|
-
const patterns = [];
|
|
7775
|
-
// Always invalidate list on mutations
|
|
7776
|
-
if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
|
|
7777
|
-
if (parent && ids.length > 0) {
|
|
7778
|
-
patterns.push(`${resource}:list:${parent}=${ids[0]}*`);
|
|
7779
|
-
}
|
|
7780
|
-
patterns.push(`${resource}:list:*`);
|
|
7781
|
-
}
|
|
7782
|
-
// Invalidate specific item on PUT/PATCH/DELETE
|
|
7783
|
-
if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
|
|
7784
|
-
if (ids.length > 0) {
|
|
7785
|
-
patterns.push(`${resource}:${ids.join(':')}*`);
|
|
7786
|
-
}
|
|
7787
|
-
}
|
|
7788
|
-
// Special cases
|
|
7789
|
-
if (resource === 'cashier' && (method === 'PUT' || method === 'DELETE')) {
|
|
7790
|
-
patterns.push('cashier:me');
|
|
7791
|
-
}
|
|
7792
|
-
log$4.debug('Invalidation patterns:', { url, method, patterns });
|
|
7793
|
-
return patterns;
|
|
7794
|
-
}
|
|
7795
|
-
parseUrl(url) {
|
|
7796
|
-
// Remove query string for pattern matching
|
|
7797
|
-
const urlPath = url.split('?')[0];
|
|
7798
|
-
for (const pattern of URL_PATTERNS) {
|
|
7799
|
-
const match = urlPath?.match(pattern.pattern);
|
|
7800
|
-
if (match) {
|
|
7801
|
-
// Extract IDs from capture groups
|
|
7802
|
-
const ids = match.slice(1).filter(Boolean);
|
|
7803
|
-
return {
|
|
7804
|
-
resource: pattern.resource,
|
|
7805
|
-
ids,
|
|
7806
|
-
action: pattern.action,
|
|
7807
|
-
isList: pattern.isList,
|
|
7808
|
-
parent: pattern.parent,
|
|
7809
|
-
};
|
|
7810
|
-
}
|
|
7811
|
-
}
|
|
7812
|
-
return null;
|
|
7813
|
-
}
|
|
7814
|
-
serializeParams(params) {
|
|
7815
|
-
const sortedKeys = Object.keys(params).sort();
|
|
7816
|
-
const parts = [];
|
|
7817
|
-
for (const key of sortedKeys) {
|
|
7818
|
-
const value = params[key];
|
|
7819
|
-
if (value !== undefined && value !== null) {
|
|
7820
|
-
parts.push(`${key}=${String(value)}`);
|
|
7821
|
-
}
|
|
7822
|
-
}
|
|
7823
|
-
return parts.join('&');
|
|
7824
|
-
}
|
|
7825
|
-
}
|
|
7826
|
-
|
|
7827
|
-
const log$3 = createPrefixedLogger('CACHE');
|
|
7828
|
-
class CachingHttpDecorator {
|
|
7829
|
-
constructor(http, cache, keyGenerator, networkMonitor, config = {}) {
|
|
7830
|
-
this.http = http;
|
|
7831
|
-
this.cache = cache;
|
|
7832
|
-
this.keyGenerator = keyGenerator;
|
|
7833
|
-
this.networkMonitor = networkMonitor;
|
|
7834
|
-
this.config = config;
|
|
7835
|
-
this.currentOnlineState = true;
|
|
7836
|
-
this.authToken = null;
|
|
7837
|
-
log$3.info('CachingHttpDecorator initialized', {
|
|
7838
|
-
enabled: config.enabled !== false,
|
|
7839
|
-
hasNetworkMonitor: !!networkMonitor,
|
|
7840
|
-
});
|
|
7841
|
-
this.setupNetworkMonitoring();
|
|
5196
|
+
lastMessageFromAde: output.last_message_from_ade
|
|
5197
|
+
? this.messageFromApi(output.last_message_from_ade)
|
|
5198
|
+
: null,
|
|
5199
|
+
lottery: this.lotteryFromApi(output.lottery),
|
|
5200
|
+
};
|
|
7842
5201
|
}
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
this.currentOnlineState = online;
|
|
7850
|
-
});
|
|
7851
|
-
}
|
|
5202
|
+
static merchantFromApi(output) {
|
|
5203
|
+
return {
|
|
5204
|
+
vatNumber: output.vat_number,
|
|
5205
|
+
fiscalCode: output.fiscal_code,
|
|
5206
|
+
businessName: output.business_name,
|
|
5207
|
+
};
|
|
7852
5208
|
}
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
}
|
|
7860
|
-
return true;
|
|
5209
|
+
static supplierFromApi(output) {
|
|
5210
|
+
return {
|
|
5211
|
+
vatNumber: output.vat_number,
|
|
5212
|
+
fiscalCode: output.fiscal_code,
|
|
5213
|
+
businessName: output.business_name,
|
|
5214
|
+
};
|
|
7861
5215
|
}
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
}
|
|
7869
|
-
// Check if this URL should be cached
|
|
7870
|
-
const shouldCache = this.keyGenerator.shouldCache(url);
|
|
7871
|
-
if (!shouldCache) {
|
|
7872
|
-
log$3.debug('GET (not cacheable - likely a list endpoint):', { url });
|
|
7873
|
-
return this.http.get(url, config);
|
|
7874
|
-
}
|
|
7875
|
-
const cacheKey = this.keyGenerator.generate(url, config?.params);
|
|
7876
|
-
const ttl = this.keyGenerator.getTTL(url);
|
|
7877
|
-
const resource = this.keyGenerator.parseResource(url);
|
|
7878
|
-
log$3.info('GET request starting:', {
|
|
7879
|
-
url,
|
|
7880
|
-
resource,
|
|
7881
|
-
cacheKey,
|
|
7882
|
-
ttlMs: ttl,
|
|
7883
|
-
ttlMin: Math.round(ttl / 60000),
|
|
7884
|
-
params: config?.params,
|
|
7885
|
-
});
|
|
7886
|
-
// Check cache
|
|
7887
|
-
let cached = null;
|
|
7888
|
-
try {
|
|
7889
|
-
cached = await this.cache.get(cacheKey);
|
|
7890
|
-
if (cached) {
|
|
7891
|
-
log$3.debug('Cache entry found:', {
|
|
7892
|
-
cacheKey,
|
|
7893
|
-
timestamp: new Date(cached.timestamp).toISOString(),
|
|
7894
|
-
ageMs: Date.now() - cached.timestamp,
|
|
7895
|
-
});
|
|
7896
|
-
}
|
|
7897
|
-
else {
|
|
7898
|
-
log$3.debug('Cache entry not found:', { cacheKey });
|
|
7899
|
-
}
|
|
7900
|
-
}
|
|
7901
|
-
catch (error) {
|
|
7902
|
-
log$3.warn('Cache lookup failed:', {
|
|
7903
|
-
cacheKey,
|
|
7904
|
-
error: error instanceof Error ? error.message : error,
|
|
7905
|
-
});
|
|
7906
|
-
}
|
|
7907
|
-
if (cached) {
|
|
7908
|
-
const age = Date.now() - cached.timestamp;
|
|
7909
|
-
const isExpired = age >= ttl;
|
|
7910
|
-
log$3.debug('Cache analysis:', {
|
|
7911
|
-
cacheKey,
|
|
7912
|
-
ageMs: age,
|
|
7913
|
-
ageSec: Math.round(age / 1000),
|
|
7914
|
-
ttlMs: ttl,
|
|
7915
|
-
isExpired,
|
|
7916
|
-
isOnline: this.isOnline(),
|
|
7917
|
-
});
|
|
7918
|
-
// If within TTL, return cached data
|
|
7919
|
-
if (!isExpired) {
|
|
7920
|
-
const duration = Date.now() - startTime;
|
|
7921
|
-
log$3.info('CACHE HIT:', {
|
|
7922
|
-
url,
|
|
7923
|
-
cacheKey,
|
|
7924
|
-
ageMs: age,
|
|
7925
|
-
durationMs: duration,
|
|
7926
|
-
});
|
|
7927
|
-
return {
|
|
7928
|
-
data: cached.data,
|
|
7929
|
-
status: 200,
|
|
7930
|
-
headers: { 'x-cache': 'HIT' },
|
|
7931
|
-
};
|
|
7932
|
-
}
|
|
7933
|
-
// If offline and cache is stale, return stale data
|
|
7934
|
-
if (!this.isOnline()) {
|
|
7935
|
-
const duration = Date.now() - startTime;
|
|
7936
|
-
log$3.info('CACHE STALE (offline):', {
|
|
7937
|
-
url,
|
|
7938
|
-
cacheKey,
|
|
7939
|
-
ageMs: age,
|
|
7940
|
-
durationMs: duration,
|
|
7941
|
-
});
|
|
7942
|
-
return {
|
|
7943
|
-
data: cached.data,
|
|
7944
|
-
status: 200,
|
|
7945
|
-
headers: { 'x-cache': 'STALE' },
|
|
7946
|
-
};
|
|
7947
|
-
}
|
|
7948
|
-
log$3.debug('Cache expired, fetching fresh data:', { cacheKey, ageMs: age, ttlMs: ttl });
|
|
7949
|
-
}
|
|
7950
|
-
// Fetch fresh data
|
|
7951
|
-
try {
|
|
7952
|
-
log$3.debug('Fetching from network:', { url });
|
|
7953
|
-
const response = await this.http.get(url, config);
|
|
7954
|
-
// Cache the response
|
|
7955
|
-
try {
|
|
7956
|
-
await this.cache.set(cacheKey, response.data);
|
|
7957
|
-
log$3.debug('Response cached successfully:', { cacheKey });
|
|
7958
|
-
}
|
|
7959
|
-
catch (error) {
|
|
7960
|
-
log$3.error('Failed to cache response:', {
|
|
7961
|
-
cacheKey,
|
|
7962
|
-
error: error instanceof Error ? error.message : error,
|
|
7963
|
-
});
|
|
7964
|
-
}
|
|
7965
|
-
const duration = Date.now() - startTime;
|
|
7966
|
-
log$3.info('CACHE MISS (fetched fresh):', {
|
|
7967
|
-
url,
|
|
7968
|
-
cacheKey,
|
|
7969
|
-
status: response.status,
|
|
7970
|
-
durationMs: duration,
|
|
7971
|
-
});
|
|
7972
|
-
return {
|
|
7973
|
-
...response,
|
|
7974
|
-
headers: { ...response.headers, 'x-cache': 'MISS' },
|
|
7975
|
-
};
|
|
7976
|
-
}
|
|
7977
|
-
catch (error) {
|
|
7978
|
-
// On error, return stale cache if available
|
|
7979
|
-
if (cached) {
|
|
7980
|
-
const duration = Date.now() - startTime;
|
|
7981
|
-
log$3.warn('CACHE STALE (network error):', {
|
|
7982
|
-
url,
|
|
7983
|
-
cacheKey,
|
|
7984
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
7985
|
-
durationMs: duration,
|
|
7986
|
-
});
|
|
7987
|
-
return {
|
|
7988
|
-
data: cached.data,
|
|
7989
|
-
status: 200,
|
|
7990
|
-
headers: { 'x-cache': 'STALE' },
|
|
7991
|
-
};
|
|
7992
|
-
}
|
|
7993
|
-
log$3.error('Network error with no cache fallback:', {
|
|
7994
|
-
url,
|
|
7995
|
-
error: error instanceof Error ? error.message : error,
|
|
7996
|
-
});
|
|
7997
|
-
throw error;
|
|
7998
|
-
}
|
|
5216
|
+
static softwareVersionFromApi(output) {
|
|
5217
|
+
return {
|
|
5218
|
+
version: output.version,
|
|
5219
|
+
swid: output.swid,
|
|
5220
|
+
installedAt: output.installed_at,
|
|
5221
|
+
status: output.status,
|
|
5222
|
+
};
|
|
7999
5223
|
}
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
5224
|
+
static softwareFromApi(output) {
|
|
5225
|
+
return {
|
|
5226
|
+
code: output.code,
|
|
5227
|
+
name: output.name,
|
|
5228
|
+
approvalReference: output.approval_reference,
|
|
5229
|
+
versionInfo: output.version_info ? this.softwareVersionFromApi(output.version_info) : null,
|
|
5230
|
+
};
|
|
8005
5231
|
}
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
5232
|
+
static pendingReceiptsFromApi(output) {
|
|
5233
|
+
return {
|
|
5234
|
+
count: output.count,
|
|
5235
|
+
totalAmount: output.total_amount,
|
|
5236
|
+
};
|
|
8011
5237
|
}
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
5238
|
+
static transmissionFromApi(output) {
|
|
5239
|
+
return {
|
|
5240
|
+
attemptedAt: output.attempted_at,
|
|
5241
|
+
outcome: output.outcome,
|
|
5242
|
+
};
|
|
8017
5243
|
}
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
5244
|
+
static messageFromApi(output) {
|
|
5245
|
+
return {
|
|
5246
|
+
receivedAt: output.received_at,
|
|
5247
|
+
content: output.content,
|
|
5248
|
+
};
|
|
8023
5249
|
}
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
});
|
|
8030
|
-
this.authToken = token;
|
|
8031
|
-
this.http.setAuthToken(token);
|
|
5250
|
+
static secretRequestFromApi(output) {
|
|
5251
|
+
return {
|
|
5252
|
+
requestedAt: output.requested_at,
|
|
5253
|
+
outcome: output.outcome,
|
|
5254
|
+
};
|
|
8032
5255
|
}
|
|
8033
|
-
|
|
8034
|
-
return
|
|
5256
|
+
static lotteryFromApi(output) {
|
|
5257
|
+
return {
|
|
5258
|
+
lastTransmission: output.last_transmission
|
|
5259
|
+
? this.transmissionFromApi(output.last_transmission)
|
|
5260
|
+
: null,
|
|
5261
|
+
secretRequest: output.secret_request
|
|
5262
|
+
? this.secretRequestFromApi(output.secret_request)
|
|
5263
|
+
: null,
|
|
5264
|
+
};
|
|
8035
5265
|
}
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
}
|
|
8042
|
-
log$3.info('Invalidating cache patterns:', { url, method, patterns });
|
|
8043
|
-
for (const pattern of patterns) {
|
|
8044
|
-
try {
|
|
8045
|
-
await this.cache.invalidate(pattern);
|
|
8046
|
-
log$3.debug('Cache pattern invalidated:', { pattern });
|
|
8047
|
-
}
|
|
8048
|
-
catch (error) {
|
|
8049
|
-
log$3.error('Failed to invalidate pattern:', {
|
|
8050
|
-
pattern,
|
|
8051
|
-
error: error instanceof Error ? error.message : error,
|
|
8052
|
-
});
|
|
8053
|
-
}
|
|
8054
|
-
}
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5268
|
+
class TelemetryRepositoryImpl {
|
|
5269
|
+
constructor(http) {
|
|
5270
|
+
this.http = http;
|
|
8055
5271
|
}
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
5272
|
+
async getTelemetry(pemId) {
|
|
5273
|
+
const response = await this.http.get(`/mf1/pems/${pemId}/telemetry`);
|
|
5274
|
+
return TelemetryMapper.fromApiOutput(response.data);
|
|
8059
5275
|
}
|
|
8060
5276
|
}
|
|
8061
5277
|
|
|
@@ -8328,7 +5544,6 @@ class SDKFactory {
|
|
|
8328
5544
|
baseUrl: config.baseUrl,
|
|
8329
5545
|
timeout: config.timeout,
|
|
8330
5546
|
});
|
|
8331
|
-
container.register(DI_TOKENS.BASE_HTTP_PORT, httpAdapter);
|
|
8332
5547
|
container.register(DI_TOKENS.HTTP_PORT, httpAdapter);
|
|
8333
5548
|
container.registerFactory(DI_TOKENS.RECEIPT_REPOSITORY, () => {
|
|
8334
5549
|
const http = container.get(DI_TOKENS.HTTP_PORT);
|
|
@@ -8376,23 +5591,6 @@ class SDKFactory {
|
|
|
8376
5591
|
});
|
|
8377
5592
|
return container;
|
|
8378
5593
|
}
|
|
8379
|
-
static registerCacheServices(container, cache, network) {
|
|
8380
|
-
container.register(DI_TOKENS.CACHE_PORT, cache);
|
|
8381
|
-
if (network) {
|
|
8382
|
-
container.register(DI_TOKENS.NETWORK_PORT, network);
|
|
8383
|
-
}
|
|
8384
|
-
const keyGenerator = new CacheKeyGenerator();
|
|
8385
|
-
container.register(DI_TOKENS.CACHE_KEY_GENERATOR, keyGenerator);
|
|
8386
|
-
const baseHttp = container.get(DI_TOKENS.BASE_HTTP_PORT);
|
|
8387
|
-
const cachingHttp = new CachingHttpDecorator(baseHttp, cache, keyGenerator, network);
|
|
8388
|
-
container.register(DI_TOKENS.HTTP_PORT, cachingHttp);
|
|
8389
|
-
}
|
|
8390
|
-
static getCacheKeyGenerator(container) {
|
|
8391
|
-
if (container.has(DI_TOKENS.CACHE_KEY_GENERATOR)) {
|
|
8392
|
-
return container.get(DI_TOKENS.CACHE_KEY_GENERATOR);
|
|
8393
|
-
}
|
|
8394
|
-
return undefined;
|
|
8395
|
-
}
|
|
8396
5594
|
static registerAuthServices(container, secureStorage, config) {
|
|
8397
5595
|
const tokenStorage = new TokenStorageAdapter(secureStorage);
|
|
8398
5596
|
container.register(DI_TOKENS.TOKEN_STORAGE_PORT, tokenStorage);
|
|
@@ -8430,7 +5628,7 @@ class SDKFactory {
|
|
|
8430
5628
|
}
|
|
8431
5629
|
}
|
|
8432
5630
|
|
|
8433
|
-
const log$
|
|
5631
|
+
const log$1 = createPrefixedLogger('SDK');
|
|
8434
5632
|
class ACubeSDK {
|
|
8435
5633
|
constructor(config, customAdapters, events = {}) {
|
|
8436
5634
|
this.events = events;
|
|
@@ -8444,23 +5642,22 @@ class ACubeSDK {
|
|
|
8444
5642
|
}
|
|
8445
5643
|
async initialize() {
|
|
8446
5644
|
if (this.isInitialized) {
|
|
8447
|
-
log$
|
|
5645
|
+
log$1.debug('SDK already initialized, skipping');
|
|
8448
5646
|
return;
|
|
8449
5647
|
}
|
|
8450
|
-
log$
|
|
5648
|
+
log$1.info('Initializing SDK', {
|
|
8451
5649
|
apiUrl: this.config.getApiUrl(),
|
|
8452
5650
|
authUrl: this.config.getAuthUrl(),
|
|
8453
5651
|
debugEnabled: this.config.isDebugEnabled(),
|
|
8454
5652
|
});
|
|
8455
5653
|
try {
|
|
8456
5654
|
if (!this.adapters) {
|
|
8457
|
-
log$
|
|
5655
|
+
log$1.debug('Loading platform adapters');
|
|
8458
5656
|
const mtlsConfig = createACubeMTLSConfig(this.config.getApiUrl(), this.config.getTimeout(), true);
|
|
8459
5657
|
this.adapters = loadPlatformAdapters({
|
|
8460
5658
|
mtlsConfig,
|
|
8461
5659
|
});
|
|
8462
|
-
log$
|
|
8463
|
-
hasCache: !!this.adapters.cache,
|
|
5660
|
+
log$1.info('Platform adapters loaded', {
|
|
8464
5661
|
hasNetworkMonitor: !!this.adapters.networkMonitor,
|
|
8465
5662
|
hasMtls: !!this.adapters.mtls,
|
|
8466
5663
|
hasSecureStorage: !!this.adapters.secureStorage,
|
|
@@ -8472,30 +5669,15 @@ class ACubeSDK {
|
|
|
8472
5669
|
timeout: this.config.getTimeout(),
|
|
8473
5670
|
debugEnabled: this.config.isDebugEnabled(),
|
|
8474
5671
|
};
|
|
8475
|
-
log$
|
|
5672
|
+
log$1.debug('Creating DI container');
|
|
8476
5673
|
this.container = SDKFactory.createContainer(factoryConfig);
|
|
8477
|
-
log$
|
|
5674
|
+
log$1.debug('Registering auth services');
|
|
8478
5675
|
SDKFactory.registerAuthServices(this.container, this.adapters.secureStorage, factoryConfig);
|
|
8479
|
-
|
|
8480
|
-
log$2.info('Registering cache services', {
|
|
8481
|
-
hasNetworkMonitor: !!this.adapters.networkMonitor,
|
|
8482
|
-
});
|
|
8483
|
-
SDKFactory.registerCacheServices(this.container, this.adapters.cache, this.adapters.networkMonitor);
|
|
8484
|
-
}
|
|
8485
|
-
else {
|
|
8486
|
-
log$2.debug('No cache adapter available, caching disabled');
|
|
8487
|
-
}
|
|
8488
|
-
log$2.debug('Initializing certificate service');
|
|
5676
|
+
log$1.debug('Initializing certificate service');
|
|
8489
5677
|
this.certificateService = new CertificateService(this.adapters.secureStorage);
|
|
8490
5678
|
const tokenStorage = this.container.get(DI_TOKENS.TOKEN_STORAGE_PORT);
|
|
8491
5679
|
const httpPort = this.container.get(DI_TOKENS.HTTP_PORT);
|
|
8492
|
-
|
|
8493
|
-
log$2.debug('HTTP ports initialized', {
|
|
8494
|
-
httpPortType: httpPort.constructor.name,
|
|
8495
|
-
baseHttpPortType: baseHttpPort.constructor.name,
|
|
8496
|
-
areSameInstance: httpPort === baseHttpPort,
|
|
8497
|
-
});
|
|
8498
|
-
log$2.debug('Initializing authentication service');
|
|
5680
|
+
log$1.debug('Initializing authentication service');
|
|
8499
5681
|
this.authService = new AuthenticationService(httpPort, tokenStorage, {
|
|
8500
5682
|
authUrl: this.config.getAuthUrl(),
|
|
8501
5683
|
timeout: this.config.getTimeout(),
|
|
@@ -8505,51 +5687,33 @@ class ACubeSDK {
|
|
|
8505
5687
|
this.events.onAuthError?.(new ACubeSDKError('AUTH_ERROR', error.message, error));
|
|
8506
5688
|
},
|
|
8507
5689
|
});
|
|
8508
|
-
log$2.debug('Initializing offline manager');
|
|
8509
|
-
const queueEvents = {
|
|
8510
|
-
onOperationAdded: (operation) => {
|
|
8511
|
-
this.events.onOfflineOperationAdded?.(operation.id);
|
|
8512
|
-
},
|
|
8513
|
-
onOperationCompleted: (result) => {
|
|
8514
|
-
this.events.onOfflineOperationCompleted?.(result.operation.id, result.success);
|
|
8515
|
-
},
|
|
8516
|
-
onOperationFailed: (result) => {
|
|
8517
|
-
this.events.onOfflineOperationCompleted?.(result.operation.id, false);
|
|
8518
|
-
},
|
|
8519
|
-
};
|
|
8520
|
-
this.offlineManager = new OfflineManager(this.adapters.storage, httpPort, this.adapters.networkMonitor, {
|
|
8521
|
-
syncInterval: 30000,
|
|
8522
|
-
}, queueEvents);
|
|
8523
5690
|
this.networkSubscription = this.adapters.networkMonitor.online$.subscribe((online) => {
|
|
8524
5691
|
this.currentOnlineState = online;
|
|
8525
5692
|
this.events.onNetworkStatusChanged?.(online);
|
|
8526
|
-
if (online && this.offlineManager) {
|
|
8527
|
-
this.offlineManager.sync().catch(() => { });
|
|
8528
|
-
}
|
|
8529
5693
|
});
|
|
8530
5694
|
const isAuth = await this.authService.isAuthenticated();
|
|
8531
|
-
log$
|
|
5695
|
+
log$1.debug('Checking authentication status during init', { isAuthenticated: isAuth });
|
|
8532
5696
|
if (isAuth) {
|
|
8533
5697
|
const token = await this.authService.getAccessToken();
|
|
8534
|
-
log$
|
|
5698
|
+
log$1.debug('Token retrieved during init', {
|
|
8535
5699
|
hasToken: !!token,
|
|
8536
5700
|
tokenPrefix: token?.substring(0, 20),
|
|
8537
5701
|
});
|
|
8538
5702
|
if (token) {
|
|
8539
5703
|
httpPort.setAuthToken(token);
|
|
8540
|
-
log$
|
|
5704
|
+
log$1.info('Auth token set on HTTP port during initialization');
|
|
8541
5705
|
}
|
|
8542
5706
|
}
|
|
8543
5707
|
else {
|
|
8544
|
-
log$
|
|
5708
|
+
log$1.warn('User not authenticated during SDK init - token will be set after login');
|
|
8545
5709
|
}
|
|
8546
|
-
if (this.adapters?.mtls && 'setMTLSAdapter' in
|
|
8547
|
-
log$
|
|
8548
|
-
const httpWithMtls =
|
|
5710
|
+
if (this.adapters?.mtls && 'setMTLSAdapter' in httpPort) {
|
|
5711
|
+
log$1.debug('Connecting mTLS adapter to HTTP port');
|
|
5712
|
+
const httpWithMtls = httpPort;
|
|
8549
5713
|
httpWithMtls.setMTLSAdapter(this.adapters.mtls);
|
|
8550
5714
|
}
|
|
8551
|
-
if ('setAuthStrategy' in
|
|
8552
|
-
log$
|
|
5715
|
+
if ('setAuthStrategy' in httpPort) {
|
|
5716
|
+
log$1.debug('Configuring auth strategy');
|
|
8553
5717
|
const jwtHandler = new JwtAuthHandler(tokenStorage);
|
|
8554
5718
|
const certificatePort = this.certificateService
|
|
8555
5719
|
? {
|
|
@@ -8580,7 +5744,7 @@ class ACubeSDK {
|
|
|
8580
5744
|
},
|
|
8581
5745
|
};
|
|
8582
5746
|
const authStrategy = new AuthStrategy(jwtHandler, mtlsHandler, userProvider, this.adapters?.mtls || null);
|
|
8583
|
-
const httpWithStrategy =
|
|
5747
|
+
const httpWithStrategy = httpPort;
|
|
8584
5748
|
httpWithStrategy.setAuthStrategy(authStrategy);
|
|
8585
5749
|
}
|
|
8586
5750
|
if (this.adapters?.mtls && this.certificateService) {
|
|
@@ -8598,19 +5762,18 @@ class ACubeSDK {
|
|
|
8598
5762
|
}
|
|
8599
5763
|
}
|
|
8600
5764
|
catch (certError) {
|
|
8601
|
-
log$
|
|
5765
|
+
log$1.warn('Certificate auto-configuration failed, will retry on demand', {
|
|
8602
5766
|
error: certError instanceof Error ? certError.message : certError,
|
|
8603
5767
|
});
|
|
8604
5768
|
}
|
|
8605
5769
|
}
|
|
8606
5770
|
this.isInitialized = true;
|
|
8607
|
-
log$
|
|
8608
|
-
hasCache: !!this.adapters.cache,
|
|
5771
|
+
log$1.info('SDK initialized successfully', {
|
|
8609
5772
|
hasMtls: !!this.adapters.mtls,
|
|
8610
5773
|
});
|
|
8611
5774
|
}
|
|
8612
5775
|
catch (error) {
|
|
8613
|
-
log$
|
|
5776
|
+
log$1.error('SDK initialization failed', {
|
|
8614
5777
|
error: error instanceof Error ? error.message : error,
|
|
8615
5778
|
});
|
|
8616
5779
|
throw new ACubeSDKError('SDK_INITIALIZATION_ERROR', `Failed to initialize SDK: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
@@ -8666,19 +5829,19 @@ class ACubeSDK {
|
|
|
8666
5829
|
}
|
|
8667
5830
|
async login(credentials) {
|
|
8668
5831
|
this.ensureInitialized();
|
|
8669
|
-
log$
|
|
5832
|
+
log$1.info('Login attempt', { email: credentials.email });
|
|
8670
5833
|
const user = await this.authService.login(credentials);
|
|
8671
|
-
log$
|
|
5834
|
+
log$1.info('Login successful', { roles: user.roles });
|
|
8672
5835
|
const token = await this.authService.getAccessToken();
|
|
8673
5836
|
if (token) {
|
|
8674
5837
|
this.httpPort.setAuthToken(token);
|
|
8675
|
-
log$
|
|
5838
|
+
log$1.debug('Auth token set on HTTP port');
|
|
8676
5839
|
}
|
|
8677
5840
|
return user;
|
|
8678
5841
|
}
|
|
8679
5842
|
async logout() {
|
|
8680
5843
|
this.ensureInitialized();
|
|
8681
|
-
log$
|
|
5844
|
+
log$1.info('Logout');
|
|
8682
5845
|
await this.authService.logout();
|
|
8683
5846
|
this.httpPort.setAuthToken(null);
|
|
8684
5847
|
}
|
|
@@ -8696,10 +5859,6 @@ class ACubeSDK {
|
|
|
8696
5859
|
this.ensureInitialized();
|
|
8697
5860
|
return await this.authService.isAuthenticated();
|
|
8698
5861
|
}
|
|
8699
|
-
getOfflineManager() {
|
|
8700
|
-
this.ensureInitialized();
|
|
8701
|
-
return this.offlineManager;
|
|
8702
|
-
}
|
|
8703
5862
|
isOnline() {
|
|
8704
5863
|
this.ensureInitialized();
|
|
8705
5864
|
return this.currentOnlineState;
|
|
@@ -8827,7 +5986,6 @@ class ACubeSDK {
|
|
|
8827
5986
|
}
|
|
8828
5987
|
destroy() {
|
|
8829
5988
|
this.networkSubscription?.unsubscribe();
|
|
8830
|
-
this.offlineManager?.destroy();
|
|
8831
5989
|
this.container?.clear();
|
|
8832
5990
|
this.isInitialized = false;
|
|
8833
5991
|
}
|
|
@@ -9113,7 +6271,6 @@ class TelemetryService {
|
|
|
9113
6271
|
this.events = events;
|
|
9114
6272
|
this.stateSubject = new BehaviorSubject({
|
|
9115
6273
|
data: null,
|
|
9116
|
-
isCached: false,
|
|
9117
6274
|
isLoading: false,
|
|
9118
6275
|
lastFetchedAt: null,
|
|
9119
6276
|
});
|
|
@@ -9183,7 +6340,6 @@ class TelemetryService {
|
|
|
9183
6340
|
const data = await this.repository.getTelemetry(this.currentPemId);
|
|
9184
6341
|
const newState = {
|
|
9185
6342
|
data,
|
|
9186
|
-
isCached: false,
|
|
9187
6343
|
isLoading: false,
|
|
9188
6344
|
lastFetchedAt: Date.now(),
|
|
9189
6345
|
};
|
|
@@ -9208,7 +6364,6 @@ class TelemetryService {
|
|
|
9208
6364
|
clearTelemetry() {
|
|
9209
6365
|
this.stateSubject.next({
|
|
9210
6366
|
data: null,
|
|
9211
|
-
isCached: false,
|
|
9212
6367
|
isLoading: false,
|
|
9213
6368
|
lastFetchedAt: null,
|
|
9214
6369
|
});
|
|
@@ -9221,7 +6376,7 @@ class TelemetryService {
|
|
|
9221
6376
|
}
|
|
9222
6377
|
}
|
|
9223
6378
|
|
|
9224
|
-
const log
|
|
6379
|
+
const log = createPrefixedLogger('SDK-MANAGER');
|
|
9225
6380
|
/**
|
|
9226
6381
|
* SDKManager - Singleton wrapper for ACubeSDK with simplified API
|
|
9227
6382
|
*
|
|
@@ -9288,7 +6443,7 @@ class SDKManager {
|
|
|
9288
6443
|
if (canPoll && !this.isPollingActive) {
|
|
9289
6444
|
const hasCert = await this.checkCertificate();
|
|
9290
6445
|
if (!hasCert) {
|
|
9291
|
-
log
|
|
6446
|
+
log.warn('Certificate missing — polling blocked until certificate is installed');
|
|
9292
6447
|
return;
|
|
9293
6448
|
}
|
|
9294
6449
|
this.notificationService?.startPolling();
|
|
@@ -9390,7 +6545,7 @@ class SDKManager {
|
|
|
9390
6545
|
this.isPollingActive = true;
|
|
9391
6546
|
}
|
|
9392
6547
|
else {
|
|
9393
|
-
log
|
|
6548
|
+
log.warn('Certificate missing at init — polling blocked until certificate is installed');
|
|
9394
6549
|
}
|
|
9395
6550
|
}
|
|
9396
6551
|
// AppStateService remains active for all users (handles OFFLINE network state)
|
|
@@ -9433,7 +6588,7 @@ class SDKManager {
|
|
|
9433
6588
|
return this.certificateMissingSubject.asObservable();
|
|
9434
6589
|
}
|
|
9435
6590
|
/**
|
|
9436
|
-
* Observable stream of telemetry state (data, isLoading,
|
|
6591
|
+
* Observable stream of telemetry state (data, isLoading, error)
|
|
9437
6592
|
*/
|
|
9438
6593
|
get telemetryState$() {
|
|
9439
6594
|
this.ensureInitialized();
|
|
@@ -9517,7 +6672,7 @@ class SDKManager {
|
|
|
9517
6672
|
const user = await sdk.getCurrentUser();
|
|
9518
6673
|
const canPoll = user && hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
|
|
9519
6674
|
if (canPoll) {
|
|
9520
|
-
log
|
|
6675
|
+
log.info('Certificate installed — starting polling');
|
|
9521
6676
|
this.notificationService?.startPolling();
|
|
9522
6677
|
await this.startTelemetryPollingAuto();
|
|
9523
6678
|
this.isPollingActive = true;
|
|
@@ -9530,7 +6685,7 @@ class SDKManager {
|
|
|
9530
6685
|
this.certificateMissingSubject.next(true);
|
|
9531
6686
|
// Stop polling since certificate is required
|
|
9532
6687
|
if (this.isPollingActive) {
|
|
9533
|
-
log
|
|
6688
|
+
log.info('Certificate removed — stopping polling');
|
|
9534
6689
|
this.notificationService?.stopPolling();
|
|
9535
6690
|
this.telemetryService?.stopPolling();
|
|
9536
6691
|
this.telemetryService?.clearTelemetry();
|
|
@@ -9610,400 +6765,59 @@ const RECEIPT_SENT = 'sent';
|
|
|
9610
6765
|
const RECEIPT_SORT_DESCENDING = 'descending';
|
|
9611
6766
|
const RECEIPT_SORT_ASCENDING = 'ascending';
|
|
9612
6767
|
|
|
9613
|
-
const log = createPrefixedLogger('CACHE-HANDLER');
|
|
9614
|
-
class CacheHandler {
|
|
9615
|
-
constructor(cache, networkMonitor) {
|
|
9616
|
-
this.cache = cache;
|
|
9617
|
-
this.networkMonitor = networkMonitor;
|
|
9618
|
-
this.currentOnlineState = true;
|
|
9619
|
-
this.setupNetworkMonitoring();
|
|
9620
|
-
}
|
|
9621
|
-
setupNetworkMonitoring() {
|
|
9622
|
-
if (this.networkMonitor) {
|
|
9623
|
-
this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
|
|
9624
|
-
this.currentOnlineState = online;
|
|
9625
|
-
});
|
|
9626
|
-
}
|
|
9627
|
-
}
|
|
9628
|
-
isOnline() {
|
|
9629
|
-
if (this.networkMonitor) {
|
|
9630
|
-
return this.currentOnlineState;
|
|
9631
|
-
}
|
|
9632
|
-
if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
|
|
9633
|
-
return navigator.onLine;
|
|
9634
|
-
}
|
|
9635
|
-
return false;
|
|
9636
|
-
}
|
|
9637
|
-
async handleCachedRequest(url, requestFn, config) {
|
|
9638
|
-
if (!this.cache || config?.useCache === false) {
|
|
9639
|
-
const response = await requestFn();
|
|
9640
|
-
return response.data;
|
|
9641
|
-
}
|
|
9642
|
-
const cacheKey = this.generateCacheKey(url);
|
|
9643
|
-
const online = this.isOnline();
|
|
9644
|
-
log.debug('Request:', { url, cacheKey, online });
|
|
9645
|
-
if (online) {
|
|
9646
|
-
try {
|
|
9647
|
-
const response = await requestFn();
|
|
9648
|
-
if (this.cache) {
|
|
9649
|
-
await this.cache.set(cacheKey, response.data).catch((error) => {
|
|
9650
|
-
log.error('Failed to cache:', error instanceof Error ? error.message : error);
|
|
9651
|
-
});
|
|
9652
|
-
}
|
|
9653
|
-
return response.data;
|
|
9654
|
-
}
|
|
9655
|
-
catch (error) {
|
|
9656
|
-
const cached = await this.cache.get(cacheKey).catch(() => null);
|
|
9657
|
-
if (cached) {
|
|
9658
|
-
log.debug('Network failed, using cache fallback');
|
|
9659
|
-
return cached.data;
|
|
9660
|
-
}
|
|
9661
|
-
throw error;
|
|
9662
|
-
}
|
|
9663
|
-
}
|
|
9664
|
-
else {
|
|
9665
|
-
const cached = await this.cache.get(cacheKey).catch(() => null);
|
|
9666
|
-
if (cached) {
|
|
9667
|
-
log.debug('Offline, returning cached data');
|
|
9668
|
-
return cached.data;
|
|
9669
|
-
}
|
|
9670
|
-
throw new Error('Offline: No cached data available');
|
|
9671
|
-
}
|
|
9672
|
-
}
|
|
9673
|
-
generateCacheKey(url) {
|
|
9674
|
-
return url;
|
|
9675
|
-
}
|
|
9676
|
-
async invalidateCache(pattern) {
|
|
9677
|
-
if (!this.cache)
|
|
9678
|
-
return;
|
|
9679
|
-
try {
|
|
9680
|
-
await this.cache.invalidate(pattern);
|
|
9681
|
-
}
|
|
9682
|
-
catch (error) {
|
|
9683
|
-
log.error('Invalidation failed:', error instanceof Error ? error.message : error);
|
|
9684
|
-
}
|
|
9685
|
-
}
|
|
9686
|
-
getCacheStatus() {
|
|
9687
|
-
return {
|
|
9688
|
-
available: !!this.cache,
|
|
9689
|
-
networkMonitorAvailable: !!this.networkMonitor,
|
|
9690
|
-
isOnline: this.isOnline(),
|
|
9691
|
-
};
|
|
9692
|
-
}
|
|
9693
|
-
destroy() {
|
|
9694
|
-
this.networkSubscription?.unsubscribe();
|
|
9695
|
-
}
|
|
9696
|
-
}
|
|
9697
|
-
|
|
9698
|
-
function transformError(error) {
|
|
9699
|
-
if (axios.isAxiosError(error)) {
|
|
9700
|
-
const response = error.response;
|
|
9701
|
-
if (!response) {
|
|
9702
|
-
return new ACubeSDKError('NETWORK_ERROR', 'Network error occurred', error);
|
|
9703
|
-
}
|
|
9704
|
-
const status = response.status;
|
|
9705
|
-
const data = response.data;
|
|
9706
|
-
const violations = data?.violations;
|
|
9707
|
-
let message = 'Unknown error occurred';
|
|
9708
|
-
if (data?.detail) {
|
|
9709
|
-
message = data.detail;
|
|
9710
|
-
}
|
|
9711
|
-
else if (data?.title) {
|
|
9712
|
-
message = data.title;
|
|
9713
|
-
}
|
|
9714
|
-
else if (error.message) {
|
|
9715
|
-
message = error.message;
|
|
9716
|
-
}
|
|
9717
|
-
switch (status) {
|
|
9718
|
-
case 400:
|
|
9719
|
-
return new ACubeSDKError('VALIDATION_ERROR', message, error, status, violations);
|
|
9720
|
-
case 401:
|
|
9721
|
-
return new ACubeSDKError('AUTH_ERROR', message, error, status, violations);
|
|
9722
|
-
case 403:
|
|
9723
|
-
return new ACubeSDKError('FORBIDDEN_ERROR', message, error, status, violations);
|
|
9724
|
-
case 404:
|
|
9725
|
-
return new ACubeSDKError('NOT_FOUND_ERROR', message, error, status, violations);
|
|
9726
|
-
case 422:
|
|
9727
|
-
return new ACubeSDKError('VALIDATION_ERROR', message, error, status, violations);
|
|
9728
|
-
default:
|
|
9729
|
-
return new ACubeSDKError('UNKNOWN_ERROR', message, error, status, violations);
|
|
9730
|
-
}
|
|
9731
|
-
}
|
|
9732
|
-
return new ACubeSDKError('UNKNOWN_ERROR', 'Unknown error occurred', error);
|
|
9733
|
-
}
|
|
9734
|
-
|
|
9735
|
-
exports.ErrorCategory = void 0;
|
|
9736
|
-
(function (ErrorCategory) {
|
|
9737
|
-
ErrorCategory["SERVER_ERROR"] = "SERVER_ERROR";
|
|
9738
|
-
ErrorCategory["CLIENT_ERROR"] = "CLIENT_ERROR";
|
|
9739
|
-
ErrorCategory["AUTH_ERROR"] = "AUTH_ERROR";
|
|
9740
|
-
ErrorCategory["CERTIFICATE_ERROR"] = "CERTIFICATE_ERROR";
|
|
9741
|
-
ErrorCategory["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
9742
|
-
ErrorCategory["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
9743
|
-
})(exports.ErrorCategory || (exports.ErrorCategory = {}));
|
|
9744
|
-
function extractStatusCode(error) {
|
|
9745
|
-
if (error instanceof MTLSError && error.statusCode) {
|
|
9746
|
-
return error.statusCode;
|
|
9747
|
-
}
|
|
9748
|
-
const errorObj = error;
|
|
9749
|
-
if (errorObj?.response?.status) {
|
|
9750
|
-
return errorObj.response.status;
|
|
9751
|
-
}
|
|
9752
|
-
if (typeof errorObj?.statusCode === 'number') {
|
|
9753
|
-
return errorObj.statusCode;
|
|
9754
|
-
}
|
|
9755
|
-
return undefined;
|
|
9756
|
-
}
|
|
9757
|
-
function classifyError(error) {
|
|
9758
|
-
const statusCode = extractStatusCode(error);
|
|
9759
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
9760
|
-
if (statusCode && statusCode >= 500 && statusCode < 600) {
|
|
9761
|
-
return {
|
|
9762
|
-
category: exports.ErrorCategory.SERVER_ERROR,
|
|
9763
|
-
statusCode,
|
|
9764
|
-
message: errorMessage,
|
|
9765
|
-
shouldRetry: false,
|
|
9766
|
-
userMessage: `Server error (${statusCode}): The server encountered an error.`,
|
|
9767
|
-
};
|
|
9768
|
-
}
|
|
9769
|
-
if (statusCode === 401 || statusCode === 403) {
|
|
9770
|
-
return {
|
|
9771
|
-
category: exports.ErrorCategory.AUTH_ERROR,
|
|
9772
|
-
statusCode,
|
|
9773
|
-
message: errorMessage,
|
|
9774
|
-
shouldRetry: false,
|
|
9775
|
-
userMessage: `Authentication error (${statusCode}): Invalid credentials or insufficient permissions.`,
|
|
9776
|
-
};
|
|
9777
|
-
}
|
|
9778
|
-
if (statusCode && statusCode >= 400 && statusCode < 500) {
|
|
9779
|
-
return {
|
|
9780
|
-
category: exports.ErrorCategory.CLIENT_ERROR,
|
|
9781
|
-
statusCode,
|
|
9782
|
-
message: errorMessage,
|
|
9783
|
-
shouldRetry: false,
|
|
9784
|
-
userMessage: `Request error (${statusCode}): ${errorMessage}`,
|
|
9785
|
-
};
|
|
9786
|
-
}
|
|
9787
|
-
if (error instanceof MTLSError) {
|
|
9788
|
-
return {
|
|
9789
|
-
category: exports.ErrorCategory.CERTIFICATE_ERROR,
|
|
9790
|
-
statusCode,
|
|
9791
|
-
message: errorMessage,
|
|
9792
|
-
shouldRetry: true,
|
|
9793
|
-
userMessage: 'Certificate error: Unable to establish secure connection.',
|
|
9794
|
-
};
|
|
9795
|
-
}
|
|
9796
|
-
if (!statusCode &&
|
|
9797
|
-
(errorMessage.toLowerCase().includes('network') ||
|
|
9798
|
-
errorMessage.toLowerCase().includes('timeout') ||
|
|
9799
|
-
errorMessage.toLowerCase().includes('connection'))) {
|
|
9800
|
-
return {
|
|
9801
|
-
category: exports.ErrorCategory.NETWORK_ERROR,
|
|
9802
|
-
message: errorMessage,
|
|
9803
|
-
shouldRetry: true,
|
|
9804
|
-
userMessage: 'Network error: Unable to connect to server.',
|
|
9805
|
-
};
|
|
9806
|
-
}
|
|
9807
|
-
return {
|
|
9808
|
-
category: exports.ErrorCategory.UNKNOWN_ERROR,
|
|
9809
|
-
statusCode,
|
|
9810
|
-
message: errorMessage,
|
|
9811
|
-
shouldRetry: false,
|
|
9812
|
-
userMessage: `Unexpected error: ${errorMessage}`,
|
|
9813
|
-
};
|
|
9814
|
-
}
|
|
9815
|
-
function shouldReconfigureCertificate(error) {
|
|
9816
|
-
const classification = classifyError(error);
|
|
9817
|
-
return classification.category === exports.ErrorCategory.CERTIFICATE_ERROR;
|
|
9818
|
-
}
|
|
9819
|
-
function shouldRetryRequest(error, isRetryAttempt) {
|
|
9820
|
-
if (isRetryAttempt) {
|
|
9821
|
-
return false;
|
|
9822
|
-
}
|
|
9823
|
-
const classification = classifyError(error);
|
|
9824
|
-
return classification.shouldRetry;
|
|
9825
|
-
}
|
|
9826
|
-
function getUserFriendlyMessage(error) {
|
|
9827
|
-
const classification = classifyError(error);
|
|
9828
|
-
return classification.userMessage;
|
|
9829
|
-
}
|
|
9830
|
-
|
|
9831
|
-
var MTLSErrorType;
|
|
9832
|
-
(function (MTLSErrorType) {
|
|
9833
|
-
MTLSErrorType["NOT_SUPPORTED"] = "MTLS_NOT_SUPPORTED";
|
|
9834
|
-
MTLSErrorType["CERTIFICATE_NOT_FOUND"] = "MTLS_CERTIFICATE_NOT_FOUND";
|
|
9835
|
-
MTLSErrorType["CERTIFICATE_EXPIRED"] = "MTLS_CERTIFICATE_EXPIRED";
|
|
9836
|
-
MTLSErrorType["CERTIFICATE_INVALID"] = "MTLS_CERTIFICATE_INVALID";
|
|
9837
|
-
MTLSErrorType["CONNECTION_FAILED"] = "MTLS_CONNECTION_FAILED";
|
|
9838
|
-
MTLSErrorType["AUTHENTICATION_FAILED"] = "MTLS_AUTHENTICATION_FAILED";
|
|
9839
|
-
MTLSErrorType["CONFIGURATION_ERROR"] = "MTLS_CONFIGURATION_ERROR";
|
|
9840
|
-
})(MTLSErrorType || (MTLSErrorType = {}));
|
|
9841
|
-
class PlatformDetector {
|
|
9842
|
-
static detectPlatform() {
|
|
9843
|
-
if ((typeof global !== 'undefined' && global.__expo) ||
|
|
9844
|
-
(typeof navigator !== 'undefined' &&
|
|
9845
|
-
navigator.product === 'ReactNative')) {
|
|
9846
|
-
return 'react-native';
|
|
9847
|
-
}
|
|
9848
|
-
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
9849
|
-
return 'node';
|
|
9850
|
-
}
|
|
9851
|
-
return 'web';
|
|
9852
|
-
}
|
|
9853
|
-
static isReactNative() {
|
|
9854
|
-
return this.detectPlatform() === 'react-native';
|
|
9855
|
-
}
|
|
9856
|
-
static isNode() {
|
|
9857
|
-
return this.detectPlatform() === 'node';
|
|
9858
|
-
}
|
|
9859
|
-
static isWeb() {
|
|
9860
|
-
return this.detectPlatform() === 'web';
|
|
9861
|
-
}
|
|
9862
|
-
static getPlatformDetails() {
|
|
9863
|
-
const platform = this.detectPlatform();
|
|
9864
|
-
return {
|
|
9865
|
-
platform,
|
|
9866
|
-
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
|
|
9867
|
-
nodeVersion: typeof process !== 'undefined' ? process.version : undefined,
|
|
9868
|
-
isExpo: typeof global !== 'undefined' && !!global.__expo,
|
|
9869
|
-
hasWindow: typeof window !== 'undefined',
|
|
9870
|
-
hasDocument: typeof document !== 'undefined',
|
|
9871
|
-
hasProcess: typeof process !== 'undefined',
|
|
9872
|
-
};
|
|
9873
|
-
}
|
|
9874
|
-
}
|
|
9875
|
-
|
|
9876
6768
|
exports.ACubeSDK = ACubeSDK;
|
|
9877
6769
|
exports.ACubeSDKError = ACubeSDKError;
|
|
9878
|
-
exports.ActivationRequestSchema = ActivationRequestSchema;
|
|
9879
6770
|
exports.AddressMapper = AddressMapper;
|
|
9880
|
-
exports.AddressSchema = AddressSchema;
|
|
9881
6771
|
exports.AppStateService = AppStateService;
|
|
9882
|
-
exports.AuthStrategy = AuthStrategy;
|
|
9883
6772
|
exports.AuthenticationService = AuthenticationService;
|
|
9884
|
-
exports.AxiosHttpAdapter = AxiosHttpAdapter;
|
|
9885
|
-
exports.CacheHandler = CacheHandler;
|
|
9886
|
-
exports.CashRegisterCreateSchema = CashRegisterCreateSchema;
|
|
9887
6773
|
exports.CashRegisterMapper = CashRegisterMapper;
|
|
9888
|
-
exports.CashRegisterRepositoryImpl = CashRegisterRepositoryImpl;
|
|
9889
|
-
exports.CashierCreateInputSchema = CashierCreateInputSchema;
|
|
9890
6774
|
exports.CashierMapper = CashierMapper;
|
|
9891
|
-
exports.CashierRepositoryImpl = CashierRepositoryImpl;
|
|
9892
6775
|
exports.CertificateService = CertificateService;
|
|
9893
6776
|
exports.CertificateValidator = CertificateValidator;
|
|
9894
6777
|
exports.ConfigManager = ConfigManager;
|
|
9895
6778
|
exports.DAILY_REPORT_STATUS_OPTIONS = DAILY_REPORT_STATUS_OPTIONS;
|
|
9896
|
-
exports.DEFAULT_QUEUE_CONFIG = DEFAULT_QUEUE_CONFIG;
|
|
9897
|
-
exports.DIContainer = DIContainer;
|
|
9898
|
-
exports.DI_TOKENS = DI_TOKENS;
|
|
9899
6779
|
exports.DailyReportMapper = DailyReportMapper;
|
|
9900
|
-
exports.DailyReportRepositoryImpl = DailyReportRepositoryImpl;
|
|
9901
|
-
exports.DailyReportStatusSchema = DailyReportStatusSchema;
|
|
9902
|
-
exports.DailyReportsParamsSchema = DailyReportsParamsSchema;
|
|
9903
6780
|
exports.EXEMPT_VAT_CODES = EXEMPT_VAT_CODES;
|
|
9904
6781
|
exports.GOOD_OR_SERVICE_OPTIONS = GOOD_OR_SERVICE_OPTIONS;
|
|
9905
6782
|
exports.GoodOrServiceSchema = GoodOrServiceSchema;
|
|
9906
|
-
exports.JournalCloseInputSchema = JournalCloseInputSchema;
|
|
9907
6783
|
exports.JournalMapper = JournalMapper;
|
|
9908
|
-
exports.JournalRepositoryImpl = JournalRepositoryImpl;
|
|
9909
|
-
exports.JwtAuthHandler = JwtAuthHandler;
|
|
9910
|
-
exports.LotterySchema = LotterySchema;
|
|
9911
|
-
exports.LotterySecretRequestSchema = LotterySecretRequestSchema;
|
|
9912
6784
|
exports.MTLSError = MTLSError;
|
|
9913
|
-
exports.MerchantCreateInputSchema = MerchantCreateInputSchema;
|
|
9914
6785
|
exports.MerchantMapper = MerchantMapper;
|
|
9915
|
-
exports.MerchantRepositoryImpl = MerchantRepositoryImpl;
|
|
9916
|
-
exports.MerchantUpdateInputSchema = MerchantUpdateInputSchema;
|
|
9917
|
-
exports.MessageSchema = MessageSchema;
|
|
9918
|
-
exports.MtlsAuthHandler = MtlsAuthHandler;
|
|
9919
6786
|
exports.NOTIFICATION_CODES = NOTIFICATION_CODES;
|
|
9920
6787
|
exports.NOTIFICATION_LEVELS = NOTIFICATION_LEVELS;
|
|
9921
6788
|
exports.NOTIFICATION_SCOPES = NOTIFICATION_SCOPES;
|
|
9922
6789
|
exports.NOTIFICATION_SOURCES = NOTIFICATION_SOURCES;
|
|
9923
6790
|
exports.NOTIFICATION_TYPES = NOTIFICATION_TYPES;
|
|
9924
|
-
exports.NotificationDataBlockAtSchema = NotificationDataBlockAtSchema;
|
|
9925
|
-
exports.NotificationDataPemStatusSchema = NotificationDataPemStatusSchema;
|
|
9926
|
-
exports.NotificationListResponseSchema = NotificationListResponseSchema;
|
|
9927
6791
|
exports.NotificationMapper = NotificationMapper;
|
|
9928
|
-
exports.NotificationMf2UnreachableSchema = NotificationMf2UnreachableSchema;
|
|
9929
|
-
exports.NotificationPemBackOnlineSchema = NotificationPemBackOnlineSchema;
|
|
9930
|
-
exports.NotificationPemsBlockedSchema = NotificationPemsBlockedSchema;
|
|
9931
|
-
exports.NotificationRepositoryImpl = NotificationRepositoryImpl;
|
|
9932
|
-
exports.NotificationSchema = NotificationSchema;
|
|
9933
|
-
exports.NotificationScopeSchema = NotificationScopeSchema;
|
|
9934
6792
|
exports.NotificationService = NotificationService;
|
|
9935
|
-
exports.OfflineManager = OfflineManager;
|
|
9936
|
-
exports.OperationQueue = OperationQueue;
|
|
9937
|
-
exports.PEMStatusOfflineRequestSchema = PEMStatusOfflineRequestSchema;
|
|
9938
|
-
exports.PEMStatusSchema = PEMStatusSchema;
|
|
9939
6793
|
exports.PEM_STATUS_OPTIONS = PEM_STATUS_OPTIONS;
|
|
9940
6794
|
exports.PEM_TYPE_OPTIONS = PEM_TYPE_OPTIONS;
|
|
9941
|
-
exports.PemCreateInputSchema = PemCreateInputSchema;
|
|
9942
|
-
exports.PemDataSchema = PemDataSchema;
|
|
9943
6795
|
exports.PemMapper = PemMapper;
|
|
9944
|
-
exports.PemRepositoryImpl = PemRepositoryImpl;
|
|
9945
|
-
exports.PemStatusSchema = PemStatusSchema;
|
|
9946
|
-
exports.PendingReceiptsSchema = PendingReceiptsSchema;
|
|
9947
|
-
exports.PlatformDetector = PlatformDetector;
|
|
9948
6796
|
exports.PointOfSaleMapper = PointOfSaleMapper;
|
|
9949
|
-
exports.PointOfSaleRepositoryImpl = PointOfSaleRepositoryImpl;
|
|
9950
6797
|
exports.RECEIPT_PROOF_TYPE_OPTIONS = RECEIPT_PROOF_TYPE_OPTIONS;
|
|
9951
6798
|
exports.RECEIPT_READY = RECEIPT_READY;
|
|
9952
6799
|
exports.RECEIPT_SENT = RECEIPT_SENT;
|
|
9953
6800
|
exports.RECEIPT_SORT_ASCENDING = RECEIPT_SORT_ASCENDING;
|
|
9954
6801
|
exports.RECEIPT_SORT_DESCENDING = RECEIPT_SORT_DESCENDING;
|
|
9955
|
-
exports.ReceiptInputSchema = ReceiptInputSchema;
|
|
9956
|
-
exports.ReceiptItemSchema = ReceiptItemSchema;
|
|
9957
6802
|
exports.ReceiptMapper = ReceiptMapper;
|
|
9958
6803
|
exports.ReceiptProofTypeSchema = ReceiptProofTypeSchema;
|
|
9959
|
-
exports.ReceiptRepositoryImpl = ReceiptRepositoryImpl;
|
|
9960
|
-
exports.ReceiptReturnInputSchema = ReceiptReturnInputSchema;
|
|
9961
|
-
exports.ReceiptReturnItemSchema = ReceiptReturnItemSchema;
|
|
9962
|
-
exports.ReceiptReturnOrVoidViaPEMInputSchema = ReceiptReturnOrVoidViaPEMInputSchema;
|
|
9963
|
-
exports.ReceiptReturnOrVoidWithProofInputSchema = ReceiptReturnOrVoidWithProofInputSchema;
|
|
9964
|
-
exports.SDKFactory = SDKFactory;
|
|
9965
6804
|
exports.SDKManager = SDKManager;
|
|
9966
6805
|
exports.STANDARD_VAT_RATES = STANDARD_VAT_RATES;
|
|
9967
|
-
exports.SupplierCreateInputSchema = SupplierCreateInputSchema;
|
|
9968
6806
|
exports.SupplierMapper = SupplierMapper;
|
|
9969
|
-
exports.SupplierRepositoryImpl = SupplierRepositoryImpl;
|
|
9970
|
-
exports.SupplierUpdateInputSchema = SupplierUpdateInputSchema;
|
|
9971
|
-
exports.SyncManager = SyncManager;
|
|
9972
6807
|
exports.TelemetryMapper = TelemetryMapper;
|
|
9973
|
-
exports.TelemetryMerchantSchema = TelemetryMerchantSchema;
|
|
9974
|
-
exports.TelemetryRepositoryImpl = TelemetryRepositoryImpl;
|
|
9975
|
-
exports.TelemetrySchema = TelemetrySchema;
|
|
9976
6808
|
exports.TelemetryService = TelemetryService;
|
|
9977
|
-
exports.TelemetrySoftwareSchema = TelemetrySoftwareSchema;
|
|
9978
|
-
exports.TelemetrySoftwareVersionSchema = TelemetrySoftwareVersionSchema;
|
|
9979
|
-
exports.TelemetrySupplierSchema = TelemetrySupplierSchema;
|
|
9980
|
-
exports.TransmissionSchema = TransmissionSchema;
|
|
9981
6809
|
exports.VAT_RATE_CODES = VAT_RATE_CODES;
|
|
9982
6810
|
exports.VAT_RATE_CODE_OPTIONS = VAT_RATE_CODE_OPTIONS;
|
|
9983
|
-
exports.ValidationMessages = ValidationMessages;
|
|
9984
6811
|
exports.VatRateCodeSchema = VatRateCodeSchema;
|
|
9985
|
-
exports.VoidReceiptInputSchema = VoidReceiptInputSchema;
|
|
9986
|
-
exports.classifyError = classifyError;
|
|
9987
|
-
exports.clearObject = clearObject;
|
|
9988
|
-
exports.clearObjectShallow = clearObjectShallow;
|
|
9989
6812
|
exports.createACubeMTLSConfig = createACubeMTLSConfig;
|
|
9990
6813
|
exports.createACubeSDK = createACubeSDK;
|
|
9991
6814
|
exports.createPrefixedLogger = createPrefixedLogger;
|
|
9992
6815
|
exports.default = createACubeSDK;
|
|
9993
|
-
exports.detectPlatform = detectPlatform;
|
|
9994
6816
|
exports.extractRoles = extractRoles;
|
|
9995
|
-
exports.formatDecimal = formatDecimal;
|
|
9996
|
-
exports.getUserFriendlyMessage = getUserFriendlyMessage;
|
|
9997
6817
|
exports.hasAnyRole = hasAnyRole;
|
|
9998
|
-
exports.hasNonEmptyValues = hasNonEmptyValues;
|
|
9999
6818
|
exports.hasRole = hasRole;
|
|
10000
|
-
exports.isEmpty = isEmpty;
|
|
10001
6819
|
exports.isTokenExpired = isTokenExpired;
|
|
10002
6820
|
exports.loadPlatformAdapters = loadPlatformAdapters;
|
|
10003
6821
|
exports.logger = logger;
|
|
10004
6822
|
exports.parseJwt = parseJwt;
|
|
10005
|
-
exports.shouldReconfigureCertificate = shouldReconfigureCertificate;
|
|
10006
|
-
exports.shouldRetryRequest = shouldRetryRequest;
|
|
10007
|
-
exports.transformError = transformError;
|
|
10008
|
-
exports.validateInput = validateInput;
|
|
10009
6823
|
//# sourceMappingURL=index.cjs.js.map
|