@a-cube-io/ereceipts-js-sdk 2.0.9 → 2.1.0
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 +135 -2651
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +8 -251
- package/dist/index.esm.js +136 -2647
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.js +136 -2647
- package/dist/index.native.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -2135,7 +2135,7 @@ function formatDecimal(value, decimals = 2) {
|
|
|
2135
2135
|
return num.toFixed(decimals);
|
|
2136
2136
|
}
|
|
2137
2137
|
|
|
2138
|
-
const log$
|
|
2138
|
+
const log$b = createPrefixedLogger('AUTH-SERVICE');
|
|
2139
2139
|
class AuthenticationService {
|
|
2140
2140
|
get user$() {
|
|
2141
2141
|
return this.userSubject.asObservable();
|
|
@@ -2157,7 +2157,7 @@ class AuthenticationService {
|
|
|
2157
2157
|
}
|
|
2158
2158
|
async login(credentials) {
|
|
2159
2159
|
this.authStateSubject.next('authenticating');
|
|
2160
|
-
log$
|
|
2160
|
+
log$b.info('Login attempt', {
|
|
2161
2161
|
authUrl: this.config.authUrl,
|
|
2162
2162
|
email: credentials.email,
|
|
2163
2163
|
});
|
|
@@ -2168,7 +2168,7 @@ class AuthenticationService {
|
|
|
2168
2168
|
});
|
|
2169
2169
|
const jwtPayload = parseJwt(response.data.token);
|
|
2170
2170
|
const expiresAt = jwtPayload.exp * 1000;
|
|
2171
|
-
log$
|
|
2171
|
+
log$b.info('Login successful', {
|
|
2172
2172
|
authUrl: this.config.authUrl,
|
|
2173
2173
|
tokenPrefix: response.data.token.substring(0, 30) + '...',
|
|
2174
2174
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
@@ -2197,21 +2197,21 @@ class AuthenticationService {
|
|
|
2197
2197
|
const token = await this.tokenStorage.getAccessToken();
|
|
2198
2198
|
if (!token) {
|
|
2199
2199
|
// No token - clear any stale user state
|
|
2200
|
-
log$
|
|
2200
|
+
log$b.debug('getCurrentUser: No token in storage');
|
|
2201
2201
|
if (this.userSubject.value) {
|
|
2202
2202
|
this.userSubject.next(null);
|
|
2203
2203
|
this.authStateSubject.next('idle');
|
|
2204
2204
|
}
|
|
2205
2205
|
return null;
|
|
2206
2206
|
}
|
|
2207
|
-
log$
|
|
2207
|
+
log$b.debug('getCurrentUser: Token found', {
|
|
2208
2208
|
tokenPrefix: token.substring(0, 30) + '...',
|
|
2209
2209
|
tokenLength: token.length,
|
|
2210
2210
|
});
|
|
2211
2211
|
const jwtPayload = parseJwt(token);
|
|
2212
2212
|
if (isTokenExpired(jwtPayload)) {
|
|
2213
2213
|
// Token expired - clear everything
|
|
2214
|
-
log$
|
|
2214
|
+
log$b.warn('getCurrentUser: Token expired');
|
|
2215
2215
|
await this.tokenStorage.clearTokens();
|
|
2216
2216
|
this.userSubject.next(null);
|
|
2217
2217
|
this.authStateSubject.next('idle');
|
|
@@ -2221,7 +2221,7 @@ class AuthenticationService {
|
|
|
2221
2221
|
// Token is valid - return cached user if available
|
|
2222
2222
|
const currentUser = this.userSubject.value;
|
|
2223
2223
|
if (currentUser) {
|
|
2224
|
-
log$
|
|
2224
|
+
log$b.debug('getCurrentUser: Returning cached user', {
|
|
2225
2225
|
email: currentUser.email,
|
|
2226
2226
|
roles: currentUser.roles,
|
|
2227
2227
|
});
|
|
@@ -2244,12 +2244,12 @@ class AuthenticationService {
|
|
|
2244
2244
|
async isAuthenticated() {
|
|
2245
2245
|
const token = await this.tokenStorage.getAccessToken();
|
|
2246
2246
|
if (!token) {
|
|
2247
|
-
log$
|
|
2247
|
+
log$b.debug('isAuthenticated: No token in storage');
|
|
2248
2248
|
return false;
|
|
2249
2249
|
}
|
|
2250
2250
|
const jwtPayload = parseJwt(token);
|
|
2251
2251
|
const expired = isTokenExpired(jwtPayload);
|
|
2252
|
-
log$
|
|
2252
|
+
log$b.debug('isAuthenticated: Token check', {
|
|
2253
2253
|
hasToken: true,
|
|
2254
2254
|
expired,
|
|
2255
2255
|
expiresAt: new Date(jwtPayload.exp * 1000).toISOString(),
|
|
@@ -2921,7 +2921,7 @@ class ACubeSDKError extends Error {
|
|
|
2921
2921
|
}
|
|
2922
2922
|
}
|
|
2923
2923
|
|
|
2924
|
-
const log$
|
|
2924
|
+
const log$a = createPrefixedLogger('AUTH-STRATEGY');
|
|
2925
2925
|
class AuthStrategy {
|
|
2926
2926
|
constructor(jwtHandler, mtlsHandler, userProvider, mtlsAdapter) {
|
|
2927
2927
|
this.jwtHandler = jwtHandler;
|
|
@@ -2936,7 +2936,7 @@ class AuthStrategy {
|
|
|
2936
2936
|
const platform = this.detectPlatform();
|
|
2937
2937
|
const userRole = await this.getUserRole();
|
|
2938
2938
|
const isReceiptEndpoint = this.isReceiptEndpoint(url);
|
|
2939
|
-
log$
|
|
2939
|
+
log$a.debug('Determining auth config', {
|
|
2940
2940
|
url,
|
|
2941
2941
|
method,
|
|
2942
2942
|
platform,
|
|
@@ -3067,7 +3067,7 @@ class JwtAuthHandler {
|
|
|
3067
3067
|
}
|
|
3068
3068
|
}
|
|
3069
3069
|
|
|
3070
|
-
const log$
|
|
3070
|
+
const log$9 = createPrefixedLogger('MTLS-HANDLER');
|
|
3071
3071
|
class MtlsAuthHandler {
|
|
3072
3072
|
constructor(mtlsAdapter, certificatePort) {
|
|
3073
3073
|
this.mtlsAdapter = mtlsAdapter;
|
|
@@ -3109,7 +3109,7 @@ class MtlsAuthHandler {
|
|
|
3109
3109
|
async makeRequest(url, config, jwtToken) {
|
|
3110
3110
|
const requestKey = this.generateRequestKey(url, config, jwtToken);
|
|
3111
3111
|
if (this.pendingRequests.has(requestKey)) {
|
|
3112
|
-
log$
|
|
3112
|
+
log$9.debug('Deduplicating concurrent request:', url);
|
|
3113
3113
|
return this.pendingRequests.get(requestKey);
|
|
3114
3114
|
}
|
|
3115
3115
|
const requestPromise = this.executeRequest(url, config, jwtToken, false);
|
|
@@ -3133,10 +3133,10 @@ class MtlsAuthHandler {
|
|
|
3133
3133
|
};
|
|
3134
3134
|
if (jwtToken) {
|
|
3135
3135
|
headers['Authorization'] = `Bearer ${jwtToken}`;
|
|
3136
|
-
log$
|
|
3136
|
+
log$9.debug('JWT token present:', jwtToken.substring(0, 20) + '...');
|
|
3137
3137
|
}
|
|
3138
3138
|
else {
|
|
3139
|
-
log$
|
|
3139
|
+
log$9.warn('No JWT token provided for mTLS request');
|
|
3140
3140
|
}
|
|
3141
3141
|
const fullUrl = this.constructMtlsUrl(url);
|
|
3142
3142
|
const mtlsConfig = {
|
|
@@ -3147,25 +3147,25 @@ class MtlsAuthHandler {
|
|
|
3147
3147
|
timeout: config.timeout,
|
|
3148
3148
|
responseType: config.responseType,
|
|
3149
3149
|
};
|
|
3150
|
-
log$
|
|
3151
|
-
log$
|
|
3150
|
+
log$9.debug('header-mtls', headers);
|
|
3151
|
+
log$9.debug(`${config.method} ${fullUrl}`);
|
|
3152
3152
|
if (config.data) {
|
|
3153
|
-
log$
|
|
3153
|
+
log$9.debug('Request body:', config.data);
|
|
3154
3154
|
}
|
|
3155
3155
|
try {
|
|
3156
3156
|
const response = await this.mtlsAdapter.request(mtlsConfig);
|
|
3157
|
-
log$
|
|
3157
|
+
log$9.debug(`Response ${response.status} from ${fullUrl}`);
|
|
3158
3158
|
if (response.data) {
|
|
3159
|
-
log$
|
|
3159
|
+
log$9.debug('Response body:', response.data);
|
|
3160
3160
|
}
|
|
3161
3161
|
return response.data;
|
|
3162
3162
|
}
|
|
3163
3163
|
catch (error) {
|
|
3164
|
-
log$
|
|
3164
|
+
log$9.error(`Response error from ${fullUrl}:`, error);
|
|
3165
3165
|
if (error && typeof error === 'object' && 'response' in error) {
|
|
3166
3166
|
const axiosError = error;
|
|
3167
3167
|
if (axiosError.response?.data) {
|
|
3168
|
-
log$
|
|
3168
|
+
log$9.error('Response body:', axiosError.response.data);
|
|
3169
3169
|
}
|
|
3170
3170
|
}
|
|
3171
3171
|
if (isRetryAttempt) {
|
|
@@ -3175,7 +3175,7 @@ class MtlsAuthHandler {
|
|
|
3175
3175
|
if (!shouldRetry) {
|
|
3176
3176
|
throw error;
|
|
3177
3177
|
}
|
|
3178
|
-
log$
|
|
3178
|
+
log$9.debug('Request failed, reconfiguring certificate and retrying...');
|
|
3179
3179
|
try {
|
|
3180
3180
|
await this.configureCertificate(certificate);
|
|
3181
3181
|
return await this.executeRequest(url, config, jwtToken, true);
|
|
@@ -3283,1902 +3283,6 @@ class MtlsAuthHandler {
|
|
|
3283
3283
|
}
|
|
3284
3284
|
}
|
|
3285
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
|
|
4993
|
-
}
|
|
4994
|
-
}
|
|
4995
|
-
async setBatch(items) {
|
|
4996
|
-
if (items.length === 0)
|
|
4997
|
-
return;
|
|
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
|
-
}
|
|
5027
|
-
}
|
|
5028
|
-
return {
|
|
5029
|
-
key,
|
|
5030
|
-
data: finalData,
|
|
5031
|
-
timestamp: item.timestamp,
|
|
5032
|
-
compressed: isCompressed,
|
|
5033
|
-
};
|
|
5034
|
-
}
|
|
5035
|
-
async invalidate(pattern) {
|
|
5036
|
-
await this.ensureInitialized();
|
|
5037
|
-
const keys = await this.getKeys(pattern);
|
|
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
|
|
5052
|
-
}
|
|
5053
|
-
}
|
|
5054
|
-
async getSize() {
|
|
5055
|
-
await this.ensureInitialized();
|
|
5056
|
-
try {
|
|
5057
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
|
|
5058
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
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();
|
|
5068
|
-
}
|
|
5069
|
-
return {
|
|
5070
|
-
entries,
|
|
5071
|
-
bytes,
|
|
5072
|
-
lastCleanup: Date.now(),
|
|
5073
|
-
};
|
|
5074
|
-
}
|
|
5075
|
-
catch (error) {
|
|
5076
|
-
log$d.debug('Error getting cache size', error);
|
|
5077
|
-
return {
|
|
5078
|
-
entries: 0,
|
|
5079
|
-
bytes: 0,
|
|
5080
|
-
lastCleanup: Date.now(),
|
|
5081
|
-
};
|
|
5082
|
-
}
|
|
5083
|
-
}
|
|
5084
|
-
async cleanup() {
|
|
5085
|
-
// No cleanup needed - cache never expires
|
|
5086
|
-
return 0;
|
|
5087
|
-
}
|
|
5088
|
-
async getKeys(pattern) {
|
|
5089
|
-
await this.ensureInitialized();
|
|
5090
|
-
try {
|
|
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;
|
|
5096
|
-
}
|
|
5097
|
-
const regex = this.patternToRegex(pattern);
|
|
5098
|
-
return allKeys.filter((key) => regex.test(key));
|
|
5099
|
-
}
|
|
5100
|
-
catch (error) {
|
|
5101
|
-
log$d.debug('Error getting cache keys', error);
|
|
5102
|
-
return [];
|
|
5103
|
-
}
|
|
5104
|
-
}
|
|
5105
|
-
async delete(key) {
|
|
5106
|
-
await this.ensureInitialized();
|
|
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 });
|
|
5115
|
-
return false;
|
|
5116
|
-
}
|
|
5117
|
-
}
|
|
5118
|
-
async ensureInitialized() {
|
|
5119
|
-
if (!this.initPromise) {
|
|
5120
|
-
this.initPromise = this.initialize();
|
|
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;
|
|
5132
|
-
}
|
|
5133
|
-
}
|
|
5134
|
-
patternToRegex(pattern) {
|
|
5135
|
-
// Convert simple glob patterns to regex
|
|
5136
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
5137
|
-
const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
5138
|
-
return new RegExp(`^${regexPattern}$`);
|
|
5139
|
-
}
|
|
5140
|
-
}
|
|
5141
|
-
WebCacheAdapter.DB_NAME = 'acube_cache';
|
|
5142
|
-
WebCacheAdapter.DB_VERSION = 2;
|
|
5143
|
-
WebCacheAdapter.STORE_NAME = 'cache_entries';
|
|
5144
|
-
|
|
5145
|
-
const log$c = createPrefixedLogger('CACHE-LOADER');
|
|
5146
|
-
function loadCacheAdapter(platform) {
|
|
5147
|
-
try {
|
|
5148
|
-
switch (platform) {
|
|
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
|
-
});
|
|
5167
|
-
}
|
|
5168
|
-
case 'node':
|
|
5169
|
-
default:
|
|
5170
|
-
return new MemoryCacheAdapter({
|
|
5171
|
-
maxSize: 10 * 1024 * 1024,
|
|
5172
|
-
maxEntries: 5000,
|
|
5173
|
-
});
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
|
-
catch (error) {
|
|
5177
|
-
log$c.warn(`Cache adapter not available for platform ${platform}:`, error);
|
|
5178
|
-
return undefined;
|
|
5179
|
-
}
|
|
5180
|
-
}
|
|
5181
|
-
|
|
5182
3286
|
/**
|
|
5183
3287
|
* Mixin that adds multiGet, multiSet, multiRemove to any storage adapter
|
|
5184
3288
|
* Eliminates duplicate code across Node, Web, and React Native adapters
|
|
@@ -5231,7 +3335,7 @@ class BaseSecureStorageAdapter {
|
|
|
5231
3335
|
}
|
|
5232
3336
|
}
|
|
5233
3337
|
|
|
5234
|
-
const log$
|
|
3338
|
+
const log$8 = createPrefixedLogger('NETWORK-BASE');
|
|
5235
3339
|
class NetworkBase {
|
|
5236
3340
|
constructor(initialOnline = true, debounceMs = 300) {
|
|
5237
3341
|
this.destroy$ = new Subject();
|
|
@@ -5251,14 +3355,14 @@ class NetworkBase {
|
|
|
5251
3355
|
const current = this.statusSubject.getValue();
|
|
5252
3356
|
if (current.online !== online) {
|
|
5253
3357
|
this.statusSubject.next({ online, timestamp: Date.now() });
|
|
5254
|
-
log$
|
|
3358
|
+
log$8.debug(`Network status changed: ${online ? 'online' : 'offline'}`);
|
|
5255
3359
|
}
|
|
5256
3360
|
}
|
|
5257
3361
|
destroy() {
|
|
5258
3362
|
this.destroy$.next();
|
|
5259
3363
|
this.destroy$.complete();
|
|
5260
3364
|
this.statusSubject.complete();
|
|
5261
|
-
log$
|
|
3365
|
+
log$8.debug('Network monitor destroyed');
|
|
5262
3366
|
}
|
|
5263
3367
|
}
|
|
5264
3368
|
|
|
@@ -5318,7 +3422,7 @@ class NodeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5318
3422
|
}
|
|
5319
3423
|
}
|
|
5320
3424
|
|
|
5321
|
-
const log$
|
|
3425
|
+
const log$7 = createPrefixedLogger('RN-STORAGE');
|
|
5322
3426
|
/**
|
|
5323
3427
|
* React Native storage adapter using AsyncStorage
|
|
5324
3428
|
* Note: Uses native batch operations for better performance (not base class)
|
|
@@ -5350,7 +3454,7 @@ class ReactNativeStorageAdapter {
|
|
|
5350
3454
|
return await this.AsyncStorage.getItem(key);
|
|
5351
3455
|
}
|
|
5352
3456
|
catch (error) {
|
|
5353
|
-
log$
|
|
3457
|
+
log$7.error('Failed to get item from AsyncStorage:', error);
|
|
5354
3458
|
return null;
|
|
5355
3459
|
}
|
|
5356
3460
|
}
|
|
@@ -5391,7 +3495,7 @@ class ReactNativeStorageAdapter {
|
|
|
5391
3495
|
return await this.AsyncStorage.getAllKeys();
|
|
5392
3496
|
}
|
|
5393
3497
|
catch (error) {
|
|
5394
|
-
log$
|
|
3498
|
+
log$7.error('Failed to get all keys:', error);
|
|
5395
3499
|
return [];
|
|
5396
3500
|
}
|
|
5397
3501
|
}
|
|
@@ -5408,7 +3512,7 @@ class ReactNativeStorageAdapter {
|
|
|
5408
3512
|
return result;
|
|
5409
3513
|
}
|
|
5410
3514
|
catch (error) {
|
|
5411
|
-
log$
|
|
3515
|
+
log$7.error('Failed to get multiple items:', error);
|
|
5412
3516
|
const result = {};
|
|
5413
3517
|
keys.forEach((key) => {
|
|
5414
3518
|
result[key] = null;
|
|
@@ -5458,7 +3562,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5458
3562
|
return;
|
|
5459
3563
|
}
|
|
5460
3564
|
catch {
|
|
5461
|
-
log$
|
|
3565
|
+
log$7.debug('expo-secure-store not available, trying react-native-keychain');
|
|
5462
3566
|
}
|
|
5463
3567
|
try {
|
|
5464
3568
|
const Keychain = require('react-native-keychain');
|
|
@@ -5467,7 +3571,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5467
3571
|
return;
|
|
5468
3572
|
}
|
|
5469
3573
|
catch {
|
|
5470
|
-
log$
|
|
3574
|
+
log$7.error('react-native-keychain not available');
|
|
5471
3575
|
}
|
|
5472
3576
|
throw new Error('No secure storage available. Please install expo-secure-store or react-native-keychain');
|
|
5473
3577
|
}
|
|
@@ -5485,7 +3589,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5485
3589
|
}
|
|
5486
3590
|
}
|
|
5487
3591
|
catch (error) {
|
|
5488
|
-
log$
|
|
3592
|
+
log$7.error('Failed to get secure item:', error);
|
|
5489
3593
|
}
|
|
5490
3594
|
return null;
|
|
5491
3595
|
}
|
|
@@ -5522,10 +3626,10 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5522
3626
|
}
|
|
5523
3627
|
}
|
|
5524
3628
|
async clear() {
|
|
5525
|
-
log$
|
|
3629
|
+
log$7.warn('Clear all secure items not fully implemented for React Native');
|
|
5526
3630
|
}
|
|
5527
3631
|
async getAllKeys() {
|
|
5528
|
-
log$
|
|
3632
|
+
log$7.warn('Get all secure keys not implemented for React Native');
|
|
5529
3633
|
return [];
|
|
5530
3634
|
}
|
|
5531
3635
|
async isAvailable() {
|
|
@@ -5743,7 +3847,7 @@ class NodeNetworkMonitor extends NetworkBase {
|
|
|
5743
3847
|
}
|
|
5744
3848
|
}
|
|
5745
3849
|
|
|
5746
|
-
const log$
|
|
3850
|
+
const log$6 = createPrefixedLogger('RN-NETWORK');
|
|
5747
3851
|
/**
|
|
5748
3852
|
* React Native network monitor using RxJS
|
|
5749
3853
|
* Supports both @react-native-community/netinfo and expo-network
|
|
@@ -5756,7 +3860,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5756
3860
|
this.moduleReady$ = new Subject();
|
|
5757
3861
|
this.isExpo = detectPlatform().isExpo;
|
|
5758
3862
|
this.init().catch((error) => {
|
|
5759
|
-
log$
|
|
3863
|
+
log$6.error('Network monitor initialization failed:', error);
|
|
5760
3864
|
});
|
|
5761
3865
|
}
|
|
5762
3866
|
async init() {
|
|
@@ -5775,10 +3879,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5775
3879
|
try {
|
|
5776
3880
|
const module = require('@react-native-community/netinfo');
|
|
5777
3881
|
this.netInfoModule = module.default || module;
|
|
5778
|
-
log$
|
|
3882
|
+
log$6.debug('Loaded @react-native-community/netinfo module');
|
|
5779
3883
|
}
|
|
5780
3884
|
catch (error) {
|
|
5781
|
-
log$
|
|
3885
|
+
log$6.error('Failed to load React Native NetInfo module:', error);
|
|
5782
3886
|
this.netInfoModule = null;
|
|
5783
3887
|
}
|
|
5784
3888
|
}
|
|
@@ -5786,10 +3890,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5786
3890
|
try {
|
|
5787
3891
|
const module = require('expo-network');
|
|
5788
3892
|
this.netInfoModule = module.default || module;
|
|
5789
|
-
log$
|
|
3893
|
+
log$6.debug('Loaded expo-network module');
|
|
5790
3894
|
}
|
|
5791
3895
|
catch (error) {
|
|
5792
|
-
log$
|
|
3896
|
+
log$6.error('Failed to load Expo Network module:', error);
|
|
5793
3897
|
this.netInfoModule = null;
|
|
5794
3898
|
}
|
|
5795
3899
|
}
|
|
@@ -5806,16 +3910,16 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5806
3910
|
}
|
|
5807
3911
|
const online = !!(state.isConnected && state.isInternetReachable !== false);
|
|
5808
3912
|
this.updateStatus(online);
|
|
5809
|
-
log$
|
|
3913
|
+
log$6.debug('Initial network state:', online ? 'online' : 'offline');
|
|
5810
3914
|
}
|
|
5811
3915
|
catch (error) {
|
|
5812
|
-
log$
|
|
3916
|
+
log$6.warn('Could not fetch initial network state:', error);
|
|
5813
3917
|
}
|
|
5814
3918
|
}
|
|
5815
3919
|
subscribeToStateChanges() {
|
|
5816
3920
|
if (!this.netInfoModule)
|
|
5817
3921
|
return;
|
|
5818
|
-
log$
|
|
3922
|
+
log$6.debug('Subscribing to network state changes');
|
|
5819
3923
|
const handleState = (state) => {
|
|
5820
3924
|
const online = !!(state.isConnected && (state.isInternetReachable ?? true));
|
|
5821
3925
|
this.updateStatus(online);
|
|
@@ -5857,7 +3961,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5857
3961
|
};
|
|
5858
3962
|
}
|
|
5859
3963
|
catch (error) {
|
|
5860
|
-
log$
|
|
3964
|
+
log$6.error('Failed to fetch detailed network info:', error);
|
|
5861
3965
|
return null;
|
|
5862
3966
|
}
|
|
5863
3967
|
}
|
|
@@ -5993,7 +4097,7 @@ class CertificateValidator {
|
|
|
5993
4097
|
}
|
|
5994
4098
|
}
|
|
5995
4099
|
|
|
5996
|
-
const log$
|
|
4100
|
+
const log$5 = createPrefixedLogger('RN-MTLS');
|
|
5997
4101
|
/**
|
|
5998
4102
|
* React Native mTLS Adapter using @a-cube-io/expo-mutual-tls
|
|
5999
4103
|
*/
|
|
@@ -6015,7 +4119,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6015
4119
|
this.expoMTLS = ExpoMutualTls;
|
|
6016
4120
|
// Set up debug logging with the correct event signature
|
|
6017
4121
|
const debugListener = ExpoMutualTls.onDebugLog((event) => {
|
|
6018
|
-
log$
|
|
4122
|
+
log$5.debug(`${event.type}: ${event.message}`, {
|
|
6019
4123
|
method: event.method,
|
|
6020
4124
|
url: event.url,
|
|
6021
4125
|
statusCode: event.statusCode,
|
|
@@ -6025,28 +4129,28 @@ class ReactNativeMTLSAdapter {
|
|
|
6025
4129
|
this.eventListeners.push(debugListener);
|
|
6026
4130
|
// Set up error logging with the correct event signature
|
|
6027
4131
|
const errorListener = ExpoMutualTls.onError((event) => {
|
|
6028
|
-
log$
|
|
4132
|
+
log$5.error(event.message, {
|
|
6029
4133
|
code: event.code,
|
|
6030
4134
|
});
|
|
6031
4135
|
});
|
|
6032
4136
|
this.eventListeners.push(errorListener);
|
|
6033
4137
|
// Set up certificate expiry monitoring with the correct event signature
|
|
6034
4138
|
const expiryListener = ExpoMutualTls.onCertificateExpiry((event) => {
|
|
6035
|
-
log$
|
|
4139
|
+
log$5.warn(`Certificate ${event.subject} expires at ${new Date(event.expiry)}`, {
|
|
6036
4140
|
alias: event.alias,
|
|
6037
4141
|
warning: event.warning,
|
|
6038
4142
|
});
|
|
6039
4143
|
});
|
|
6040
4144
|
this.eventListeners.push(expiryListener);
|
|
6041
|
-
log$
|
|
4145
|
+
log$5.debug('Expo mTLS module loaded successfully');
|
|
6042
4146
|
}
|
|
6043
4147
|
catch (error) {
|
|
6044
|
-
log$
|
|
4148
|
+
log$5.warn('@a-cube-io/expo-mutual-tls not available:', error);
|
|
6045
4149
|
}
|
|
6046
4150
|
}
|
|
6047
4151
|
async isMTLSSupported() {
|
|
6048
4152
|
const supported = this.expoMTLS !== null;
|
|
6049
|
-
log$
|
|
4153
|
+
log$5.debug('mTLS support check:', {
|
|
6050
4154
|
supported,
|
|
6051
4155
|
platform: this.getPlatformInfo().platform,
|
|
6052
4156
|
moduleAvailable: !!this.expoMTLS,
|
|
@@ -6058,7 +4162,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6058
4162
|
throw new MTLSError(exports.MTLSErrorType.NOT_SUPPORTED, 'Expo mTLS module not available');
|
|
6059
4163
|
}
|
|
6060
4164
|
this.config = config;
|
|
6061
|
-
log$
|
|
4165
|
+
log$5.debug('Initialized with config:', {
|
|
6062
4166
|
baseUrl: config.baseUrl,
|
|
6063
4167
|
port: config.port,
|
|
6064
4168
|
timeout: config.timeout,
|
|
@@ -6072,7 +4176,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6072
4176
|
if (!this.config) {
|
|
6073
4177
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Adapter not initialized. Call initialize() first.');
|
|
6074
4178
|
}
|
|
6075
|
-
log$
|
|
4179
|
+
log$5.debug('Configuring certificate:', {
|
|
6076
4180
|
format: certificateData.format,
|
|
6077
4181
|
hasPassword: !!certificateData.password,
|
|
6078
4182
|
certificateLength: certificateData.certificate.length,
|
|
@@ -6089,14 +4193,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6089
4193
|
'client-key-service', // keyService
|
|
6090
4194
|
true // enableLogging - let the native module handle its own debug logging
|
|
6091
4195
|
);
|
|
6092
|
-
log$
|
|
4196
|
+
log$5.debug('PEM services configured:', configResult);
|
|
6093
4197
|
if (!configResult.success) {
|
|
6094
4198
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, `PEM configuration failed: ${configResult.state}`);
|
|
6095
4199
|
}
|
|
6096
4200
|
// Step 2: Store the actual PEM certificate and private key
|
|
6097
4201
|
const storeResult = await this.expoMTLS.storePEM(certificateData.certificate, certificateData.privateKey, certificateData.password // passphrase (optional)
|
|
6098
4202
|
);
|
|
6099
|
-
log$
|
|
4203
|
+
log$5.debug('PEM certificate store result:', storeResult);
|
|
6100
4204
|
if (!storeResult) {
|
|
6101
4205
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, 'Failed to store PEM certificate');
|
|
6102
4206
|
}
|
|
@@ -6109,14 +4213,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6109
4213
|
const configResult = await this.expoMTLS.configureP12('client-p12-service', // keychainService
|
|
6110
4214
|
true // enableLogging - let the native module handle its own debug logging
|
|
6111
4215
|
);
|
|
6112
|
-
log$
|
|
4216
|
+
log$5.debug('P12 service configured:', configResult);
|
|
6113
4217
|
if (!configResult.success) {
|
|
6114
4218
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, `P12 configuration failed: ${configResult.state}`);
|
|
6115
4219
|
}
|
|
6116
4220
|
// Step 2: Store the P12 certificate data
|
|
6117
4221
|
const storeResult = await this.expoMTLS.storeP12(certificateData.certificate, // P12 data in certificate field
|
|
6118
4222
|
certificateData.password);
|
|
6119
|
-
log$
|
|
4223
|
+
log$5.debug('P12 certificate store result:', storeResult);
|
|
6120
4224
|
if (!storeResult) {
|
|
6121
4225
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, 'Failed to store P12 certificate');
|
|
6122
4226
|
}
|
|
@@ -6130,7 +4234,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6130
4234
|
if (error instanceof MTLSError) {
|
|
6131
4235
|
throw error;
|
|
6132
4236
|
}
|
|
6133
|
-
log$
|
|
4237
|
+
log$5.error('Certificate configuration failed:', error);
|
|
6134
4238
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Failed to configure certificate', error);
|
|
6135
4239
|
}
|
|
6136
4240
|
}
|
|
@@ -6141,38 +4245,38 @@ class ReactNativeMTLSAdapter {
|
|
|
6141
4245
|
try {
|
|
6142
4246
|
// Use static method call
|
|
6143
4247
|
const hasCert = await this.expoMTLS.hasCertificate();
|
|
6144
|
-
log$
|
|
4248
|
+
log$5.debug('Certificate availability check:', hasCert);
|
|
6145
4249
|
return hasCert;
|
|
6146
4250
|
}
|
|
6147
4251
|
catch (error) {
|
|
6148
|
-
log$
|
|
4252
|
+
log$5.error('Certificate check failed:', error);
|
|
6149
4253
|
return false;
|
|
6150
4254
|
}
|
|
6151
4255
|
}
|
|
6152
4256
|
async getCertificateInfo() {
|
|
6153
4257
|
if (!this.expoMTLS) {
|
|
6154
|
-
log$
|
|
4258
|
+
log$5.debug('Certificate info requested but module not available');
|
|
6155
4259
|
return null;
|
|
6156
4260
|
}
|
|
6157
4261
|
try {
|
|
6158
4262
|
const hasCert = await this.hasCertificate();
|
|
6159
4263
|
if (!hasCert) {
|
|
6160
|
-
log$
|
|
4264
|
+
log$5.debug('No certificate stored');
|
|
6161
4265
|
return null;
|
|
6162
4266
|
}
|
|
6163
4267
|
// Use getCertificatesInfo to retrieve information about stored certificates
|
|
6164
4268
|
const result = await this.expoMTLS.getCertificatesInfo();
|
|
6165
4269
|
if (!result || !result.certificates || result.certificates.length === 0) {
|
|
6166
|
-
log$
|
|
4270
|
+
log$5.debug('No certificate information available');
|
|
6167
4271
|
return null;
|
|
6168
4272
|
}
|
|
6169
4273
|
// Get the first certificate (primary client certificate)
|
|
6170
4274
|
const cert = result.certificates[0];
|
|
6171
4275
|
if (!cert) {
|
|
6172
|
-
log$
|
|
4276
|
+
log$5.debug('Certificate data is empty');
|
|
6173
4277
|
return null;
|
|
6174
4278
|
}
|
|
6175
|
-
log$
|
|
4279
|
+
log$5.debug('Retrieved certificate info:', {
|
|
6176
4280
|
subject: cert.subject.commonName,
|
|
6177
4281
|
issuer: cert.issuer.commonName,
|
|
6178
4282
|
validFrom: new Date(cert.validFrom),
|
|
@@ -6191,7 +4295,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6191
4295
|
};
|
|
6192
4296
|
}
|
|
6193
4297
|
catch (error) {
|
|
6194
|
-
log$
|
|
4298
|
+
log$5.error('Failed to get certificate info:', error);
|
|
6195
4299
|
return null;
|
|
6196
4300
|
}
|
|
6197
4301
|
}
|
|
@@ -6202,7 +4306,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6202
4306
|
*/
|
|
6203
4307
|
async parseCertificateData(certificateData) {
|
|
6204
4308
|
if (!this.expoMTLS) {
|
|
6205
|
-
log$
|
|
4309
|
+
log$5.debug('Parse certificate: Module not available');
|
|
6206
4310
|
return null;
|
|
6207
4311
|
}
|
|
6208
4312
|
try {
|
|
@@ -6219,14 +4323,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6219
4323
|
else {
|
|
6220
4324
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, `Unsupported certificate format: ${certificateData.format}`);
|
|
6221
4325
|
}
|
|
6222
|
-
log$
|
|
4326
|
+
log$5.debug('Certificate parsed successfully:', {
|
|
6223
4327
|
certificateCount: result.certificates.length,
|
|
6224
4328
|
subjects: result.certificates.map((cert) => cert.subject.commonName),
|
|
6225
4329
|
});
|
|
6226
4330
|
return result;
|
|
6227
4331
|
}
|
|
6228
4332
|
catch (error) {
|
|
6229
|
-
log$
|
|
4333
|
+
log$5.error('Failed to parse certificate:', error);
|
|
6230
4334
|
if (error instanceof MTLSError) {
|
|
6231
4335
|
throw error;
|
|
6232
4336
|
}
|
|
@@ -6241,7 +4345,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6241
4345
|
if (!hasCert) {
|
|
6242
4346
|
throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_NOT_FOUND, 'No certificate configured');
|
|
6243
4347
|
}
|
|
6244
|
-
log$
|
|
4348
|
+
log$5.debug('Making mTLS request:', {
|
|
6245
4349
|
method: requestConfig.method || 'GET',
|
|
6246
4350
|
url: requestConfig.url,
|
|
6247
4351
|
headers: requestConfig.headers,
|
|
@@ -6249,7 +4353,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6249
4353
|
responseType: requestConfig.responseType,
|
|
6250
4354
|
});
|
|
6251
4355
|
if (requestConfig.data) {
|
|
6252
|
-
log$
|
|
4356
|
+
log$5.debug('mTLS request body:', requestConfig.data);
|
|
6253
4357
|
}
|
|
6254
4358
|
try {
|
|
6255
4359
|
const response = await this.expoMTLS.request(requestConfig.url, {
|
|
@@ -6258,7 +4362,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6258
4362
|
body: requestConfig.data ? JSON.stringify(requestConfig.data) : undefined,
|
|
6259
4363
|
responseType: requestConfig.responseType,
|
|
6260
4364
|
});
|
|
6261
|
-
log$
|
|
4365
|
+
log$5.debug('mTLS request successful:', response);
|
|
6262
4366
|
if (!response.success) {
|
|
6263
4367
|
throw new MTLSError(exports.MTLSErrorType.CONNECTION_FAILED, `mTLS request failed: ${response.statusMessage} (${response.statusCode})`, undefined, response.statusCode);
|
|
6264
4368
|
}
|
|
@@ -6270,7 +4374,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6270
4374
|
data = JSON.parse(response.body);
|
|
6271
4375
|
}
|
|
6272
4376
|
catch (parseError) {
|
|
6273
|
-
log$
|
|
4377
|
+
log$5.warn('Failed to parse JSON response:', parseError);
|
|
6274
4378
|
// If parsing fails, keep raw body
|
|
6275
4379
|
}
|
|
6276
4380
|
}
|
|
@@ -6287,7 +4391,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6287
4391
|
};
|
|
6288
4392
|
}
|
|
6289
4393
|
catch (error) {
|
|
6290
|
-
log$
|
|
4394
|
+
log$5.error('mTLS request failed:', error);
|
|
6291
4395
|
throw new MTLSError(exports.MTLSErrorType.CONNECTION_FAILED, 'mTLS request failed', error);
|
|
6292
4396
|
}
|
|
6293
4397
|
}
|
|
@@ -6300,18 +4404,18 @@ class ReactNativeMTLSAdapter {
|
|
|
6300
4404
|
*/
|
|
6301
4405
|
async testConnection() {
|
|
6302
4406
|
if (!this.expoMTLS || !this.config) {
|
|
6303
|
-
log$
|
|
4407
|
+
log$5.debug('Diagnostic test: No mTLS module or config available');
|
|
6304
4408
|
return false;
|
|
6305
4409
|
}
|
|
6306
4410
|
try {
|
|
6307
4411
|
const hasCert = await this.hasCertificate();
|
|
6308
4412
|
if (!hasCert) {
|
|
6309
|
-
log$
|
|
4413
|
+
log$5.debug('Diagnostic test: No certificate configured');
|
|
6310
4414
|
return false;
|
|
6311
4415
|
}
|
|
6312
|
-
log$
|
|
4416
|
+
log$5.debug('Running diagnostic test (may fail even if mTLS works):', this.config.baseUrl);
|
|
6313
4417
|
const result = await this.expoMTLS.testConnection(this.config.baseUrl);
|
|
6314
|
-
log$
|
|
4418
|
+
log$5.debug('Diagnostic test result (NOT validation):', {
|
|
6315
4419
|
success: result.success,
|
|
6316
4420
|
statusCode: result.statusCode,
|
|
6317
4421
|
statusMessage: result.statusMessage,
|
|
@@ -6322,13 +4426,13 @@ class ReactNativeMTLSAdapter {
|
|
|
6322
4426
|
return result.success;
|
|
6323
4427
|
}
|
|
6324
4428
|
catch (error) {
|
|
6325
|
-
log$
|
|
4429
|
+
log$5.warn('Diagnostic test failed (this is expected):', error);
|
|
6326
4430
|
return false;
|
|
6327
4431
|
}
|
|
6328
4432
|
}
|
|
6329
4433
|
async removeCertificate() {
|
|
6330
4434
|
if (!this.expoMTLS) {
|
|
6331
|
-
log$
|
|
4435
|
+
log$5.debug('Remove certificate: Module not available');
|
|
6332
4436
|
return;
|
|
6333
4437
|
}
|
|
6334
4438
|
try {
|
|
@@ -6337,10 +4441,10 @@ class ReactNativeMTLSAdapter {
|
|
|
6337
4441
|
this.isConfigured = false;
|
|
6338
4442
|
// Cleanup event listeners
|
|
6339
4443
|
this.cleanupEventListeners();
|
|
6340
|
-
log$
|
|
4444
|
+
log$5.debug('Certificate removed successfully');
|
|
6341
4445
|
}
|
|
6342
4446
|
catch (error) {
|
|
6343
|
-
log$
|
|
4447
|
+
log$5.error('Failed to remove certificate:', error);
|
|
6344
4448
|
throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Failed to remove certificate', error);
|
|
6345
4449
|
}
|
|
6346
4450
|
}
|
|
@@ -6349,7 +4453,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6349
4453
|
*/
|
|
6350
4454
|
cleanupEventListeners() {
|
|
6351
4455
|
if (this.eventListeners.length > 0) {
|
|
6352
|
-
log$
|
|
4456
|
+
log$5.debug(`Cleaning up ${this.eventListeners.length} event listeners`);
|
|
6353
4457
|
}
|
|
6354
4458
|
// Remove individual listeners if they have remove methods
|
|
6355
4459
|
this.eventListeners.forEach((listener) => {
|
|
@@ -6386,7 +4490,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6386
4490
|
}
|
|
6387
4491
|
}
|
|
6388
4492
|
|
|
6389
|
-
const log$
|
|
4493
|
+
const log$4 = createPrefixedLogger('WEB-MTLS');
|
|
6390
4494
|
/**
|
|
6391
4495
|
* Web mTLS Adapter - Graceful fallback for web browsers
|
|
6392
4496
|
*
|
|
@@ -6400,13 +4504,13 @@ const log$7 = createPrefixedLogger('WEB-MTLS');
|
|
|
6400
4504
|
*/
|
|
6401
4505
|
class WebMTLSAdapter {
|
|
6402
4506
|
constructor() {
|
|
6403
|
-
log$
|
|
6404
|
-
log$
|
|
4507
|
+
log$4.warn('Web browsers do not support programmatic mTLS configuration');
|
|
4508
|
+
log$4.info('Use JWT authentication or configure client certificates in browser settings');
|
|
6405
4509
|
}
|
|
6406
4510
|
async isMTLSSupported() {
|
|
6407
4511
|
// mTLS is not supported programmatically in web browsers
|
|
6408
4512
|
const supported = false;
|
|
6409
|
-
log$
|
|
4513
|
+
log$4.debug('mTLS support check:', {
|
|
6410
4514
|
supported,
|
|
6411
4515
|
platform: this.getPlatformInfo().platform,
|
|
6412
4516
|
reason: 'Browser security model prevents programmatic certificate configuration',
|
|
@@ -6415,14 +4519,14 @@ class WebMTLSAdapter {
|
|
|
6415
4519
|
return supported;
|
|
6416
4520
|
}
|
|
6417
4521
|
async initialize(config) {
|
|
6418
|
-
log$
|
|
4522
|
+
log$4.warn('Initialized but mTLS not available in web browsers:', {
|
|
6419
4523
|
baseUrl: config.baseUrl,
|
|
6420
4524
|
port: config.port,
|
|
6421
4525
|
recommendation: 'Use standard HTTPS with JWT authentication',
|
|
6422
4526
|
});
|
|
6423
4527
|
}
|
|
6424
4528
|
async configureCertificate(certificateData) {
|
|
6425
|
-
log$
|
|
4529
|
+
log$4.error('Certificate configuration attempted:', {
|
|
6426
4530
|
format: certificateData.format,
|
|
6427
4531
|
reason: 'Not supported in web browsers',
|
|
6428
4532
|
alternatives: [
|
|
@@ -6437,15 +4541,15 @@ class WebMTLSAdapter {
|
|
|
6437
4541
|
}
|
|
6438
4542
|
async hasCertificate() {
|
|
6439
4543
|
// We cannot detect if the browser has certificates configured
|
|
6440
|
-
log$
|
|
4544
|
+
log$4.debug('Certificate availability check: Cannot detect browser certificates programmatically');
|
|
6441
4545
|
return false;
|
|
6442
4546
|
}
|
|
6443
4547
|
async getCertificateInfo() {
|
|
6444
|
-
log$
|
|
4548
|
+
log$4.debug('Certificate info requested: Not accessible in web browsers');
|
|
6445
4549
|
return null;
|
|
6446
4550
|
}
|
|
6447
4551
|
async request(requestConfig) {
|
|
6448
|
-
log$
|
|
4552
|
+
log$4.error('mTLS request attempted:', {
|
|
6449
4553
|
method: requestConfig.method,
|
|
6450
4554
|
url: requestConfig.url,
|
|
6451
4555
|
reason: 'Not supported in web browsers',
|
|
@@ -6460,11 +4564,11 @@ class WebMTLSAdapter {
|
|
|
6460
4564
|
'are properly configured in the browser certificate store.');
|
|
6461
4565
|
}
|
|
6462
4566
|
async testConnection() {
|
|
6463
|
-
log$
|
|
4567
|
+
log$4.debug('Connection test: mTLS not available in web browsers');
|
|
6464
4568
|
return false;
|
|
6465
4569
|
}
|
|
6466
4570
|
async removeCertificate() {
|
|
6467
|
-
log$
|
|
4571
|
+
log$4.debug('Remove certificate: No certificates to remove (not supported in web browsers)');
|
|
6468
4572
|
// No-op - cannot remove certificates programmatically in browsers
|
|
6469
4573
|
}
|
|
6470
4574
|
/**
|
|
@@ -6472,7 +4576,7 @@ class WebMTLSAdapter {
|
|
|
6472
4576
|
* Always returns null for web browsers as mTLS is not supported
|
|
6473
4577
|
*/
|
|
6474
4578
|
getBaseUrl() {
|
|
6475
|
-
log$
|
|
4579
|
+
log$4.debug('Base URL requested: Not supported in web browsers');
|
|
6476
4580
|
return null;
|
|
6477
4581
|
}
|
|
6478
4582
|
getPlatformInfo() {
|
|
@@ -6505,7 +4609,7 @@ class WebMTLSAdapter {
|
|
|
6505
4609
|
}
|
|
6506
4610
|
}
|
|
6507
4611
|
|
|
6508
|
-
const log$
|
|
4612
|
+
const log$3 = createPrefixedLogger('MTLS-LOADER');
|
|
6509
4613
|
function loadMTLSAdapter(platform, config) {
|
|
6510
4614
|
try {
|
|
6511
4615
|
let adapter;
|
|
@@ -6539,7 +4643,7 @@ function loadMTLSAdapter(platform, config) {
|
|
|
6539
4643
|
return adapter;
|
|
6540
4644
|
}
|
|
6541
4645
|
catch (error) {
|
|
6542
|
-
log$
|
|
4646
|
+
log$3.warn(`mTLS adapter not available for platform ${platform}:`, error);
|
|
6543
4647
|
return null;
|
|
6544
4648
|
}
|
|
6545
4649
|
}
|
|
@@ -6549,7 +4653,7 @@ async function initializeAdapterAsync(adapter, config) {
|
|
|
6549
4653
|
if (isSupported) {
|
|
6550
4654
|
await adapter.initialize(config);
|
|
6551
4655
|
const platformInfo = adapter.getPlatformInfo();
|
|
6552
|
-
log$
|
|
4656
|
+
log$3.debug('mTLS adapter initialized:', {
|
|
6553
4657
|
platform: platformInfo.platform,
|
|
6554
4658
|
mtlsSupported: platformInfo.mtlsSupported,
|
|
6555
4659
|
certificateStorage: platformInfo.certificateStorage,
|
|
@@ -6558,31 +4662,28 @@ async function initializeAdapterAsync(adapter, config) {
|
|
|
6558
4662
|
}
|
|
6559
4663
|
}
|
|
6560
4664
|
catch (error) {
|
|
6561
|
-
log$
|
|
4665
|
+
log$3.warn('Failed to initialize mTLS adapter:', error);
|
|
6562
4666
|
}
|
|
6563
4667
|
}
|
|
6564
4668
|
|
|
6565
|
-
const log$
|
|
4669
|
+
const log$2 = createPrefixedLogger('ADAPTER-LOADER');
|
|
6566
4670
|
function loadPlatformAdapters(options = {}) {
|
|
6567
4671
|
const { mtlsConfig } = options;
|
|
6568
4672
|
const { platform } = detectPlatform();
|
|
6569
|
-
log$
|
|
4673
|
+
log$2.debug('Loading adapters for platform:', platform);
|
|
6570
4674
|
const storageAdapters = loadStorageAdapters(platform);
|
|
6571
4675
|
const networkMonitor = loadNetworkMonitor(platform);
|
|
6572
|
-
const cache = loadCacheAdapter(platform);
|
|
6573
4676
|
const mtls = loadMTLSAdapter(platform, mtlsConfig);
|
|
6574
|
-
log$
|
|
4677
|
+
log$2.debug('Adapters loaded:', {
|
|
6575
4678
|
platform,
|
|
6576
4679
|
hasStorage: !!storageAdapters.storage,
|
|
6577
4680
|
hasSecureStorage: !!storageAdapters.secureStorage,
|
|
6578
4681
|
hasNetworkMonitor: !!networkMonitor,
|
|
6579
|
-
hasCache: !!cache,
|
|
6580
4682
|
hasMTLS: !!mtls,
|
|
6581
4683
|
});
|
|
6582
4684
|
return {
|
|
6583
4685
|
...storageAdapters,
|
|
6584
4686
|
networkMonitor,
|
|
6585
|
-
cache,
|
|
6586
4687
|
mtls: mtls || undefined,
|
|
6587
4688
|
};
|
|
6588
4689
|
}
|
|
@@ -6599,12 +4700,9 @@ function createACubeMTLSConfig(baseUrl, timeout, autoInitialize = true, forcePor
|
|
|
6599
4700
|
|
|
6600
4701
|
const DI_TOKENS = {
|
|
6601
4702
|
HTTP_PORT: Symbol('HTTP_PORT'),
|
|
6602
|
-
BASE_HTTP_PORT: Symbol('BASE_HTTP_PORT'),
|
|
6603
4703
|
STORAGE_PORT: Symbol('STORAGE_PORT'),
|
|
6604
4704
|
SECURE_STORAGE_PORT: Symbol('SECURE_STORAGE_PORT'),
|
|
6605
4705
|
NETWORK_PORT: Symbol('NETWORK_PORT'),
|
|
6606
|
-
CACHE_PORT: Symbol('CACHE_PORT'),
|
|
6607
|
-
CACHE_KEY_GENERATOR: Symbol('CACHE_KEY_GENERATOR'),
|
|
6608
4706
|
MTLS_PORT: Symbol('MTLS_PORT'),
|
|
6609
4707
|
TOKEN_STORAGE_PORT: Symbol('TOKEN_STORAGE_PORT'),
|
|
6610
4708
|
RECEIPT_REPOSITORY: Symbol('RECEIPT_REPOSITORY'),
|
|
@@ -6622,7 +4720,6 @@ const DI_TOKENS = {
|
|
|
6622
4720
|
AUTH_SERVICE: Symbol('AUTH_SERVICE'),
|
|
6623
4721
|
AUTHENTICATION_SERVICE: Symbol('AUTHENTICATION_SERVICE'),
|
|
6624
4722
|
CERTIFICATE_SERVICE: Symbol('CERTIFICATE_SERVICE'),
|
|
6625
|
-
OFFLINE_SERVICE: Symbol('OFFLINE_SERVICE'),
|
|
6626
4723
|
NOTIFICATION_SERVICE: Symbol('NOTIFICATION_SERVICE'),
|
|
6627
4724
|
TELEMETRY_SERVICE: Symbol('TELEMETRY_SERVICE'),
|
|
6628
4725
|
};
|
|
@@ -7597,468 +5694,6 @@ class TelemetryRepositoryImpl {
|
|
|
7597
5694
|
}
|
|
7598
5695
|
}
|
|
7599
5696
|
|
|
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();
|
|
7842
|
-
}
|
|
7843
|
-
setupNetworkMonitoring() {
|
|
7844
|
-
if (this.networkMonitor) {
|
|
7845
|
-
this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
|
|
7846
|
-
if (this.currentOnlineState !== online) {
|
|
7847
|
-
log$3.info('Network state changed:', { online });
|
|
7848
|
-
}
|
|
7849
|
-
this.currentOnlineState = online;
|
|
7850
|
-
});
|
|
7851
|
-
}
|
|
7852
|
-
}
|
|
7853
|
-
isOnline() {
|
|
7854
|
-
if (this.networkMonitor) {
|
|
7855
|
-
return this.currentOnlineState;
|
|
7856
|
-
}
|
|
7857
|
-
if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
|
|
7858
|
-
return navigator.onLine;
|
|
7859
|
-
}
|
|
7860
|
-
return true;
|
|
7861
|
-
}
|
|
7862
|
-
async get(url, config) {
|
|
7863
|
-
const startTime = Date.now();
|
|
7864
|
-
// Check if caching is disabled globally
|
|
7865
|
-
if (this.config.enabled === false) {
|
|
7866
|
-
log$3.debug('GET (cache disabled globally):', { url });
|
|
7867
|
-
return this.http.get(url, config);
|
|
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
|
-
}
|
|
7999
|
-
}
|
|
8000
|
-
async post(url, data, config) {
|
|
8001
|
-
log$3.info('POST request:', { url });
|
|
8002
|
-
const response = await this.http.post(url, data, config);
|
|
8003
|
-
await this.invalidateRelated(url, 'POST');
|
|
8004
|
-
return response;
|
|
8005
|
-
}
|
|
8006
|
-
async put(url, data, config) {
|
|
8007
|
-
log$3.info('PUT request:', { url });
|
|
8008
|
-
const response = await this.http.put(url, data, config);
|
|
8009
|
-
await this.invalidateRelated(url, 'PUT');
|
|
8010
|
-
return response;
|
|
8011
|
-
}
|
|
8012
|
-
async patch(url, data, config) {
|
|
8013
|
-
log$3.info('PATCH request:', { url });
|
|
8014
|
-
const response = await this.http.patch(url, data, config);
|
|
8015
|
-
await this.invalidateRelated(url, 'PATCH');
|
|
8016
|
-
return response;
|
|
8017
|
-
}
|
|
8018
|
-
async delete(url, config) {
|
|
8019
|
-
log$3.info('DELETE request:', { url });
|
|
8020
|
-
const response = await this.http.delete(url, config);
|
|
8021
|
-
await this.invalidateRelated(url, 'DELETE');
|
|
8022
|
-
return response;
|
|
8023
|
-
}
|
|
8024
|
-
setAuthToken(token) {
|
|
8025
|
-
log$3.debug('CachingHttpDecorator.setAuthToken called:', {
|
|
8026
|
-
hasToken: !!token,
|
|
8027
|
-
tokenPrefix: token?.substring(0, 20),
|
|
8028
|
-
underlyingHttpType: this.http.constructor.name,
|
|
8029
|
-
});
|
|
8030
|
-
this.authToken = token;
|
|
8031
|
-
this.http.setAuthToken(token);
|
|
8032
|
-
}
|
|
8033
|
-
getAuthToken() {
|
|
8034
|
-
return this.authToken;
|
|
8035
|
-
}
|
|
8036
|
-
async invalidateRelated(url, method) {
|
|
8037
|
-
const patterns = this.keyGenerator.getInvalidationPatterns(url, method);
|
|
8038
|
-
if (patterns.length === 0) {
|
|
8039
|
-
log$3.debug('No cache patterns to invalidate:', { url, method });
|
|
8040
|
-
return;
|
|
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
|
-
}
|
|
8055
|
-
}
|
|
8056
|
-
destroy() {
|
|
8057
|
-
log$3.debug('CachingHttpDecorator destroyed');
|
|
8058
|
-
this.networkSubscription?.unsubscribe();
|
|
8059
|
-
}
|
|
8060
|
-
}
|
|
8061
|
-
|
|
8062
5697
|
const logJwt = createPrefixedLogger('HTTP-JWT');
|
|
8063
5698
|
const logMtls = createPrefixedLogger('HTTP-MTLS');
|
|
8064
5699
|
class AxiosHttpAdapter {
|
|
@@ -8328,7 +5963,6 @@ class SDKFactory {
|
|
|
8328
5963
|
baseUrl: config.baseUrl,
|
|
8329
5964
|
timeout: config.timeout,
|
|
8330
5965
|
});
|
|
8331
|
-
container.register(DI_TOKENS.BASE_HTTP_PORT, httpAdapter);
|
|
8332
5966
|
container.register(DI_TOKENS.HTTP_PORT, httpAdapter);
|
|
8333
5967
|
container.registerFactory(DI_TOKENS.RECEIPT_REPOSITORY, () => {
|
|
8334
5968
|
const http = container.get(DI_TOKENS.HTTP_PORT);
|
|
@@ -8376,23 +6010,6 @@ class SDKFactory {
|
|
|
8376
6010
|
});
|
|
8377
6011
|
return container;
|
|
8378
6012
|
}
|
|
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
6013
|
static registerAuthServices(container, secureStorage, config) {
|
|
8397
6014
|
const tokenStorage = new TokenStorageAdapter(secureStorage);
|
|
8398
6015
|
container.register(DI_TOKENS.TOKEN_STORAGE_PORT, tokenStorage);
|
|
@@ -8430,7 +6047,7 @@ class SDKFactory {
|
|
|
8430
6047
|
}
|
|
8431
6048
|
}
|
|
8432
6049
|
|
|
8433
|
-
const log$
|
|
6050
|
+
const log$1 = createPrefixedLogger('SDK');
|
|
8434
6051
|
class ACubeSDK {
|
|
8435
6052
|
constructor(config, customAdapters, events = {}) {
|
|
8436
6053
|
this.events = events;
|
|
@@ -8444,23 +6061,22 @@ class ACubeSDK {
|
|
|
8444
6061
|
}
|
|
8445
6062
|
async initialize() {
|
|
8446
6063
|
if (this.isInitialized) {
|
|
8447
|
-
log$
|
|
6064
|
+
log$1.debug('SDK already initialized, skipping');
|
|
8448
6065
|
return;
|
|
8449
6066
|
}
|
|
8450
|
-
log$
|
|
6067
|
+
log$1.info('Initializing SDK', {
|
|
8451
6068
|
apiUrl: this.config.getApiUrl(),
|
|
8452
6069
|
authUrl: this.config.getAuthUrl(),
|
|
8453
6070
|
debugEnabled: this.config.isDebugEnabled(),
|
|
8454
6071
|
});
|
|
8455
6072
|
try {
|
|
8456
6073
|
if (!this.adapters) {
|
|
8457
|
-
log$
|
|
6074
|
+
log$1.debug('Loading platform adapters');
|
|
8458
6075
|
const mtlsConfig = createACubeMTLSConfig(this.config.getApiUrl(), this.config.getTimeout(), true);
|
|
8459
6076
|
this.adapters = loadPlatformAdapters({
|
|
8460
6077
|
mtlsConfig,
|
|
8461
6078
|
});
|
|
8462
|
-
log$
|
|
8463
|
-
hasCache: !!this.adapters.cache,
|
|
6079
|
+
log$1.info('Platform adapters loaded', {
|
|
8464
6080
|
hasNetworkMonitor: !!this.adapters.networkMonitor,
|
|
8465
6081
|
hasMtls: !!this.adapters.mtls,
|
|
8466
6082
|
hasSecureStorage: !!this.adapters.secureStorage,
|
|
@@ -8472,30 +6088,15 @@ class ACubeSDK {
|
|
|
8472
6088
|
timeout: this.config.getTimeout(),
|
|
8473
6089
|
debugEnabled: this.config.isDebugEnabled(),
|
|
8474
6090
|
};
|
|
8475
|
-
log$
|
|
6091
|
+
log$1.debug('Creating DI container');
|
|
8476
6092
|
this.container = SDKFactory.createContainer(factoryConfig);
|
|
8477
|
-
log$
|
|
6093
|
+
log$1.debug('Registering auth services');
|
|
8478
6094
|
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');
|
|
6095
|
+
log$1.debug('Initializing certificate service');
|
|
8489
6096
|
this.certificateService = new CertificateService(this.adapters.secureStorage);
|
|
8490
6097
|
const tokenStorage = this.container.get(DI_TOKENS.TOKEN_STORAGE_PORT);
|
|
8491
6098
|
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');
|
|
6099
|
+
log$1.debug('Initializing authentication service');
|
|
8499
6100
|
this.authService = new AuthenticationService(httpPort, tokenStorage, {
|
|
8500
6101
|
authUrl: this.config.getAuthUrl(),
|
|
8501
6102
|
timeout: this.config.getTimeout(),
|
|
@@ -8505,51 +6106,33 @@ class ACubeSDK {
|
|
|
8505
6106
|
this.events.onAuthError?.(new ACubeSDKError('AUTH_ERROR', error.message, error));
|
|
8506
6107
|
},
|
|
8507
6108
|
});
|
|
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
6109
|
this.networkSubscription = this.adapters.networkMonitor.online$.subscribe((online) => {
|
|
8524
6110
|
this.currentOnlineState = online;
|
|
8525
6111
|
this.events.onNetworkStatusChanged?.(online);
|
|
8526
|
-
if (online && this.offlineManager) {
|
|
8527
|
-
this.offlineManager.sync().catch(() => { });
|
|
8528
|
-
}
|
|
8529
6112
|
});
|
|
8530
6113
|
const isAuth = await this.authService.isAuthenticated();
|
|
8531
|
-
log$
|
|
6114
|
+
log$1.debug('Checking authentication status during init', { isAuthenticated: isAuth });
|
|
8532
6115
|
if (isAuth) {
|
|
8533
6116
|
const token = await this.authService.getAccessToken();
|
|
8534
|
-
log$
|
|
6117
|
+
log$1.debug('Token retrieved during init', {
|
|
8535
6118
|
hasToken: !!token,
|
|
8536
6119
|
tokenPrefix: token?.substring(0, 20),
|
|
8537
6120
|
});
|
|
8538
6121
|
if (token) {
|
|
8539
6122
|
httpPort.setAuthToken(token);
|
|
8540
|
-
log$
|
|
6123
|
+
log$1.info('Auth token set on HTTP port during initialization');
|
|
8541
6124
|
}
|
|
8542
6125
|
}
|
|
8543
6126
|
else {
|
|
8544
|
-
log$
|
|
6127
|
+
log$1.warn('User not authenticated during SDK init - token will be set after login');
|
|
8545
6128
|
}
|
|
8546
|
-
if (this.adapters?.mtls && 'setMTLSAdapter' in
|
|
8547
|
-
log$
|
|
8548
|
-
const httpWithMtls =
|
|
6129
|
+
if (this.adapters?.mtls && 'setMTLSAdapter' in httpPort) {
|
|
6130
|
+
log$1.debug('Connecting mTLS adapter to HTTP port');
|
|
6131
|
+
const httpWithMtls = httpPort;
|
|
8549
6132
|
httpWithMtls.setMTLSAdapter(this.adapters.mtls);
|
|
8550
6133
|
}
|
|
8551
|
-
if ('setAuthStrategy' in
|
|
8552
|
-
log$
|
|
6134
|
+
if ('setAuthStrategy' in httpPort) {
|
|
6135
|
+
log$1.debug('Configuring auth strategy');
|
|
8553
6136
|
const jwtHandler = new JwtAuthHandler(tokenStorage);
|
|
8554
6137
|
const certificatePort = this.certificateService
|
|
8555
6138
|
? {
|
|
@@ -8580,7 +6163,7 @@ class ACubeSDK {
|
|
|
8580
6163
|
},
|
|
8581
6164
|
};
|
|
8582
6165
|
const authStrategy = new AuthStrategy(jwtHandler, mtlsHandler, userProvider, this.adapters?.mtls || null);
|
|
8583
|
-
const httpWithStrategy =
|
|
6166
|
+
const httpWithStrategy = httpPort;
|
|
8584
6167
|
httpWithStrategy.setAuthStrategy(authStrategy);
|
|
8585
6168
|
}
|
|
8586
6169
|
if (this.adapters?.mtls && this.certificateService) {
|
|
@@ -8598,19 +6181,18 @@ class ACubeSDK {
|
|
|
8598
6181
|
}
|
|
8599
6182
|
}
|
|
8600
6183
|
catch (certError) {
|
|
8601
|
-
log$
|
|
6184
|
+
log$1.warn('Certificate auto-configuration failed, will retry on demand', {
|
|
8602
6185
|
error: certError instanceof Error ? certError.message : certError,
|
|
8603
6186
|
});
|
|
8604
6187
|
}
|
|
8605
6188
|
}
|
|
8606
6189
|
this.isInitialized = true;
|
|
8607
|
-
log$
|
|
8608
|
-
hasCache: !!this.adapters.cache,
|
|
6190
|
+
log$1.info('SDK initialized successfully', {
|
|
8609
6191
|
hasMtls: !!this.adapters.mtls,
|
|
8610
6192
|
});
|
|
8611
6193
|
}
|
|
8612
6194
|
catch (error) {
|
|
8613
|
-
log$
|
|
6195
|
+
log$1.error('SDK initialization failed', {
|
|
8614
6196
|
error: error instanceof Error ? error.message : error,
|
|
8615
6197
|
});
|
|
8616
6198
|
throw new ACubeSDKError('SDK_INITIALIZATION_ERROR', `Failed to initialize SDK: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
@@ -8666,19 +6248,19 @@ class ACubeSDK {
|
|
|
8666
6248
|
}
|
|
8667
6249
|
async login(credentials) {
|
|
8668
6250
|
this.ensureInitialized();
|
|
8669
|
-
log$
|
|
6251
|
+
log$1.info('Login attempt', { email: credentials.email });
|
|
8670
6252
|
const user = await this.authService.login(credentials);
|
|
8671
|
-
log$
|
|
6253
|
+
log$1.info('Login successful', { roles: user.roles });
|
|
8672
6254
|
const token = await this.authService.getAccessToken();
|
|
8673
6255
|
if (token) {
|
|
8674
6256
|
this.httpPort.setAuthToken(token);
|
|
8675
|
-
log$
|
|
6257
|
+
log$1.debug('Auth token set on HTTP port');
|
|
8676
6258
|
}
|
|
8677
6259
|
return user;
|
|
8678
6260
|
}
|
|
8679
6261
|
async logout() {
|
|
8680
6262
|
this.ensureInitialized();
|
|
8681
|
-
log$
|
|
6263
|
+
log$1.info('Logout');
|
|
8682
6264
|
await this.authService.logout();
|
|
8683
6265
|
this.httpPort.setAuthToken(null);
|
|
8684
6266
|
}
|
|
@@ -8696,10 +6278,6 @@ class ACubeSDK {
|
|
|
8696
6278
|
this.ensureInitialized();
|
|
8697
6279
|
return await this.authService.isAuthenticated();
|
|
8698
6280
|
}
|
|
8699
|
-
getOfflineManager() {
|
|
8700
|
-
this.ensureInitialized();
|
|
8701
|
-
return this.offlineManager;
|
|
8702
|
-
}
|
|
8703
6281
|
isOnline() {
|
|
8704
6282
|
this.ensureInitialized();
|
|
8705
6283
|
return this.currentOnlineState;
|
|
@@ -8827,7 +6405,6 @@ class ACubeSDK {
|
|
|
8827
6405
|
}
|
|
8828
6406
|
destroy() {
|
|
8829
6407
|
this.networkSubscription?.unsubscribe();
|
|
8830
|
-
this.offlineManager?.destroy();
|
|
8831
6408
|
this.container?.clear();
|
|
8832
6409
|
this.isInitialized = false;
|
|
8833
6410
|
}
|
|
@@ -9113,7 +6690,6 @@ class TelemetryService {
|
|
|
9113
6690
|
this.events = events;
|
|
9114
6691
|
this.stateSubject = new BehaviorSubject({
|
|
9115
6692
|
data: null,
|
|
9116
|
-
isCached: false,
|
|
9117
6693
|
isLoading: false,
|
|
9118
6694
|
lastFetchedAt: null,
|
|
9119
6695
|
});
|
|
@@ -9183,7 +6759,6 @@ class TelemetryService {
|
|
|
9183
6759
|
const data = await this.repository.getTelemetry(this.currentPemId);
|
|
9184
6760
|
const newState = {
|
|
9185
6761
|
data,
|
|
9186
|
-
isCached: false,
|
|
9187
6762
|
isLoading: false,
|
|
9188
6763
|
lastFetchedAt: Date.now(),
|
|
9189
6764
|
};
|
|
@@ -9208,7 +6783,6 @@ class TelemetryService {
|
|
|
9208
6783
|
clearTelemetry() {
|
|
9209
6784
|
this.stateSubject.next({
|
|
9210
6785
|
data: null,
|
|
9211
|
-
isCached: false,
|
|
9212
6786
|
isLoading: false,
|
|
9213
6787
|
lastFetchedAt: null,
|
|
9214
6788
|
});
|
|
@@ -9221,7 +6795,7 @@ class TelemetryService {
|
|
|
9221
6795
|
}
|
|
9222
6796
|
}
|
|
9223
6797
|
|
|
9224
|
-
const log
|
|
6798
|
+
const log = createPrefixedLogger('SDK-MANAGER');
|
|
9225
6799
|
/**
|
|
9226
6800
|
* SDKManager - Singleton wrapper for ACubeSDK with simplified API
|
|
9227
6801
|
*
|
|
@@ -9288,7 +6862,7 @@ class SDKManager {
|
|
|
9288
6862
|
if (canPoll && !this.isPollingActive) {
|
|
9289
6863
|
const hasCert = await this.checkCertificate();
|
|
9290
6864
|
if (!hasCert) {
|
|
9291
|
-
log
|
|
6865
|
+
log.warn('Certificate missing — polling blocked until certificate is installed');
|
|
9292
6866
|
return;
|
|
9293
6867
|
}
|
|
9294
6868
|
this.notificationService?.startPolling();
|
|
@@ -9390,7 +6964,7 @@ class SDKManager {
|
|
|
9390
6964
|
this.isPollingActive = true;
|
|
9391
6965
|
}
|
|
9392
6966
|
else {
|
|
9393
|
-
log
|
|
6967
|
+
log.warn('Certificate missing at init — polling blocked until certificate is installed');
|
|
9394
6968
|
}
|
|
9395
6969
|
}
|
|
9396
6970
|
// AppStateService remains active for all users (handles OFFLINE network state)
|
|
@@ -9433,7 +7007,7 @@ class SDKManager {
|
|
|
9433
7007
|
return this.certificateMissingSubject.asObservable();
|
|
9434
7008
|
}
|
|
9435
7009
|
/**
|
|
9436
|
-
* Observable stream of telemetry state (data, isLoading,
|
|
7010
|
+
* Observable stream of telemetry state (data, isLoading, error)
|
|
9437
7011
|
*/
|
|
9438
7012
|
get telemetryState$() {
|
|
9439
7013
|
this.ensureInitialized();
|
|
@@ -9517,7 +7091,7 @@ class SDKManager {
|
|
|
9517
7091
|
const user = await sdk.getCurrentUser();
|
|
9518
7092
|
const canPoll = user && hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
|
|
9519
7093
|
if (canPoll) {
|
|
9520
|
-
log
|
|
7094
|
+
log.info('Certificate installed — starting polling');
|
|
9521
7095
|
this.notificationService?.startPolling();
|
|
9522
7096
|
await this.startTelemetryPollingAuto();
|
|
9523
7097
|
this.isPollingActive = true;
|
|
@@ -9530,7 +7104,7 @@ class SDKManager {
|
|
|
9530
7104
|
this.certificateMissingSubject.next(true);
|
|
9531
7105
|
// Stop polling since certificate is required
|
|
9532
7106
|
if (this.isPollingActive) {
|
|
9533
|
-
log
|
|
7107
|
+
log.info('Certificate removed — stopping polling');
|
|
9534
7108
|
this.notificationService?.stopPolling();
|
|
9535
7109
|
this.telemetryService?.stopPolling();
|
|
9536
7110
|
this.telemetryService?.clearTelemetry();
|
|
@@ -9610,91 +7184,6 @@ const RECEIPT_SENT = 'sent';
|
|
|
9610
7184
|
const RECEIPT_SORT_DESCENDING = 'descending';
|
|
9611
7185
|
const RECEIPT_SORT_ASCENDING = 'ascending';
|
|
9612
7186
|
|
|
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
7187
|
function transformError(error) {
|
|
9699
7188
|
if (axios.isAxiosError(error)) {
|
|
9700
7189
|
const response = error.response;
|
|
@@ -9882,7 +7371,6 @@ exports.AppStateService = AppStateService;
|
|
|
9882
7371
|
exports.AuthStrategy = AuthStrategy;
|
|
9883
7372
|
exports.AuthenticationService = AuthenticationService;
|
|
9884
7373
|
exports.AxiosHttpAdapter = AxiosHttpAdapter;
|
|
9885
|
-
exports.CacheHandler = CacheHandler;
|
|
9886
7374
|
exports.CashRegisterCreateSchema = CashRegisterCreateSchema;
|
|
9887
7375
|
exports.CashRegisterMapper = CashRegisterMapper;
|
|
9888
7376
|
exports.CashRegisterRepositoryImpl = CashRegisterRepositoryImpl;
|
|
@@ -9893,7 +7381,6 @@ exports.CertificateService = CertificateService;
|
|
|
9893
7381
|
exports.CertificateValidator = CertificateValidator;
|
|
9894
7382
|
exports.ConfigManager = ConfigManager;
|
|
9895
7383
|
exports.DAILY_REPORT_STATUS_OPTIONS = DAILY_REPORT_STATUS_OPTIONS;
|
|
9896
|
-
exports.DEFAULT_QUEUE_CONFIG = DEFAULT_QUEUE_CONFIG;
|
|
9897
7384
|
exports.DIContainer = DIContainer;
|
|
9898
7385
|
exports.DI_TOKENS = DI_TOKENS;
|
|
9899
7386
|
exports.DailyReportMapper = DailyReportMapper;
|
|
@@ -9932,8 +7419,6 @@ exports.NotificationRepositoryImpl = NotificationRepositoryImpl;
|
|
|
9932
7419
|
exports.NotificationSchema = NotificationSchema;
|
|
9933
7420
|
exports.NotificationScopeSchema = NotificationScopeSchema;
|
|
9934
7421
|
exports.NotificationService = NotificationService;
|
|
9935
|
-
exports.OfflineManager = OfflineManager;
|
|
9936
|
-
exports.OperationQueue = OperationQueue;
|
|
9937
7422
|
exports.PEMStatusOfflineRequestSchema = PEMStatusOfflineRequestSchema;
|
|
9938
7423
|
exports.PEMStatusSchema = PEMStatusSchema;
|
|
9939
7424
|
exports.PEM_STATUS_OPTIONS = PEM_STATUS_OPTIONS;
|
|
@@ -9968,7 +7453,6 @@ exports.SupplierCreateInputSchema = SupplierCreateInputSchema;
|
|
|
9968
7453
|
exports.SupplierMapper = SupplierMapper;
|
|
9969
7454
|
exports.SupplierRepositoryImpl = SupplierRepositoryImpl;
|
|
9970
7455
|
exports.SupplierUpdateInputSchema = SupplierUpdateInputSchema;
|
|
9971
|
-
exports.SyncManager = SyncManager;
|
|
9972
7456
|
exports.TelemetryMapper = TelemetryMapper;
|
|
9973
7457
|
exports.TelemetryMerchantSchema = TelemetryMerchantSchema;
|
|
9974
7458
|
exports.TelemetryRepositoryImpl = TelemetryRepositoryImpl;
|