@a-cube-io/ereceipts-js-sdk 2.0.8 → 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/dist/index.cjs.js CHANGED
@@ -1415,6 +1415,15 @@ function from(input, scheduler) {
1415
1415
  return scheduler ? scheduled(input, scheduler) : innerFrom(input);
1416
1416
  }
1417
1417
 
1418
+ function of() {
1419
+ var args = [];
1420
+ for (var _i = 0; _i < arguments.length; _i++) {
1421
+ args[_i] = arguments[_i];
1422
+ }
1423
+ var scheduler = popScheduler(args);
1424
+ return from(args, scheduler);
1425
+ }
1426
+
1418
1427
  function isValidDate(value) {
1419
1428
  return value instanceof Date && !isNaN(value);
1420
1429
  }
@@ -2126,7 +2135,7 @@ function formatDecimal(value, decimals = 2) {
2126
2135
  return num.toFixed(decimals);
2127
2136
  }
2128
2137
 
2129
- const log$g = createPrefixedLogger('AUTH-SERVICE');
2138
+ const log$b = createPrefixedLogger('AUTH-SERVICE');
2130
2139
  class AuthenticationService {
2131
2140
  get user$() {
2132
2141
  return this.userSubject.asObservable();
@@ -2148,7 +2157,7 @@ class AuthenticationService {
2148
2157
  }
2149
2158
  async login(credentials) {
2150
2159
  this.authStateSubject.next('authenticating');
2151
- log$g.info('Login attempt', {
2160
+ log$b.info('Login attempt', {
2152
2161
  authUrl: this.config.authUrl,
2153
2162
  email: credentials.email,
2154
2163
  });
@@ -2159,7 +2168,7 @@ class AuthenticationService {
2159
2168
  });
2160
2169
  const jwtPayload = parseJwt(response.data.token);
2161
2170
  const expiresAt = jwtPayload.exp * 1000;
2162
- log$g.info('Login successful', {
2171
+ log$b.info('Login successful', {
2163
2172
  authUrl: this.config.authUrl,
2164
2173
  tokenPrefix: response.data.token.substring(0, 30) + '...',
2165
2174
  expiresAt: new Date(expiresAt).toISOString(),
@@ -2188,21 +2197,21 @@ class AuthenticationService {
2188
2197
  const token = await this.tokenStorage.getAccessToken();
2189
2198
  if (!token) {
2190
2199
  // No token - clear any stale user state
2191
- log$g.debug('getCurrentUser: No token in storage');
2200
+ log$b.debug('getCurrentUser: No token in storage');
2192
2201
  if (this.userSubject.value) {
2193
2202
  this.userSubject.next(null);
2194
2203
  this.authStateSubject.next('idle');
2195
2204
  }
2196
2205
  return null;
2197
2206
  }
2198
- log$g.debug('getCurrentUser: Token found', {
2207
+ log$b.debug('getCurrentUser: Token found', {
2199
2208
  tokenPrefix: token.substring(0, 30) + '...',
2200
2209
  tokenLength: token.length,
2201
2210
  });
2202
2211
  const jwtPayload = parseJwt(token);
2203
2212
  if (isTokenExpired(jwtPayload)) {
2204
2213
  // Token expired - clear everything
2205
- log$g.warn('getCurrentUser: Token expired');
2214
+ log$b.warn('getCurrentUser: Token expired');
2206
2215
  await this.tokenStorage.clearTokens();
2207
2216
  this.userSubject.next(null);
2208
2217
  this.authStateSubject.next('idle');
@@ -2212,7 +2221,7 @@ class AuthenticationService {
2212
2221
  // Token is valid - return cached user if available
2213
2222
  const currentUser = this.userSubject.value;
2214
2223
  if (currentUser) {
2215
- log$g.debug('getCurrentUser: Returning cached user', {
2224
+ log$b.debug('getCurrentUser: Returning cached user', {
2216
2225
  email: currentUser.email,
2217
2226
  roles: currentUser.roles,
2218
2227
  });
@@ -2235,12 +2244,12 @@ class AuthenticationService {
2235
2244
  async isAuthenticated() {
2236
2245
  const token = await this.tokenStorage.getAccessToken();
2237
2246
  if (!token) {
2238
- log$g.debug('isAuthenticated: No token in storage');
2247
+ log$b.debug('isAuthenticated: No token in storage');
2239
2248
  return false;
2240
2249
  }
2241
2250
  const jwtPayload = parseJwt(token);
2242
2251
  const expired = isTokenExpired(jwtPayload);
2243
- log$g.debug('isAuthenticated: Token check', {
2252
+ log$b.debug('isAuthenticated: Token check', {
2244
2253
  hasToken: true,
2245
2254
  expired,
2246
2255
  expiresAt: new Date(jwtPayload.exp * 1000).toISOString(),
@@ -2912,7 +2921,7 @@ class ACubeSDKError extends Error {
2912
2921
  }
2913
2922
  }
2914
2923
 
2915
- const log$f = createPrefixedLogger('AUTH-STRATEGY');
2924
+ const log$a = createPrefixedLogger('AUTH-STRATEGY');
2916
2925
  class AuthStrategy {
2917
2926
  constructor(jwtHandler, mtlsHandler, userProvider, mtlsAdapter) {
2918
2927
  this.jwtHandler = jwtHandler;
@@ -2927,7 +2936,7 @@ class AuthStrategy {
2927
2936
  const platform = this.detectPlatform();
2928
2937
  const userRole = await this.getUserRole();
2929
2938
  const isReceiptEndpoint = this.isReceiptEndpoint(url);
2930
- log$f.debug('Determining auth config', {
2939
+ log$a.debug('Determining auth config', {
2931
2940
  url,
2932
2941
  method,
2933
2942
  platform,
@@ -3058,7 +3067,7 @@ class JwtAuthHandler {
3058
3067
  }
3059
3068
  }
3060
3069
 
3061
- const log$e = createPrefixedLogger('MTLS-HANDLER');
3070
+ const log$9 = createPrefixedLogger('MTLS-HANDLER');
3062
3071
  class MtlsAuthHandler {
3063
3072
  constructor(mtlsAdapter, certificatePort) {
3064
3073
  this.mtlsAdapter = mtlsAdapter;
@@ -3100,7 +3109,7 @@ class MtlsAuthHandler {
3100
3109
  async makeRequest(url, config, jwtToken) {
3101
3110
  const requestKey = this.generateRequestKey(url, config, jwtToken);
3102
3111
  if (this.pendingRequests.has(requestKey)) {
3103
- log$e.debug('Deduplicating concurrent request:', url);
3112
+ log$9.debug('Deduplicating concurrent request:', url);
3104
3113
  return this.pendingRequests.get(requestKey);
3105
3114
  }
3106
3115
  const requestPromise = this.executeRequest(url, config, jwtToken, false);
@@ -3124,10 +3133,10 @@ class MtlsAuthHandler {
3124
3133
  };
3125
3134
  if (jwtToken) {
3126
3135
  headers['Authorization'] = `Bearer ${jwtToken}`;
3127
- log$e.debug('JWT token present:', jwtToken.substring(0, 20) + '...');
3136
+ log$9.debug('JWT token present:', jwtToken.substring(0, 20) + '...');
3128
3137
  }
3129
3138
  else {
3130
- log$e.warn('No JWT token provided for mTLS request');
3139
+ log$9.warn('No JWT token provided for mTLS request');
3131
3140
  }
3132
3141
  const fullUrl = this.constructMtlsUrl(url);
3133
3142
  const mtlsConfig = {
@@ -3138,25 +3147,25 @@ class MtlsAuthHandler {
3138
3147
  timeout: config.timeout,
3139
3148
  responseType: config.responseType,
3140
3149
  };
3141
- log$e.debug('header-mtls', headers);
3142
- log$e.debug(`${config.method} ${fullUrl}`);
3150
+ log$9.debug('header-mtls', headers);
3151
+ log$9.debug(`${config.method} ${fullUrl}`);
3143
3152
  if (config.data) {
3144
- log$e.debug('Request body:', config.data);
3153
+ log$9.debug('Request body:', config.data);
3145
3154
  }
3146
3155
  try {
3147
3156
  const response = await this.mtlsAdapter.request(mtlsConfig);
3148
- log$e.debug(`Response ${response.status} from ${fullUrl}`);
3157
+ log$9.debug(`Response ${response.status} from ${fullUrl}`);
3149
3158
  if (response.data) {
3150
- log$e.debug('Response body:', response.data);
3159
+ log$9.debug('Response body:', response.data);
3151
3160
  }
3152
3161
  return response.data;
3153
3162
  }
3154
3163
  catch (error) {
3155
- log$e.error(`Response error from ${fullUrl}:`, error);
3164
+ log$9.error(`Response error from ${fullUrl}:`, error);
3156
3165
  if (error && typeof error === 'object' && 'response' in error) {
3157
3166
  const axiosError = error;
3158
3167
  if (axiosError.response?.data) {
3159
- log$e.error('Response body:', axiosError.response.data);
3168
+ log$9.error('Response body:', axiosError.response.data);
3160
3169
  }
3161
3170
  }
3162
3171
  if (isRetryAttempt) {
@@ -3166,7 +3175,7 @@ class MtlsAuthHandler {
3166
3175
  if (!shouldRetry) {
3167
3176
  throw error;
3168
3177
  }
3169
- log$e.debug('Request failed, reconfiguring certificate and retrying...');
3178
+ log$9.debug('Request failed, reconfiguring certificate and retrying...');
3170
3179
  try {
3171
3180
  await this.configureCertificate(certificate);
3172
3181
  return await this.executeRequest(url, config, jwtToken, true);
@@ -3250,1923 +3259,27 @@ class MtlsAuthHandler {
3250
3259
  adapterAvailable: !!this.mtlsAdapter,
3251
3260
  certificatePortAvailable: !!this.certificatePort,
3252
3261
  isReady: false,
3253
- hasCertificate: false,
3254
- certificateInfo: null,
3255
- platformInfo: this.mtlsAdapter?.getPlatformInfo() || null,
3256
- pendingRequestsCount: this.pendingRequests.size,
3257
- };
3258
- if (this.certificatePort) {
3259
- try {
3260
- status.hasCertificate = await this.certificatePort.hasCertificate();
3261
- if (status.hasCertificate) {
3262
- status.certificateInfo = await this.certificatePort.getCertificateInfo();
3263
- }
3264
- }
3265
- catch {
3266
- // Ignore errors
3267
- }
3268
- }
3269
- status.isReady = await this.isMtlsReady();
3270
- return status;
3271
- }
3272
- clearPendingRequests() {
3273
- this.pendingRequests.clear();
3274
- }
3275
- }
3276
-
3277
- const DEFAULT_QUEUE_CONFIG = {
3278
- maxRetries: 3,
3279
- retryDelay: 1000,
3280
- maxRetryDelay: 30000,
3281
- backoffMultiplier: 2,
3282
- maxQueueSize: 1000,
3283
- batchSize: 10,
3284
- syncInterval: 30000,
3285
- };
3286
-
3287
- class OperationQueue {
3288
- constructor(storage, config = DEFAULT_QUEUE_CONFIG, events = {}) {
3289
- this.storage = storage;
3290
- this.config = config;
3291
- this.events = events;
3292
- this.queue = [];
3293
- this.processing = false;
3294
- this.config = { ...DEFAULT_QUEUE_CONFIG, ...config };
3295
- this.loadQueue();
3296
- if (this.config.syncInterval > 0) {
3297
- this.startAutoSync();
3298
- }
3299
- }
3300
- async addOperation(type, resource, endpoint, method, data, priority = 1) {
3301
- if (this.queue.length >= this.config.maxQueueSize) {
3302
- const lowPriorityIndex = this.queue.findIndex((op) => op.priority === 1);
3303
- if (lowPriorityIndex !== -1) {
3304
- this.queue.splice(lowPriorityIndex, 1);
3305
- }
3306
- else {
3307
- throw new Error('Queue is full');
3308
- }
3309
- }
3310
- const operation = {
3311
- id: this.generateId(),
3312
- type,
3313
- resource,
3314
- endpoint,
3315
- method,
3316
- data,
3317
- status: 'pending',
3318
- createdAt: Date.now(),
3319
- updatedAt: Date.now(),
3320
- retryCount: 0,
3321
- maxRetries: this.config.maxRetries,
3322
- priority,
3323
- };
3324
- const insertIndex = this.queue.findIndex((op) => op.priority < priority);
3325
- if (insertIndex === -1) {
3326
- this.queue.push(operation);
3327
- }
3328
- else {
3329
- this.queue.splice(insertIndex, 0, operation);
3330
- }
3331
- await this.saveQueue();
3332
- this.events.onOperationAdded?.(operation);
3333
- return operation.id;
3334
- }
3335
- getPendingOperations() {
3336
- return this.queue.filter((op) => op.status === 'pending' || op.status === 'failed');
3337
- }
3338
- getOperation(id) {
3339
- return this.queue.find((op) => op.id === id);
3340
- }
3341
- async removeOperation(id) {
3342
- const index = this.queue.findIndex((op) => op.id === id);
3343
- if (index === -1)
3344
- return false;
3345
- this.queue.splice(index, 1);
3346
- await this.saveQueue();
3347
- return true;
3348
- }
3349
- async updateOperation(id, updates) {
3350
- const operation = this.queue.find((op) => op.id === id);
3351
- if (!operation)
3352
- return false;
3353
- Object.assign(operation, { ...updates, updatedAt: Date.now() });
3354
- await this.saveQueue();
3355
- return true;
3356
- }
3357
- getStats() {
3358
- return {
3359
- total: this.queue.length,
3360
- pending: this.queue.filter((op) => op.status === 'pending').length,
3361
- processing: this.queue.filter((op) => op.status === 'processing').length,
3362
- completed: this.queue.filter((op) => op.status === 'completed').length,
3363
- failed: this.queue.filter((op) => op.status === 'failed').length,
3364
- };
3365
- }
3366
- async clearQueue() {
3367
- this.queue = [];
3368
- await this.saveQueue();
3369
- }
3370
- async clearCompleted() {
3371
- this.queue = this.queue.filter((op) => op.status !== 'completed');
3372
- await this.saveQueue();
3373
- }
3374
- async clearFailed() {
3375
- this.queue = this.queue.filter((op) => op.status !== 'failed');
3376
- await this.saveQueue();
3377
- }
3378
- async retryFailed() {
3379
- for (const operation of this.queue.filter((op) => op.status === 'failed')) {
3380
- if (operation.retryCount < operation.maxRetries) {
3381
- operation.status = 'pending';
3382
- operation.retryCount++;
3383
- operation.updatedAt = Date.now();
3384
- delete operation.error;
3385
- }
3386
- }
3387
- await this.saveQueue();
3388
- }
3389
- getNextBatch() {
3390
- return this.queue
3391
- .filter((op) => op.status === 'pending')
3392
- .sort((a, b) => b.priority - a.priority || a.createdAt - b.createdAt)
3393
- .slice(0, this.config.batchSize);
3394
- }
3395
- isEmpty() {
3396
- return this.getPendingOperations().length === 0;
3397
- }
3398
- startAutoSync() {
3399
- if (this.syncIntervalId)
3400
- return;
3401
- this.syncIntervalId = setInterval(() => {
3402
- if (!this.isEmpty() && !this.processing) {
3403
- this.events.onQueueEmpty?.();
3404
- }
3405
- }, this.config.syncInterval);
3406
- }
3407
- stopAutoSync() {
3408
- if (this.syncIntervalId) {
3409
- clearInterval(this.syncIntervalId);
3410
- this.syncIntervalId = undefined;
3411
- }
3412
- }
3413
- setProcessing(value) {
3414
- this.processing = value;
3415
- }
3416
- isCurrentlyProcessing() {
3417
- return this.processing;
3418
- }
3419
- async loadQueue() {
3420
- try {
3421
- const queueData = await this.storage.get(OperationQueue.QUEUE_KEY);
3422
- if (queueData) {
3423
- this.queue = JSON.parse(queueData);
3424
- this.queue.forEach((op) => {
3425
- if (op.status === 'processing') {
3426
- op.status = 'pending';
3427
- }
3428
- });
3429
- }
3430
- }
3431
- catch {
3432
- this.queue = [];
3433
- }
3434
- }
3435
- async saveQueue() {
3436
- try {
3437
- await this.storage.set(OperationQueue.QUEUE_KEY, JSON.stringify(this.queue));
3438
- }
3439
- catch (error) {
3440
- this.events.onError?.(new Error(`Failed to save queue: ${error}`));
3441
- }
3442
- }
3443
- generateId() {
3444
- return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
3445
- }
3446
- destroy() {
3447
- this.stopAutoSync();
3448
- }
3449
- }
3450
- OperationQueue.QUEUE_KEY = 'acube_operation_queue';
3451
-
3452
- class SyncManager {
3453
- constructor(queue, httpPort, networkMonitor, config, events = {}) {
3454
- this.queue = queue;
3455
- this.httpPort = httpPort;
3456
- this.networkMonitor = networkMonitor;
3457
- this.config = config;
3458
- this.events = events;
3459
- this.isOnline = true;
3460
- this.destroy$ = new Subject();
3461
- this.setupNetworkMonitoring();
3462
- }
3463
- setupNetworkMonitoring() {
3464
- // Subscribe to online$ to track current state
3465
- this.networkSubscription = this.networkMonitor.online$
3466
- .pipe(startWith(true), // Assume online initially
3467
- pairwise(), filter(([wasOnline, isNowOnline]) => !wasOnline && isNowOnline), takeUntil(this.destroy$))
3468
- .subscribe(() => {
3469
- // Offline → Online transition detected
3470
- this.syncPendingOperations();
3471
- });
3472
- // Track current online state
3473
- this.networkMonitor.online$.pipe(takeUntil(this.destroy$)).subscribe((online) => {
3474
- this.isOnline = online;
3475
- });
3476
- }
3477
- async syncPendingOperations() {
3478
- if (!this.isOnline) {
3479
- throw new Error('Cannot sync while offline');
3480
- }
3481
- if (this.queue.isCurrentlyProcessing()) {
3482
- throw new Error('Sync already in progress');
3483
- }
3484
- this.queue.setProcessing(true);
3485
- try {
3486
- const results = [];
3487
- let successCount = 0;
3488
- let failureCount = 0;
3489
- while (!this.queue.isEmpty()) {
3490
- const batch = this.queue.getNextBatch();
3491
- if (batch.length === 0)
3492
- break;
3493
- const batchPromises = batch.map((operation) => this.processOperation(operation));
3494
- const batchResults = await Promise.allSettled(batchPromises);
3495
- batchResults.forEach((result, index) => {
3496
- const operation = batch[index];
3497
- if (!operation)
3498
- return;
3499
- if (result.status === 'fulfilled') {
3500
- const syncResult = result.value;
3501
- results.push(syncResult);
3502
- if (syncResult.success) {
3503
- successCount++;
3504
- this.events.onOperationCompleted?.(syncResult);
3505
- }
3506
- else {
3507
- failureCount++;
3508
- this.events.onOperationFailed?.(syncResult);
3509
- }
3510
- }
3511
- else {
3512
- const syncResult = {
3513
- operation,
3514
- success: false,
3515
- error: result.reason?.message || 'Unknown error',
3516
- };
3517
- results.push(syncResult);
3518
- failureCount++;
3519
- this.events.onOperationFailed?.(syncResult);
3520
- this.queue.updateOperation(operation.id, {
3521
- status: 'failed',
3522
- error: syncResult.error,
3523
- });
3524
- }
3525
- });
3526
- if (!this.queue.isEmpty()) {
3527
- await this.delay(500);
3528
- }
3529
- }
3530
- const batchResult = {
3531
- totalOperations: results.length,
3532
- successCount,
3533
- failureCount,
3534
- results,
3535
- };
3536
- this.events.onBatchSyncCompleted?.(batchResult);
3537
- if (this.queue.isEmpty()) {
3538
- this.events.onQueueEmpty?.();
3539
- }
3540
- return batchResult;
3541
- }
3542
- finally {
3543
- this.queue.setProcessing(false);
3544
- }
3545
- }
3546
- async processOperation(operation) {
3547
- await this.queue.updateOperation(operation.id, { status: 'processing' });
3548
- try {
3549
- const response = await this.executeOperation(operation);
3550
- await this.queue.updateOperation(operation.id, { status: 'completed' });
3551
- return { operation, success: true, response };
3552
- }
3553
- catch (error) {
3554
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
3555
- if (operation.retryCount < operation.maxRetries && this.isRetryableError(error)) {
3556
- const delay = this.calculateRetryDelay(operation.retryCount);
3557
- await this.queue.updateOperation(operation.id, {
3558
- status: 'pending',
3559
- retryCount: operation.retryCount + 1,
3560
- error: errorMessage,
3561
- });
3562
- setTimeout(() => {
3563
- if (this.isOnline && !this.queue.isCurrentlyProcessing()) {
3564
- this.syncPendingOperations();
3565
- }
3566
- }, delay);
3567
- return { operation, success: false, error: `Retrying: ${errorMessage}` };
3568
- }
3569
- else {
3570
- await this.queue.updateOperation(operation.id, {
3571
- status: 'failed',
3572
- error: errorMessage,
3573
- });
3574
- return { operation, success: false, error: errorMessage };
3575
- }
3576
- }
3577
- }
3578
- async executeOperation(operation) {
3579
- const { method, endpoint, data, headers } = operation;
3580
- const config = headers ? { headers } : undefined;
3581
- switch (method) {
3582
- case 'GET':
3583
- return (await this.httpPort.get(endpoint, config)).data;
3584
- case 'POST':
3585
- return (await this.httpPort.post(endpoint, data, config)).data;
3586
- case 'PUT':
3587
- return (await this.httpPort.put(endpoint, data, config)).data;
3588
- case 'PATCH':
3589
- return (await this.httpPort.patch(endpoint, data, config)).data;
3590
- case 'DELETE':
3591
- return (await this.httpPort.delete(endpoint, config)).data;
3592
- default:
3593
- throw new Error(`Unsupported HTTP method: ${method}`);
3594
- }
3595
- }
3596
- isRetryableError(error) {
3597
- const errorObj = error;
3598
- if (errorObj.code === 'NETWORK_ERROR')
3599
- return true;
3600
- if (errorObj.statusCode && errorObj.statusCode >= 500)
3601
- return true;
3602
- if (errorObj.statusCode === 429)
3603
- return true;
3604
- const errorMessage = error?.message;
3605
- if (errorObj.code === 'ECONNABORTED' || errorMessage?.includes('timeout'))
3606
- return true;
3607
- return false;
3608
- }
3609
- calculateRetryDelay(retryCount) {
3610
- const delay = this.config.retryDelay * Math.pow(this.config.backoffMultiplier, retryCount);
3611
- return Math.min(delay, this.config.maxRetryDelay);
3612
- }
3613
- delay(ms) {
3614
- return new Promise((resolve) => setTimeout(resolve, ms));
3615
- }
3616
- isCurrentlyOnline() {
3617
- return this.isOnline;
3618
- }
3619
- async triggerSync() {
3620
- if (!this.isOnline)
3621
- return null;
3622
- if (this.queue.isEmpty()) {
3623
- return { totalOperations: 0, successCount: 0, failureCount: 0, results: [] };
3624
- }
3625
- return await this.syncPendingOperations();
3626
- }
3627
- getSyncStatus() {
3628
- return {
3629
- isOnline: this.isOnline,
3630
- isProcessing: this.queue.isCurrentlyProcessing(),
3631
- queueStats: this.queue.getStats(),
3632
- };
3633
- }
3634
- destroy() {
3635
- this.destroy$.next();
3636
- this.destroy$.complete();
3637
- this.networkSubscription?.unsubscribe();
3638
- }
3639
- }
3640
-
3641
- class OfflineManager {
3642
- get queue$() {
3643
- return this.queueSubject.asObservable();
3644
- }
3645
- get syncStatus$() {
3646
- return this.syncStatusSubject.asObservable();
3647
- }
3648
- constructor(storage, httpPort, networkMonitor, config = {}, events = {}, _cache) {
3649
- this.queueSubject = new BehaviorSubject([]);
3650
- this.syncStatusSubject = new BehaviorSubject({
3651
- isOnline: true,
3652
- isProcessing: false,
3653
- queueStats: { total: 0, pending: 0, processing: 0, completed: 0, failed: 0 },
3654
- });
3655
- this.destroy$ = new Subject();
3656
- const finalConfig = { ...DEFAULT_QUEUE_CONFIG, ...config };
3657
- const wrappedEvents = {
3658
- ...events,
3659
- onOperationAdded: (op) => {
3660
- this.updateQueueState();
3661
- events.onOperationAdded?.(op);
3662
- },
3663
- onOperationCompleted: (result) => {
3664
- this.updateQueueState();
3665
- events.onOperationCompleted?.(result);
3666
- },
3667
- onOperationFailed: (result) => {
3668
- this.updateQueueState();
3669
- events.onOperationFailed?.(result);
3670
- },
3671
- onBatchSyncCompleted: (result) => {
3672
- this.updateQueueState();
3673
- events.onBatchSyncCompleted?.(result);
3674
- },
3675
- };
3676
- this.queue = new OperationQueue(storage, finalConfig, wrappedEvents);
3677
- this.syncManager = new SyncManager(this.queue, httpPort, networkMonitor, finalConfig, wrappedEvents);
3678
- this.updateQueueState();
3679
- }
3680
- updateQueueState() {
3681
- this.queueSubject.next(this.queue.getPendingOperations());
3682
- this.syncStatusSubject.next(this.syncManager.getSyncStatus());
3683
- }
3684
- async queueOperation(type, resource, endpoint, method, data, priority = 1) {
3685
- const id = await this.queue.addOperation(type, resource, endpoint, method, data, priority);
3686
- this.updateQueueState();
3687
- return id;
3688
- }
3689
- async queueReceiptCreation(receiptData, priority = 2) {
3690
- return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts', 'POST', receiptData, priority);
3691
- }
3692
- async queueReceiptVoid(voidData, priority = 3) {
3693
- return await this.queueOperation('DELETE', 'receipt', '/mf1/receipts', 'DELETE', voidData, priority);
3694
- }
3695
- async queueReceiptReturn(returnData, priority = 3) {
3696
- return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts/return', 'POST', returnData, priority);
3697
- }
3698
- async queueCashierCreation(cashierData, priority = 1) {
3699
- return await this.queueOperation('CREATE', 'cashier', '/mf1/cashiers', 'POST', cashierData, priority);
3700
- }
3701
- isOnline() {
3702
- return this.syncManager.isCurrentlyOnline();
3703
- }
3704
- getStatus() {
3705
- return this.syncManager.getSyncStatus();
3706
- }
3707
- getPendingCount() {
3708
- return this.queue.getPendingOperations().length;
3709
- }
3710
- isEmpty() {
3711
- return this.queue.isEmpty();
3712
- }
3713
- async sync() {
3714
- const result = await this.syncManager.triggerSync();
3715
- this.updateQueueState();
3716
- return result;
3717
- }
3718
- async retryFailed() {
3719
- await this.queue.retryFailed();
3720
- this.updateQueueState();
3721
- if (this.isOnline()) {
3722
- await this.sync();
3723
- }
3724
- }
3725
- async clearCompleted() {
3726
- await this.queue.clearCompleted();
3727
- this.updateQueueState();
3728
- }
3729
- async clearFailed() {
3730
- await this.queue.clearFailed();
3731
- this.updateQueueState();
3732
- }
3733
- async clearAll() {
3734
- await this.queue.clearQueue();
3735
- this.updateQueueState();
3736
- }
3737
- getOperation(id) {
3738
- return this.queue.getOperation(id);
3739
- }
3740
- async removeOperation(id) {
3741
- const result = await this.queue.removeOperation(id);
3742
- this.updateQueueState();
3743
- return result;
3744
- }
3745
- getQueueStats() {
3746
- return this.queue.getStats();
3747
- }
3748
- startAutoSync() {
3749
- this.queue.startAutoSync();
3750
- }
3751
- stopAutoSync() {
3752
- this.queue.stopAutoSync();
3753
- }
3754
- destroy() {
3755
- this.destroy$.next();
3756
- this.destroy$.complete();
3757
- this.queue.destroy();
3758
- this.syncManager.destroy();
3759
- }
3760
- }
3761
-
3762
- class CompressionAdapter {
3763
- compress(data, threshold = 1024) {
3764
- const originalSize = data.length * 2;
3765
- if (originalSize < threshold) {
3766
- return {
3767
- data,
3768
- compressed: false,
3769
- originalSize,
3770
- compressedSize: originalSize,
3771
- };
3772
- }
3773
- try {
3774
- const compressed = this.compressString(data);
3775
- const compressedSize = compressed.length * 2;
3776
- if (compressedSize < originalSize) {
3777
- return {
3778
- data: compressed,
3779
- compressed: true,
3780
- originalSize,
3781
- compressedSize,
3782
- };
3783
- }
3784
- return {
3785
- data,
3786
- compressed: false,
3787
- originalSize,
3788
- compressedSize: originalSize,
3789
- };
3790
- }
3791
- catch {
3792
- return {
3793
- data,
3794
- compressed: false,
3795
- originalSize,
3796
- compressedSize: originalSize,
3797
- };
3798
- }
3799
- }
3800
- decompress(data, compressed) {
3801
- if (!compressed) {
3802
- return { data, wasCompressed: false };
3803
- }
3804
- try {
3805
- const decompressed = this.decompressString(data);
3806
- return { data: decompressed, wasCompressed: true };
3807
- }
3808
- catch {
3809
- return { data, wasCompressed: false };
3810
- }
3811
- }
3812
- estimateSavings(data) {
3813
- const repeated = data.match(/(.)\1{3,}/g);
3814
- if (!repeated)
3815
- return 0;
3816
- let savings = 0;
3817
- for (const match of repeated) {
3818
- const originalBytes = match.length * 2;
3819
- const compressedBytes = 6;
3820
- if (originalBytes > compressedBytes) {
3821
- savings += originalBytes - compressedBytes;
3822
- }
3823
- }
3824
- return Math.min(savings, data.length * 2 * 0.5);
3825
- }
3826
- compressString(input) {
3827
- let compressed = '';
3828
- let i = 0;
3829
- while (i < input.length) {
3830
- let count = 1;
3831
- const char = input[i];
3832
- while (i + count < input.length && input[i + count] === char && count < 255) {
3833
- count++;
3834
- }
3835
- if (count > 3) {
3836
- compressed += `~${count}${char}`;
3837
- }
3838
- else {
3839
- for (let j = 0; j < count; j++) {
3840
- compressed += char;
3841
- }
3842
- }
3843
- i += count;
3844
- }
3845
- return `COMP:${btoa(compressed)}`;
3846
- }
3847
- decompressString(input) {
3848
- if (!input.startsWith('COMP:')) {
3849
- return input;
3850
- }
3851
- const encodedData = input.substring(5);
3852
- if (!encodedData) {
3853
- return input;
3854
- }
3855
- const compressed = atob(encodedData);
3856
- let decompressed = '';
3857
- let i = 0;
3858
- while (i < compressed.length) {
3859
- if (compressed[i] === '~' && i + 2 < compressed.length) {
3860
- let countStr = '';
3861
- i++;
3862
- while (i < compressed.length) {
3863
- const char = compressed[i];
3864
- if (char && /\d/.test(char)) {
3865
- countStr += char;
3866
- i++;
3867
- }
3868
- else {
3869
- break;
3870
- }
3871
- }
3872
- if (countStr && i < compressed.length) {
3873
- const count = parseInt(countStr, 10);
3874
- const char = compressed[i];
3875
- for (let j = 0; j < count; j++) {
3876
- decompressed += char;
3877
- }
3878
- i++;
3879
- }
3880
- }
3881
- else {
3882
- decompressed += compressed[i];
3883
- i++;
3884
- }
3885
- }
3886
- return decompressed;
3887
- }
3888
- }
3889
- function compressData(data, threshold = 1024) {
3890
- return new CompressionAdapter().compress(data, threshold);
3891
- }
3892
- function decompressData(data, compressed) {
3893
- return new CompressionAdapter().decompress(data, compressed);
3894
- }
3895
-
3896
- const log$d = createPrefixedLogger('CACHE-RN');
3897
- /**
3898
- * React Native cache adapter using SQLite (Expo or react-native-sqlite-storage)
3899
- * Cache never expires - data persists until explicitly invalidated
3900
- */
3901
- class ReactNativeCacheAdapter {
3902
- constructor(options = {}) {
3903
- this.db = null;
3904
- this.initPromise = null;
3905
- this.isExpo = false;
3906
- this.hasCompressedColumn = false;
3907
- this.options = {
3908
- maxSize: 50 * 1024 * 1024, // 50MB
3909
- maxEntries: 10000,
3910
- compression: false,
3911
- compressionThreshold: 1024,
3912
- ...options,
3913
- };
3914
- this.initPromise = this.initialize();
3915
- }
3916
- normalizeResults(results) {
3917
- if (this.isExpo) {
3918
- const expoResults = results;
3919
- if (Array.isArray(expoResults)) {
3920
- return expoResults;
3921
- }
3922
- return expoResults.results || [];
3923
- }
3924
- else {
3925
- const rnResults = results;
3926
- const rows = rnResults.rows;
3927
- if (!rows || rows.length === 0)
3928
- return [];
3929
- const normalizedRows = [];
3930
- for (let i = 0; i < rows.length; i++) {
3931
- normalizedRows.push(rows.item(i));
3932
- }
3933
- return normalizedRows;
3934
- }
3935
- }
3936
- async initialize() {
3937
- if (this.db)
3938
- return;
3939
- try {
3940
- // Try Expo SQLite first
3941
- const ExpoSQLite = require('expo-sqlite');
3942
- this.db = await ExpoSQLite.openDatabaseAsync(ReactNativeCacheAdapter.DB_NAME);
3943
- this.isExpo = true;
3944
- await this.createTables();
3945
- }
3946
- catch (expoError) {
3947
- try {
3948
- // Fallback to react-native-sqlite-storage
3949
- const SQLite = require('react-native-sqlite-storage');
3950
- this.db = await new Promise((resolve, reject) => {
3951
- SQLite.openDatabase({
3952
- name: ReactNativeCacheAdapter.DB_NAME,
3953
- location: 'default',
3954
- }, resolve, reject);
3955
- });
3956
- this.isExpo = false;
3957
- await this.createTables();
3958
- }
3959
- catch (rnError) {
3960
- throw new Error(`Failed to initialize SQLite: Expo error: ${expoError}, RN error: ${rnError}`);
3961
- }
3962
- }
3963
- }
3964
- async createTables() {
3965
- // Create table with simplified schema (no TTL)
3966
- const createTableSQL = `
3967
- CREATE TABLE IF NOT EXISTS ${ReactNativeCacheAdapter.TABLE_NAME} (
3968
- cache_key TEXT PRIMARY KEY,
3969
- data TEXT NOT NULL,
3970
- timestamp INTEGER NOT NULL
3971
- );
3972
-
3973
- CREATE INDEX IF NOT EXISTS idx_timestamp ON ${ReactNativeCacheAdapter.TABLE_NAME}(timestamp);
3974
- `;
3975
- await this.executeSql(createTableSQL);
3976
- // Then, run migrations to add new columns if they don't exist
3977
- await this.runMigrations();
3978
- }
3979
- async runMigrations() {
3980
- log$d.debug('Running database migrations...');
3981
- try {
3982
- // Check if compressed column exists
3983
- this.hasCompressedColumn = await this.checkColumnExists('compressed');
3984
- if (!this.hasCompressedColumn) {
3985
- log$d.debug('Adding compressed column to cache table');
3986
- const addColumnSQL = `ALTER TABLE ${ReactNativeCacheAdapter.TABLE_NAME} ADD COLUMN compressed INTEGER DEFAULT 0`;
3987
- await this.executeSql(addColumnSQL);
3988
- this.hasCompressedColumn = true;
3989
- log$d.debug('Successfully added compressed column');
3990
- }
3991
- else {
3992
- log$d.debug('Compressed column already exists');
3993
- }
3994
- log$d.debug('Database migrations completed', {
3995
- hasCompressedColumn: this.hasCompressedColumn,
3996
- });
3997
- }
3998
- catch (error) {
3999
- log$d.debug('Migration failed, disabling compression features', error);
4000
- this.hasCompressedColumn = false;
4001
- // Don't throw - allow the app to continue even if migration fails
4002
- // The compressed feature will just be disabled
4003
- }
4004
- }
4005
- async checkColumnExists(columnName) {
4006
- try {
4007
- const pragmaSQL = `PRAGMA table_info(${ReactNativeCacheAdapter.TABLE_NAME})`;
4008
- const results = await this.executeSql(pragmaSQL);
4009
- const columns = this.normalizeResults(results);
4010
- log$d.debug('Table columns found', { columns: columns.map((c) => c.name) });
4011
- return columns.some((column) => column.name === columnName);
4012
- }
4013
- catch (error) {
4014
- log$d.debug('Error checking column existence', error);
4015
- return false;
4016
- }
4017
- }
4018
- async get(key) {
4019
- await this.ensureInitialized();
4020
- const sql = `SELECT * FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key = ?`;
4021
- log$d.debug('Executing get query', { sql, key });
4022
- const results = await this.executeSql(sql, [key]);
4023
- log$d.debug('Get query results', { key, hasResults: !!results });
4024
- // Normalize results from different SQLite implementations
4025
- const rows = this.normalizeResults(results);
4026
- if (!rows || rows.length === 0) {
4027
- return null;
4028
- }
4029
- const row = rows[0];
4030
- if (!row) {
4031
- return null;
4032
- }
4033
- const isCompressed = this.hasCompressedColumn ? !!row.compressed : false;
4034
- const rawData = isCompressed ? decompressData(row.data, true).data : row.data;
4035
- return {
4036
- data: JSON.parse(rawData),
4037
- timestamp: row.timestamp,
4038
- compressed: isCompressed,
4039
- };
4040
- }
4041
- async set(key, data) {
4042
- const item = {
4043
- data,
4044
- timestamp: Date.now(),
4045
- };
4046
- log$d.debug('Setting cache item', { key });
4047
- return this.setItem(key, item);
4048
- }
4049
- async setItem(key, item) {
4050
- await this.ensureInitialized();
4051
- // Handle compression if enabled and compressed column is available
4052
- const serializedData = JSON.stringify(item.data);
4053
- let finalData = serializedData;
4054
- let isCompressed = false;
4055
- if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
4056
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4057
- finalData = compressionResult.data;
4058
- isCompressed = compressionResult.compressed;
4059
- log$d.debug('Compression result', {
4060
- key,
4061
- originalSize: compressionResult.originalSize,
4062
- compressedSize: compressionResult.compressedSize,
4063
- compressed: isCompressed,
4064
- savings: compressionResult.originalSize - compressionResult.compressedSize,
4065
- });
4066
- }
4067
- log$d.debug('Setting item with metadata', {
4068
- key,
4069
- timestamp: item.timestamp,
4070
- compressed: isCompressed,
4071
- hasCompressedColumn: this.hasCompressedColumn,
4072
- });
4073
- // Build SQL and parameters based on available columns
4074
- let sql;
4075
- let params;
4076
- if (this.hasCompressedColumn) {
4077
- sql = `
4078
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4079
- (cache_key, data, timestamp, compressed)
4080
- VALUES (?, ?, ?, ?)
4081
- `;
4082
- params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
4083
- }
4084
- else {
4085
- // Fallback for databases without compressed column
4086
- sql = `
4087
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4088
- (cache_key, data, timestamp)
4089
- VALUES (?, ?, ?)
4090
- `;
4091
- params = [key, finalData, item.timestamp];
4092
- }
4093
- log$d.debug('Executing setItem SQL', { key, paramsCount: params.length });
4094
- await this.executeSql(sql, params);
4095
- }
4096
- async setBatch(items) {
4097
- if (items.length === 0)
4098
- return;
4099
- await this.ensureInitialized();
4100
- log$d.debug('Batch setting items', { count: items.length });
4101
- if (this.isExpo) {
4102
- await this.db.withTransactionAsync(async () => {
4103
- for (const [key, item] of items) {
4104
- await this.setBatchItem(key, item);
4105
- }
4106
- });
4107
- }
4108
- else {
4109
- return new Promise((resolve, reject) => {
4110
- this.db.transaction((tx) => {
4111
- const promises = items.map(([key, item]) => this.setBatchItemRN(tx, key, item));
4112
- Promise.all(promises)
4113
- .then(() => resolve())
4114
- .catch(reject);
4115
- }, reject, () => resolve());
4116
- });
4117
- }
4118
- log$d.debug('Batch operation completed', { count: items.length });
4119
- }
4120
- async setBatchItem(key, item) {
4121
- // Handle compression if enabled and compressed column is available
4122
- const serializedData = JSON.stringify(item.data);
4123
- let finalData = serializedData;
4124
- let isCompressed = false;
4125
- if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
4126
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4127
- finalData = compressionResult.data;
4128
- isCompressed = compressionResult.compressed;
4129
- }
4130
- // Build SQL and parameters based on available columns
4131
- let sql;
4132
- let params;
4133
- if (this.hasCompressedColumn) {
4134
- sql = `
4135
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4136
- (cache_key, data, timestamp, compressed)
4137
- VALUES (?, ?, ?, ?)
4138
- `;
4139
- params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
4140
- }
4141
- else {
4142
- sql = `
4143
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4144
- (cache_key, data, timestamp)
4145
- VALUES (?, ?, ?)
4146
- `;
4147
- params = [key, finalData, item.timestamp];
4148
- }
4149
- await this.db.runAsync(sql, params);
4150
- }
4151
- async setBatchItemRN(tx, key, item) {
4152
- // Handle compression if enabled and compressed column is available
4153
- const serializedData = JSON.stringify(item.data);
4154
- let finalData = serializedData;
4155
- let isCompressed = false;
4156
- if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
4157
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4158
- finalData = compressionResult.data;
4159
- isCompressed = compressionResult.compressed;
4160
- }
4161
- // Build SQL and parameters based on available columns
4162
- let sql;
4163
- let params;
4164
- if (this.hasCompressedColumn) {
4165
- sql = `
4166
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4167
- (cache_key, data, timestamp, compressed)
4168
- VALUES (?, ?, ?, ?)
4169
- `;
4170
- params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
4171
- }
4172
- else {
4173
- sql = `
4174
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4175
- (cache_key, data, timestamp)
4176
- VALUES (?, ?, ?)
4177
- `;
4178
- params = [key, finalData, item.timestamp];
4179
- }
4180
- return new Promise((resolve, reject) => {
4181
- tx.executeSql(sql, params, () => resolve(), (_, error) => {
4182
- reject(error);
4183
- return false;
4184
- });
4185
- });
4186
- }
4187
- async invalidate(pattern) {
4188
- await this.ensureInitialized();
4189
- const keys = await this.getKeys(pattern);
4190
- if (keys.length === 0)
4191
- return;
4192
- const placeholders = keys.map(() => '?').join(',');
4193
- const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key IN (${placeholders})`;
4194
- await this.executeSql(sql, keys);
4195
- }
4196
- async clear() {
4197
- await this.ensureInitialized();
4198
- const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
4199
- await this.executeSql(sql);
4200
- }
4201
- async getSize() {
4202
- await this.ensureInitialized();
4203
- const sql = `
4204
- SELECT
4205
- COUNT(*) as entries,
4206
- SUM(LENGTH(data)) as bytes
4207
- FROM ${ReactNativeCacheAdapter.TABLE_NAME}
4208
- `;
4209
- const results = await this.executeSql(sql);
4210
- const rows = this.normalizeResults(results);
4211
- const row = rows[0] || { entries: 0, bytes: 0 };
4212
- return {
4213
- entries: row.entries || 0,
4214
- bytes: (row.bytes || 0) * 2,
4215
- lastCleanup: Date.now(),
4216
- };
4217
- }
4218
- async cleanup() {
4219
- // No cleanup needed - cache never expires
4220
- return 0;
4221
- }
4222
- async getKeys(pattern) {
4223
- await this.ensureInitialized();
4224
- let sql = `SELECT cache_key FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
4225
- const params = [];
4226
- if (pattern) {
4227
- // Simple pattern matching with LIKE
4228
- const likePattern = pattern.replace(/\*/g, '%').replace(/\?/g, '_');
4229
- sql += ' WHERE cache_key LIKE ?';
4230
- params.push(likePattern);
4231
- }
4232
- const results = await this.executeSql(sql, params);
4233
- const keys = [];
4234
- const rows = this.normalizeResults(results);
4235
- for (const row of rows) {
4236
- keys.push(row.cache_key);
4237
- }
4238
- return keys;
4239
- }
4240
- async executeSql(sql, params = []) {
4241
- if (this.isExpo) {
4242
- const expoDB = this.db;
4243
- if (sql.toLowerCase().includes('select') || sql.toLowerCase().includes('pragma')) {
4244
- const result = await expoDB.getAllAsync(sql, params);
4245
- return Array.isArray(result) ? { results: result } : result;
4246
- }
4247
- else {
4248
- return await expoDB.runAsync(sql, params);
4249
- }
4250
- }
4251
- else {
4252
- // react-native-sqlite-storage
4253
- return new Promise((resolve, reject) => {
4254
- this.db.transaction((tx) => {
4255
- tx.executeSql(sql, params, (_, results) => resolve(results), (_, error) => {
4256
- reject(error);
4257
- return false;
4258
- });
4259
- });
4260
- });
4261
- }
4262
- }
4263
- async ensureInitialized() {
4264
- if (!this.initPromise) {
4265
- this.initPromise = this.initialize();
4266
- }
4267
- await this.initPromise;
4268
- }
4269
- }
4270
- ReactNativeCacheAdapter.DB_NAME = 'acube_cache.db';
4271
- ReactNativeCacheAdapter.TABLE_NAME = 'cache_entries';
4272
- /**
4273
- * Memory-based fallback cache adapter for environments without SQLite
4274
- * Cache never expires - data persists until explicitly invalidated
4275
- */
4276
- class MemoryCacheAdapter {
4277
- constructor(options = {}) {
4278
- this.cache = new Map();
4279
- this.totalBytes = 0;
4280
- this.options = {
4281
- maxEntries: 1000,
4282
- ...options,
4283
- };
4284
- }
4285
- calculateItemSize(key, item) {
4286
- // Calculate rough size estimation for memory usage
4287
- const keySize = key.length * 2; // UTF-16 estimation
4288
- const itemSize = JSON.stringify(item).length * 2; // UTF-16 estimation
4289
- return keySize + itemSize;
4290
- }
4291
- async get(key) {
4292
- log$d.debug('Getting cache item', { key });
4293
- const item = this.cache.get(key);
4294
- if (!item) {
4295
- log$d.debug('Cache miss', { key });
4296
- return null;
4297
- }
4298
- // Handle decompression if needed
4299
- const isCompressed = !!item.compressed;
4300
- let finalData = item.data;
4301
- if (isCompressed) {
4302
- const decompressed = decompressData(item.data, true);
4303
- finalData = JSON.parse(decompressed.data);
4304
- }
4305
- log$d.debug('Cache hit', { key, compressed: isCompressed });
4306
- return {
4307
- ...item,
4308
- data: finalData,
4309
- compressed: isCompressed,
4310
- };
4311
- }
4312
- async set(key, data) {
4313
- log$d.debug('Setting cache item', { key });
4314
- // Handle compression if enabled
4315
- let finalData = data;
4316
- let isCompressed = false;
4317
- if (this.options.compression && this.options.compressionThreshold) {
4318
- const serializedData = JSON.stringify(data);
4319
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4320
- if (compressionResult.compressed) {
4321
- finalData = compressionResult.data;
4322
- isCompressed = true;
4323
- log$d.debug('Compression result', {
4324
- key,
4325
- originalSize: compressionResult.originalSize,
4326
- compressedSize: compressionResult.compressedSize,
4327
- savings: compressionResult.originalSize - compressionResult.compressedSize,
4328
- });
4329
- }
4330
- }
4331
- const item = {
4332
- data: finalData,
4333
- timestamp: Date.now(),
4334
- compressed: isCompressed,
4335
- };
4336
- return this.setItem(key, item);
4337
- }
4338
- async setItem(key, item) {
4339
- // Calculate size of new item
4340
- const newItemSize = this.calculateItemSize(key, item);
4341
- // If item already exists, subtract old size
4342
- if (this.cache.has(key)) {
4343
- const oldItem = this.cache.get(key);
4344
- const oldItemSize = this.calculateItemSize(key, oldItem);
4345
- this.totalBytes -= oldItemSize;
4346
- }
4347
- // Enforce max entries limit
4348
- if (this.cache.size >= (this.options.maxEntries || 1000) && !this.cache.has(key)) {
4349
- const oldestKey = this.cache.keys().next().value;
4350
- if (oldestKey) {
4351
- const oldestItem = this.cache.get(oldestKey);
4352
- const oldestItemSize = this.calculateItemSize(oldestKey, oldestItem);
4353
- this.totalBytes -= oldestItemSize;
4354
- this.cache.delete(oldestKey);
4355
- log$d.debug('Removed oldest item for capacity', { oldestKey, freedBytes: oldestItemSize });
4356
- }
4357
- }
4358
- // Set new item and update total size
4359
- this.cache.set(key, item);
4360
- this.totalBytes += newItemSize;
4361
- log$d.debug('Updated cache size', {
4362
- entries: this.cache.size,
4363
- totalBytes: this.totalBytes,
4364
- newItemSize,
4365
- });
4366
- }
4367
- async setBatch(items) {
4368
- if (items.length === 0)
4369
- return;
4370
- log$d.debug('Batch setting items', { count: items.length });
4371
- let totalNewBytes = 0;
4372
- let totalOldBytes = 0;
4373
- const itemsToRemove = [];
4374
- // First pass: calculate size changes and identify capacity issues
4375
- for (const [key, item] of items) {
4376
- const newItemSize = this.calculateItemSize(key, item);
4377
- totalNewBytes += newItemSize;
4378
- // If item already exists, track old size for removal
4379
- if (this.cache.has(key)) {
4380
- const oldItem = this.cache.get(key);
4381
- const oldItemSize = this.calculateItemSize(key, oldItem);
4382
- totalOldBytes += oldItemSize;
4383
- }
4384
- }
4385
- // Handle capacity limits - remove oldest items if needed
4386
- const projectedEntries = this.cache.size + items.filter(([key]) => !this.cache.has(key)).length;
4387
- const maxEntries = this.options.maxEntries || 1000;
4388
- if (projectedEntries > maxEntries) {
4389
- const entriesToRemove = projectedEntries - maxEntries;
4390
- const oldestKeys = Array.from(this.cache.keys()).slice(0, entriesToRemove);
4391
- for (const oldKey of oldestKeys) {
4392
- const oldItem = this.cache.get(oldKey);
4393
- const oldItemSize = this.calculateItemSize(oldKey, oldItem);
4394
- this.totalBytes -= oldItemSize;
4395
- this.cache.delete(oldKey);
4396
- itemsToRemove.push(oldKey);
4397
- }
4398
- if (itemsToRemove.length > 0) {
4399
- log$d.debug('Removed items for batch capacity', {
4400
- removedCount: itemsToRemove.length,
4401
- removedKeys: itemsToRemove,
4402
- });
4403
- }
4404
- }
4405
- // Update total bytes accounting
4406
- this.totalBytes = this.totalBytes - totalOldBytes + totalNewBytes;
4407
- // Second pass: set all items
4408
- for (const [key, item] of items) {
4409
- this.cache.set(key, item);
4410
- }
4411
- log$d.debug('Batch operation completed', {
4412
- count: items.length,
4413
- totalBytes: this.totalBytes,
4414
- entries: this.cache.size,
4415
- bytesAdded: totalNewBytes - totalOldBytes,
4416
- });
4417
- }
4418
- async invalidate(pattern) {
4419
- const regex = this.patternToRegex(pattern);
4420
- let removed = 0;
4421
- let bytesFreed = 0;
4422
- for (const key of this.cache.keys()) {
4423
- if (regex.test(key)) {
4424
- const item = this.cache.get(key);
4425
- const itemSize = this.calculateItemSize(key, item);
4426
- this.cache.delete(key);
4427
- this.totalBytes -= itemSize;
4428
- bytesFreed += itemSize;
4429
- removed++;
4430
- }
4431
- }
4432
- if (removed > 0) {
4433
- log$d.debug('Invalidation completed', {
4434
- pattern,
4435
- entriesRemoved: removed,
4436
- bytesFreed,
4437
- remainingEntries: this.cache.size,
4438
- remainingBytes: this.totalBytes,
4439
- });
4440
- }
4441
- }
4442
- async clear() {
4443
- this.cache.clear();
4444
- this.totalBytes = 0;
4445
- log$d.debug('Cache cleared', { entries: 0, bytes: 0 });
4446
- }
4447
- async getSize() {
4448
- return {
4449
- entries: this.cache.size,
4450
- bytes: this.totalBytes,
4451
- lastCleanup: Date.now(),
4452
- };
4453
- }
4454
- async cleanup() {
4455
- // No cleanup needed - cache never expires
4456
- return 0;
4457
- }
4458
- async getKeys(pattern) {
4459
- const keys = Array.from(this.cache.keys());
4460
- if (!pattern)
4461
- return keys;
4462
- const regex = this.patternToRegex(pattern);
4463
- return keys.filter((key) => regex.test(key));
4464
- }
4465
- patternToRegex(pattern) {
4466
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
4467
- const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
4468
- return new RegExp(`^${regexPattern}$`);
4469
- }
4470
- }
4471
-
4472
- const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
4473
-
4474
- let idbProxyableTypes;
4475
- let cursorAdvanceMethods;
4476
- // This is a function to prevent it throwing up in node environments.
4477
- function getIdbProxyableTypes() {
4478
- return (idbProxyableTypes ||
4479
- (idbProxyableTypes = [
4480
- IDBDatabase,
4481
- IDBObjectStore,
4482
- IDBIndex,
4483
- IDBCursor,
4484
- IDBTransaction,
4485
- ]));
4486
- }
4487
- // This is a function to prevent it throwing up in node environments.
4488
- function getCursorAdvanceMethods() {
4489
- return (cursorAdvanceMethods ||
4490
- (cursorAdvanceMethods = [
4491
- IDBCursor.prototype.advance,
4492
- IDBCursor.prototype.continue,
4493
- IDBCursor.prototype.continuePrimaryKey,
4494
- ]));
4495
- }
4496
- const transactionDoneMap = new WeakMap();
4497
- const transformCache = new WeakMap();
4498
- const reverseTransformCache = new WeakMap();
4499
- function promisifyRequest(request) {
4500
- const promise = new Promise((resolve, reject) => {
4501
- const unlisten = () => {
4502
- request.removeEventListener('success', success);
4503
- request.removeEventListener('error', error);
4504
- };
4505
- const success = () => {
4506
- resolve(wrap(request.result));
4507
- unlisten();
4508
- };
4509
- const error = () => {
4510
- reject(request.error);
4511
- unlisten();
4512
- };
4513
- request.addEventListener('success', success);
4514
- request.addEventListener('error', error);
4515
- });
4516
- // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This
4517
- // is because we create many promises from a single IDBRequest.
4518
- reverseTransformCache.set(promise, request);
4519
- return promise;
4520
- }
4521
- function cacheDonePromiseForTransaction(tx) {
4522
- // Early bail if we've already created a done promise for this transaction.
4523
- if (transactionDoneMap.has(tx))
4524
- return;
4525
- const done = new Promise((resolve, reject) => {
4526
- const unlisten = () => {
4527
- tx.removeEventListener('complete', complete);
4528
- tx.removeEventListener('error', error);
4529
- tx.removeEventListener('abort', error);
4530
- };
4531
- const complete = () => {
4532
- resolve();
4533
- unlisten();
4534
- };
4535
- const error = () => {
4536
- reject(tx.error || new DOMException('AbortError', 'AbortError'));
4537
- unlisten();
4538
- };
4539
- tx.addEventListener('complete', complete);
4540
- tx.addEventListener('error', error);
4541
- tx.addEventListener('abort', error);
4542
- });
4543
- // Cache it for later retrieval.
4544
- transactionDoneMap.set(tx, done);
4545
- }
4546
- let idbProxyTraps = {
4547
- get(target, prop, receiver) {
4548
- if (target instanceof IDBTransaction) {
4549
- // Special handling for transaction.done.
4550
- if (prop === 'done')
4551
- return transactionDoneMap.get(target);
4552
- // Make tx.store return the only store in the transaction, or undefined if there are many.
4553
- if (prop === 'store') {
4554
- return receiver.objectStoreNames[1]
4555
- ? undefined
4556
- : receiver.objectStore(receiver.objectStoreNames[0]);
4557
- }
4558
- }
4559
- // Else transform whatever we get back.
4560
- return wrap(target[prop]);
4561
- },
4562
- set(target, prop, value) {
4563
- target[prop] = value;
4564
- return true;
4565
- },
4566
- has(target, prop) {
4567
- if (target instanceof IDBTransaction &&
4568
- (prop === 'done' || prop === 'store')) {
4569
- return true;
4570
- }
4571
- return prop in target;
4572
- },
4573
- };
4574
- function replaceTraps(callback) {
4575
- idbProxyTraps = callback(idbProxyTraps);
4576
- }
4577
- function wrapFunction(func) {
4578
- // Due to expected object equality (which is enforced by the caching in `wrap`), we
4579
- // only create one new func per func.
4580
- // Cursor methods are special, as the behaviour is a little more different to standard IDB. In
4581
- // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
4582
- // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
4583
- // with real promises, so each advance methods returns a new promise for the cursor object, or
4584
- // undefined if the end of the cursor has been reached.
4585
- if (getCursorAdvanceMethods().includes(func)) {
4586
- return function (...args) {
4587
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
4588
- // the original object.
4589
- func.apply(unwrap(this), args);
4590
- return wrap(this.request);
4591
- };
4592
- }
4593
- return function (...args) {
4594
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
4595
- // the original object.
4596
- return wrap(func.apply(unwrap(this), args));
4597
- };
4598
- }
4599
- function transformCachableValue(value) {
4600
- if (typeof value === 'function')
4601
- return wrapFunction(value);
4602
- // This doesn't return, it just creates a 'done' promise for the transaction,
4603
- // which is later returned for transaction.done (see idbObjectHandler).
4604
- if (value instanceof IDBTransaction)
4605
- cacheDonePromiseForTransaction(value);
4606
- if (instanceOfAny(value, getIdbProxyableTypes()))
4607
- return new Proxy(value, idbProxyTraps);
4608
- // Return the same value back if we're not going to transform it.
4609
- return value;
4610
- }
4611
- function wrap(value) {
4612
- // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
4613
- // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
4614
- if (value instanceof IDBRequest)
4615
- return promisifyRequest(value);
4616
- // If we've already transformed this value before, reuse the transformed value.
4617
- // This is faster, but it also provides object equality.
4618
- if (transformCache.has(value))
4619
- return transformCache.get(value);
4620
- const newValue = transformCachableValue(value);
4621
- // Not all types are transformed.
4622
- // These may be primitive types, so they can't be WeakMap keys.
4623
- if (newValue !== value) {
4624
- transformCache.set(value, newValue);
4625
- reverseTransformCache.set(newValue, value);
4626
- }
4627
- return newValue;
4628
- }
4629
- const unwrap = (value) => reverseTransformCache.get(value);
4630
-
4631
- /**
4632
- * Open a database.
4633
- *
4634
- * @param name Name of the database.
4635
- * @param version Schema version.
4636
- * @param callbacks Additional callbacks.
4637
- */
4638
- function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
4639
- const request = indexedDB.open(name, version);
4640
- const openPromise = wrap(request);
4641
- if (upgrade) {
4642
- request.addEventListener('upgradeneeded', (event) => {
4643
- upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
4644
- });
4645
- }
4646
- if (blocked) {
4647
- request.addEventListener('blocked', (event) => blocked(
4648
- // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
4649
- event.oldVersion, event.newVersion, event));
4650
- }
4651
- openPromise
4652
- .then((db) => {
4653
- if (terminated)
4654
- db.addEventListener('close', () => terminated());
4655
- if (blocking) {
4656
- db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));
4657
- }
4658
- })
4659
- .catch(() => { });
4660
- return openPromise;
4661
- }
4662
- /**
4663
- * Delete a database.
4664
- *
4665
- * @param name Name of the database.
4666
- */
4667
- function deleteDB(name, { blocked } = {}) {
4668
- const request = indexedDB.deleteDatabase(name);
4669
- if (blocked) {
4670
- request.addEventListener('blocked', (event) => blocked(
4671
- // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
4672
- event.oldVersion, event));
4673
- }
4674
- return wrap(request).then(() => undefined);
4675
- }
4676
-
4677
- const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
4678
- const writeMethods = ['put', 'add', 'delete', 'clear'];
4679
- const cachedMethods = new Map();
4680
- function getMethod(target, prop) {
4681
- if (!(target instanceof IDBDatabase &&
4682
- !(prop in target) &&
4683
- typeof prop === 'string')) {
4684
- return;
4685
- }
4686
- if (cachedMethods.get(prop))
4687
- return cachedMethods.get(prop);
4688
- const targetFuncName = prop.replace(/FromIndex$/, '');
4689
- const useIndex = prop !== targetFuncName;
4690
- const isWrite = writeMethods.includes(targetFuncName);
4691
- if (
4692
- // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
4693
- !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
4694
- !(isWrite || readMethods.includes(targetFuncName))) {
4695
- return;
4696
- }
4697
- const method = async function (storeName, ...args) {
4698
- // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
4699
- const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
4700
- let target = tx.store;
4701
- if (useIndex)
4702
- target = target.index(args.shift());
4703
- // Must reject if op rejects.
4704
- // If it's a write operation, must reject if tx.done rejects.
4705
- // Must reject with op rejection first.
4706
- // Must resolve with op value.
4707
- // Must handle both promises (no unhandled rejections)
4708
- return (await Promise.all([
4709
- target[targetFuncName](...args),
4710
- isWrite && tx.done,
4711
- ]))[0];
4712
- };
4713
- cachedMethods.set(prop, method);
4714
- return method;
4715
- }
4716
- replaceTraps((oldTraps) => ({
4717
- ...oldTraps,
4718
- get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
4719
- has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
4720
- }));
4721
-
4722
- const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
4723
- const methodMap = {};
4724
- const advanceResults = new WeakMap();
4725
- const ittrProxiedCursorToOriginalProxy = new WeakMap();
4726
- const cursorIteratorTraps = {
4727
- get(target, prop) {
4728
- if (!advanceMethodProps.includes(prop))
4729
- return target[prop];
4730
- let cachedFunc = methodMap[prop];
4731
- if (!cachedFunc) {
4732
- cachedFunc = methodMap[prop] = function (...args) {
4733
- advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
4734
- };
4735
- }
4736
- return cachedFunc;
4737
- },
4738
- };
4739
- async function* iterate(...args) {
4740
- // tslint:disable-next-line:no-this-assignment
4741
- let cursor = this;
4742
- if (!(cursor instanceof IDBCursor)) {
4743
- cursor = await cursor.openCursor(...args);
4744
- }
4745
- if (!cursor)
4746
- return;
4747
- cursor = cursor;
4748
- const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
4749
- ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
4750
- // Map this double-proxy back to the original, so other cursor methods work.
4751
- reverseTransformCache.set(proxiedCursor, unwrap(cursor));
4752
- while (cursor) {
4753
- yield proxiedCursor;
4754
- // If one of the advancing methods was not called, call continue().
4755
- cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
4756
- advanceResults.delete(proxiedCursor);
4757
- }
4758
- }
4759
- function isIteratorProp(target, prop) {
4760
- return ((prop === Symbol.asyncIterator &&
4761
- instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||
4762
- (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));
4763
- }
4764
- replaceTraps((oldTraps) => ({
4765
- ...oldTraps,
4766
- get(target, prop, receiver) {
4767
- if (isIteratorProp(target, prop))
4768
- return iterate;
4769
- return oldTraps.get(target, prop, receiver);
4770
- },
4771
- has(target, prop) {
4772
- return isIteratorProp(target, prop) || oldTraps.has(target, prop);
4773
- },
4774
- }));
4775
-
4776
- const log$c = createPrefixedLogger('CACHE-WEB');
4777
- /**
4778
- * Web cache adapter using IndexedDB with automatic error recovery
4779
- * Cache never expires - data persists until explicitly invalidated
4780
- */
4781
- class WebCacheAdapter {
4782
- constructor(options = {}) {
4783
- this.db = null;
4784
- this.initPromise = null;
4785
- this.retryCount = 0;
4786
- this.maxRetries = 3;
4787
- this.options = {
4788
- maxSize: 50 * 1024 * 1024, // 50MB
4789
- maxEntries: 10000,
4790
- compression: false,
4791
- compressionThreshold: 1024,
4792
- ...options,
4793
- };
4794
- this.initPromise = this.initialize();
4795
- }
4796
- async initialize() {
4797
- if (this.db)
4798
- return;
4799
- log$c.debug('Initializing IndexedDB cache', {
4800
- dbName: WebCacheAdapter.DB_NAME,
4801
- version: WebCacheAdapter.DB_VERSION,
4802
- });
4803
- try {
4804
- this.db = await this.openDatabase();
4805
- log$c.debug('IndexedDB cache initialized successfully');
4806
- this.retryCount = 0; // Reset retry count on success
4807
- }
4808
- catch (error) {
4809
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
4810
- log$c.debug('Failed to initialize IndexedDB', { error: errorMessage });
4811
- // Check if this is a version conflict error
4812
- if (this.isVersionConflictError(errorMessage)) {
4813
- await this.handleVersionConflict();
4814
- }
4815
- else {
4816
- throw new Error(`Failed to initialize IndexedDB: ${errorMessage}`);
4817
- }
4818
- }
4819
- }
4820
- async openDatabase() {
4821
- return await openDB(WebCacheAdapter.DB_NAME, WebCacheAdapter.DB_VERSION, {
4822
- upgrade: (db, oldVersion, newVersion, transaction) => {
4823
- log$c.debug('Database upgrade needed', { oldVersion, newVersion });
4824
- this.handleUpgrade(db, oldVersion, newVersion, transaction);
4825
- },
4826
- blocked: () => {
4827
- log$c.debug('Database blocked - another tab may be open');
4828
- },
4829
- blocking: () => {
4830
- log$c.debug('Database blocking - will close connection');
4831
- if (this.db) {
4832
- this.db.close();
4833
- this.db = null;
4834
- }
4835
- },
4836
- terminated: () => {
4837
- log$c.debug('Database connection terminated unexpectedly');
4838
- this.db = null;
4839
- },
4840
- });
4841
- }
4842
- handleUpgrade(db, oldVersion, newVersion, transaction) {
4843
- log$c.debug('Handling database upgrade', { oldVersion, newVersion });
4844
- // Create cache store if it doesn't exist (initial setup)
4845
- if (!db.objectStoreNames.contains(WebCacheAdapter.STORE_NAME)) {
4846
- const store = db.createObjectStore(WebCacheAdapter.STORE_NAME, { keyPath: 'key' });
4847
- store.createIndex('timestamp', 'timestamp', { unique: false });
4848
- log$c.debug('Created cache store and timestamp index');
4849
- }
4850
- // Handle migration from version 1 to 2
4851
- if (oldVersion < 2) {
4852
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4853
- // Remove unused indexes from simplified cache structure
4854
- const indexesToRemove = ['tags', 'source', 'syncStatus'];
4855
- indexesToRemove.forEach((indexName) => {
4856
- try {
4857
- if (store.indexNames.contains(indexName)) {
4858
- store.deleteIndex(indexName);
4859
- log$c.debug(`Removed unused index: ${indexName}`);
4860
- }
4861
- }
4862
- catch (error) {
4863
- // Ignore errors if indexes don't exist
4864
- log$c.debug(`Warning: Could not remove index ${indexName}`, error);
4865
- }
4866
- });
4867
- }
4868
- log$c.debug('Database upgrade completed');
4869
- }
4870
- isVersionConflictError(errorMessage) {
4871
- return (errorMessage.includes('less than the existing version') ||
4872
- errorMessage.includes('version conflict') ||
4873
- errorMessage.includes('VersionError'));
4874
- }
4875
- async handleVersionConflict() {
4876
- log$c.debug('Handling version conflict, attempting recovery...');
4877
- if (this.retryCount >= this.maxRetries) {
4878
- throw new Error('Failed to resolve IndexedDB version conflict after multiple attempts');
4879
- }
4880
- this.retryCount++;
4881
- try {
4882
- // Close any existing connection
4883
- if (this.db) {
4884
- this.db.close();
4885
- this.db = null;
4886
- }
4887
- // Delete the problematic database
4888
- log$c.debug('Deleting problematic database to resolve version conflict');
4889
- await deleteDB(WebCacheAdapter.DB_NAME);
4890
- // Wait a bit for the deletion to complete
4891
- await new Promise((resolve) => setTimeout(resolve, 200));
4892
- // Try to open the database again
4893
- log$c.debug(`Retrying database initialization (attempt ${this.retryCount}/${this.maxRetries})`);
4894
- this.db = await this.openDatabase();
4895
- log$c.debug('Successfully recovered from version conflict');
4896
- this.retryCount = 0; // Reset retry count on success
4897
- }
4898
- catch (retryError) {
4899
- const retryErrorMessage = retryError instanceof Error ? retryError.message : 'Unknown error';
4900
- log$c.debug('Recovery attempt failed', { attempt: this.retryCount, error: retryErrorMessage });
4901
- if (this.retryCount < this.maxRetries) {
4902
- // Try again
4903
- await this.handleVersionConflict();
4904
- }
4905
- else {
4906
- throw new Error(`Failed to recover from IndexedDB version conflict: ${retryErrorMessage}`);
4907
- }
4908
- }
4909
- }
4910
- async get(key) {
4911
- await this.ensureInitialized();
4912
- log$c.debug('Getting cache item', { key });
4913
- try {
4914
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
4915
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4916
- const result = await store.get(key);
4917
- if (!result) {
4918
- return null;
4919
- }
4920
- const item = result;
4921
- // Handle decompression if needed
4922
- const isCompressed = !!item.compressed;
4923
- let finalData;
4924
- if (isCompressed) {
4925
- const decompressed = decompressData(item.data, true);
4926
- finalData = JSON.parse(decompressed.data);
4927
- }
4928
- else {
4929
- finalData = item.data;
4930
- }
4931
- return {
4932
- data: finalData,
4933
- timestamp: item.timestamp,
4934
- compressed: isCompressed,
4935
- };
4936
- }
4937
- catch (error) {
4938
- log$c.debug('Error getting cache item', { key, error });
4939
- return null; // Return null on error instead of throwing
4940
- }
4941
- }
4942
- async set(key, data) {
4943
- const item = {
4944
- data,
4945
- timestamp: Date.now(),
4946
- };
4947
- return this.setItem(key, item);
4948
- }
4949
- async setItem(key, item) {
4950
- await this.ensureInitialized();
4951
- // Handle compression if enabled
4952
- let finalData = item.data;
4953
- let isCompressed = false;
4954
- if (this.options.compression && this.options.compressionThreshold) {
4955
- const serializedData = JSON.stringify(item.data);
4956
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4957
- if (compressionResult.compressed) {
4958
- finalData = compressionResult.data;
4959
- isCompressed = true;
4960
- log$c.debug('Compression result', {
4961
- key,
4962
- originalSize: compressionResult.originalSize,
4963
- compressedSize: compressionResult.compressedSize,
4964
- compressed: isCompressed,
4965
- savings: compressionResult.originalSize - compressionResult.compressedSize,
4966
- });
4967
- }
4968
- }
4969
- log$c.debug('Setting cache item', { key, timestamp: item.timestamp, compressed: isCompressed });
4970
- const storedItem = {
4971
- key,
4972
- data: finalData,
4973
- timestamp: item.timestamp,
4974
- compressed: isCompressed,
4975
- };
4976
- try {
4977
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
4978
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4979
- await store.put(storedItem);
4980
- }
4981
- catch (error) {
4982
- log$c.debug('Error setting cache item', { key, error });
4983
- // Silently fail for cache writes
4984
- }
4985
- }
4986
- async setBatch(items) {
4987
- if (items.length === 0)
4988
- return;
4989
- await this.ensureInitialized();
4990
- log$c.debug('Batch setting items', { count: items.length });
4991
- try {
4992
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
4993
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4994
- // Process all items in the transaction
4995
- const promises = items.map(([key, item]) => {
4996
- const storedItem = this.prepareBatchItem(key, item);
4997
- return store.put(storedItem);
4998
- });
4999
- await Promise.all(promises);
5000
- log$c.debug('Batch operation completed', { count: items.length });
5001
- }
5002
- catch (error) {
5003
- log$c.debug('Error in batch operation', { count: items.length, error });
5004
- // Silently fail for batch writes
5005
- }
5006
- }
5007
- prepareBatchItem(key, item) {
5008
- // Handle compression if enabled (same logic as setItem)
5009
- let finalData = item.data;
5010
- let isCompressed = false;
5011
- if (this.options.compression && this.options.compressionThreshold) {
5012
- const serializedData = JSON.stringify(item.data);
5013
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
5014
- if (compressionResult.compressed) {
5015
- finalData = compressionResult.data;
5016
- isCompressed = true;
5017
- }
5018
- }
5019
- return {
5020
- key,
5021
- data: finalData,
5022
- timestamp: item.timestamp,
5023
- compressed: isCompressed,
5024
- };
5025
- }
5026
- async invalidate(pattern) {
5027
- await this.ensureInitialized();
5028
- const keys = await this.getKeys(pattern);
5029
- const deletePromises = keys.map((key) => this.delete(key));
5030
- await Promise.all(deletePromises);
5031
- }
5032
- async clear() {
5033
- await this.ensureInitialized();
5034
- try {
5035
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
5036
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5037
- await store.clear();
5038
- log$c.debug('Cache cleared successfully');
5039
- }
5040
- catch (error) {
5041
- log$c.debug('Error clearing cache', error);
5042
- // Silently fail for cache clear
5043
- }
5044
- }
5045
- async getSize() {
5046
- await this.ensureInitialized();
5047
- try {
5048
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
5049
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5050
- let entries = 0;
5051
- let bytes = 0;
5052
- // Use cursor for efficient iteration
5053
- let cursor = await store.openCursor();
5054
- while (cursor) {
5055
- entries++;
5056
- // Rough estimation of size
5057
- bytes += JSON.stringify(cursor.value).length * 2; // UTF-16 encoding
5058
- cursor = await cursor.continue();
5059
- }
5060
- return {
5061
- entries,
5062
- bytes,
5063
- lastCleanup: Date.now(),
5064
- };
5065
- }
5066
- catch (error) {
5067
- log$c.debug('Error getting cache size', error);
5068
- return {
5069
- entries: 0,
5070
- bytes: 0,
5071
- lastCleanup: Date.now(),
5072
- };
5073
- }
5074
- }
5075
- async cleanup() {
5076
- // No cleanup needed - cache never expires
5077
- return 0;
5078
- }
5079
- async getKeys(pattern) {
5080
- await this.ensureInitialized();
5081
- try {
5082
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
5083
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5084
- const allKeys = (await store.getAllKeys());
5085
- if (!pattern) {
5086
- return allKeys;
5087
- }
5088
- const regex = this.patternToRegex(pattern);
5089
- return allKeys.filter((key) => regex.test(key));
5090
- }
5091
- catch (error) {
5092
- log$c.debug('Error getting cache keys', error);
5093
- return [];
5094
- }
5095
- }
5096
- async delete(key) {
5097
- await this.ensureInitialized();
5098
- try {
5099
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
5100
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5101
- await store.delete(key);
5102
- return true;
5103
- }
5104
- catch (error) {
5105
- log$c.debug('Error deleting cache item', { key, error });
5106
- return false;
5107
- }
5108
- }
5109
- async ensureInitialized() {
5110
- if (!this.initPromise) {
5111
- this.initPromise = this.initialize();
5112
- }
5113
- try {
5114
- await this.initPromise;
5115
- }
5116
- catch (error) {
5117
- log$c.debug('Failed to ensure initialization', error);
5118
- // Reset and try once more
5119
- this.initPromise = null;
5120
- this.db = null;
5121
- this.initPromise = this.initialize();
5122
- await this.initPromise;
5123
- }
5124
- }
5125
- patternToRegex(pattern) {
5126
- // Convert simple glob patterns to regex
5127
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
5128
- const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
5129
- return new RegExp(`^${regexPattern}$`);
5130
- }
5131
- }
5132
- WebCacheAdapter.DB_NAME = 'acube_cache';
5133
- WebCacheAdapter.DB_VERSION = 2;
5134
- WebCacheAdapter.STORE_NAME = 'cache_entries';
5135
-
5136
- const log$b = createPrefixedLogger('CACHE-LOADER');
5137
- function loadCacheAdapter(platform) {
5138
- try {
5139
- switch (platform) {
5140
- case 'web':
5141
- return new WebCacheAdapter({
5142
- maxSize: 50 * 1024 * 1024,
5143
- maxEntries: 10000,
5144
- compression: false,
5145
- });
5146
- case 'react-native':
5147
- try {
5148
- return new ReactNativeCacheAdapter({
5149
- maxSize: 100 * 1024 * 1024,
5150
- maxEntries: 15000,
5151
- });
5152
- }
5153
- catch {
5154
- return new MemoryCacheAdapter({
5155
- maxSize: 10 * 1024 * 1024,
5156
- maxEntries: 5000,
5157
- });
3262
+ hasCertificate: false,
3263
+ certificateInfo: null,
3264
+ platformInfo: this.mtlsAdapter?.getPlatformInfo() || null,
3265
+ pendingRequestsCount: this.pendingRequests.size,
3266
+ };
3267
+ if (this.certificatePort) {
3268
+ try {
3269
+ status.hasCertificate = await this.certificatePort.hasCertificate();
3270
+ if (status.hasCertificate) {
3271
+ status.certificateInfo = await this.certificatePort.getCertificateInfo();
5158
3272
  }
5159
- case 'node':
5160
- default:
5161
- return new MemoryCacheAdapter({
5162
- maxSize: 10 * 1024 * 1024,
5163
- maxEntries: 5000,
5164
- });
3273
+ }
3274
+ catch {
3275
+ // Ignore errors
3276
+ }
5165
3277
  }
3278
+ status.isReady = await this.isMtlsReady();
3279
+ return status;
5166
3280
  }
5167
- catch (error) {
5168
- log$b.warn(`Cache adapter not available for platform ${platform}:`, error);
5169
- return undefined;
3281
+ clearPendingRequests() {
3282
+ this.pendingRequests.clear();
5170
3283
  }
5171
3284
  }
5172
3285
 
@@ -5222,7 +3335,7 @@ class BaseSecureStorageAdapter {
5222
3335
  }
5223
3336
  }
5224
3337
 
5225
- const log$a = createPrefixedLogger('NETWORK-BASE');
3338
+ const log$8 = createPrefixedLogger('NETWORK-BASE');
5226
3339
  class NetworkBase {
5227
3340
  constructor(initialOnline = true, debounceMs = 300) {
5228
3341
  this.destroy$ = new Subject();
@@ -5242,14 +3355,14 @@ class NetworkBase {
5242
3355
  const current = this.statusSubject.getValue();
5243
3356
  if (current.online !== online) {
5244
3357
  this.statusSubject.next({ online, timestamp: Date.now() });
5245
- log$a.debug(`Network status changed: ${online ? 'online' : 'offline'}`);
3358
+ log$8.debug(`Network status changed: ${online ? 'online' : 'offline'}`);
5246
3359
  }
5247
3360
  }
5248
3361
  destroy() {
5249
3362
  this.destroy$.next();
5250
3363
  this.destroy$.complete();
5251
3364
  this.statusSubject.complete();
5252
- log$a.debug('Network monitor destroyed');
3365
+ log$8.debug('Network monitor destroyed');
5253
3366
  }
5254
3367
  }
5255
3368
 
@@ -5309,7 +3422,7 @@ class NodeSecureStorageAdapter extends BaseSecureStorageAdapter {
5309
3422
  }
5310
3423
  }
5311
3424
 
5312
- const log$9 = createPrefixedLogger('RN-STORAGE');
3425
+ const log$7 = createPrefixedLogger('RN-STORAGE');
5313
3426
  /**
5314
3427
  * React Native storage adapter using AsyncStorage
5315
3428
  * Note: Uses native batch operations for better performance (not base class)
@@ -5341,7 +3454,7 @@ class ReactNativeStorageAdapter {
5341
3454
  return await this.AsyncStorage.getItem(key);
5342
3455
  }
5343
3456
  catch (error) {
5344
- log$9.error('Failed to get item from AsyncStorage:', error);
3457
+ log$7.error('Failed to get item from AsyncStorage:', error);
5345
3458
  return null;
5346
3459
  }
5347
3460
  }
@@ -5382,7 +3495,7 @@ class ReactNativeStorageAdapter {
5382
3495
  return await this.AsyncStorage.getAllKeys();
5383
3496
  }
5384
3497
  catch (error) {
5385
- log$9.error('Failed to get all keys:', error);
3498
+ log$7.error('Failed to get all keys:', error);
5386
3499
  return [];
5387
3500
  }
5388
3501
  }
@@ -5399,7 +3512,7 @@ class ReactNativeStorageAdapter {
5399
3512
  return result;
5400
3513
  }
5401
3514
  catch (error) {
5402
- log$9.error('Failed to get multiple items:', error);
3515
+ log$7.error('Failed to get multiple items:', error);
5403
3516
  const result = {};
5404
3517
  keys.forEach((key) => {
5405
3518
  result[key] = null;
@@ -5449,7 +3562,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5449
3562
  return;
5450
3563
  }
5451
3564
  catch {
5452
- log$9.debug('expo-secure-store not available, trying react-native-keychain');
3565
+ log$7.debug('expo-secure-store not available, trying react-native-keychain');
5453
3566
  }
5454
3567
  try {
5455
3568
  const Keychain = require('react-native-keychain');
@@ -5458,7 +3571,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5458
3571
  return;
5459
3572
  }
5460
3573
  catch {
5461
- log$9.error('react-native-keychain not available');
3574
+ log$7.error('react-native-keychain not available');
5462
3575
  }
5463
3576
  throw new Error('No secure storage available. Please install expo-secure-store or react-native-keychain');
5464
3577
  }
@@ -5476,7 +3589,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5476
3589
  }
5477
3590
  }
5478
3591
  catch (error) {
5479
- log$9.error('Failed to get secure item:', error);
3592
+ log$7.error('Failed to get secure item:', error);
5480
3593
  }
5481
3594
  return null;
5482
3595
  }
@@ -5513,10 +3626,10 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5513
3626
  }
5514
3627
  }
5515
3628
  async clear() {
5516
- log$9.warn('Clear all secure items not fully implemented for React Native');
3629
+ log$7.warn('Clear all secure items not fully implemented for React Native');
5517
3630
  }
5518
3631
  async getAllKeys() {
5519
- log$9.warn('Get all secure keys not implemented for React Native');
3632
+ log$7.warn('Get all secure keys not implemented for React Native');
5520
3633
  return [];
5521
3634
  }
5522
3635
  async isAvailable() {
@@ -5734,7 +3847,7 @@ class NodeNetworkMonitor extends NetworkBase {
5734
3847
  }
5735
3848
  }
5736
3849
 
5737
- const log$8 = createPrefixedLogger('RN-NETWORK');
3850
+ const log$6 = createPrefixedLogger('RN-NETWORK');
5738
3851
  /**
5739
3852
  * React Native network monitor using RxJS
5740
3853
  * Supports both @react-native-community/netinfo and expo-network
@@ -5747,7 +3860,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5747
3860
  this.moduleReady$ = new Subject();
5748
3861
  this.isExpo = detectPlatform().isExpo;
5749
3862
  this.init().catch((error) => {
5750
- log$8.error('Network monitor initialization failed:', error);
3863
+ log$6.error('Network monitor initialization failed:', error);
5751
3864
  });
5752
3865
  }
5753
3866
  async init() {
@@ -5766,10 +3879,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5766
3879
  try {
5767
3880
  const module = require('@react-native-community/netinfo');
5768
3881
  this.netInfoModule = module.default || module;
5769
- log$8.debug('Loaded @react-native-community/netinfo module');
3882
+ log$6.debug('Loaded @react-native-community/netinfo module');
5770
3883
  }
5771
3884
  catch (error) {
5772
- log$8.error('Failed to load React Native NetInfo module:', error);
3885
+ log$6.error('Failed to load React Native NetInfo module:', error);
5773
3886
  this.netInfoModule = null;
5774
3887
  }
5775
3888
  }
@@ -5777,10 +3890,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5777
3890
  try {
5778
3891
  const module = require('expo-network');
5779
3892
  this.netInfoModule = module.default || module;
5780
- log$8.debug('Loaded expo-network module');
3893
+ log$6.debug('Loaded expo-network module');
5781
3894
  }
5782
3895
  catch (error) {
5783
- log$8.error('Failed to load Expo Network module:', error);
3896
+ log$6.error('Failed to load Expo Network module:', error);
5784
3897
  this.netInfoModule = null;
5785
3898
  }
5786
3899
  }
@@ -5797,16 +3910,16 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5797
3910
  }
5798
3911
  const online = !!(state.isConnected && state.isInternetReachable !== false);
5799
3912
  this.updateStatus(online);
5800
- log$8.debug('Initial network state:', online ? 'online' : 'offline');
3913
+ log$6.debug('Initial network state:', online ? 'online' : 'offline');
5801
3914
  }
5802
3915
  catch (error) {
5803
- log$8.warn('Could not fetch initial network state:', error);
3916
+ log$6.warn('Could not fetch initial network state:', error);
5804
3917
  }
5805
3918
  }
5806
3919
  subscribeToStateChanges() {
5807
3920
  if (!this.netInfoModule)
5808
3921
  return;
5809
- log$8.debug('Subscribing to network state changes');
3922
+ log$6.debug('Subscribing to network state changes');
5810
3923
  const handleState = (state) => {
5811
3924
  const online = !!(state.isConnected && (state.isInternetReachable ?? true));
5812
3925
  this.updateStatus(online);
@@ -5848,7 +3961,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5848
3961
  };
5849
3962
  }
5850
3963
  catch (error) {
5851
- log$8.error('Failed to fetch detailed network info:', error);
3964
+ log$6.error('Failed to fetch detailed network info:', error);
5852
3965
  return null;
5853
3966
  }
5854
3967
  }
@@ -5984,7 +4097,7 @@ class CertificateValidator {
5984
4097
  }
5985
4098
  }
5986
4099
 
5987
- const log$7 = createPrefixedLogger('RN-MTLS');
4100
+ const log$5 = createPrefixedLogger('RN-MTLS');
5988
4101
  /**
5989
4102
  * React Native mTLS Adapter using @a-cube-io/expo-mutual-tls
5990
4103
  */
@@ -6006,7 +4119,7 @@ class ReactNativeMTLSAdapter {
6006
4119
  this.expoMTLS = ExpoMutualTls;
6007
4120
  // Set up debug logging with the correct event signature
6008
4121
  const debugListener = ExpoMutualTls.onDebugLog((event) => {
6009
- log$7.debug(`${event.type}: ${event.message}`, {
4122
+ log$5.debug(`${event.type}: ${event.message}`, {
6010
4123
  method: event.method,
6011
4124
  url: event.url,
6012
4125
  statusCode: event.statusCode,
@@ -6016,28 +4129,28 @@ class ReactNativeMTLSAdapter {
6016
4129
  this.eventListeners.push(debugListener);
6017
4130
  // Set up error logging with the correct event signature
6018
4131
  const errorListener = ExpoMutualTls.onError((event) => {
6019
- log$7.error(event.message, {
4132
+ log$5.error(event.message, {
6020
4133
  code: event.code,
6021
4134
  });
6022
4135
  });
6023
4136
  this.eventListeners.push(errorListener);
6024
4137
  // Set up certificate expiry monitoring with the correct event signature
6025
4138
  const expiryListener = ExpoMutualTls.onCertificateExpiry((event) => {
6026
- log$7.warn(`Certificate ${event.subject} expires at ${new Date(event.expiry)}`, {
4139
+ log$5.warn(`Certificate ${event.subject} expires at ${new Date(event.expiry)}`, {
6027
4140
  alias: event.alias,
6028
4141
  warning: event.warning,
6029
4142
  });
6030
4143
  });
6031
4144
  this.eventListeners.push(expiryListener);
6032
- log$7.debug('Expo mTLS module loaded successfully');
4145
+ log$5.debug('Expo mTLS module loaded successfully');
6033
4146
  }
6034
4147
  catch (error) {
6035
- log$7.warn('@a-cube-io/expo-mutual-tls not available:', error);
4148
+ log$5.warn('@a-cube-io/expo-mutual-tls not available:', error);
6036
4149
  }
6037
4150
  }
6038
4151
  async isMTLSSupported() {
6039
4152
  const supported = this.expoMTLS !== null;
6040
- log$7.debug('mTLS support check:', {
4153
+ log$5.debug('mTLS support check:', {
6041
4154
  supported,
6042
4155
  platform: this.getPlatformInfo().platform,
6043
4156
  moduleAvailable: !!this.expoMTLS,
@@ -6049,7 +4162,7 @@ class ReactNativeMTLSAdapter {
6049
4162
  throw new MTLSError(exports.MTLSErrorType.NOT_SUPPORTED, 'Expo mTLS module not available');
6050
4163
  }
6051
4164
  this.config = config;
6052
- log$7.debug('Initialized with config:', {
4165
+ log$5.debug('Initialized with config:', {
6053
4166
  baseUrl: config.baseUrl,
6054
4167
  port: config.port,
6055
4168
  timeout: config.timeout,
@@ -6063,7 +4176,7 @@ class ReactNativeMTLSAdapter {
6063
4176
  if (!this.config) {
6064
4177
  throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Adapter not initialized. Call initialize() first.');
6065
4178
  }
6066
- log$7.debug('Configuring certificate:', {
4179
+ log$5.debug('Configuring certificate:', {
6067
4180
  format: certificateData.format,
6068
4181
  hasPassword: !!certificateData.password,
6069
4182
  certificateLength: certificateData.certificate.length,
@@ -6080,14 +4193,14 @@ class ReactNativeMTLSAdapter {
6080
4193
  'client-key-service', // keyService
6081
4194
  true // enableLogging - let the native module handle its own debug logging
6082
4195
  );
6083
- log$7.debug('PEM services configured:', configResult);
4196
+ log$5.debug('PEM services configured:', configResult);
6084
4197
  if (!configResult.success) {
6085
4198
  throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, `PEM configuration failed: ${configResult.state}`);
6086
4199
  }
6087
4200
  // Step 2: Store the actual PEM certificate and private key
6088
4201
  const storeResult = await this.expoMTLS.storePEM(certificateData.certificate, certificateData.privateKey, certificateData.password // passphrase (optional)
6089
4202
  );
6090
- log$7.debug('PEM certificate store result:', storeResult);
4203
+ log$5.debug('PEM certificate store result:', storeResult);
6091
4204
  if (!storeResult) {
6092
4205
  throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, 'Failed to store PEM certificate');
6093
4206
  }
@@ -6100,14 +4213,14 @@ class ReactNativeMTLSAdapter {
6100
4213
  const configResult = await this.expoMTLS.configureP12('client-p12-service', // keychainService
6101
4214
  true // enableLogging - let the native module handle its own debug logging
6102
4215
  );
6103
- log$7.debug('P12 service configured:', configResult);
4216
+ log$5.debug('P12 service configured:', configResult);
6104
4217
  if (!configResult.success) {
6105
4218
  throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, `P12 configuration failed: ${configResult.state}`);
6106
4219
  }
6107
4220
  // Step 2: Store the P12 certificate data
6108
4221
  const storeResult = await this.expoMTLS.storeP12(certificateData.certificate, // P12 data in certificate field
6109
4222
  certificateData.password);
6110
- log$7.debug('P12 certificate store result:', storeResult);
4223
+ log$5.debug('P12 certificate store result:', storeResult);
6111
4224
  if (!storeResult) {
6112
4225
  throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, 'Failed to store P12 certificate');
6113
4226
  }
@@ -6121,7 +4234,7 @@ class ReactNativeMTLSAdapter {
6121
4234
  if (error instanceof MTLSError) {
6122
4235
  throw error;
6123
4236
  }
6124
- log$7.error('Certificate configuration failed:', error);
4237
+ log$5.error('Certificate configuration failed:', error);
6125
4238
  throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Failed to configure certificate', error);
6126
4239
  }
6127
4240
  }
@@ -6132,38 +4245,38 @@ class ReactNativeMTLSAdapter {
6132
4245
  try {
6133
4246
  // Use static method call
6134
4247
  const hasCert = await this.expoMTLS.hasCertificate();
6135
- log$7.debug('Certificate availability check:', hasCert);
4248
+ log$5.debug('Certificate availability check:', hasCert);
6136
4249
  return hasCert;
6137
4250
  }
6138
4251
  catch (error) {
6139
- log$7.error('Certificate check failed:', error);
4252
+ log$5.error('Certificate check failed:', error);
6140
4253
  return false;
6141
4254
  }
6142
4255
  }
6143
4256
  async getCertificateInfo() {
6144
4257
  if (!this.expoMTLS) {
6145
- log$7.debug('Certificate info requested but module not available');
4258
+ log$5.debug('Certificate info requested but module not available');
6146
4259
  return null;
6147
4260
  }
6148
4261
  try {
6149
4262
  const hasCert = await this.hasCertificate();
6150
4263
  if (!hasCert) {
6151
- log$7.debug('No certificate stored');
4264
+ log$5.debug('No certificate stored');
6152
4265
  return null;
6153
4266
  }
6154
4267
  // Use getCertificatesInfo to retrieve information about stored certificates
6155
4268
  const result = await this.expoMTLS.getCertificatesInfo();
6156
4269
  if (!result || !result.certificates || result.certificates.length === 0) {
6157
- log$7.debug('No certificate information available');
4270
+ log$5.debug('No certificate information available');
6158
4271
  return null;
6159
4272
  }
6160
4273
  // Get the first certificate (primary client certificate)
6161
4274
  const cert = result.certificates[0];
6162
4275
  if (!cert) {
6163
- log$7.debug('Certificate data is empty');
4276
+ log$5.debug('Certificate data is empty');
6164
4277
  return null;
6165
4278
  }
6166
- log$7.debug('Retrieved certificate info:', {
4279
+ log$5.debug('Retrieved certificate info:', {
6167
4280
  subject: cert.subject.commonName,
6168
4281
  issuer: cert.issuer.commonName,
6169
4282
  validFrom: new Date(cert.validFrom),
@@ -6182,7 +4295,7 @@ class ReactNativeMTLSAdapter {
6182
4295
  };
6183
4296
  }
6184
4297
  catch (error) {
6185
- log$7.error('Failed to get certificate info:', error);
4298
+ log$5.error('Failed to get certificate info:', error);
6186
4299
  return null;
6187
4300
  }
6188
4301
  }
@@ -6193,7 +4306,7 @@ class ReactNativeMTLSAdapter {
6193
4306
  */
6194
4307
  async parseCertificateData(certificateData) {
6195
4308
  if (!this.expoMTLS) {
6196
- log$7.debug('Parse certificate: Module not available');
4309
+ log$5.debug('Parse certificate: Module not available');
6197
4310
  return null;
6198
4311
  }
6199
4312
  try {
@@ -6210,14 +4323,14 @@ class ReactNativeMTLSAdapter {
6210
4323
  else {
6211
4324
  throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_INVALID, `Unsupported certificate format: ${certificateData.format}`);
6212
4325
  }
6213
- log$7.debug('Certificate parsed successfully:', {
4326
+ log$5.debug('Certificate parsed successfully:', {
6214
4327
  certificateCount: result.certificates.length,
6215
4328
  subjects: result.certificates.map((cert) => cert.subject.commonName),
6216
4329
  });
6217
4330
  return result;
6218
4331
  }
6219
4332
  catch (error) {
6220
- log$7.error('Failed to parse certificate:', error);
4333
+ log$5.error('Failed to parse certificate:', error);
6221
4334
  if (error instanceof MTLSError) {
6222
4335
  throw error;
6223
4336
  }
@@ -6232,7 +4345,7 @@ class ReactNativeMTLSAdapter {
6232
4345
  if (!hasCert) {
6233
4346
  throw new MTLSError(exports.MTLSErrorType.CERTIFICATE_NOT_FOUND, 'No certificate configured');
6234
4347
  }
6235
- log$7.debug('Making mTLS request:', {
4348
+ log$5.debug('Making mTLS request:', {
6236
4349
  method: requestConfig.method || 'GET',
6237
4350
  url: requestConfig.url,
6238
4351
  headers: requestConfig.headers,
@@ -6240,7 +4353,7 @@ class ReactNativeMTLSAdapter {
6240
4353
  responseType: requestConfig.responseType,
6241
4354
  });
6242
4355
  if (requestConfig.data) {
6243
- log$7.debug('mTLS request body:', requestConfig.data);
4356
+ log$5.debug('mTLS request body:', requestConfig.data);
6244
4357
  }
6245
4358
  try {
6246
4359
  const response = await this.expoMTLS.request(requestConfig.url, {
@@ -6249,7 +4362,7 @@ class ReactNativeMTLSAdapter {
6249
4362
  body: requestConfig.data ? JSON.stringify(requestConfig.data) : undefined,
6250
4363
  responseType: requestConfig.responseType,
6251
4364
  });
6252
- log$7.debug('mTLS request successful:', response);
4365
+ log$5.debug('mTLS request successful:', response);
6253
4366
  if (!response.success) {
6254
4367
  throw new MTLSError(exports.MTLSErrorType.CONNECTION_FAILED, `mTLS request failed: ${response.statusMessage} (${response.statusCode})`, undefined, response.statusCode);
6255
4368
  }
@@ -6261,7 +4374,7 @@ class ReactNativeMTLSAdapter {
6261
4374
  data = JSON.parse(response.body);
6262
4375
  }
6263
4376
  catch (parseError) {
6264
- log$7.warn('Failed to parse JSON response:', parseError);
4377
+ log$5.warn('Failed to parse JSON response:', parseError);
6265
4378
  // If parsing fails, keep raw body
6266
4379
  }
6267
4380
  }
@@ -6278,7 +4391,7 @@ class ReactNativeMTLSAdapter {
6278
4391
  };
6279
4392
  }
6280
4393
  catch (error) {
6281
- log$7.error('mTLS request failed:', error);
4394
+ log$5.error('mTLS request failed:', error);
6282
4395
  throw new MTLSError(exports.MTLSErrorType.CONNECTION_FAILED, 'mTLS request failed', error);
6283
4396
  }
6284
4397
  }
@@ -6291,18 +4404,18 @@ class ReactNativeMTLSAdapter {
6291
4404
  */
6292
4405
  async testConnection() {
6293
4406
  if (!this.expoMTLS || !this.config) {
6294
- log$7.debug('Diagnostic test: No mTLS module or config available');
4407
+ log$5.debug('Diagnostic test: No mTLS module or config available');
6295
4408
  return false;
6296
4409
  }
6297
4410
  try {
6298
4411
  const hasCert = await this.hasCertificate();
6299
4412
  if (!hasCert) {
6300
- log$7.debug('Diagnostic test: No certificate configured');
4413
+ log$5.debug('Diagnostic test: No certificate configured');
6301
4414
  return false;
6302
4415
  }
6303
- log$7.debug('Running diagnostic test (may fail even if mTLS works):', this.config.baseUrl);
4416
+ log$5.debug('Running diagnostic test (may fail even if mTLS works):', this.config.baseUrl);
6304
4417
  const result = await this.expoMTLS.testConnection(this.config.baseUrl);
6305
- log$7.debug('Diagnostic test result (NOT validation):', {
4418
+ log$5.debug('Diagnostic test result (NOT validation):', {
6306
4419
  success: result.success,
6307
4420
  statusCode: result.statusCode,
6308
4421
  statusMessage: result.statusMessage,
@@ -6313,13 +4426,13 @@ class ReactNativeMTLSAdapter {
6313
4426
  return result.success;
6314
4427
  }
6315
4428
  catch (error) {
6316
- log$7.warn('Diagnostic test failed (this is expected):', error);
4429
+ log$5.warn('Diagnostic test failed (this is expected):', error);
6317
4430
  return false;
6318
4431
  }
6319
4432
  }
6320
4433
  async removeCertificate() {
6321
4434
  if (!this.expoMTLS) {
6322
- log$7.debug('Remove certificate: Module not available');
4435
+ log$5.debug('Remove certificate: Module not available');
6323
4436
  return;
6324
4437
  }
6325
4438
  try {
@@ -6328,10 +4441,10 @@ class ReactNativeMTLSAdapter {
6328
4441
  this.isConfigured = false;
6329
4442
  // Cleanup event listeners
6330
4443
  this.cleanupEventListeners();
6331
- log$7.debug('Certificate removed successfully');
4444
+ log$5.debug('Certificate removed successfully');
6332
4445
  }
6333
4446
  catch (error) {
6334
- log$7.error('Failed to remove certificate:', error);
4447
+ log$5.error('Failed to remove certificate:', error);
6335
4448
  throw new MTLSError(exports.MTLSErrorType.CONFIGURATION_ERROR, 'Failed to remove certificate', error);
6336
4449
  }
6337
4450
  }
@@ -6340,7 +4453,7 @@ class ReactNativeMTLSAdapter {
6340
4453
  */
6341
4454
  cleanupEventListeners() {
6342
4455
  if (this.eventListeners.length > 0) {
6343
- log$7.debug(`Cleaning up ${this.eventListeners.length} event listeners`);
4456
+ log$5.debug(`Cleaning up ${this.eventListeners.length} event listeners`);
6344
4457
  }
6345
4458
  // Remove individual listeners if they have remove methods
6346
4459
  this.eventListeners.forEach((listener) => {
@@ -6377,7 +4490,7 @@ class ReactNativeMTLSAdapter {
6377
4490
  }
6378
4491
  }
6379
4492
 
6380
- const log$6 = createPrefixedLogger('WEB-MTLS');
4493
+ const log$4 = createPrefixedLogger('WEB-MTLS');
6381
4494
  /**
6382
4495
  * Web mTLS Adapter - Graceful fallback for web browsers
6383
4496
  *
@@ -6391,13 +4504,13 @@ const log$6 = createPrefixedLogger('WEB-MTLS');
6391
4504
  */
6392
4505
  class WebMTLSAdapter {
6393
4506
  constructor() {
6394
- log$6.warn('Web browsers do not support programmatic mTLS configuration');
6395
- log$6.info('Use JWT authentication or configure client certificates in browser settings');
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');
6396
4509
  }
6397
4510
  async isMTLSSupported() {
6398
4511
  // mTLS is not supported programmatically in web browsers
6399
4512
  const supported = false;
6400
- log$6.debug('mTLS support check:', {
4513
+ log$4.debug('mTLS support check:', {
6401
4514
  supported,
6402
4515
  platform: this.getPlatformInfo().platform,
6403
4516
  reason: 'Browser security model prevents programmatic certificate configuration',
@@ -6406,14 +4519,14 @@ class WebMTLSAdapter {
6406
4519
  return supported;
6407
4520
  }
6408
4521
  async initialize(config) {
6409
- log$6.warn('Initialized but mTLS not available in web browsers:', {
4522
+ log$4.warn('Initialized but mTLS not available in web browsers:', {
6410
4523
  baseUrl: config.baseUrl,
6411
4524
  port: config.port,
6412
4525
  recommendation: 'Use standard HTTPS with JWT authentication',
6413
4526
  });
6414
4527
  }
6415
4528
  async configureCertificate(certificateData) {
6416
- log$6.error('Certificate configuration attempted:', {
4529
+ log$4.error('Certificate configuration attempted:', {
6417
4530
  format: certificateData.format,
6418
4531
  reason: 'Not supported in web browsers',
6419
4532
  alternatives: [
@@ -6428,15 +4541,15 @@ class WebMTLSAdapter {
6428
4541
  }
6429
4542
  async hasCertificate() {
6430
4543
  // We cannot detect if the browser has certificates configured
6431
- log$6.debug('Certificate availability check: Cannot detect browser certificates programmatically');
4544
+ log$4.debug('Certificate availability check: Cannot detect browser certificates programmatically');
6432
4545
  return false;
6433
4546
  }
6434
4547
  async getCertificateInfo() {
6435
- log$6.debug('Certificate info requested: Not accessible in web browsers');
4548
+ log$4.debug('Certificate info requested: Not accessible in web browsers');
6436
4549
  return null;
6437
4550
  }
6438
4551
  async request(requestConfig) {
6439
- log$6.error('mTLS request attempted:', {
4552
+ log$4.error('mTLS request attempted:', {
6440
4553
  method: requestConfig.method,
6441
4554
  url: requestConfig.url,
6442
4555
  reason: 'Not supported in web browsers',
@@ -6451,11 +4564,11 @@ class WebMTLSAdapter {
6451
4564
  'are properly configured in the browser certificate store.');
6452
4565
  }
6453
4566
  async testConnection() {
6454
- log$6.debug('Connection test: mTLS not available in web browsers');
4567
+ log$4.debug('Connection test: mTLS not available in web browsers');
6455
4568
  return false;
6456
4569
  }
6457
4570
  async removeCertificate() {
6458
- log$6.debug('Remove certificate: No certificates to remove (not supported in web browsers)');
4571
+ log$4.debug('Remove certificate: No certificates to remove (not supported in web browsers)');
6459
4572
  // No-op - cannot remove certificates programmatically in browsers
6460
4573
  }
6461
4574
  /**
@@ -6463,7 +4576,7 @@ class WebMTLSAdapter {
6463
4576
  * Always returns null for web browsers as mTLS is not supported
6464
4577
  */
6465
4578
  getBaseUrl() {
6466
- log$6.debug('Base URL requested: Not supported in web browsers');
4579
+ log$4.debug('Base URL requested: Not supported in web browsers');
6467
4580
  return null;
6468
4581
  }
6469
4582
  getPlatformInfo() {
@@ -6496,7 +4609,7 @@ class WebMTLSAdapter {
6496
4609
  }
6497
4610
  }
6498
4611
 
6499
- const log$5 = createPrefixedLogger('MTLS-LOADER');
4612
+ const log$3 = createPrefixedLogger('MTLS-LOADER');
6500
4613
  function loadMTLSAdapter(platform, config) {
6501
4614
  try {
6502
4615
  let adapter;
@@ -6530,7 +4643,7 @@ function loadMTLSAdapter(platform, config) {
6530
4643
  return adapter;
6531
4644
  }
6532
4645
  catch (error) {
6533
- log$5.warn(`mTLS adapter not available for platform ${platform}:`, error);
4646
+ log$3.warn(`mTLS adapter not available for platform ${platform}:`, error);
6534
4647
  return null;
6535
4648
  }
6536
4649
  }
@@ -6540,7 +4653,7 @@ async function initializeAdapterAsync(adapter, config) {
6540
4653
  if (isSupported) {
6541
4654
  await adapter.initialize(config);
6542
4655
  const platformInfo = adapter.getPlatformInfo();
6543
- log$5.debug('mTLS adapter initialized:', {
4656
+ log$3.debug('mTLS adapter initialized:', {
6544
4657
  platform: platformInfo.platform,
6545
4658
  mtlsSupported: platformInfo.mtlsSupported,
6546
4659
  certificateStorage: platformInfo.certificateStorage,
@@ -6549,31 +4662,28 @@ async function initializeAdapterAsync(adapter, config) {
6549
4662
  }
6550
4663
  }
6551
4664
  catch (error) {
6552
- log$5.warn('Failed to initialize mTLS adapter:', error);
4665
+ log$3.warn('Failed to initialize mTLS adapter:', error);
6553
4666
  }
6554
4667
  }
6555
4668
 
6556
- const log$4 = createPrefixedLogger('ADAPTER-LOADER');
4669
+ const log$2 = createPrefixedLogger('ADAPTER-LOADER');
6557
4670
  function loadPlatformAdapters(options = {}) {
6558
4671
  const { mtlsConfig } = options;
6559
4672
  const { platform } = detectPlatform();
6560
- log$4.debug('Loading adapters for platform:', platform);
4673
+ log$2.debug('Loading adapters for platform:', platform);
6561
4674
  const storageAdapters = loadStorageAdapters(platform);
6562
4675
  const networkMonitor = loadNetworkMonitor(platform);
6563
- const cache = loadCacheAdapter(platform);
6564
4676
  const mtls = loadMTLSAdapter(platform, mtlsConfig);
6565
- log$4.debug('Adapters loaded:', {
4677
+ log$2.debug('Adapters loaded:', {
6566
4678
  platform,
6567
4679
  hasStorage: !!storageAdapters.storage,
6568
4680
  hasSecureStorage: !!storageAdapters.secureStorage,
6569
4681
  hasNetworkMonitor: !!networkMonitor,
6570
- hasCache: !!cache,
6571
4682
  hasMTLS: !!mtls,
6572
4683
  });
6573
4684
  return {
6574
4685
  ...storageAdapters,
6575
4686
  networkMonitor,
6576
- cache,
6577
4687
  mtls: mtls || undefined,
6578
4688
  };
6579
4689
  }
@@ -6590,12 +4700,9 @@ function createACubeMTLSConfig(baseUrl, timeout, autoInitialize = true, forcePor
6590
4700
 
6591
4701
  const DI_TOKENS = {
6592
4702
  HTTP_PORT: Symbol('HTTP_PORT'),
6593
- BASE_HTTP_PORT: Symbol('BASE_HTTP_PORT'),
6594
4703
  STORAGE_PORT: Symbol('STORAGE_PORT'),
6595
4704
  SECURE_STORAGE_PORT: Symbol('SECURE_STORAGE_PORT'),
6596
4705
  NETWORK_PORT: Symbol('NETWORK_PORT'),
6597
- CACHE_PORT: Symbol('CACHE_PORT'),
6598
- CACHE_KEY_GENERATOR: Symbol('CACHE_KEY_GENERATOR'),
6599
4706
  MTLS_PORT: Symbol('MTLS_PORT'),
6600
4707
  TOKEN_STORAGE_PORT: Symbol('TOKEN_STORAGE_PORT'),
6601
4708
  RECEIPT_REPOSITORY: Symbol('RECEIPT_REPOSITORY'),
@@ -6613,7 +4720,6 @@ const DI_TOKENS = {
6613
4720
  AUTH_SERVICE: Symbol('AUTH_SERVICE'),
6614
4721
  AUTHENTICATION_SERVICE: Symbol('AUTHENTICATION_SERVICE'),
6615
4722
  CERTIFICATE_SERVICE: Symbol('CERTIFICATE_SERVICE'),
6616
- OFFLINE_SERVICE: Symbol('OFFLINE_SERVICE'),
6617
4723
  NOTIFICATION_SERVICE: Symbol('NOTIFICATION_SERVICE'),
6618
4724
  TELEMETRY_SERVICE: Symbol('TELEMETRY_SERVICE'),
6619
4725
  };
@@ -7588,468 +5694,6 @@ class TelemetryRepositoryImpl {
7588
5694
  }
7589
5695
  }
7590
5696
 
7591
- const log$3 = createPrefixedLogger('CACHE-KEY');
7592
- const URL_PATTERNS = [
7593
- // Receipt (mf1) - specific patterns first
7594
- {
7595
- pattern: /^\/mf1\/receipts\/([^/]+)\/returnable-items$/,
7596
- resource: 'receipt',
7597
- action: 'returnable',
7598
- },
7599
- {
7600
- pattern: /^\/mf1\/receipts\/([^/]+)\/details$/,
7601
- resource: 'receipt',
7602
- action: 'details',
7603
- },
7604
- { pattern: /^\/mf1\/receipts\/([^/]+)$/, resource: 'receipt' },
7605
- {
7606
- pattern: /^\/mf1\/pems\/([^/]+)\/receipts$/,
7607
- resource: 'receipt',
7608
- parent: 'point-of-sale',
7609
- isList: true,
7610
- },
7611
- { pattern: /^\/mf1\/receipts$/, resource: 'receipt', isList: true },
7612
- // Merchant (mf2)
7613
- { pattern: /^\/mf2\/merchants\/([^/]+)$/, resource: 'merchant' },
7614
- { pattern: /^\/mf2\/merchants$/, resource: 'merchant', isList: true },
7615
- // Cashier (mf1)
7616
- { pattern: /^\/mf1\/cashiers\/me$/, resource: 'cashier', action: 'me' },
7617
- { pattern: /^\/mf1\/cashiers\/([^/]+)$/, resource: 'cashier' },
7618
- { pattern: /^\/mf1\/cashiers$/, resource: 'cashier', isList: true },
7619
- // Cash Register (mf1)
7620
- { pattern: /^\/mf1\/cash-registers\/([^/]+)$/, resource: 'cash-register' },
7621
- { pattern: /^\/mf1\/cash-registers$/, resource: 'cash-register', isList: true },
7622
- // Point of Sale (mf1)
7623
- { pattern: /^\/mf1\/pems\/([^/]+)$/, resource: 'point-of-sale' },
7624
- { pattern: /^\/mf1\/pems$/, resource: 'point-of-sale', isList: true },
7625
- // Nested resources under merchant (mf2)
7626
- {
7627
- pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers\/([^/]+)$/,
7628
- resource: 'supplier',
7629
- parent: 'merchant',
7630
- },
7631
- {
7632
- pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers$/,
7633
- resource: 'supplier',
7634
- parent: 'merchant',
7635
- isList: true,
7636
- },
7637
- {
7638
- pattern: /^\/mf2\/merchants\/([^/]+)\/daily-reports/,
7639
- resource: 'daily-report',
7640
- parent: 'merchant',
7641
- isList: true,
7642
- },
7643
- {
7644
- pattern: /^\/mf2\/merchants\/([^/]+)\/journals/,
7645
- resource: 'journal',
7646
- parent: 'merchant',
7647
- isList: true,
7648
- },
7649
- // PEM (mf2)
7650
- {
7651
- pattern: /^\/mf2\/pems\/([^/]+)\/certificates$/,
7652
- resource: 'pem',
7653
- action: 'certificates',
7654
- },
7655
- { pattern: /^\/mf2\/pems\/([^/]+)$/, resource: 'pem' },
7656
- // Others
7657
- { pattern: /^\/mf1\/notifications/, resource: 'notification', isList: true },
7658
- {
7659
- pattern: /^\/mf1\/pems\/([^/]+)\/telemetry$/,
7660
- resource: 'telemetry',
7661
- },
7662
- ];
7663
- const DEFAULT_TTL_CONFIG = {
7664
- // Data that rarely changes - 30 min TTL for items only
7665
- merchant: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
7666
- 'point-of-sale': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
7667
- 'cash-register': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
7668
- pem: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: false },
7669
- // Data that changes moderately - 10 min TTL for items only
7670
- cashier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
7671
- supplier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
7672
- // Data that can change - 5 min TTL for items only
7673
- receipt: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
7674
- 'daily-report': { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
7675
- journal: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
7676
- // Real-time data - 1 min TTL
7677
- notification: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
7678
- telemetry: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
7679
- };
7680
- const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
7681
- class CacheKeyGenerator {
7682
- constructor(customConfig) {
7683
- this.config = { ...DEFAULT_TTL_CONFIG, ...customConfig };
7684
- log$3.info('CacheKeyGenerator initialized with config:', {
7685
- resources: Object.keys(this.config),
7686
- });
7687
- }
7688
- generate(url, params) {
7689
- const parsed = this.parseUrl(url);
7690
- if (!parsed) {
7691
- // Fallback: use URL as key
7692
- const paramStr = params ? this.serializeParams(params) : '';
7693
- const key = paramStr ? `${url}?${paramStr}` : url;
7694
- log$3.debug('URL not matched, using fallback key:', { url, key });
7695
- return key;
7696
- }
7697
- const { resource, ids, action, isList, parent } = parsed;
7698
- if (isList) {
7699
- const paramStr = params ? this.serializeParams(params) : '';
7700
- const parentPart = parent && ids.length > 0 ? `${parent}=${ids[0]}&` : '';
7701
- const key = `${resource}:list:${parentPart}${paramStr}`;
7702
- log$3.debug('Generated list cache key:', { url, key, resource });
7703
- return key;
7704
- }
7705
- // Single item
7706
- if (ids.length === 0 && action) {
7707
- // Special case for endpoints like /cashiers/me
7708
- const key = `${resource}:${action}`;
7709
- log$3.debug('Generated special action cache key:', { url, key, resource, action });
7710
- return key;
7711
- }
7712
- let key = `${resource}:${ids.join(':')}`;
7713
- if (action) {
7714
- key += `:${action}`;
7715
- }
7716
- log$3.debug('Generated item cache key:', { url, key, resource, ids, action });
7717
- return key;
7718
- }
7719
- parseResource(url) {
7720
- const parsed = this.parseUrl(url);
7721
- return parsed?.resource;
7722
- }
7723
- getTTL(url) {
7724
- const resource = this.parseResource(url);
7725
- if (!resource) {
7726
- log$3.debug('No resource found for URL, using default TTL:', { url, ttl: DEFAULT_TTL });
7727
- return DEFAULT_TTL;
7728
- }
7729
- const ttl = this.config[resource].ttlMs;
7730
- log$3.debug('TTL for resource:', { url, resource, ttlMs: ttl, ttlMin: ttl / 60000 });
7731
- return ttl;
7732
- }
7733
- shouldCache(url) {
7734
- const parsed = this.parseUrl(url);
7735
- if (!parsed) {
7736
- log$3.debug('URL not recognized, should not cache:', { url });
7737
- return false;
7738
- }
7739
- const { resource, isList } = parsed;
7740
- const config = this.config[resource];
7741
- if (isList) {
7742
- log$3.debug('List endpoint cache decision:', {
7743
- url,
7744
- resource,
7745
- isList: true,
7746
- shouldCache: config.cacheList,
7747
- });
7748
- return config.cacheList;
7749
- }
7750
- log$3.debug('Item endpoint cache decision:', {
7751
- url,
7752
- resource,
7753
- isList: false,
7754
- shouldCache: config.cacheItem,
7755
- });
7756
- return config.cacheItem;
7757
- }
7758
- getInvalidationPatterns(url, method) {
7759
- const parsed = this.parseUrl(url);
7760
- if (!parsed) {
7761
- log$3.debug('No patterns to invalidate for URL:', { url, method });
7762
- return [];
7763
- }
7764
- const { resource, ids, parent } = parsed;
7765
- const patterns = [];
7766
- // Always invalidate list on mutations
7767
- if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
7768
- if (parent && ids.length > 0) {
7769
- patterns.push(`${resource}:list:${parent}=${ids[0]}*`);
7770
- }
7771
- patterns.push(`${resource}:list:*`);
7772
- }
7773
- // Invalidate specific item on PUT/PATCH/DELETE
7774
- if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
7775
- if (ids.length > 0) {
7776
- patterns.push(`${resource}:${ids.join(':')}*`);
7777
- }
7778
- }
7779
- // Special cases
7780
- if (resource === 'cashier' && (method === 'PUT' || method === 'DELETE')) {
7781
- patterns.push('cashier:me');
7782
- }
7783
- log$3.debug('Invalidation patterns:', { url, method, patterns });
7784
- return patterns;
7785
- }
7786
- parseUrl(url) {
7787
- // Remove query string for pattern matching
7788
- const urlPath = url.split('?')[0];
7789
- for (const pattern of URL_PATTERNS) {
7790
- const match = urlPath?.match(pattern.pattern);
7791
- if (match) {
7792
- // Extract IDs from capture groups
7793
- const ids = match.slice(1).filter(Boolean);
7794
- return {
7795
- resource: pattern.resource,
7796
- ids,
7797
- action: pattern.action,
7798
- isList: pattern.isList,
7799
- parent: pattern.parent,
7800
- };
7801
- }
7802
- }
7803
- return null;
7804
- }
7805
- serializeParams(params) {
7806
- const sortedKeys = Object.keys(params).sort();
7807
- const parts = [];
7808
- for (const key of sortedKeys) {
7809
- const value = params[key];
7810
- if (value !== undefined && value !== null) {
7811
- parts.push(`${key}=${String(value)}`);
7812
- }
7813
- }
7814
- return parts.join('&');
7815
- }
7816
- }
7817
-
7818
- const log$2 = createPrefixedLogger('CACHE');
7819
- class CachingHttpDecorator {
7820
- constructor(http, cache, keyGenerator, networkMonitor, config = {}) {
7821
- this.http = http;
7822
- this.cache = cache;
7823
- this.keyGenerator = keyGenerator;
7824
- this.networkMonitor = networkMonitor;
7825
- this.config = config;
7826
- this.currentOnlineState = true;
7827
- this.authToken = null;
7828
- log$2.info('CachingHttpDecorator initialized', {
7829
- enabled: config.enabled !== false,
7830
- hasNetworkMonitor: !!networkMonitor,
7831
- });
7832
- this.setupNetworkMonitoring();
7833
- }
7834
- setupNetworkMonitoring() {
7835
- if (this.networkMonitor) {
7836
- this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
7837
- if (this.currentOnlineState !== online) {
7838
- log$2.info('Network state changed:', { online });
7839
- }
7840
- this.currentOnlineState = online;
7841
- });
7842
- }
7843
- }
7844
- isOnline() {
7845
- if (this.networkMonitor) {
7846
- return this.currentOnlineState;
7847
- }
7848
- if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
7849
- return navigator.onLine;
7850
- }
7851
- return true;
7852
- }
7853
- async get(url, config) {
7854
- const startTime = Date.now();
7855
- // Check if caching is disabled globally
7856
- if (this.config.enabled === false) {
7857
- log$2.debug('GET (cache disabled globally):', { url });
7858
- return this.http.get(url, config);
7859
- }
7860
- // Check if this URL should be cached
7861
- const shouldCache = this.keyGenerator.shouldCache(url);
7862
- if (!shouldCache) {
7863
- log$2.debug('GET (not cacheable - likely a list endpoint):', { url });
7864
- return this.http.get(url, config);
7865
- }
7866
- const cacheKey = this.keyGenerator.generate(url, config?.params);
7867
- const ttl = this.keyGenerator.getTTL(url);
7868
- const resource = this.keyGenerator.parseResource(url);
7869
- log$2.info('GET request starting:', {
7870
- url,
7871
- resource,
7872
- cacheKey,
7873
- ttlMs: ttl,
7874
- ttlMin: Math.round(ttl / 60000),
7875
- params: config?.params,
7876
- });
7877
- // Check cache
7878
- let cached = null;
7879
- try {
7880
- cached = await this.cache.get(cacheKey);
7881
- if (cached) {
7882
- log$2.debug('Cache entry found:', {
7883
- cacheKey,
7884
- timestamp: new Date(cached.timestamp).toISOString(),
7885
- ageMs: Date.now() - cached.timestamp,
7886
- });
7887
- }
7888
- else {
7889
- log$2.debug('Cache entry not found:', { cacheKey });
7890
- }
7891
- }
7892
- catch (error) {
7893
- log$2.warn('Cache lookup failed:', {
7894
- cacheKey,
7895
- error: error instanceof Error ? error.message : error,
7896
- });
7897
- }
7898
- if (cached) {
7899
- const age = Date.now() - cached.timestamp;
7900
- const isExpired = age >= ttl;
7901
- log$2.debug('Cache analysis:', {
7902
- cacheKey,
7903
- ageMs: age,
7904
- ageSec: Math.round(age / 1000),
7905
- ttlMs: ttl,
7906
- isExpired,
7907
- isOnline: this.isOnline(),
7908
- });
7909
- // If within TTL, return cached data
7910
- if (!isExpired) {
7911
- const duration = Date.now() - startTime;
7912
- log$2.info('CACHE HIT:', {
7913
- url,
7914
- cacheKey,
7915
- ageMs: age,
7916
- durationMs: duration,
7917
- });
7918
- return {
7919
- data: cached.data,
7920
- status: 200,
7921
- headers: { 'x-cache': 'HIT' },
7922
- };
7923
- }
7924
- // If offline and cache is stale, return stale data
7925
- if (!this.isOnline()) {
7926
- const duration = Date.now() - startTime;
7927
- log$2.info('CACHE STALE (offline):', {
7928
- url,
7929
- cacheKey,
7930
- ageMs: age,
7931
- durationMs: duration,
7932
- });
7933
- return {
7934
- data: cached.data,
7935
- status: 200,
7936
- headers: { 'x-cache': 'STALE' },
7937
- };
7938
- }
7939
- log$2.debug('Cache expired, fetching fresh data:', { cacheKey, ageMs: age, ttlMs: ttl });
7940
- }
7941
- // Fetch fresh data
7942
- try {
7943
- log$2.debug('Fetching from network:', { url });
7944
- const response = await this.http.get(url, config);
7945
- // Cache the response
7946
- try {
7947
- await this.cache.set(cacheKey, response.data);
7948
- log$2.debug('Response cached successfully:', { cacheKey });
7949
- }
7950
- catch (error) {
7951
- log$2.error('Failed to cache response:', {
7952
- cacheKey,
7953
- error: error instanceof Error ? error.message : error,
7954
- });
7955
- }
7956
- const duration = Date.now() - startTime;
7957
- log$2.info('CACHE MISS (fetched fresh):', {
7958
- url,
7959
- cacheKey,
7960
- status: response.status,
7961
- durationMs: duration,
7962
- });
7963
- return {
7964
- ...response,
7965
- headers: { ...response.headers, 'x-cache': 'MISS' },
7966
- };
7967
- }
7968
- catch (error) {
7969
- // On error, return stale cache if available
7970
- if (cached) {
7971
- const duration = Date.now() - startTime;
7972
- log$2.warn('CACHE STALE (network error):', {
7973
- url,
7974
- cacheKey,
7975
- error: error instanceof Error ? error.message : 'Unknown error',
7976
- durationMs: duration,
7977
- });
7978
- return {
7979
- data: cached.data,
7980
- status: 200,
7981
- headers: { 'x-cache': 'STALE' },
7982
- };
7983
- }
7984
- log$2.error('Network error with no cache fallback:', {
7985
- url,
7986
- error: error instanceof Error ? error.message : error,
7987
- });
7988
- throw error;
7989
- }
7990
- }
7991
- async post(url, data, config) {
7992
- log$2.info('POST request:', { url });
7993
- const response = await this.http.post(url, data, config);
7994
- await this.invalidateRelated(url, 'POST');
7995
- return response;
7996
- }
7997
- async put(url, data, config) {
7998
- log$2.info('PUT request:', { url });
7999
- const response = await this.http.put(url, data, config);
8000
- await this.invalidateRelated(url, 'PUT');
8001
- return response;
8002
- }
8003
- async patch(url, data, config) {
8004
- log$2.info('PATCH request:', { url });
8005
- const response = await this.http.patch(url, data, config);
8006
- await this.invalidateRelated(url, 'PATCH');
8007
- return response;
8008
- }
8009
- async delete(url, config) {
8010
- log$2.info('DELETE request:', { url });
8011
- const response = await this.http.delete(url, config);
8012
- await this.invalidateRelated(url, 'DELETE');
8013
- return response;
8014
- }
8015
- setAuthToken(token) {
8016
- log$2.debug('CachingHttpDecorator.setAuthToken called:', {
8017
- hasToken: !!token,
8018
- tokenPrefix: token?.substring(0, 20),
8019
- underlyingHttpType: this.http.constructor.name,
8020
- });
8021
- this.authToken = token;
8022
- this.http.setAuthToken(token);
8023
- }
8024
- getAuthToken() {
8025
- return this.authToken;
8026
- }
8027
- async invalidateRelated(url, method) {
8028
- const patterns = this.keyGenerator.getInvalidationPatterns(url, method);
8029
- if (patterns.length === 0) {
8030
- log$2.debug('No cache patterns to invalidate:', { url, method });
8031
- return;
8032
- }
8033
- log$2.info('Invalidating cache patterns:', { url, method, patterns });
8034
- for (const pattern of patterns) {
8035
- try {
8036
- await this.cache.invalidate(pattern);
8037
- log$2.debug('Cache pattern invalidated:', { pattern });
8038
- }
8039
- catch (error) {
8040
- log$2.error('Failed to invalidate pattern:', {
8041
- pattern,
8042
- error: error instanceof Error ? error.message : error,
8043
- });
8044
- }
8045
- }
8046
- }
8047
- destroy() {
8048
- log$2.debug('CachingHttpDecorator destroyed');
8049
- this.networkSubscription?.unsubscribe();
8050
- }
8051
- }
8052
-
8053
5697
  const logJwt = createPrefixedLogger('HTTP-JWT');
8054
5698
  const logMtls = createPrefixedLogger('HTTP-MTLS');
8055
5699
  class AxiosHttpAdapter {
@@ -8319,7 +5963,6 @@ class SDKFactory {
8319
5963
  baseUrl: config.baseUrl,
8320
5964
  timeout: config.timeout,
8321
5965
  });
8322
- container.register(DI_TOKENS.BASE_HTTP_PORT, httpAdapter);
8323
5966
  container.register(DI_TOKENS.HTTP_PORT, httpAdapter);
8324
5967
  container.registerFactory(DI_TOKENS.RECEIPT_REPOSITORY, () => {
8325
5968
  const http = container.get(DI_TOKENS.HTTP_PORT);
@@ -8367,23 +6010,6 @@ class SDKFactory {
8367
6010
  });
8368
6011
  return container;
8369
6012
  }
8370
- static registerCacheServices(container, cache, network) {
8371
- container.register(DI_TOKENS.CACHE_PORT, cache);
8372
- if (network) {
8373
- container.register(DI_TOKENS.NETWORK_PORT, network);
8374
- }
8375
- const keyGenerator = new CacheKeyGenerator();
8376
- container.register(DI_TOKENS.CACHE_KEY_GENERATOR, keyGenerator);
8377
- const baseHttp = container.get(DI_TOKENS.BASE_HTTP_PORT);
8378
- const cachingHttp = new CachingHttpDecorator(baseHttp, cache, keyGenerator, network);
8379
- container.register(DI_TOKENS.HTTP_PORT, cachingHttp);
8380
- }
8381
- static getCacheKeyGenerator(container) {
8382
- if (container.has(DI_TOKENS.CACHE_KEY_GENERATOR)) {
8383
- return container.get(DI_TOKENS.CACHE_KEY_GENERATOR);
8384
- }
8385
- return undefined;
8386
- }
8387
6013
  static registerAuthServices(container, secureStorage, config) {
8388
6014
  const tokenStorage = new TokenStorageAdapter(secureStorage);
8389
6015
  container.register(DI_TOKENS.TOKEN_STORAGE_PORT, tokenStorage);
@@ -8451,7 +6077,6 @@ class ACubeSDK {
8451
6077
  mtlsConfig,
8452
6078
  });
8453
6079
  log$1.info('Platform adapters loaded', {
8454
- hasCache: !!this.adapters.cache,
8455
6080
  hasNetworkMonitor: !!this.adapters.networkMonitor,
8456
6081
  hasMtls: !!this.adapters.mtls,
8457
6082
  hasSecureStorage: !!this.adapters.secureStorage,
@@ -8467,25 +6092,10 @@ class ACubeSDK {
8467
6092
  this.container = SDKFactory.createContainer(factoryConfig);
8468
6093
  log$1.debug('Registering auth services');
8469
6094
  SDKFactory.registerAuthServices(this.container, this.adapters.secureStorage, factoryConfig);
8470
- if (this.adapters.cache) {
8471
- log$1.info('Registering cache services', {
8472
- hasNetworkMonitor: !!this.adapters.networkMonitor,
8473
- });
8474
- SDKFactory.registerCacheServices(this.container, this.adapters.cache, this.adapters.networkMonitor);
8475
- }
8476
- else {
8477
- log$1.debug('No cache adapter available, caching disabled');
8478
- }
8479
6095
  log$1.debug('Initializing certificate service');
8480
6096
  this.certificateService = new CertificateService(this.adapters.secureStorage);
8481
6097
  const tokenStorage = this.container.get(DI_TOKENS.TOKEN_STORAGE_PORT);
8482
6098
  const httpPort = this.container.get(DI_TOKENS.HTTP_PORT);
8483
- const baseHttpPort = this.container.get(DI_TOKENS.BASE_HTTP_PORT);
8484
- log$1.debug('HTTP ports initialized', {
8485
- httpPortType: httpPort.constructor.name,
8486
- baseHttpPortType: baseHttpPort.constructor.name,
8487
- areSameInstance: httpPort === baseHttpPort,
8488
- });
8489
6099
  log$1.debug('Initializing authentication service');
8490
6100
  this.authService = new AuthenticationService(httpPort, tokenStorage, {
8491
6101
  authUrl: this.config.getAuthUrl(),
@@ -8496,27 +6106,9 @@ class ACubeSDK {
8496
6106
  this.events.onAuthError?.(new ACubeSDKError('AUTH_ERROR', error.message, error));
8497
6107
  },
8498
6108
  });
8499
- log$1.debug('Initializing offline manager');
8500
- const queueEvents = {
8501
- onOperationAdded: (operation) => {
8502
- this.events.onOfflineOperationAdded?.(operation.id);
8503
- },
8504
- onOperationCompleted: (result) => {
8505
- this.events.onOfflineOperationCompleted?.(result.operation.id, result.success);
8506
- },
8507
- onOperationFailed: (result) => {
8508
- this.events.onOfflineOperationCompleted?.(result.operation.id, false);
8509
- },
8510
- };
8511
- this.offlineManager = new OfflineManager(this.adapters.storage, httpPort, this.adapters.networkMonitor, {
8512
- syncInterval: 30000,
8513
- }, queueEvents);
8514
6109
  this.networkSubscription = this.adapters.networkMonitor.online$.subscribe((online) => {
8515
6110
  this.currentOnlineState = online;
8516
6111
  this.events.onNetworkStatusChanged?.(online);
8517
- if (online && this.offlineManager) {
8518
- this.offlineManager.sync().catch(() => { });
8519
- }
8520
6112
  });
8521
6113
  const isAuth = await this.authService.isAuthenticated();
8522
6114
  log$1.debug('Checking authentication status during init', { isAuthenticated: isAuth });
@@ -8534,12 +6126,12 @@ class ACubeSDK {
8534
6126
  else {
8535
6127
  log$1.warn('User not authenticated during SDK init - token will be set after login');
8536
6128
  }
8537
- if (this.adapters?.mtls && 'setMTLSAdapter' in baseHttpPort) {
6129
+ if (this.adapters?.mtls && 'setMTLSAdapter' in httpPort) {
8538
6130
  log$1.debug('Connecting mTLS adapter to HTTP port');
8539
- const httpWithMtls = baseHttpPort;
6131
+ const httpWithMtls = httpPort;
8540
6132
  httpWithMtls.setMTLSAdapter(this.adapters.mtls);
8541
6133
  }
8542
- if ('setAuthStrategy' in baseHttpPort) {
6134
+ if ('setAuthStrategy' in httpPort) {
8543
6135
  log$1.debug('Configuring auth strategy');
8544
6136
  const jwtHandler = new JwtAuthHandler(tokenStorage);
8545
6137
  const certificatePort = this.certificateService
@@ -8571,7 +6163,7 @@ class ACubeSDK {
8571
6163
  },
8572
6164
  };
8573
6165
  const authStrategy = new AuthStrategy(jwtHandler, mtlsHandler, userProvider, this.adapters?.mtls || null);
8574
- const httpWithStrategy = baseHttpPort;
6166
+ const httpWithStrategy = httpPort;
8575
6167
  httpWithStrategy.setAuthStrategy(authStrategy);
8576
6168
  }
8577
6169
  if (this.adapters?.mtls && this.certificateService) {
@@ -8596,7 +6188,6 @@ class ACubeSDK {
8596
6188
  }
8597
6189
  this.isInitialized = true;
8598
6190
  log$1.info('SDK initialized successfully', {
8599
- hasCache: !!this.adapters.cache,
8600
6191
  hasMtls: !!this.adapters.mtls,
8601
6192
  });
8602
6193
  }
@@ -8687,10 +6278,6 @@ class ACubeSDK {
8687
6278
  this.ensureInitialized();
8688
6279
  return await this.authService.isAuthenticated();
8689
6280
  }
8690
- getOfflineManager() {
8691
- this.ensureInitialized();
8692
- return this.offlineManager;
8693
- }
8694
6281
  isOnline() {
8695
6282
  this.ensureInitialized();
8696
6283
  return this.currentOnlineState;
@@ -8818,7 +6405,6 @@ class ACubeSDK {
8818
6405
  }
8819
6406
  destroy() {
8820
6407
  this.networkSubscription?.unsubscribe();
8821
- this.offlineManager?.destroy();
8822
6408
  this.container?.clear();
8823
6409
  this.isInitialized = false;
8824
6410
  }
@@ -8843,6 +6429,7 @@ const INITIAL_STATE = {
8843
6429
  remainingMs: 0,
8844
6430
  },
8845
6431
  lastNotification: null,
6432
+ certificateMissing: false,
8846
6433
  };
8847
6434
  class AppStateService {
8848
6435
  get state$() {
@@ -8857,28 +6444,33 @@ class AppStateService {
8857
6444
  get warning$() {
8858
6445
  return this.state$.pipe(map((s) => s.warning), distinctUntilChanged((a, b) => a.active === b.active && a.remainingMs === b.remainingMs));
8859
6446
  }
8860
- constructor(notifications$, networkPort) {
6447
+ get certificateMissing$() {
6448
+ return this.state$.pipe(map((s) => s.certificateMissing), distinctUntilChanged());
6449
+ }
6450
+ constructor(notifications$, networkPort, certificateMissingInput$ = of(false)) {
8861
6451
  this.notifications$ = notifications$;
8862
6452
  this.networkPort = networkPort;
6453
+ this.certificateMissingInput$ = certificateMissingInput$;
8863
6454
  this.stateSubject = new BehaviorSubject(INITIAL_STATE);
8864
6455
  this.destroy$ = new Subject();
8865
6456
  this.warningTimerId = null;
8866
6457
  this.setupSubscriptions();
8867
6458
  }
8868
6459
  setupSubscriptions() {
8869
- combineLatest([this.notifications$, this.networkPort.online$])
6460
+ combineLatest([this.notifications$, this.networkPort.online$, this.certificateMissingInput$])
8870
6461
  .pipe(takeUntil(this.destroy$))
8871
- .subscribe(([notifications, isOnline]) => {
8872
- this.processState(notifications, isOnline);
6462
+ .subscribe(([notifications, isOnline, certificateMissing]) => {
6463
+ this.processState(notifications, isOnline, certificateMissing);
8873
6464
  });
8874
6465
  }
8875
- processState(notifications, isOnline) {
6466
+ processState(notifications, isOnline, certificateMissing) {
8876
6467
  if (!isOnline) {
8877
6468
  this.updateState({
8878
6469
  mode: 'OFFLINE',
8879
6470
  isOnline: false,
8880
6471
  warning: { active: false, blockAt: null, remainingMs: 0 },
8881
6472
  lastNotification: null,
6473
+ certificateMissing,
8882
6474
  });
8883
6475
  this.stopWarningTimer();
8884
6476
  return;
@@ -8940,6 +6532,7 @@ class AppStateService {
8940
6532
  isOnline: true,
8941
6533
  warning: warningState,
8942
6534
  lastNotification,
6535
+ certificateMissing,
8943
6536
  });
8944
6537
  }
8945
6538
  getLatestNotificationByCode(notifications) {
@@ -9097,7 +6690,6 @@ class TelemetryService {
9097
6690
  this.events = events;
9098
6691
  this.stateSubject = new BehaviorSubject({
9099
6692
  data: null,
9100
- isCached: false,
9101
6693
  isLoading: false,
9102
6694
  lastFetchedAt: null,
9103
6695
  });
@@ -9167,7 +6759,6 @@ class TelemetryService {
9167
6759
  const data = await this.repository.getTelemetry(this.currentPemId);
9168
6760
  const newState = {
9169
6761
  data,
9170
- isCached: false,
9171
6762
  isLoading: false,
9172
6763
  lastFetchedAt: Date.now(),
9173
6764
  };
@@ -9192,7 +6783,6 @@ class TelemetryService {
9192
6783
  clearTelemetry() {
9193
6784
  this.stateSubject.next({
9194
6785
  data: null,
9195
- isCached: false,
9196
6786
  isLoading: false,
9197
6787
  lastFetchedAt: null,
9198
6788
  });
@@ -9205,6 +6795,7 @@ class TelemetryService {
9205
6795
  }
9206
6796
  }
9207
6797
 
6798
+ const log = createPrefixedLogger('SDK-MANAGER');
9208
6799
  /**
9209
6800
  * SDKManager - Singleton wrapper for ACubeSDK with simplified API
9210
6801
  *
@@ -9255,6 +6846,7 @@ class SDKManager {
9255
6846
  this.appStateService = null;
9256
6847
  this.isInitialized = false;
9257
6848
  this.isPollingActive = false;
6849
+ this.certificateMissingSubject = new BehaviorSubject(false);
9258
6850
  /**
9259
6851
  * Handle user state changes (login/logout/token expiration)
9260
6852
  * Manages polling lifecycle based on user role
@@ -9265,9 +6857,14 @@ class SDKManager {
9265
6857
  if (!this.isInitialized)
9266
6858
  return;
9267
6859
  if (user) {
9268
- // User logged in - check role and start polling if allowed
6860
+ // User logged in - check role and certificate before starting polling
9269
6861
  const canPoll = hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
9270
6862
  if (canPoll && !this.isPollingActive) {
6863
+ const hasCert = await this.checkCertificate();
6864
+ if (!hasCert) {
6865
+ log.warn('Certificate missing — polling blocked until certificate is installed');
6866
+ return;
6867
+ }
9271
6868
  this.notificationService?.startPolling();
9272
6869
  await this.startTelemetryPollingAuto();
9273
6870
  this.isPollingActive = true;
@@ -9281,6 +6878,7 @@ class SDKManager {
9281
6878
  this.telemetryService?.clearTelemetry();
9282
6879
  this.isPollingActive = false;
9283
6880
  }
6881
+ this.certificateMissingSubject.next(false);
9284
6882
  }
9285
6883
  };
9286
6884
  }
@@ -9347,7 +6945,7 @@ class SDKManager {
9347
6945
  this.telemetryService = new TelemetryService(telemetryRepo, networkPort, {
9348
6946
  pollIntervalMs: this.config.telemetryPollIntervalMs ?? 60000,
9349
6947
  });
9350
- this.appStateService = new AppStateService(this.notificationService.notifications$, networkPort);
6948
+ this.appStateService = new AppStateService(this.notificationService.notifications$, networkPort, this.certificateMissingSubject.asObservable());
9351
6949
  if (this.events?.onAppStateChanged) {
9352
6950
  this.appStateService.state$.subscribe(this.events.onAppStateChanged);
9353
6951
  }
@@ -9359,9 +6957,15 @@ class SDKManager {
9359
6957
  const user = await this.sdk.getCurrentUser();
9360
6958
  const canPoll = user && hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
9361
6959
  if (canPoll) {
9362
- this.notificationService.startPolling();
9363
- await this.startTelemetryPollingAuto();
9364
- this.isPollingActive = true;
6960
+ const hasCert = await this.checkCertificate();
6961
+ if (hasCert) {
6962
+ this.notificationService.startPolling();
6963
+ await this.startTelemetryPollingAuto();
6964
+ this.isPollingActive = true;
6965
+ }
6966
+ else {
6967
+ log.warn('Certificate missing at init — polling blocked until certificate is installed');
6968
+ }
9365
6969
  }
9366
6970
  // AppStateService remains active for all users (handles OFFLINE network state)
9367
6971
  }
@@ -9396,7 +7000,14 @@ class SDKManager {
9396
7000
  return this.appStateService.warning$;
9397
7001
  }
9398
7002
  /**
9399
- * Observable stream of telemetry state (data, isLoading, isCached, error)
7003
+ * Observable stream indicating if certificate is missing
7004
+ * When true, polling is blocked and the user should install a certificate
7005
+ */
7006
+ get certificateMissing$() {
7007
+ return this.certificateMissingSubject.asObservable();
7008
+ }
7009
+ /**
7010
+ * Observable stream of telemetry state (data, isLoading, error)
9400
7011
  */
9401
7012
  get telemetryState$() {
9402
7013
  this.ensureInitialized();
@@ -9472,9 +7083,37 @@ class SDKManager {
9472
7083
  logout: () => sdk.logout(),
9473
7084
  getCurrentUser: () => sdk.getCurrentUser(),
9474
7085
  isAuthenticated: () => sdk.isAuthenticated(),
9475
- storeCertificate: (certificate, privateKey, options) => sdk.storeCertificate(certificate, privateKey, options),
7086
+ storeCertificate: async (certificate, privateKey, options) => {
7087
+ await sdk.storeCertificate(certificate, privateKey, options);
7088
+ this.certificateMissingSubject.next(false);
7089
+ // Start polling if user can poll and polling is not active
7090
+ if (!this.isPollingActive) {
7091
+ const user = await sdk.getCurrentUser();
7092
+ const canPoll = user && hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
7093
+ if (canPoll) {
7094
+ log.info('Certificate installed — starting polling');
7095
+ this.notificationService?.startPolling();
7096
+ await this.startTelemetryPollingAuto();
7097
+ this.isPollingActive = true;
7098
+ }
7099
+ }
7100
+ },
9476
7101
  hasCertificate: () => sdk.hasCertificate(),
9477
- clearCertificate: () => sdk.clearCertificate(),
7102
+ clearCertificate: async () => {
7103
+ await sdk.clearCertificate();
7104
+ this.certificateMissingSubject.next(true);
7105
+ // Stop polling since certificate is required
7106
+ if (this.isPollingActive) {
7107
+ log.info('Certificate removed — stopping polling');
7108
+ this.notificationService?.stopPolling();
7109
+ this.telemetryService?.stopPolling();
7110
+ this.telemetryService?.clearTelemetry();
7111
+ this.isPollingActive = false;
7112
+ }
7113
+ },
7114
+ getCertificate: () => sdk.getCertificate(),
7115
+ getCertificatesInfo: () => sdk.getCertificatesInfo(),
7116
+ notifications: sdk.notifications,
9478
7117
  isOnline: () => sdk.isOnline(),
9479
7118
  };
9480
7119
  }
@@ -9505,6 +7144,21 @@ class SDKManager {
9505
7144
  this.ensureInitialized();
9506
7145
  return this.sdk;
9507
7146
  }
7147
+ /**
7148
+ * Check certificate availability and update certificateMissing state.
7149
+ * Returns true if certificate is available, false otherwise.
7150
+ */
7151
+ async checkCertificate() {
7152
+ try {
7153
+ const hasCert = await this.sdk.hasCertificate();
7154
+ this.certificateMissingSubject.next(!hasCert);
7155
+ return hasCert;
7156
+ }
7157
+ catch {
7158
+ this.certificateMissingSubject.next(true);
7159
+ return false;
7160
+ }
7161
+ }
9508
7162
  cleanup() {
9509
7163
  this.notificationService?.destroy();
9510
7164
  this.telemetryService?.destroy();
@@ -9530,91 +7184,6 @@ const RECEIPT_SENT = 'sent';
9530
7184
  const RECEIPT_SORT_DESCENDING = 'descending';
9531
7185
  const RECEIPT_SORT_ASCENDING = 'ascending';
9532
7186
 
9533
- const log = createPrefixedLogger('CACHE-HANDLER');
9534
- class CacheHandler {
9535
- constructor(cache, networkMonitor) {
9536
- this.cache = cache;
9537
- this.networkMonitor = networkMonitor;
9538
- this.currentOnlineState = true;
9539
- this.setupNetworkMonitoring();
9540
- }
9541
- setupNetworkMonitoring() {
9542
- if (this.networkMonitor) {
9543
- this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
9544
- this.currentOnlineState = online;
9545
- });
9546
- }
9547
- }
9548
- isOnline() {
9549
- if (this.networkMonitor) {
9550
- return this.currentOnlineState;
9551
- }
9552
- if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
9553
- return navigator.onLine;
9554
- }
9555
- return false;
9556
- }
9557
- async handleCachedRequest(url, requestFn, config) {
9558
- if (!this.cache || config?.useCache === false) {
9559
- const response = await requestFn();
9560
- return response.data;
9561
- }
9562
- const cacheKey = this.generateCacheKey(url);
9563
- const online = this.isOnline();
9564
- log.debug('Request:', { url, cacheKey, online });
9565
- if (online) {
9566
- try {
9567
- const response = await requestFn();
9568
- if (this.cache) {
9569
- await this.cache.set(cacheKey, response.data).catch((error) => {
9570
- log.error('Failed to cache:', error instanceof Error ? error.message : error);
9571
- });
9572
- }
9573
- return response.data;
9574
- }
9575
- catch (error) {
9576
- const cached = await this.cache.get(cacheKey).catch(() => null);
9577
- if (cached) {
9578
- log.debug('Network failed, using cache fallback');
9579
- return cached.data;
9580
- }
9581
- throw error;
9582
- }
9583
- }
9584
- else {
9585
- const cached = await this.cache.get(cacheKey).catch(() => null);
9586
- if (cached) {
9587
- log.debug('Offline, returning cached data');
9588
- return cached.data;
9589
- }
9590
- throw new Error('Offline: No cached data available');
9591
- }
9592
- }
9593
- generateCacheKey(url) {
9594
- return url;
9595
- }
9596
- async invalidateCache(pattern) {
9597
- if (!this.cache)
9598
- return;
9599
- try {
9600
- await this.cache.invalidate(pattern);
9601
- }
9602
- catch (error) {
9603
- log.error('Invalidation failed:', error instanceof Error ? error.message : error);
9604
- }
9605
- }
9606
- getCacheStatus() {
9607
- return {
9608
- available: !!this.cache,
9609
- networkMonitorAvailable: !!this.networkMonitor,
9610
- isOnline: this.isOnline(),
9611
- };
9612
- }
9613
- destroy() {
9614
- this.networkSubscription?.unsubscribe();
9615
- }
9616
- }
9617
-
9618
7187
  function transformError(error) {
9619
7188
  if (axios.isAxiosError(error)) {
9620
7189
  const response = error.response;
@@ -9802,7 +7371,6 @@ exports.AppStateService = AppStateService;
9802
7371
  exports.AuthStrategy = AuthStrategy;
9803
7372
  exports.AuthenticationService = AuthenticationService;
9804
7373
  exports.AxiosHttpAdapter = AxiosHttpAdapter;
9805
- exports.CacheHandler = CacheHandler;
9806
7374
  exports.CashRegisterCreateSchema = CashRegisterCreateSchema;
9807
7375
  exports.CashRegisterMapper = CashRegisterMapper;
9808
7376
  exports.CashRegisterRepositoryImpl = CashRegisterRepositoryImpl;
@@ -9813,7 +7381,6 @@ exports.CertificateService = CertificateService;
9813
7381
  exports.CertificateValidator = CertificateValidator;
9814
7382
  exports.ConfigManager = ConfigManager;
9815
7383
  exports.DAILY_REPORT_STATUS_OPTIONS = DAILY_REPORT_STATUS_OPTIONS;
9816
- exports.DEFAULT_QUEUE_CONFIG = DEFAULT_QUEUE_CONFIG;
9817
7384
  exports.DIContainer = DIContainer;
9818
7385
  exports.DI_TOKENS = DI_TOKENS;
9819
7386
  exports.DailyReportMapper = DailyReportMapper;
@@ -9852,8 +7419,6 @@ exports.NotificationRepositoryImpl = NotificationRepositoryImpl;
9852
7419
  exports.NotificationSchema = NotificationSchema;
9853
7420
  exports.NotificationScopeSchema = NotificationScopeSchema;
9854
7421
  exports.NotificationService = NotificationService;
9855
- exports.OfflineManager = OfflineManager;
9856
- exports.OperationQueue = OperationQueue;
9857
7422
  exports.PEMStatusOfflineRequestSchema = PEMStatusOfflineRequestSchema;
9858
7423
  exports.PEMStatusSchema = PEMStatusSchema;
9859
7424
  exports.PEM_STATUS_OPTIONS = PEM_STATUS_OPTIONS;
@@ -9888,7 +7453,6 @@ exports.SupplierCreateInputSchema = SupplierCreateInputSchema;
9888
7453
  exports.SupplierMapper = SupplierMapper;
9889
7454
  exports.SupplierRepositoryImpl = SupplierRepositoryImpl;
9890
7455
  exports.SupplierUpdateInputSchema = SupplierUpdateInputSchema;
9891
- exports.SyncManager = SyncManager;
9892
7456
  exports.TelemetryMapper = TelemetryMapper;
9893
7457
  exports.TelemetryMerchantSchema = TelemetryMerchantSchema;
9894
7458
  exports.TelemetryRepositoryImpl = TelemetryRepositoryImpl;