@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.esm.js CHANGED
@@ -1392,6 +1392,15 @@ function from(input, scheduler) {
1392
1392
  return scheduler ? scheduled(input, scheduler) : innerFrom(input);
1393
1393
  }
1394
1394
 
1395
+ function of() {
1396
+ var args = [];
1397
+ for (var _i = 0; _i < arguments.length; _i++) {
1398
+ args[_i] = arguments[_i];
1399
+ }
1400
+ var scheduler = popScheduler(args);
1401
+ return from(args, scheduler);
1402
+ }
1403
+
1395
1404
  function isValidDate(value) {
1396
1405
  return value instanceof Date && !isNaN(value);
1397
1406
  }
@@ -2103,7 +2112,7 @@ function formatDecimal(value, decimals = 2) {
2103
2112
  return num.toFixed(decimals);
2104
2113
  }
2105
2114
 
2106
- const log$g = createPrefixedLogger('AUTH-SERVICE');
2115
+ const log$b = createPrefixedLogger('AUTH-SERVICE');
2107
2116
  class AuthenticationService {
2108
2117
  get user$() {
2109
2118
  return this.userSubject.asObservable();
@@ -2125,7 +2134,7 @@ class AuthenticationService {
2125
2134
  }
2126
2135
  async login(credentials) {
2127
2136
  this.authStateSubject.next('authenticating');
2128
- log$g.info('Login attempt', {
2137
+ log$b.info('Login attempt', {
2129
2138
  authUrl: this.config.authUrl,
2130
2139
  email: credentials.email,
2131
2140
  });
@@ -2136,7 +2145,7 @@ class AuthenticationService {
2136
2145
  });
2137
2146
  const jwtPayload = parseJwt(response.data.token);
2138
2147
  const expiresAt = jwtPayload.exp * 1000;
2139
- log$g.info('Login successful', {
2148
+ log$b.info('Login successful', {
2140
2149
  authUrl: this.config.authUrl,
2141
2150
  tokenPrefix: response.data.token.substring(0, 30) + '...',
2142
2151
  expiresAt: new Date(expiresAt).toISOString(),
@@ -2165,21 +2174,21 @@ class AuthenticationService {
2165
2174
  const token = await this.tokenStorage.getAccessToken();
2166
2175
  if (!token) {
2167
2176
  // No token - clear any stale user state
2168
- log$g.debug('getCurrentUser: No token in storage');
2177
+ log$b.debug('getCurrentUser: No token in storage');
2169
2178
  if (this.userSubject.value) {
2170
2179
  this.userSubject.next(null);
2171
2180
  this.authStateSubject.next('idle');
2172
2181
  }
2173
2182
  return null;
2174
2183
  }
2175
- log$g.debug('getCurrentUser: Token found', {
2184
+ log$b.debug('getCurrentUser: Token found', {
2176
2185
  tokenPrefix: token.substring(0, 30) + '...',
2177
2186
  tokenLength: token.length,
2178
2187
  });
2179
2188
  const jwtPayload = parseJwt(token);
2180
2189
  if (isTokenExpired(jwtPayload)) {
2181
2190
  // Token expired - clear everything
2182
- log$g.warn('getCurrentUser: Token expired');
2191
+ log$b.warn('getCurrentUser: Token expired');
2183
2192
  await this.tokenStorage.clearTokens();
2184
2193
  this.userSubject.next(null);
2185
2194
  this.authStateSubject.next('idle');
@@ -2189,7 +2198,7 @@ class AuthenticationService {
2189
2198
  // Token is valid - return cached user if available
2190
2199
  const currentUser = this.userSubject.value;
2191
2200
  if (currentUser) {
2192
- log$g.debug('getCurrentUser: Returning cached user', {
2201
+ log$b.debug('getCurrentUser: Returning cached user', {
2193
2202
  email: currentUser.email,
2194
2203
  roles: currentUser.roles,
2195
2204
  });
@@ -2212,12 +2221,12 @@ class AuthenticationService {
2212
2221
  async isAuthenticated() {
2213
2222
  const token = await this.tokenStorage.getAccessToken();
2214
2223
  if (!token) {
2215
- log$g.debug('isAuthenticated: No token in storage');
2224
+ log$b.debug('isAuthenticated: No token in storage');
2216
2225
  return false;
2217
2226
  }
2218
2227
  const jwtPayload = parseJwt(token);
2219
2228
  const expired = isTokenExpired(jwtPayload);
2220
- log$g.debug('isAuthenticated: Token check', {
2229
+ log$b.debug('isAuthenticated: Token check', {
2221
2230
  hasToken: true,
2222
2231
  expired,
2223
2232
  expiresAt: new Date(jwtPayload.exp * 1000).toISOString(),
@@ -2889,7 +2898,7 @@ class ACubeSDKError extends Error {
2889
2898
  }
2890
2899
  }
2891
2900
 
2892
- const log$f = createPrefixedLogger('AUTH-STRATEGY');
2901
+ const log$a = createPrefixedLogger('AUTH-STRATEGY');
2893
2902
  class AuthStrategy {
2894
2903
  constructor(jwtHandler, mtlsHandler, userProvider, mtlsAdapter) {
2895
2904
  this.jwtHandler = jwtHandler;
@@ -2904,7 +2913,7 @@ class AuthStrategy {
2904
2913
  const platform = this.detectPlatform();
2905
2914
  const userRole = await this.getUserRole();
2906
2915
  const isReceiptEndpoint = this.isReceiptEndpoint(url);
2907
- log$f.debug('Determining auth config', {
2916
+ log$a.debug('Determining auth config', {
2908
2917
  url,
2909
2918
  method,
2910
2919
  platform,
@@ -3035,7 +3044,7 @@ class JwtAuthHandler {
3035
3044
  }
3036
3045
  }
3037
3046
 
3038
- const log$e = createPrefixedLogger('MTLS-HANDLER');
3047
+ const log$9 = createPrefixedLogger('MTLS-HANDLER');
3039
3048
  class MtlsAuthHandler {
3040
3049
  constructor(mtlsAdapter, certificatePort) {
3041
3050
  this.mtlsAdapter = mtlsAdapter;
@@ -3077,7 +3086,7 @@ class MtlsAuthHandler {
3077
3086
  async makeRequest(url, config, jwtToken) {
3078
3087
  const requestKey = this.generateRequestKey(url, config, jwtToken);
3079
3088
  if (this.pendingRequests.has(requestKey)) {
3080
- log$e.debug('Deduplicating concurrent request:', url);
3089
+ log$9.debug('Deduplicating concurrent request:', url);
3081
3090
  return this.pendingRequests.get(requestKey);
3082
3091
  }
3083
3092
  const requestPromise = this.executeRequest(url, config, jwtToken, false);
@@ -3101,10 +3110,10 @@ class MtlsAuthHandler {
3101
3110
  };
3102
3111
  if (jwtToken) {
3103
3112
  headers['Authorization'] = `Bearer ${jwtToken}`;
3104
- log$e.debug('JWT token present:', jwtToken.substring(0, 20) + '...');
3113
+ log$9.debug('JWT token present:', jwtToken.substring(0, 20) + '...');
3105
3114
  }
3106
3115
  else {
3107
- log$e.warn('No JWT token provided for mTLS request');
3116
+ log$9.warn('No JWT token provided for mTLS request');
3108
3117
  }
3109
3118
  const fullUrl = this.constructMtlsUrl(url);
3110
3119
  const mtlsConfig = {
@@ -3115,25 +3124,25 @@ class MtlsAuthHandler {
3115
3124
  timeout: config.timeout,
3116
3125
  responseType: config.responseType,
3117
3126
  };
3118
- log$e.debug('header-mtls', headers);
3119
- log$e.debug(`${config.method} ${fullUrl}`);
3127
+ log$9.debug('header-mtls', headers);
3128
+ log$9.debug(`${config.method} ${fullUrl}`);
3120
3129
  if (config.data) {
3121
- log$e.debug('Request body:', config.data);
3130
+ log$9.debug('Request body:', config.data);
3122
3131
  }
3123
3132
  try {
3124
3133
  const response = await this.mtlsAdapter.request(mtlsConfig);
3125
- log$e.debug(`Response ${response.status} from ${fullUrl}`);
3134
+ log$9.debug(`Response ${response.status} from ${fullUrl}`);
3126
3135
  if (response.data) {
3127
- log$e.debug('Response body:', response.data);
3136
+ log$9.debug('Response body:', response.data);
3128
3137
  }
3129
3138
  return response.data;
3130
3139
  }
3131
3140
  catch (error) {
3132
- log$e.error(`Response error from ${fullUrl}:`, error);
3141
+ log$9.error(`Response error from ${fullUrl}:`, error);
3133
3142
  if (error && typeof error === 'object' && 'response' in error) {
3134
3143
  const axiosError = error;
3135
3144
  if (axiosError.response?.data) {
3136
- log$e.error('Response body:', axiosError.response.data);
3145
+ log$9.error('Response body:', axiosError.response.data);
3137
3146
  }
3138
3147
  }
3139
3148
  if (isRetryAttempt) {
@@ -3143,7 +3152,7 @@ class MtlsAuthHandler {
3143
3152
  if (!shouldRetry) {
3144
3153
  throw error;
3145
3154
  }
3146
- log$e.debug('Request failed, reconfiguring certificate and retrying...');
3155
+ log$9.debug('Request failed, reconfiguring certificate and retrying...');
3147
3156
  try {
3148
3157
  await this.configureCertificate(certificate);
3149
3158
  return await this.executeRequest(url, config, jwtToken, true);
@@ -3227,1923 +3236,27 @@ class MtlsAuthHandler {
3227
3236
  adapterAvailable: !!this.mtlsAdapter,
3228
3237
  certificatePortAvailable: !!this.certificatePort,
3229
3238
  isReady: false,
3230
- hasCertificate: false,
3231
- certificateInfo: null,
3232
- platformInfo: this.mtlsAdapter?.getPlatformInfo() || null,
3233
- pendingRequestsCount: this.pendingRequests.size,
3234
- };
3235
- if (this.certificatePort) {
3236
- try {
3237
- status.hasCertificate = await this.certificatePort.hasCertificate();
3238
- if (status.hasCertificate) {
3239
- status.certificateInfo = await this.certificatePort.getCertificateInfo();
3240
- }
3241
- }
3242
- catch {
3243
- // Ignore errors
3244
- }
3245
- }
3246
- status.isReady = await this.isMtlsReady();
3247
- return status;
3248
- }
3249
- clearPendingRequests() {
3250
- this.pendingRequests.clear();
3251
- }
3252
- }
3253
-
3254
- const DEFAULT_QUEUE_CONFIG = {
3255
- maxRetries: 3,
3256
- retryDelay: 1000,
3257
- maxRetryDelay: 30000,
3258
- backoffMultiplier: 2,
3259
- maxQueueSize: 1000,
3260
- batchSize: 10,
3261
- syncInterval: 30000,
3262
- };
3263
-
3264
- class OperationQueue {
3265
- constructor(storage, config = DEFAULT_QUEUE_CONFIG, events = {}) {
3266
- this.storage = storage;
3267
- this.config = config;
3268
- this.events = events;
3269
- this.queue = [];
3270
- this.processing = false;
3271
- this.config = { ...DEFAULT_QUEUE_CONFIG, ...config };
3272
- this.loadQueue();
3273
- if (this.config.syncInterval > 0) {
3274
- this.startAutoSync();
3275
- }
3276
- }
3277
- async addOperation(type, resource, endpoint, method, data, priority = 1) {
3278
- if (this.queue.length >= this.config.maxQueueSize) {
3279
- const lowPriorityIndex = this.queue.findIndex((op) => op.priority === 1);
3280
- if (lowPriorityIndex !== -1) {
3281
- this.queue.splice(lowPriorityIndex, 1);
3282
- }
3283
- else {
3284
- throw new Error('Queue is full');
3285
- }
3286
- }
3287
- const operation = {
3288
- id: this.generateId(),
3289
- type,
3290
- resource,
3291
- endpoint,
3292
- method,
3293
- data,
3294
- status: 'pending',
3295
- createdAt: Date.now(),
3296
- updatedAt: Date.now(),
3297
- retryCount: 0,
3298
- maxRetries: this.config.maxRetries,
3299
- priority,
3300
- };
3301
- const insertIndex = this.queue.findIndex((op) => op.priority < priority);
3302
- if (insertIndex === -1) {
3303
- this.queue.push(operation);
3304
- }
3305
- else {
3306
- this.queue.splice(insertIndex, 0, operation);
3307
- }
3308
- await this.saveQueue();
3309
- this.events.onOperationAdded?.(operation);
3310
- return operation.id;
3311
- }
3312
- getPendingOperations() {
3313
- return this.queue.filter((op) => op.status === 'pending' || op.status === 'failed');
3314
- }
3315
- getOperation(id) {
3316
- return this.queue.find((op) => op.id === id);
3317
- }
3318
- async removeOperation(id) {
3319
- const index = this.queue.findIndex((op) => op.id === id);
3320
- if (index === -1)
3321
- return false;
3322
- this.queue.splice(index, 1);
3323
- await this.saveQueue();
3324
- return true;
3325
- }
3326
- async updateOperation(id, updates) {
3327
- const operation = this.queue.find((op) => op.id === id);
3328
- if (!operation)
3329
- return false;
3330
- Object.assign(operation, { ...updates, updatedAt: Date.now() });
3331
- await this.saveQueue();
3332
- return true;
3333
- }
3334
- getStats() {
3335
- return {
3336
- total: this.queue.length,
3337
- pending: this.queue.filter((op) => op.status === 'pending').length,
3338
- processing: this.queue.filter((op) => op.status === 'processing').length,
3339
- completed: this.queue.filter((op) => op.status === 'completed').length,
3340
- failed: this.queue.filter((op) => op.status === 'failed').length,
3341
- };
3342
- }
3343
- async clearQueue() {
3344
- this.queue = [];
3345
- await this.saveQueue();
3346
- }
3347
- async clearCompleted() {
3348
- this.queue = this.queue.filter((op) => op.status !== 'completed');
3349
- await this.saveQueue();
3350
- }
3351
- async clearFailed() {
3352
- this.queue = this.queue.filter((op) => op.status !== 'failed');
3353
- await this.saveQueue();
3354
- }
3355
- async retryFailed() {
3356
- for (const operation of this.queue.filter((op) => op.status === 'failed')) {
3357
- if (operation.retryCount < operation.maxRetries) {
3358
- operation.status = 'pending';
3359
- operation.retryCount++;
3360
- operation.updatedAt = Date.now();
3361
- delete operation.error;
3362
- }
3363
- }
3364
- await this.saveQueue();
3365
- }
3366
- getNextBatch() {
3367
- return this.queue
3368
- .filter((op) => op.status === 'pending')
3369
- .sort((a, b) => b.priority - a.priority || a.createdAt - b.createdAt)
3370
- .slice(0, this.config.batchSize);
3371
- }
3372
- isEmpty() {
3373
- return this.getPendingOperations().length === 0;
3374
- }
3375
- startAutoSync() {
3376
- if (this.syncIntervalId)
3377
- return;
3378
- this.syncIntervalId = setInterval(() => {
3379
- if (!this.isEmpty() && !this.processing) {
3380
- this.events.onQueueEmpty?.();
3381
- }
3382
- }, this.config.syncInterval);
3383
- }
3384
- stopAutoSync() {
3385
- if (this.syncIntervalId) {
3386
- clearInterval(this.syncIntervalId);
3387
- this.syncIntervalId = undefined;
3388
- }
3389
- }
3390
- setProcessing(value) {
3391
- this.processing = value;
3392
- }
3393
- isCurrentlyProcessing() {
3394
- return this.processing;
3395
- }
3396
- async loadQueue() {
3397
- try {
3398
- const queueData = await this.storage.get(OperationQueue.QUEUE_KEY);
3399
- if (queueData) {
3400
- this.queue = JSON.parse(queueData);
3401
- this.queue.forEach((op) => {
3402
- if (op.status === 'processing') {
3403
- op.status = 'pending';
3404
- }
3405
- });
3406
- }
3407
- }
3408
- catch {
3409
- this.queue = [];
3410
- }
3411
- }
3412
- async saveQueue() {
3413
- try {
3414
- await this.storage.set(OperationQueue.QUEUE_KEY, JSON.stringify(this.queue));
3415
- }
3416
- catch (error) {
3417
- this.events.onError?.(new Error(`Failed to save queue: ${error}`));
3418
- }
3419
- }
3420
- generateId() {
3421
- return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
3422
- }
3423
- destroy() {
3424
- this.stopAutoSync();
3425
- }
3426
- }
3427
- OperationQueue.QUEUE_KEY = 'acube_operation_queue';
3428
-
3429
- class SyncManager {
3430
- constructor(queue, httpPort, networkMonitor, config, events = {}) {
3431
- this.queue = queue;
3432
- this.httpPort = httpPort;
3433
- this.networkMonitor = networkMonitor;
3434
- this.config = config;
3435
- this.events = events;
3436
- this.isOnline = true;
3437
- this.destroy$ = new Subject();
3438
- this.setupNetworkMonitoring();
3439
- }
3440
- setupNetworkMonitoring() {
3441
- // Subscribe to online$ to track current state
3442
- this.networkSubscription = this.networkMonitor.online$
3443
- .pipe(startWith(true), // Assume online initially
3444
- pairwise(), filter(([wasOnline, isNowOnline]) => !wasOnline && isNowOnline), takeUntil(this.destroy$))
3445
- .subscribe(() => {
3446
- // Offline → Online transition detected
3447
- this.syncPendingOperations();
3448
- });
3449
- // Track current online state
3450
- this.networkMonitor.online$.pipe(takeUntil(this.destroy$)).subscribe((online) => {
3451
- this.isOnline = online;
3452
- });
3453
- }
3454
- async syncPendingOperations() {
3455
- if (!this.isOnline) {
3456
- throw new Error('Cannot sync while offline');
3457
- }
3458
- if (this.queue.isCurrentlyProcessing()) {
3459
- throw new Error('Sync already in progress');
3460
- }
3461
- this.queue.setProcessing(true);
3462
- try {
3463
- const results = [];
3464
- let successCount = 0;
3465
- let failureCount = 0;
3466
- while (!this.queue.isEmpty()) {
3467
- const batch = this.queue.getNextBatch();
3468
- if (batch.length === 0)
3469
- break;
3470
- const batchPromises = batch.map((operation) => this.processOperation(operation));
3471
- const batchResults = await Promise.allSettled(batchPromises);
3472
- batchResults.forEach((result, index) => {
3473
- const operation = batch[index];
3474
- if (!operation)
3475
- return;
3476
- if (result.status === 'fulfilled') {
3477
- const syncResult = result.value;
3478
- results.push(syncResult);
3479
- if (syncResult.success) {
3480
- successCount++;
3481
- this.events.onOperationCompleted?.(syncResult);
3482
- }
3483
- else {
3484
- failureCount++;
3485
- this.events.onOperationFailed?.(syncResult);
3486
- }
3487
- }
3488
- else {
3489
- const syncResult = {
3490
- operation,
3491
- success: false,
3492
- error: result.reason?.message || 'Unknown error',
3493
- };
3494
- results.push(syncResult);
3495
- failureCount++;
3496
- this.events.onOperationFailed?.(syncResult);
3497
- this.queue.updateOperation(operation.id, {
3498
- status: 'failed',
3499
- error: syncResult.error,
3500
- });
3501
- }
3502
- });
3503
- if (!this.queue.isEmpty()) {
3504
- await this.delay(500);
3505
- }
3506
- }
3507
- const batchResult = {
3508
- totalOperations: results.length,
3509
- successCount,
3510
- failureCount,
3511
- results,
3512
- };
3513
- this.events.onBatchSyncCompleted?.(batchResult);
3514
- if (this.queue.isEmpty()) {
3515
- this.events.onQueueEmpty?.();
3516
- }
3517
- return batchResult;
3518
- }
3519
- finally {
3520
- this.queue.setProcessing(false);
3521
- }
3522
- }
3523
- async processOperation(operation) {
3524
- await this.queue.updateOperation(operation.id, { status: 'processing' });
3525
- try {
3526
- const response = await this.executeOperation(operation);
3527
- await this.queue.updateOperation(operation.id, { status: 'completed' });
3528
- return { operation, success: true, response };
3529
- }
3530
- catch (error) {
3531
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
3532
- if (operation.retryCount < operation.maxRetries && this.isRetryableError(error)) {
3533
- const delay = this.calculateRetryDelay(operation.retryCount);
3534
- await this.queue.updateOperation(operation.id, {
3535
- status: 'pending',
3536
- retryCount: operation.retryCount + 1,
3537
- error: errorMessage,
3538
- });
3539
- setTimeout(() => {
3540
- if (this.isOnline && !this.queue.isCurrentlyProcessing()) {
3541
- this.syncPendingOperations();
3542
- }
3543
- }, delay);
3544
- return { operation, success: false, error: `Retrying: ${errorMessage}` };
3545
- }
3546
- else {
3547
- await this.queue.updateOperation(operation.id, {
3548
- status: 'failed',
3549
- error: errorMessage,
3550
- });
3551
- return { operation, success: false, error: errorMessage };
3552
- }
3553
- }
3554
- }
3555
- async executeOperation(operation) {
3556
- const { method, endpoint, data, headers } = operation;
3557
- const config = headers ? { headers } : undefined;
3558
- switch (method) {
3559
- case 'GET':
3560
- return (await this.httpPort.get(endpoint, config)).data;
3561
- case 'POST':
3562
- return (await this.httpPort.post(endpoint, data, config)).data;
3563
- case 'PUT':
3564
- return (await this.httpPort.put(endpoint, data, config)).data;
3565
- case 'PATCH':
3566
- return (await this.httpPort.patch(endpoint, data, config)).data;
3567
- case 'DELETE':
3568
- return (await this.httpPort.delete(endpoint, config)).data;
3569
- default:
3570
- throw new Error(`Unsupported HTTP method: ${method}`);
3571
- }
3572
- }
3573
- isRetryableError(error) {
3574
- const errorObj = error;
3575
- if (errorObj.code === 'NETWORK_ERROR')
3576
- return true;
3577
- if (errorObj.statusCode && errorObj.statusCode >= 500)
3578
- return true;
3579
- if (errorObj.statusCode === 429)
3580
- return true;
3581
- const errorMessage = error?.message;
3582
- if (errorObj.code === 'ECONNABORTED' || errorMessage?.includes('timeout'))
3583
- return true;
3584
- return false;
3585
- }
3586
- calculateRetryDelay(retryCount) {
3587
- const delay = this.config.retryDelay * Math.pow(this.config.backoffMultiplier, retryCount);
3588
- return Math.min(delay, this.config.maxRetryDelay);
3589
- }
3590
- delay(ms) {
3591
- return new Promise((resolve) => setTimeout(resolve, ms));
3592
- }
3593
- isCurrentlyOnline() {
3594
- return this.isOnline;
3595
- }
3596
- async triggerSync() {
3597
- if (!this.isOnline)
3598
- return null;
3599
- if (this.queue.isEmpty()) {
3600
- return { totalOperations: 0, successCount: 0, failureCount: 0, results: [] };
3601
- }
3602
- return await this.syncPendingOperations();
3603
- }
3604
- getSyncStatus() {
3605
- return {
3606
- isOnline: this.isOnline,
3607
- isProcessing: this.queue.isCurrentlyProcessing(),
3608
- queueStats: this.queue.getStats(),
3609
- };
3610
- }
3611
- destroy() {
3612
- this.destroy$.next();
3613
- this.destroy$.complete();
3614
- this.networkSubscription?.unsubscribe();
3615
- }
3616
- }
3617
-
3618
- class OfflineManager {
3619
- get queue$() {
3620
- return this.queueSubject.asObservable();
3621
- }
3622
- get syncStatus$() {
3623
- return this.syncStatusSubject.asObservable();
3624
- }
3625
- constructor(storage, httpPort, networkMonitor, config = {}, events = {}, _cache) {
3626
- this.queueSubject = new BehaviorSubject([]);
3627
- this.syncStatusSubject = new BehaviorSubject({
3628
- isOnline: true,
3629
- isProcessing: false,
3630
- queueStats: { total: 0, pending: 0, processing: 0, completed: 0, failed: 0 },
3631
- });
3632
- this.destroy$ = new Subject();
3633
- const finalConfig = { ...DEFAULT_QUEUE_CONFIG, ...config };
3634
- const wrappedEvents = {
3635
- ...events,
3636
- onOperationAdded: (op) => {
3637
- this.updateQueueState();
3638
- events.onOperationAdded?.(op);
3639
- },
3640
- onOperationCompleted: (result) => {
3641
- this.updateQueueState();
3642
- events.onOperationCompleted?.(result);
3643
- },
3644
- onOperationFailed: (result) => {
3645
- this.updateQueueState();
3646
- events.onOperationFailed?.(result);
3647
- },
3648
- onBatchSyncCompleted: (result) => {
3649
- this.updateQueueState();
3650
- events.onBatchSyncCompleted?.(result);
3651
- },
3652
- };
3653
- this.queue = new OperationQueue(storage, finalConfig, wrappedEvents);
3654
- this.syncManager = new SyncManager(this.queue, httpPort, networkMonitor, finalConfig, wrappedEvents);
3655
- this.updateQueueState();
3656
- }
3657
- updateQueueState() {
3658
- this.queueSubject.next(this.queue.getPendingOperations());
3659
- this.syncStatusSubject.next(this.syncManager.getSyncStatus());
3660
- }
3661
- async queueOperation(type, resource, endpoint, method, data, priority = 1) {
3662
- const id = await this.queue.addOperation(type, resource, endpoint, method, data, priority);
3663
- this.updateQueueState();
3664
- return id;
3665
- }
3666
- async queueReceiptCreation(receiptData, priority = 2) {
3667
- return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts', 'POST', receiptData, priority);
3668
- }
3669
- async queueReceiptVoid(voidData, priority = 3) {
3670
- return await this.queueOperation('DELETE', 'receipt', '/mf1/receipts', 'DELETE', voidData, priority);
3671
- }
3672
- async queueReceiptReturn(returnData, priority = 3) {
3673
- return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts/return', 'POST', returnData, priority);
3674
- }
3675
- async queueCashierCreation(cashierData, priority = 1) {
3676
- return await this.queueOperation('CREATE', 'cashier', '/mf1/cashiers', 'POST', cashierData, priority);
3677
- }
3678
- isOnline() {
3679
- return this.syncManager.isCurrentlyOnline();
3680
- }
3681
- getStatus() {
3682
- return this.syncManager.getSyncStatus();
3683
- }
3684
- getPendingCount() {
3685
- return this.queue.getPendingOperations().length;
3686
- }
3687
- isEmpty() {
3688
- return this.queue.isEmpty();
3689
- }
3690
- async sync() {
3691
- const result = await this.syncManager.triggerSync();
3692
- this.updateQueueState();
3693
- return result;
3694
- }
3695
- async retryFailed() {
3696
- await this.queue.retryFailed();
3697
- this.updateQueueState();
3698
- if (this.isOnline()) {
3699
- await this.sync();
3700
- }
3701
- }
3702
- async clearCompleted() {
3703
- await this.queue.clearCompleted();
3704
- this.updateQueueState();
3705
- }
3706
- async clearFailed() {
3707
- await this.queue.clearFailed();
3708
- this.updateQueueState();
3709
- }
3710
- async clearAll() {
3711
- await this.queue.clearQueue();
3712
- this.updateQueueState();
3713
- }
3714
- getOperation(id) {
3715
- return this.queue.getOperation(id);
3716
- }
3717
- async removeOperation(id) {
3718
- const result = await this.queue.removeOperation(id);
3719
- this.updateQueueState();
3720
- return result;
3721
- }
3722
- getQueueStats() {
3723
- return this.queue.getStats();
3724
- }
3725
- startAutoSync() {
3726
- this.queue.startAutoSync();
3727
- }
3728
- stopAutoSync() {
3729
- this.queue.stopAutoSync();
3730
- }
3731
- destroy() {
3732
- this.destroy$.next();
3733
- this.destroy$.complete();
3734
- this.queue.destroy();
3735
- this.syncManager.destroy();
3736
- }
3737
- }
3738
-
3739
- class CompressionAdapter {
3740
- compress(data, threshold = 1024) {
3741
- const originalSize = data.length * 2;
3742
- if (originalSize < threshold) {
3743
- return {
3744
- data,
3745
- compressed: false,
3746
- originalSize,
3747
- compressedSize: originalSize,
3748
- };
3749
- }
3750
- try {
3751
- const compressed = this.compressString(data);
3752
- const compressedSize = compressed.length * 2;
3753
- if (compressedSize < originalSize) {
3754
- return {
3755
- data: compressed,
3756
- compressed: true,
3757
- originalSize,
3758
- compressedSize,
3759
- };
3760
- }
3761
- return {
3762
- data,
3763
- compressed: false,
3764
- originalSize,
3765
- compressedSize: originalSize,
3766
- };
3767
- }
3768
- catch {
3769
- return {
3770
- data,
3771
- compressed: false,
3772
- originalSize,
3773
- compressedSize: originalSize,
3774
- };
3775
- }
3776
- }
3777
- decompress(data, compressed) {
3778
- if (!compressed) {
3779
- return { data, wasCompressed: false };
3780
- }
3781
- try {
3782
- const decompressed = this.decompressString(data);
3783
- return { data: decompressed, wasCompressed: true };
3784
- }
3785
- catch {
3786
- return { data, wasCompressed: false };
3787
- }
3788
- }
3789
- estimateSavings(data) {
3790
- const repeated = data.match(/(.)\1{3,}/g);
3791
- if (!repeated)
3792
- return 0;
3793
- let savings = 0;
3794
- for (const match of repeated) {
3795
- const originalBytes = match.length * 2;
3796
- const compressedBytes = 6;
3797
- if (originalBytes > compressedBytes) {
3798
- savings += originalBytes - compressedBytes;
3799
- }
3800
- }
3801
- return Math.min(savings, data.length * 2 * 0.5);
3802
- }
3803
- compressString(input) {
3804
- let compressed = '';
3805
- let i = 0;
3806
- while (i < input.length) {
3807
- let count = 1;
3808
- const char = input[i];
3809
- while (i + count < input.length && input[i + count] === char && count < 255) {
3810
- count++;
3811
- }
3812
- if (count > 3) {
3813
- compressed += `~${count}${char}`;
3814
- }
3815
- else {
3816
- for (let j = 0; j < count; j++) {
3817
- compressed += char;
3818
- }
3819
- }
3820
- i += count;
3821
- }
3822
- return `COMP:${btoa(compressed)}`;
3823
- }
3824
- decompressString(input) {
3825
- if (!input.startsWith('COMP:')) {
3826
- return input;
3827
- }
3828
- const encodedData = input.substring(5);
3829
- if (!encodedData) {
3830
- return input;
3831
- }
3832
- const compressed = atob(encodedData);
3833
- let decompressed = '';
3834
- let i = 0;
3835
- while (i < compressed.length) {
3836
- if (compressed[i] === '~' && i + 2 < compressed.length) {
3837
- let countStr = '';
3838
- i++;
3839
- while (i < compressed.length) {
3840
- const char = compressed[i];
3841
- if (char && /\d/.test(char)) {
3842
- countStr += char;
3843
- i++;
3844
- }
3845
- else {
3846
- break;
3847
- }
3848
- }
3849
- if (countStr && i < compressed.length) {
3850
- const count = parseInt(countStr, 10);
3851
- const char = compressed[i];
3852
- for (let j = 0; j < count; j++) {
3853
- decompressed += char;
3854
- }
3855
- i++;
3856
- }
3857
- }
3858
- else {
3859
- decompressed += compressed[i];
3860
- i++;
3861
- }
3862
- }
3863
- return decompressed;
3864
- }
3865
- }
3866
- function compressData(data, threshold = 1024) {
3867
- return new CompressionAdapter().compress(data, threshold);
3868
- }
3869
- function decompressData(data, compressed) {
3870
- return new CompressionAdapter().decompress(data, compressed);
3871
- }
3872
-
3873
- const log$d = createPrefixedLogger('CACHE-RN');
3874
- /**
3875
- * React Native cache adapter using SQLite (Expo or react-native-sqlite-storage)
3876
- * Cache never expires - data persists until explicitly invalidated
3877
- */
3878
- class ReactNativeCacheAdapter {
3879
- constructor(options = {}) {
3880
- this.db = null;
3881
- this.initPromise = null;
3882
- this.isExpo = false;
3883
- this.hasCompressedColumn = false;
3884
- this.options = {
3885
- maxSize: 50 * 1024 * 1024, // 50MB
3886
- maxEntries: 10000,
3887
- compression: false,
3888
- compressionThreshold: 1024,
3889
- ...options,
3890
- };
3891
- this.initPromise = this.initialize();
3892
- }
3893
- normalizeResults(results) {
3894
- if (this.isExpo) {
3895
- const expoResults = results;
3896
- if (Array.isArray(expoResults)) {
3897
- return expoResults;
3898
- }
3899
- return expoResults.results || [];
3900
- }
3901
- else {
3902
- const rnResults = results;
3903
- const rows = rnResults.rows;
3904
- if (!rows || rows.length === 0)
3905
- return [];
3906
- const normalizedRows = [];
3907
- for (let i = 0; i < rows.length; i++) {
3908
- normalizedRows.push(rows.item(i));
3909
- }
3910
- return normalizedRows;
3911
- }
3912
- }
3913
- async initialize() {
3914
- if (this.db)
3915
- return;
3916
- try {
3917
- // Try Expo SQLite first
3918
- const ExpoSQLite = require('expo-sqlite');
3919
- this.db = await ExpoSQLite.openDatabaseAsync(ReactNativeCacheAdapter.DB_NAME);
3920
- this.isExpo = true;
3921
- await this.createTables();
3922
- }
3923
- catch (expoError) {
3924
- try {
3925
- // Fallback to react-native-sqlite-storage
3926
- const SQLite = require('react-native-sqlite-storage');
3927
- this.db = await new Promise((resolve, reject) => {
3928
- SQLite.openDatabase({
3929
- name: ReactNativeCacheAdapter.DB_NAME,
3930
- location: 'default',
3931
- }, resolve, reject);
3932
- });
3933
- this.isExpo = false;
3934
- await this.createTables();
3935
- }
3936
- catch (rnError) {
3937
- throw new Error(`Failed to initialize SQLite: Expo error: ${expoError}, RN error: ${rnError}`);
3938
- }
3939
- }
3940
- }
3941
- async createTables() {
3942
- // Create table with simplified schema (no TTL)
3943
- const createTableSQL = `
3944
- CREATE TABLE IF NOT EXISTS ${ReactNativeCacheAdapter.TABLE_NAME} (
3945
- cache_key TEXT PRIMARY KEY,
3946
- data TEXT NOT NULL,
3947
- timestamp INTEGER NOT NULL
3948
- );
3949
-
3950
- CREATE INDEX IF NOT EXISTS idx_timestamp ON ${ReactNativeCacheAdapter.TABLE_NAME}(timestamp);
3951
- `;
3952
- await this.executeSql(createTableSQL);
3953
- // Then, run migrations to add new columns if they don't exist
3954
- await this.runMigrations();
3955
- }
3956
- async runMigrations() {
3957
- log$d.debug('Running database migrations...');
3958
- try {
3959
- // Check if compressed column exists
3960
- this.hasCompressedColumn = await this.checkColumnExists('compressed');
3961
- if (!this.hasCompressedColumn) {
3962
- log$d.debug('Adding compressed column to cache table');
3963
- const addColumnSQL = `ALTER TABLE ${ReactNativeCacheAdapter.TABLE_NAME} ADD COLUMN compressed INTEGER DEFAULT 0`;
3964
- await this.executeSql(addColumnSQL);
3965
- this.hasCompressedColumn = true;
3966
- log$d.debug('Successfully added compressed column');
3967
- }
3968
- else {
3969
- log$d.debug('Compressed column already exists');
3970
- }
3971
- log$d.debug('Database migrations completed', {
3972
- hasCompressedColumn: this.hasCompressedColumn,
3973
- });
3974
- }
3975
- catch (error) {
3976
- log$d.debug('Migration failed, disabling compression features', error);
3977
- this.hasCompressedColumn = false;
3978
- // Don't throw - allow the app to continue even if migration fails
3979
- // The compressed feature will just be disabled
3980
- }
3981
- }
3982
- async checkColumnExists(columnName) {
3983
- try {
3984
- const pragmaSQL = `PRAGMA table_info(${ReactNativeCacheAdapter.TABLE_NAME})`;
3985
- const results = await this.executeSql(pragmaSQL);
3986
- const columns = this.normalizeResults(results);
3987
- log$d.debug('Table columns found', { columns: columns.map((c) => c.name) });
3988
- return columns.some((column) => column.name === columnName);
3989
- }
3990
- catch (error) {
3991
- log$d.debug('Error checking column existence', error);
3992
- return false;
3993
- }
3994
- }
3995
- async get(key) {
3996
- await this.ensureInitialized();
3997
- const sql = `SELECT * FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key = ?`;
3998
- log$d.debug('Executing get query', { sql, key });
3999
- const results = await this.executeSql(sql, [key]);
4000
- log$d.debug('Get query results', { key, hasResults: !!results });
4001
- // Normalize results from different SQLite implementations
4002
- const rows = this.normalizeResults(results);
4003
- if (!rows || rows.length === 0) {
4004
- return null;
4005
- }
4006
- const row = rows[0];
4007
- if (!row) {
4008
- return null;
4009
- }
4010
- const isCompressed = this.hasCompressedColumn ? !!row.compressed : false;
4011
- const rawData = isCompressed ? decompressData(row.data, true).data : row.data;
4012
- return {
4013
- data: JSON.parse(rawData),
4014
- timestamp: row.timestamp,
4015
- compressed: isCompressed,
4016
- };
4017
- }
4018
- async set(key, data) {
4019
- const item = {
4020
- data,
4021
- timestamp: Date.now(),
4022
- };
4023
- log$d.debug('Setting cache item', { key });
4024
- return this.setItem(key, item);
4025
- }
4026
- async setItem(key, item) {
4027
- await this.ensureInitialized();
4028
- // Handle compression if enabled and compressed column is available
4029
- const serializedData = JSON.stringify(item.data);
4030
- let finalData = serializedData;
4031
- let isCompressed = false;
4032
- if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
4033
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4034
- finalData = compressionResult.data;
4035
- isCompressed = compressionResult.compressed;
4036
- log$d.debug('Compression result', {
4037
- key,
4038
- originalSize: compressionResult.originalSize,
4039
- compressedSize: compressionResult.compressedSize,
4040
- compressed: isCompressed,
4041
- savings: compressionResult.originalSize - compressionResult.compressedSize,
4042
- });
4043
- }
4044
- log$d.debug('Setting item with metadata', {
4045
- key,
4046
- timestamp: item.timestamp,
4047
- compressed: isCompressed,
4048
- hasCompressedColumn: this.hasCompressedColumn,
4049
- });
4050
- // Build SQL and parameters based on available columns
4051
- let sql;
4052
- let params;
4053
- if (this.hasCompressedColumn) {
4054
- sql = `
4055
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4056
- (cache_key, data, timestamp, compressed)
4057
- VALUES (?, ?, ?, ?)
4058
- `;
4059
- params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
4060
- }
4061
- else {
4062
- // Fallback for databases without compressed column
4063
- sql = `
4064
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4065
- (cache_key, data, timestamp)
4066
- VALUES (?, ?, ?)
4067
- `;
4068
- params = [key, finalData, item.timestamp];
4069
- }
4070
- log$d.debug('Executing setItem SQL', { key, paramsCount: params.length });
4071
- await this.executeSql(sql, params);
4072
- }
4073
- async setBatch(items) {
4074
- if (items.length === 0)
4075
- return;
4076
- await this.ensureInitialized();
4077
- log$d.debug('Batch setting items', { count: items.length });
4078
- if (this.isExpo) {
4079
- await this.db.withTransactionAsync(async () => {
4080
- for (const [key, item] of items) {
4081
- await this.setBatchItem(key, item);
4082
- }
4083
- });
4084
- }
4085
- else {
4086
- return new Promise((resolve, reject) => {
4087
- this.db.transaction((tx) => {
4088
- const promises = items.map(([key, item]) => this.setBatchItemRN(tx, key, item));
4089
- Promise.all(promises)
4090
- .then(() => resolve())
4091
- .catch(reject);
4092
- }, reject, () => resolve());
4093
- });
4094
- }
4095
- log$d.debug('Batch operation completed', { count: items.length });
4096
- }
4097
- async setBatchItem(key, item) {
4098
- // Handle compression if enabled and compressed column is available
4099
- const serializedData = JSON.stringify(item.data);
4100
- let finalData = serializedData;
4101
- let isCompressed = false;
4102
- if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
4103
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4104
- finalData = compressionResult.data;
4105
- isCompressed = compressionResult.compressed;
4106
- }
4107
- // Build SQL and parameters based on available columns
4108
- let sql;
4109
- let params;
4110
- if (this.hasCompressedColumn) {
4111
- sql = `
4112
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4113
- (cache_key, data, timestamp, compressed)
4114
- VALUES (?, ?, ?, ?)
4115
- `;
4116
- params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
4117
- }
4118
- else {
4119
- sql = `
4120
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4121
- (cache_key, data, timestamp)
4122
- VALUES (?, ?, ?)
4123
- `;
4124
- params = [key, finalData, item.timestamp];
4125
- }
4126
- await this.db.runAsync(sql, params);
4127
- }
4128
- async setBatchItemRN(tx, key, item) {
4129
- // Handle compression if enabled and compressed column is available
4130
- const serializedData = JSON.stringify(item.data);
4131
- let finalData = serializedData;
4132
- let isCompressed = false;
4133
- if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
4134
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4135
- finalData = compressionResult.data;
4136
- isCompressed = compressionResult.compressed;
4137
- }
4138
- // Build SQL and parameters based on available columns
4139
- let sql;
4140
- let params;
4141
- if (this.hasCompressedColumn) {
4142
- sql = `
4143
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4144
- (cache_key, data, timestamp, compressed)
4145
- VALUES (?, ?, ?, ?)
4146
- `;
4147
- params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
4148
- }
4149
- else {
4150
- sql = `
4151
- INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
4152
- (cache_key, data, timestamp)
4153
- VALUES (?, ?, ?)
4154
- `;
4155
- params = [key, finalData, item.timestamp];
4156
- }
4157
- return new Promise((resolve, reject) => {
4158
- tx.executeSql(sql, params, () => resolve(), (_, error) => {
4159
- reject(error);
4160
- return false;
4161
- });
4162
- });
4163
- }
4164
- async invalidate(pattern) {
4165
- await this.ensureInitialized();
4166
- const keys = await this.getKeys(pattern);
4167
- if (keys.length === 0)
4168
- return;
4169
- const placeholders = keys.map(() => '?').join(',');
4170
- const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key IN (${placeholders})`;
4171
- await this.executeSql(sql, keys);
4172
- }
4173
- async clear() {
4174
- await this.ensureInitialized();
4175
- const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
4176
- await this.executeSql(sql);
4177
- }
4178
- async getSize() {
4179
- await this.ensureInitialized();
4180
- const sql = `
4181
- SELECT
4182
- COUNT(*) as entries,
4183
- SUM(LENGTH(data)) as bytes
4184
- FROM ${ReactNativeCacheAdapter.TABLE_NAME}
4185
- `;
4186
- const results = await this.executeSql(sql);
4187
- const rows = this.normalizeResults(results);
4188
- const row = rows[0] || { entries: 0, bytes: 0 };
4189
- return {
4190
- entries: row.entries || 0,
4191
- bytes: (row.bytes || 0) * 2,
4192
- lastCleanup: Date.now(),
4193
- };
4194
- }
4195
- async cleanup() {
4196
- // No cleanup needed - cache never expires
4197
- return 0;
4198
- }
4199
- async getKeys(pattern) {
4200
- await this.ensureInitialized();
4201
- let sql = `SELECT cache_key FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
4202
- const params = [];
4203
- if (pattern) {
4204
- // Simple pattern matching with LIKE
4205
- const likePattern = pattern.replace(/\*/g, '%').replace(/\?/g, '_');
4206
- sql += ' WHERE cache_key LIKE ?';
4207
- params.push(likePattern);
4208
- }
4209
- const results = await this.executeSql(sql, params);
4210
- const keys = [];
4211
- const rows = this.normalizeResults(results);
4212
- for (const row of rows) {
4213
- keys.push(row.cache_key);
4214
- }
4215
- return keys;
4216
- }
4217
- async executeSql(sql, params = []) {
4218
- if (this.isExpo) {
4219
- const expoDB = this.db;
4220
- if (sql.toLowerCase().includes('select') || sql.toLowerCase().includes('pragma')) {
4221
- const result = await expoDB.getAllAsync(sql, params);
4222
- return Array.isArray(result) ? { results: result } : result;
4223
- }
4224
- else {
4225
- return await expoDB.runAsync(sql, params);
4226
- }
4227
- }
4228
- else {
4229
- // react-native-sqlite-storage
4230
- return new Promise((resolve, reject) => {
4231
- this.db.transaction((tx) => {
4232
- tx.executeSql(sql, params, (_, results) => resolve(results), (_, error) => {
4233
- reject(error);
4234
- return false;
4235
- });
4236
- });
4237
- });
4238
- }
4239
- }
4240
- async ensureInitialized() {
4241
- if (!this.initPromise) {
4242
- this.initPromise = this.initialize();
4243
- }
4244
- await this.initPromise;
4245
- }
4246
- }
4247
- ReactNativeCacheAdapter.DB_NAME = 'acube_cache.db';
4248
- ReactNativeCacheAdapter.TABLE_NAME = 'cache_entries';
4249
- /**
4250
- * Memory-based fallback cache adapter for environments without SQLite
4251
- * Cache never expires - data persists until explicitly invalidated
4252
- */
4253
- class MemoryCacheAdapter {
4254
- constructor(options = {}) {
4255
- this.cache = new Map();
4256
- this.totalBytes = 0;
4257
- this.options = {
4258
- maxEntries: 1000,
4259
- ...options,
4260
- };
4261
- }
4262
- calculateItemSize(key, item) {
4263
- // Calculate rough size estimation for memory usage
4264
- const keySize = key.length * 2; // UTF-16 estimation
4265
- const itemSize = JSON.stringify(item).length * 2; // UTF-16 estimation
4266
- return keySize + itemSize;
4267
- }
4268
- async get(key) {
4269
- log$d.debug('Getting cache item', { key });
4270
- const item = this.cache.get(key);
4271
- if (!item) {
4272
- log$d.debug('Cache miss', { key });
4273
- return null;
4274
- }
4275
- // Handle decompression if needed
4276
- const isCompressed = !!item.compressed;
4277
- let finalData = item.data;
4278
- if (isCompressed) {
4279
- const decompressed = decompressData(item.data, true);
4280
- finalData = JSON.parse(decompressed.data);
4281
- }
4282
- log$d.debug('Cache hit', { key, compressed: isCompressed });
4283
- return {
4284
- ...item,
4285
- data: finalData,
4286
- compressed: isCompressed,
4287
- };
4288
- }
4289
- async set(key, data) {
4290
- log$d.debug('Setting cache item', { key });
4291
- // Handle compression if enabled
4292
- let finalData = data;
4293
- let isCompressed = false;
4294
- if (this.options.compression && this.options.compressionThreshold) {
4295
- const serializedData = JSON.stringify(data);
4296
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4297
- if (compressionResult.compressed) {
4298
- finalData = compressionResult.data;
4299
- isCompressed = true;
4300
- log$d.debug('Compression result', {
4301
- key,
4302
- originalSize: compressionResult.originalSize,
4303
- compressedSize: compressionResult.compressedSize,
4304
- savings: compressionResult.originalSize - compressionResult.compressedSize,
4305
- });
4306
- }
4307
- }
4308
- const item = {
4309
- data: finalData,
4310
- timestamp: Date.now(),
4311
- compressed: isCompressed,
4312
- };
4313
- return this.setItem(key, item);
4314
- }
4315
- async setItem(key, item) {
4316
- // Calculate size of new item
4317
- const newItemSize = this.calculateItemSize(key, item);
4318
- // If item already exists, subtract old size
4319
- if (this.cache.has(key)) {
4320
- const oldItem = this.cache.get(key);
4321
- const oldItemSize = this.calculateItemSize(key, oldItem);
4322
- this.totalBytes -= oldItemSize;
4323
- }
4324
- // Enforce max entries limit
4325
- if (this.cache.size >= (this.options.maxEntries || 1000) && !this.cache.has(key)) {
4326
- const oldestKey = this.cache.keys().next().value;
4327
- if (oldestKey) {
4328
- const oldestItem = this.cache.get(oldestKey);
4329
- const oldestItemSize = this.calculateItemSize(oldestKey, oldestItem);
4330
- this.totalBytes -= oldestItemSize;
4331
- this.cache.delete(oldestKey);
4332
- log$d.debug('Removed oldest item for capacity', { oldestKey, freedBytes: oldestItemSize });
4333
- }
4334
- }
4335
- // Set new item and update total size
4336
- this.cache.set(key, item);
4337
- this.totalBytes += newItemSize;
4338
- log$d.debug('Updated cache size', {
4339
- entries: this.cache.size,
4340
- totalBytes: this.totalBytes,
4341
- newItemSize,
4342
- });
4343
- }
4344
- async setBatch(items) {
4345
- if (items.length === 0)
4346
- return;
4347
- log$d.debug('Batch setting items', { count: items.length });
4348
- let totalNewBytes = 0;
4349
- let totalOldBytes = 0;
4350
- const itemsToRemove = [];
4351
- // First pass: calculate size changes and identify capacity issues
4352
- for (const [key, item] of items) {
4353
- const newItemSize = this.calculateItemSize(key, item);
4354
- totalNewBytes += newItemSize;
4355
- // If item already exists, track old size for removal
4356
- if (this.cache.has(key)) {
4357
- const oldItem = this.cache.get(key);
4358
- const oldItemSize = this.calculateItemSize(key, oldItem);
4359
- totalOldBytes += oldItemSize;
4360
- }
4361
- }
4362
- // Handle capacity limits - remove oldest items if needed
4363
- const projectedEntries = this.cache.size + items.filter(([key]) => !this.cache.has(key)).length;
4364
- const maxEntries = this.options.maxEntries || 1000;
4365
- if (projectedEntries > maxEntries) {
4366
- const entriesToRemove = projectedEntries - maxEntries;
4367
- const oldestKeys = Array.from(this.cache.keys()).slice(0, entriesToRemove);
4368
- for (const oldKey of oldestKeys) {
4369
- const oldItem = this.cache.get(oldKey);
4370
- const oldItemSize = this.calculateItemSize(oldKey, oldItem);
4371
- this.totalBytes -= oldItemSize;
4372
- this.cache.delete(oldKey);
4373
- itemsToRemove.push(oldKey);
4374
- }
4375
- if (itemsToRemove.length > 0) {
4376
- log$d.debug('Removed items for batch capacity', {
4377
- removedCount: itemsToRemove.length,
4378
- removedKeys: itemsToRemove,
4379
- });
4380
- }
4381
- }
4382
- // Update total bytes accounting
4383
- this.totalBytes = this.totalBytes - totalOldBytes + totalNewBytes;
4384
- // Second pass: set all items
4385
- for (const [key, item] of items) {
4386
- this.cache.set(key, item);
4387
- }
4388
- log$d.debug('Batch operation completed', {
4389
- count: items.length,
4390
- totalBytes: this.totalBytes,
4391
- entries: this.cache.size,
4392
- bytesAdded: totalNewBytes - totalOldBytes,
4393
- });
4394
- }
4395
- async invalidate(pattern) {
4396
- const regex = this.patternToRegex(pattern);
4397
- let removed = 0;
4398
- let bytesFreed = 0;
4399
- for (const key of this.cache.keys()) {
4400
- if (regex.test(key)) {
4401
- const item = this.cache.get(key);
4402
- const itemSize = this.calculateItemSize(key, item);
4403
- this.cache.delete(key);
4404
- this.totalBytes -= itemSize;
4405
- bytesFreed += itemSize;
4406
- removed++;
4407
- }
4408
- }
4409
- if (removed > 0) {
4410
- log$d.debug('Invalidation completed', {
4411
- pattern,
4412
- entriesRemoved: removed,
4413
- bytesFreed,
4414
- remainingEntries: this.cache.size,
4415
- remainingBytes: this.totalBytes,
4416
- });
4417
- }
4418
- }
4419
- async clear() {
4420
- this.cache.clear();
4421
- this.totalBytes = 0;
4422
- log$d.debug('Cache cleared', { entries: 0, bytes: 0 });
4423
- }
4424
- async getSize() {
4425
- return {
4426
- entries: this.cache.size,
4427
- bytes: this.totalBytes,
4428
- lastCleanup: Date.now(),
4429
- };
4430
- }
4431
- async cleanup() {
4432
- // No cleanup needed - cache never expires
4433
- return 0;
4434
- }
4435
- async getKeys(pattern) {
4436
- const keys = Array.from(this.cache.keys());
4437
- if (!pattern)
4438
- return keys;
4439
- const regex = this.patternToRegex(pattern);
4440
- return keys.filter((key) => regex.test(key));
4441
- }
4442
- patternToRegex(pattern) {
4443
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
4444
- const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
4445
- return new RegExp(`^${regexPattern}$`);
4446
- }
4447
- }
4448
-
4449
- const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
4450
-
4451
- let idbProxyableTypes;
4452
- let cursorAdvanceMethods;
4453
- // This is a function to prevent it throwing up in node environments.
4454
- function getIdbProxyableTypes() {
4455
- return (idbProxyableTypes ||
4456
- (idbProxyableTypes = [
4457
- IDBDatabase,
4458
- IDBObjectStore,
4459
- IDBIndex,
4460
- IDBCursor,
4461
- IDBTransaction,
4462
- ]));
4463
- }
4464
- // This is a function to prevent it throwing up in node environments.
4465
- function getCursorAdvanceMethods() {
4466
- return (cursorAdvanceMethods ||
4467
- (cursorAdvanceMethods = [
4468
- IDBCursor.prototype.advance,
4469
- IDBCursor.prototype.continue,
4470
- IDBCursor.prototype.continuePrimaryKey,
4471
- ]));
4472
- }
4473
- const transactionDoneMap = new WeakMap();
4474
- const transformCache = new WeakMap();
4475
- const reverseTransformCache = new WeakMap();
4476
- function promisifyRequest(request) {
4477
- const promise = new Promise((resolve, reject) => {
4478
- const unlisten = () => {
4479
- request.removeEventListener('success', success);
4480
- request.removeEventListener('error', error);
4481
- };
4482
- const success = () => {
4483
- resolve(wrap(request.result));
4484
- unlisten();
4485
- };
4486
- const error = () => {
4487
- reject(request.error);
4488
- unlisten();
4489
- };
4490
- request.addEventListener('success', success);
4491
- request.addEventListener('error', error);
4492
- });
4493
- // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This
4494
- // is because we create many promises from a single IDBRequest.
4495
- reverseTransformCache.set(promise, request);
4496
- return promise;
4497
- }
4498
- function cacheDonePromiseForTransaction(tx) {
4499
- // Early bail if we've already created a done promise for this transaction.
4500
- if (transactionDoneMap.has(tx))
4501
- return;
4502
- const done = new Promise((resolve, reject) => {
4503
- const unlisten = () => {
4504
- tx.removeEventListener('complete', complete);
4505
- tx.removeEventListener('error', error);
4506
- tx.removeEventListener('abort', error);
4507
- };
4508
- const complete = () => {
4509
- resolve();
4510
- unlisten();
4511
- };
4512
- const error = () => {
4513
- reject(tx.error || new DOMException('AbortError', 'AbortError'));
4514
- unlisten();
4515
- };
4516
- tx.addEventListener('complete', complete);
4517
- tx.addEventListener('error', error);
4518
- tx.addEventListener('abort', error);
4519
- });
4520
- // Cache it for later retrieval.
4521
- transactionDoneMap.set(tx, done);
4522
- }
4523
- let idbProxyTraps = {
4524
- get(target, prop, receiver) {
4525
- if (target instanceof IDBTransaction) {
4526
- // Special handling for transaction.done.
4527
- if (prop === 'done')
4528
- return transactionDoneMap.get(target);
4529
- // Make tx.store return the only store in the transaction, or undefined if there are many.
4530
- if (prop === 'store') {
4531
- return receiver.objectStoreNames[1]
4532
- ? undefined
4533
- : receiver.objectStore(receiver.objectStoreNames[0]);
4534
- }
4535
- }
4536
- // Else transform whatever we get back.
4537
- return wrap(target[prop]);
4538
- },
4539
- set(target, prop, value) {
4540
- target[prop] = value;
4541
- return true;
4542
- },
4543
- has(target, prop) {
4544
- if (target instanceof IDBTransaction &&
4545
- (prop === 'done' || prop === 'store')) {
4546
- return true;
4547
- }
4548
- return prop in target;
4549
- },
4550
- };
4551
- function replaceTraps(callback) {
4552
- idbProxyTraps = callback(idbProxyTraps);
4553
- }
4554
- function wrapFunction(func) {
4555
- // Due to expected object equality (which is enforced by the caching in `wrap`), we
4556
- // only create one new func per func.
4557
- // Cursor methods are special, as the behaviour is a little more different to standard IDB. In
4558
- // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
4559
- // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
4560
- // with real promises, so each advance methods returns a new promise for the cursor object, or
4561
- // undefined if the end of the cursor has been reached.
4562
- if (getCursorAdvanceMethods().includes(func)) {
4563
- return function (...args) {
4564
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
4565
- // the original object.
4566
- func.apply(unwrap(this), args);
4567
- return wrap(this.request);
4568
- };
4569
- }
4570
- return function (...args) {
4571
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
4572
- // the original object.
4573
- return wrap(func.apply(unwrap(this), args));
4574
- };
4575
- }
4576
- function transformCachableValue(value) {
4577
- if (typeof value === 'function')
4578
- return wrapFunction(value);
4579
- // This doesn't return, it just creates a 'done' promise for the transaction,
4580
- // which is later returned for transaction.done (see idbObjectHandler).
4581
- if (value instanceof IDBTransaction)
4582
- cacheDonePromiseForTransaction(value);
4583
- if (instanceOfAny(value, getIdbProxyableTypes()))
4584
- return new Proxy(value, idbProxyTraps);
4585
- // Return the same value back if we're not going to transform it.
4586
- return value;
4587
- }
4588
- function wrap(value) {
4589
- // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
4590
- // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
4591
- if (value instanceof IDBRequest)
4592
- return promisifyRequest(value);
4593
- // If we've already transformed this value before, reuse the transformed value.
4594
- // This is faster, but it also provides object equality.
4595
- if (transformCache.has(value))
4596
- return transformCache.get(value);
4597
- const newValue = transformCachableValue(value);
4598
- // Not all types are transformed.
4599
- // These may be primitive types, so they can't be WeakMap keys.
4600
- if (newValue !== value) {
4601
- transformCache.set(value, newValue);
4602
- reverseTransformCache.set(newValue, value);
4603
- }
4604
- return newValue;
4605
- }
4606
- const unwrap = (value) => reverseTransformCache.get(value);
4607
-
4608
- /**
4609
- * Open a database.
4610
- *
4611
- * @param name Name of the database.
4612
- * @param version Schema version.
4613
- * @param callbacks Additional callbacks.
4614
- */
4615
- function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
4616
- const request = indexedDB.open(name, version);
4617
- const openPromise = wrap(request);
4618
- if (upgrade) {
4619
- request.addEventListener('upgradeneeded', (event) => {
4620
- upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
4621
- });
4622
- }
4623
- if (blocked) {
4624
- request.addEventListener('blocked', (event) => blocked(
4625
- // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
4626
- event.oldVersion, event.newVersion, event));
4627
- }
4628
- openPromise
4629
- .then((db) => {
4630
- if (terminated)
4631
- db.addEventListener('close', () => terminated());
4632
- if (blocking) {
4633
- db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));
4634
- }
4635
- })
4636
- .catch(() => { });
4637
- return openPromise;
4638
- }
4639
- /**
4640
- * Delete a database.
4641
- *
4642
- * @param name Name of the database.
4643
- */
4644
- function deleteDB(name, { blocked } = {}) {
4645
- const request = indexedDB.deleteDatabase(name);
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));
4650
- }
4651
- return wrap(request).then(() => undefined);
4652
- }
4653
-
4654
- const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
4655
- const writeMethods = ['put', 'add', 'delete', 'clear'];
4656
- const cachedMethods = new Map();
4657
- function getMethod(target, prop) {
4658
- if (!(target instanceof IDBDatabase &&
4659
- !(prop in target) &&
4660
- typeof prop === 'string')) {
4661
- return;
4662
- }
4663
- if (cachedMethods.get(prop))
4664
- return cachedMethods.get(prop);
4665
- const targetFuncName = prop.replace(/FromIndex$/, '');
4666
- const useIndex = prop !== targetFuncName;
4667
- const isWrite = writeMethods.includes(targetFuncName);
4668
- if (
4669
- // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
4670
- !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
4671
- !(isWrite || readMethods.includes(targetFuncName))) {
4672
- return;
4673
- }
4674
- const method = async function (storeName, ...args) {
4675
- // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
4676
- const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
4677
- let target = tx.store;
4678
- if (useIndex)
4679
- target = target.index(args.shift());
4680
- // Must reject if op rejects.
4681
- // If it's a write operation, must reject if tx.done rejects.
4682
- // Must reject with op rejection first.
4683
- // Must resolve with op value.
4684
- // Must handle both promises (no unhandled rejections)
4685
- return (await Promise.all([
4686
- target[targetFuncName](...args),
4687
- isWrite && tx.done,
4688
- ]))[0];
4689
- };
4690
- cachedMethods.set(prop, method);
4691
- return method;
4692
- }
4693
- replaceTraps((oldTraps) => ({
4694
- ...oldTraps,
4695
- get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
4696
- has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
4697
- }));
4698
-
4699
- const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
4700
- const methodMap = {};
4701
- const advanceResults = new WeakMap();
4702
- const ittrProxiedCursorToOriginalProxy = new WeakMap();
4703
- const cursorIteratorTraps = {
4704
- get(target, prop) {
4705
- if (!advanceMethodProps.includes(prop))
4706
- return target[prop];
4707
- let cachedFunc = methodMap[prop];
4708
- if (!cachedFunc) {
4709
- cachedFunc = methodMap[prop] = function (...args) {
4710
- advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
4711
- };
4712
- }
4713
- return cachedFunc;
4714
- },
4715
- };
4716
- async function* iterate(...args) {
4717
- // tslint:disable-next-line:no-this-assignment
4718
- let cursor = this;
4719
- if (!(cursor instanceof IDBCursor)) {
4720
- cursor = await cursor.openCursor(...args);
4721
- }
4722
- if (!cursor)
4723
- return;
4724
- cursor = cursor;
4725
- const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
4726
- ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
4727
- // Map this double-proxy back to the original, so other cursor methods work.
4728
- reverseTransformCache.set(proxiedCursor, unwrap(cursor));
4729
- while (cursor) {
4730
- yield proxiedCursor;
4731
- // If one of the advancing methods was not called, call continue().
4732
- cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
4733
- advanceResults.delete(proxiedCursor);
4734
- }
4735
- }
4736
- function isIteratorProp(target, prop) {
4737
- return ((prop === Symbol.asyncIterator &&
4738
- instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||
4739
- (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));
4740
- }
4741
- replaceTraps((oldTraps) => ({
4742
- ...oldTraps,
4743
- get(target, prop, receiver) {
4744
- if (isIteratorProp(target, prop))
4745
- return iterate;
4746
- return oldTraps.get(target, prop, receiver);
4747
- },
4748
- has(target, prop) {
4749
- return isIteratorProp(target, prop) || oldTraps.has(target, prop);
4750
- },
4751
- }));
4752
-
4753
- const log$c = createPrefixedLogger('CACHE-WEB');
4754
- /**
4755
- * Web cache adapter using IndexedDB with automatic error recovery
4756
- * Cache never expires - data persists until explicitly invalidated
4757
- */
4758
- class WebCacheAdapter {
4759
- constructor(options = {}) {
4760
- this.db = null;
4761
- this.initPromise = null;
4762
- this.retryCount = 0;
4763
- this.maxRetries = 3;
4764
- this.options = {
4765
- maxSize: 50 * 1024 * 1024, // 50MB
4766
- maxEntries: 10000,
4767
- compression: false,
4768
- compressionThreshold: 1024,
4769
- ...options,
4770
- };
4771
- this.initPromise = this.initialize();
4772
- }
4773
- async initialize() {
4774
- if (this.db)
4775
- return;
4776
- log$c.debug('Initializing IndexedDB cache', {
4777
- dbName: WebCacheAdapter.DB_NAME,
4778
- version: WebCacheAdapter.DB_VERSION,
4779
- });
4780
- try {
4781
- this.db = await this.openDatabase();
4782
- log$c.debug('IndexedDB cache initialized successfully');
4783
- this.retryCount = 0; // Reset retry count on success
4784
- }
4785
- catch (error) {
4786
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
4787
- log$c.debug('Failed to initialize IndexedDB', { error: errorMessage });
4788
- // Check if this is a version conflict error
4789
- if (this.isVersionConflictError(errorMessage)) {
4790
- await this.handleVersionConflict();
4791
- }
4792
- else {
4793
- throw new Error(`Failed to initialize IndexedDB: ${errorMessage}`);
4794
- }
4795
- }
4796
- }
4797
- async openDatabase() {
4798
- return await openDB(WebCacheAdapter.DB_NAME, WebCacheAdapter.DB_VERSION, {
4799
- upgrade: (db, oldVersion, newVersion, transaction) => {
4800
- log$c.debug('Database upgrade needed', { oldVersion, newVersion });
4801
- this.handleUpgrade(db, oldVersion, newVersion, transaction);
4802
- },
4803
- blocked: () => {
4804
- log$c.debug('Database blocked - another tab may be open');
4805
- },
4806
- blocking: () => {
4807
- log$c.debug('Database blocking - will close connection');
4808
- if (this.db) {
4809
- this.db.close();
4810
- this.db = null;
4811
- }
4812
- },
4813
- terminated: () => {
4814
- log$c.debug('Database connection terminated unexpectedly');
4815
- this.db = null;
4816
- },
4817
- });
4818
- }
4819
- handleUpgrade(db, oldVersion, newVersion, transaction) {
4820
- log$c.debug('Handling database upgrade', { oldVersion, newVersion });
4821
- // Create cache store if it doesn't exist (initial setup)
4822
- if (!db.objectStoreNames.contains(WebCacheAdapter.STORE_NAME)) {
4823
- const store = db.createObjectStore(WebCacheAdapter.STORE_NAME, { keyPath: 'key' });
4824
- store.createIndex('timestamp', 'timestamp', { unique: false });
4825
- log$c.debug('Created cache store and timestamp index');
4826
- }
4827
- // Handle migration from version 1 to 2
4828
- if (oldVersion < 2) {
4829
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4830
- // Remove unused indexes from simplified cache structure
4831
- const indexesToRemove = ['tags', 'source', 'syncStatus'];
4832
- indexesToRemove.forEach((indexName) => {
4833
- try {
4834
- if (store.indexNames.contains(indexName)) {
4835
- store.deleteIndex(indexName);
4836
- log$c.debug(`Removed unused index: ${indexName}`);
4837
- }
4838
- }
4839
- catch (error) {
4840
- // Ignore errors if indexes don't exist
4841
- log$c.debug(`Warning: Could not remove index ${indexName}`, error);
4842
- }
4843
- });
4844
- }
4845
- log$c.debug('Database upgrade completed');
4846
- }
4847
- isVersionConflictError(errorMessage) {
4848
- return (errorMessage.includes('less than the existing version') ||
4849
- errorMessage.includes('version conflict') ||
4850
- errorMessage.includes('VersionError'));
4851
- }
4852
- async handleVersionConflict() {
4853
- log$c.debug('Handling version conflict, attempting recovery...');
4854
- if (this.retryCount >= this.maxRetries) {
4855
- throw new Error('Failed to resolve IndexedDB version conflict after multiple attempts');
4856
- }
4857
- this.retryCount++;
4858
- try {
4859
- // Close any existing connection
4860
- if (this.db) {
4861
- this.db.close();
4862
- this.db = null;
4863
- }
4864
- // Delete the problematic database
4865
- log$c.debug('Deleting problematic database to resolve version conflict');
4866
- await deleteDB(WebCacheAdapter.DB_NAME);
4867
- // Wait a bit for the deletion to complete
4868
- await new Promise((resolve) => setTimeout(resolve, 200));
4869
- // Try to open the database again
4870
- log$c.debug(`Retrying database initialization (attempt ${this.retryCount}/${this.maxRetries})`);
4871
- this.db = await this.openDatabase();
4872
- log$c.debug('Successfully recovered from version conflict');
4873
- this.retryCount = 0; // Reset retry count on success
4874
- }
4875
- catch (retryError) {
4876
- const retryErrorMessage = retryError instanceof Error ? retryError.message : 'Unknown error';
4877
- log$c.debug('Recovery attempt failed', { attempt: this.retryCount, error: retryErrorMessage });
4878
- if (this.retryCount < this.maxRetries) {
4879
- // Try again
4880
- await this.handleVersionConflict();
4881
- }
4882
- else {
4883
- throw new Error(`Failed to recover from IndexedDB version conflict: ${retryErrorMessage}`);
4884
- }
4885
- }
4886
- }
4887
- async get(key) {
4888
- await this.ensureInitialized();
4889
- log$c.debug('Getting cache item', { key });
4890
- try {
4891
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
4892
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4893
- const result = await store.get(key);
4894
- if (!result) {
4895
- return null;
4896
- }
4897
- const item = result;
4898
- // Handle decompression if needed
4899
- const isCompressed = !!item.compressed;
4900
- let finalData;
4901
- if (isCompressed) {
4902
- const decompressed = decompressData(item.data, true);
4903
- finalData = JSON.parse(decompressed.data);
4904
- }
4905
- else {
4906
- finalData = item.data;
4907
- }
4908
- return {
4909
- data: finalData,
4910
- timestamp: item.timestamp,
4911
- compressed: isCompressed,
4912
- };
4913
- }
4914
- catch (error) {
4915
- log$c.debug('Error getting cache item', { key, error });
4916
- return null; // Return null on error instead of throwing
4917
- }
4918
- }
4919
- async set(key, data) {
4920
- const item = {
4921
- data,
4922
- timestamp: Date.now(),
4923
- };
4924
- return this.setItem(key, item);
4925
- }
4926
- async setItem(key, item) {
4927
- await this.ensureInitialized();
4928
- // Handle compression if enabled
4929
- let finalData = item.data;
4930
- let isCompressed = false;
4931
- if (this.options.compression && this.options.compressionThreshold) {
4932
- const serializedData = JSON.stringify(item.data);
4933
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4934
- if (compressionResult.compressed) {
4935
- finalData = compressionResult.data;
4936
- isCompressed = true;
4937
- log$c.debug('Compression result', {
4938
- key,
4939
- originalSize: compressionResult.originalSize,
4940
- compressedSize: compressionResult.compressedSize,
4941
- compressed: isCompressed,
4942
- savings: compressionResult.originalSize - compressionResult.compressedSize,
4943
- });
4944
- }
4945
- }
4946
- log$c.debug('Setting cache item', { key, timestamp: item.timestamp, compressed: isCompressed });
4947
- const storedItem = {
4948
- key,
4949
- data: finalData,
4950
- timestamp: item.timestamp,
4951
- compressed: isCompressed,
4952
- };
4953
- try {
4954
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
4955
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4956
- await store.put(storedItem);
4957
- }
4958
- catch (error) {
4959
- log$c.debug('Error setting cache item', { key, error });
4960
- // Silently fail for cache writes
4961
- }
4962
- }
4963
- async setBatch(items) {
4964
- if (items.length === 0)
4965
- return;
4966
- await this.ensureInitialized();
4967
- log$c.debug('Batch setting items', { count: items.length });
4968
- try {
4969
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
4970
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
4971
- // Process all items in the transaction
4972
- const promises = items.map(([key, item]) => {
4973
- const storedItem = this.prepareBatchItem(key, item);
4974
- return store.put(storedItem);
4975
- });
4976
- await Promise.all(promises);
4977
- log$c.debug('Batch operation completed', { count: items.length });
4978
- }
4979
- catch (error) {
4980
- log$c.debug('Error in batch operation', { count: items.length, error });
4981
- // Silently fail for batch writes
4982
- }
4983
- }
4984
- prepareBatchItem(key, item) {
4985
- // Handle compression if enabled (same logic as setItem)
4986
- let finalData = item.data;
4987
- let isCompressed = false;
4988
- if (this.options.compression && this.options.compressionThreshold) {
4989
- const serializedData = JSON.stringify(item.data);
4990
- const compressionResult = compressData(serializedData, this.options.compressionThreshold);
4991
- if (compressionResult.compressed) {
4992
- finalData = compressionResult.data;
4993
- isCompressed = true;
4994
- }
4995
- }
4996
- return {
4997
- key,
4998
- data: finalData,
4999
- timestamp: item.timestamp,
5000
- compressed: isCompressed,
5001
- };
5002
- }
5003
- async invalidate(pattern) {
5004
- await this.ensureInitialized();
5005
- const keys = await this.getKeys(pattern);
5006
- const deletePromises = keys.map((key) => this.delete(key));
5007
- await Promise.all(deletePromises);
5008
- }
5009
- async clear() {
5010
- await this.ensureInitialized();
5011
- try {
5012
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
5013
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5014
- await store.clear();
5015
- log$c.debug('Cache cleared successfully');
5016
- }
5017
- catch (error) {
5018
- log$c.debug('Error clearing cache', error);
5019
- // Silently fail for cache clear
5020
- }
5021
- }
5022
- async getSize() {
5023
- await this.ensureInitialized();
5024
- try {
5025
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
5026
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5027
- let entries = 0;
5028
- let bytes = 0;
5029
- // Use cursor for efficient iteration
5030
- let cursor = await store.openCursor();
5031
- while (cursor) {
5032
- entries++;
5033
- // Rough estimation of size
5034
- bytes += JSON.stringify(cursor.value).length * 2; // UTF-16 encoding
5035
- cursor = await cursor.continue();
5036
- }
5037
- return {
5038
- entries,
5039
- bytes,
5040
- lastCleanup: Date.now(),
5041
- };
5042
- }
5043
- catch (error) {
5044
- log$c.debug('Error getting cache size', error);
5045
- return {
5046
- entries: 0,
5047
- bytes: 0,
5048
- lastCleanup: Date.now(),
5049
- };
5050
- }
5051
- }
5052
- async cleanup() {
5053
- // No cleanup needed - cache never expires
5054
- return 0;
5055
- }
5056
- async getKeys(pattern) {
5057
- await this.ensureInitialized();
5058
- try {
5059
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
5060
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5061
- const allKeys = (await store.getAllKeys());
5062
- if (!pattern) {
5063
- return allKeys;
5064
- }
5065
- const regex = this.patternToRegex(pattern);
5066
- return allKeys.filter((key) => regex.test(key));
5067
- }
5068
- catch (error) {
5069
- log$c.debug('Error getting cache keys', error);
5070
- return [];
5071
- }
5072
- }
5073
- async delete(key) {
5074
- await this.ensureInitialized();
5075
- try {
5076
- const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
5077
- const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
5078
- await store.delete(key);
5079
- return true;
5080
- }
5081
- catch (error) {
5082
- log$c.debug('Error deleting cache item', { key, error });
5083
- return false;
5084
- }
5085
- }
5086
- async ensureInitialized() {
5087
- if (!this.initPromise) {
5088
- this.initPromise = this.initialize();
5089
- }
5090
- try {
5091
- await this.initPromise;
5092
- }
5093
- catch (error) {
5094
- log$c.debug('Failed to ensure initialization', error);
5095
- // Reset and try once more
5096
- this.initPromise = null;
5097
- this.db = null;
5098
- this.initPromise = this.initialize();
5099
- await this.initPromise;
5100
- }
5101
- }
5102
- patternToRegex(pattern) {
5103
- // Convert simple glob patterns to regex
5104
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
5105
- const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
5106
- return new RegExp(`^${regexPattern}$`);
5107
- }
5108
- }
5109
- WebCacheAdapter.DB_NAME = 'acube_cache';
5110
- WebCacheAdapter.DB_VERSION = 2;
5111
- WebCacheAdapter.STORE_NAME = 'cache_entries';
5112
-
5113
- const log$b = createPrefixedLogger('CACHE-LOADER');
5114
- function loadCacheAdapter(platform) {
5115
- try {
5116
- switch (platform) {
5117
- case 'web':
5118
- return new WebCacheAdapter({
5119
- maxSize: 50 * 1024 * 1024,
5120
- maxEntries: 10000,
5121
- compression: false,
5122
- });
5123
- case 'react-native':
5124
- try {
5125
- return new ReactNativeCacheAdapter({
5126
- maxSize: 100 * 1024 * 1024,
5127
- maxEntries: 15000,
5128
- });
5129
- }
5130
- catch {
5131
- return new MemoryCacheAdapter({
5132
- maxSize: 10 * 1024 * 1024,
5133
- maxEntries: 5000,
5134
- });
3239
+ hasCertificate: false,
3240
+ certificateInfo: null,
3241
+ platformInfo: this.mtlsAdapter?.getPlatformInfo() || null,
3242
+ pendingRequestsCount: this.pendingRequests.size,
3243
+ };
3244
+ if (this.certificatePort) {
3245
+ try {
3246
+ status.hasCertificate = await this.certificatePort.hasCertificate();
3247
+ if (status.hasCertificate) {
3248
+ status.certificateInfo = await this.certificatePort.getCertificateInfo();
5135
3249
  }
5136
- case 'node':
5137
- default:
5138
- return new MemoryCacheAdapter({
5139
- maxSize: 10 * 1024 * 1024,
5140
- maxEntries: 5000,
5141
- });
3250
+ }
3251
+ catch {
3252
+ // Ignore errors
3253
+ }
5142
3254
  }
3255
+ status.isReady = await this.isMtlsReady();
3256
+ return status;
5143
3257
  }
5144
- catch (error) {
5145
- log$b.warn(`Cache adapter not available for platform ${platform}:`, error);
5146
- return undefined;
3258
+ clearPendingRequests() {
3259
+ this.pendingRequests.clear();
5147
3260
  }
5148
3261
  }
5149
3262
 
@@ -5199,7 +3312,7 @@ class BaseSecureStorageAdapter {
5199
3312
  }
5200
3313
  }
5201
3314
 
5202
- const log$a = createPrefixedLogger('NETWORK-BASE');
3315
+ const log$8 = createPrefixedLogger('NETWORK-BASE');
5203
3316
  class NetworkBase {
5204
3317
  constructor(initialOnline = true, debounceMs = 300) {
5205
3318
  this.destroy$ = new Subject();
@@ -5219,14 +3332,14 @@ class NetworkBase {
5219
3332
  const current = this.statusSubject.getValue();
5220
3333
  if (current.online !== online) {
5221
3334
  this.statusSubject.next({ online, timestamp: Date.now() });
5222
- log$a.debug(`Network status changed: ${online ? 'online' : 'offline'}`);
3335
+ log$8.debug(`Network status changed: ${online ? 'online' : 'offline'}`);
5223
3336
  }
5224
3337
  }
5225
3338
  destroy() {
5226
3339
  this.destroy$.next();
5227
3340
  this.destroy$.complete();
5228
3341
  this.statusSubject.complete();
5229
- log$a.debug('Network monitor destroyed');
3342
+ log$8.debug('Network monitor destroyed');
5230
3343
  }
5231
3344
  }
5232
3345
 
@@ -5286,7 +3399,7 @@ class NodeSecureStorageAdapter extends BaseSecureStorageAdapter {
5286
3399
  }
5287
3400
  }
5288
3401
 
5289
- const log$9 = createPrefixedLogger('RN-STORAGE');
3402
+ const log$7 = createPrefixedLogger('RN-STORAGE');
5290
3403
  /**
5291
3404
  * React Native storage adapter using AsyncStorage
5292
3405
  * Note: Uses native batch operations for better performance (not base class)
@@ -5318,7 +3431,7 @@ class ReactNativeStorageAdapter {
5318
3431
  return await this.AsyncStorage.getItem(key);
5319
3432
  }
5320
3433
  catch (error) {
5321
- log$9.error('Failed to get item from AsyncStorage:', error);
3434
+ log$7.error('Failed to get item from AsyncStorage:', error);
5322
3435
  return null;
5323
3436
  }
5324
3437
  }
@@ -5359,7 +3472,7 @@ class ReactNativeStorageAdapter {
5359
3472
  return await this.AsyncStorage.getAllKeys();
5360
3473
  }
5361
3474
  catch (error) {
5362
- log$9.error('Failed to get all keys:', error);
3475
+ log$7.error('Failed to get all keys:', error);
5363
3476
  return [];
5364
3477
  }
5365
3478
  }
@@ -5376,7 +3489,7 @@ class ReactNativeStorageAdapter {
5376
3489
  return result;
5377
3490
  }
5378
3491
  catch (error) {
5379
- log$9.error('Failed to get multiple items:', error);
3492
+ log$7.error('Failed to get multiple items:', error);
5380
3493
  const result = {};
5381
3494
  keys.forEach((key) => {
5382
3495
  result[key] = null;
@@ -5426,7 +3539,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5426
3539
  return;
5427
3540
  }
5428
3541
  catch {
5429
- log$9.debug('expo-secure-store not available, trying react-native-keychain');
3542
+ log$7.debug('expo-secure-store not available, trying react-native-keychain');
5430
3543
  }
5431
3544
  try {
5432
3545
  const Keychain = require('react-native-keychain');
@@ -5435,7 +3548,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5435
3548
  return;
5436
3549
  }
5437
3550
  catch {
5438
- log$9.error('react-native-keychain not available');
3551
+ log$7.error('react-native-keychain not available');
5439
3552
  }
5440
3553
  throw new Error('No secure storage available. Please install expo-secure-store or react-native-keychain');
5441
3554
  }
@@ -5453,7 +3566,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5453
3566
  }
5454
3567
  }
5455
3568
  catch (error) {
5456
- log$9.error('Failed to get secure item:', error);
3569
+ log$7.error('Failed to get secure item:', error);
5457
3570
  }
5458
3571
  return null;
5459
3572
  }
@@ -5490,10 +3603,10 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
5490
3603
  }
5491
3604
  }
5492
3605
  async clear() {
5493
- log$9.warn('Clear all secure items not fully implemented for React Native');
3606
+ log$7.warn('Clear all secure items not fully implemented for React Native');
5494
3607
  }
5495
3608
  async getAllKeys() {
5496
- log$9.warn('Get all secure keys not implemented for React Native');
3609
+ log$7.warn('Get all secure keys not implemented for React Native');
5497
3610
  return [];
5498
3611
  }
5499
3612
  async isAvailable() {
@@ -5711,7 +3824,7 @@ class NodeNetworkMonitor extends NetworkBase {
5711
3824
  }
5712
3825
  }
5713
3826
 
5714
- const log$8 = createPrefixedLogger('RN-NETWORK');
3827
+ const log$6 = createPrefixedLogger('RN-NETWORK');
5715
3828
  /**
5716
3829
  * React Native network monitor using RxJS
5717
3830
  * Supports both @react-native-community/netinfo and expo-network
@@ -5724,7 +3837,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5724
3837
  this.moduleReady$ = new Subject();
5725
3838
  this.isExpo = detectPlatform().isExpo;
5726
3839
  this.init().catch((error) => {
5727
- log$8.error('Network monitor initialization failed:', error);
3840
+ log$6.error('Network monitor initialization failed:', error);
5728
3841
  });
5729
3842
  }
5730
3843
  async init() {
@@ -5743,10 +3856,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5743
3856
  try {
5744
3857
  const module = require('@react-native-community/netinfo');
5745
3858
  this.netInfoModule = module.default || module;
5746
- log$8.debug('Loaded @react-native-community/netinfo module');
3859
+ log$6.debug('Loaded @react-native-community/netinfo module');
5747
3860
  }
5748
3861
  catch (error) {
5749
- log$8.error('Failed to load React Native NetInfo module:', error);
3862
+ log$6.error('Failed to load React Native NetInfo module:', error);
5750
3863
  this.netInfoModule = null;
5751
3864
  }
5752
3865
  }
@@ -5754,10 +3867,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5754
3867
  try {
5755
3868
  const module = require('expo-network');
5756
3869
  this.netInfoModule = module.default || module;
5757
- log$8.debug('Loaded expo-network module');
3870
+ log$6.debug('Loaded expo-network module');
5758
3871
  }
5759
3872
  catch (error) {
5760
- log$8.error('Failed to load Expo Network module:', error);
3873
+ log$6.error('Failed to load Expo Network module:', error);
5761
3874
  this.netInfoModule = null;
5762
3875
  }
5763
3876
  }
@@ -5774,16 +3887,16 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5774
3887
  }
5775
3888
  const online = !!(state.isConnected && state.isInternetReachable !== false);
5776
3889
  this.updateStatus(online);
5777
- log$8.debug('Initial network state:', online ? 'online' : 'offline');
3890
+ log$6.debug('Initial network state:', online ? 'online' : 'offline');
5778
3891
  }
5779
3892
  catch (error) {
5780
- log$8.warn('Could not fetch initial network state:', error);
3893
+ log$6.warn('Could not fetch initial network state:', error);
5781
3894
  }
5782
3895
  }
5783
3896
  subscribeToStateChanges() {
5784
3897
  if (!this.netInfoModule)
5785
3898
  return;
5786
- log$8.debug('Subscribing to network state changes');
3899
+ log$6.debug('Subscribing to network state changes');
5787
3900
  const handleState = (state) => {
5788
3901
  const online = !!(state.isConnected && (state.isInternetReachable ?? true));
5789
3902
  this.updateStatus(online);
@@ -5825,7 +3938,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
5825
3938
  };
5826
3939
  }
5827
3940
  catch (error) {
5828
- log$8.error('Failed to fetch detailed network info:', error);
3941
+ log$6.error('Failed to fetch detailed network info:', error);
5829
3942
  return null;
5830
3943
  }
5831
3944
  }
@@ -5961,7 +4074,7 @@ class CertificateValidator {
5961
4074
  }
5962
4075
  }
5963
4076
 
5964
- const log$7 = createPrefixedLogger('RN-MTLS');
4077
+ const log$5 = createPrefixedLogger('RN-MTLS');
5965
4078
  /**
5966
4079
  * React Native mTLS Adapter using @a-cube-io/expo-mutual-tls
5967
4080
  */
@@ -5983,7 +4096,7 @@ class ReactNativeMTLSAdapter {
5983
4096
  this.expoMTLS = ExpoMutualTls;
5984
4097
  // Set up debug logging with the correct event signature
5985
4098
  const debugListener = ExpoMutualTls.onDebugLog((event) => {
5986
- log$7.debug(`${event.type}: ${event.message}`, {
4099
+ log$5.debug(`${event.type}: ${event.message}`, {
5987
4100
  method: event.method,
5988
4101
  url: event.url,
5989
4102
  statusCode: event.statusCode,
@@ -5993,28 +4106,28 @@ class ReactNativeMTLSAdapter {
5993
4106
  this.eventListeners.push(debugListener);
5994
4107
  // Set up error logging with the correct event signature
5995
4108
  const errorListener = ExpoMutualTls.onError((event) => {
5996
- log$7.error(event.message, {
4109
+ log$5.error(event.message, {
5997
4110
  code: event.code,
5998
4111
  });
5999
4112
  });
6000
4113
  this.eventListeners.push(errorListener);
6001
4114
  // Set up certificate expiry monitoring with the correct event signature
6002
4115
  const expiryListener = ExpoMutualTls.onCertificateExpiry((event) => {
6003
- log$7.warn(`Certificate ${event.subject} expires at ${new Date(event.expiry)}`, {
4116
+ log$5.warn(`Certificate ${event.subject} expires at ${new Date(event.expiry)}`, {
6004
4117
  alias: event.alias,
6005
4118
  warning: event.warning,
6006
4119
  });
6007
4120
  });
6008
4121
  this.eventListeners.push(expiryListener);
6009
- log$7.debug('Expo mTLS module loaded successfully');
4122
+ log$5.debug('Expo mTLS module loaded successfully');
6010
4123
  }
6011
4124
  catch (error) {
6012
- log$7.warn('@a-cube-io/expo-mutual-tls not available:', error);
4125
+ log$5.warn('@a-cube-io/expo-mutual-tls not available:', error);
6013
4126
  }
6014
4127
  }
6015
4128
  async isMTLSSupported() {
6016
4129
  const supported = this.expoMTLS !== null;
6017
- log$7.debug('mTLS support check:', {
4130
+ log$5.debug('mTLS support check:', {
6018
4131
  supported,
6019
4132
  platform: this.getPlatformInfo().platform,
6020
4133
  moduleAvailable: !!this.expoMTLS,
@@ -6026,7 +4139,7 @@ class ReactNativeMTLSAdapter {
6026
4139
  throw new MTLSError(MTLSErrorType$1.NOT_SUPPORTED, 'Expo mTLS module not available');
6027
4140
  }
6028
4141
  this.config = config;
6029
- log$7.debug('Initialized with config:', {
4142
+ log$5.debug('Initialized with config:', {
6030
4143
  baseUrl: config.baseUrl,
6031
4144
  port: config.port,
6032
4145
  timeout: config.timeout,
@@ -6040,7 +4153,7 @@ class ReactNativeMTLSAdapter {
6040
4153
  if (!this.config) {
6041
4154
  throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, 'Adapter not initialized. Call initialize() first.');
6042
4155
  }
6043
- log$7.debug('Configuring certificate:', {
4156
+ log$5.debug('Configuring certificate:', {
6044
4157
  format: certificateData.format,
6045
4158
  hasPassword: !!certificateData.password,
6046
4159
  certificateLength: certificateData.certificate.length,
@@ -6057,14 +4170,14 @@ class ReactNativeMTLSAdapter {
6057
4170
  'client-key-service', // keyService
6058
4171
  true // enableLogging - let the native module handle its own debug logging
6059
4172
  );
6060
- log$7.debug('PEM services configured:', configResult);
4173
+ log$5.debug('PEM services configured:', configResult);
6061
4174
  if (!configResult.success) {
6062
4175
  throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, `PEM configuration failed: ${configResult.state}`);
6063
4176
  }
6064
4177
  // Step 2: Store the actual PEM certificate and private key
6065
4178
  const storeResult = await this.expoMTLS.storePEM(certificateData.certificate, certificateData.privateKey, certificateData.password // passphrase (optional)
6066
4179
  );
6067
- log$7.debug('PEM certificate store result:', storeResult);
4180
+ log$5.debug('PEM certificate store result:', storeResult);
6068
4181
  if (!storeResult) {
6069
4182
  throw new MTLSError(MTLSErrorType$1.CERTIFICATE_INVALID, 'Failed to store PEM certificate');
6070
4183
  }
@@ -6077,14 +4190,14 @@ class ReactNativeMTLSAdapter {
6077
4190
  const configResult = await this.expoMTLS.configureP12('client-p12-service', // keychainService
6078
4191
  true // enableLogging - let the native module handle its own debug logging
6079
4192
  );
6080
- log$7.debug('P12 service configured:', configResult);
4193
+ log$5.debug('P12 service configured:', configResult);
6081
4194
  if (!configResult.success) {
6082
4195
  throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, `P12 configuration failed: ${configResult.state}`);
6083
4196
  }
6084
4197
  // Step 2: Store the P12 certificate data
6085
4198
  const storeResult = await this.expoMTLS.storeP12(certificateData.certificate, // P12 data in certificate field
6086
4199
  certificateData.password);
6087
- log$7.debug('P12 certificate store result:', storeResult);
4200
+ log$5.debug('P12 certificate store result:', storeResult);
6088
4201
  if (!storeResult) {
6089
4202
  throw new MTLSError(MTLSErrorType$1.CERTIFICATE_INVALID, 'Failed to store P12 certificate');
6090
4203
  }
@@ -6098,7 +4211,7 @@ class ReactNativeMTLSAdapter {
6098
4211
  if (error instanceof MTLSError) {
6099
4212
  throw error;
6100
4213
  }
6101
- log$7.error('Certificate configuration failed:', error);
4214
+ log$5.error('Certificate configuration failed:', error);
6102
4215
  throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, 'Failed to configure certificate', error);
6103
4216
  }
6104
4217
  }
@@ -6109,38 +4222,38 @@ class ReactNativeMTLSAdapter {
6109
4222
  try {
6110
4223
  // Use static method call
6111
4224
  const hasCert = await this.expoMTLS.hasCertificate();
6112
- log$7.debug('Certificate availability check:', hasCert);
4225
+ log$5.debug('Certificate availability check:', hasCert);
6113
4226
  return hasCert;
6114
4227
  }
6115
4228
  catch (error) {
6116
- log$7.error('Certificate check failed:', error);
4229
+ log$5.error('Certificate check failed:', error);
6117
4230
  return false;
6118
4231
  }
6119
4232
  }
6120
4233
  async getCertificateInfo() {
6121
4234
  if (!this.expoMTLS) {
6122
- log$7.debug('Certificate info requested but module not available');
4235
+ log$5.debug('Certificate info requested but module not available');
6123
4236
  return null;
6124
4237
  }
6125
4238
  try {
6126
4239
  const hasCert = await this.hasCertificate();
6127
4240
  if (!hasCert) {
6128
- log$7.debug('No certificate stored');
4241
+ log$5.debug('No certificate stored');
6129
4242
  return null;
6130
4243
  }
6131
4244
  // Use getCertificatesInfo to retrieve information about stored certificates
6132
4245
  const result = await this.expoMTLS.getCertificatesInfo();
6133
4246
  if (!result || !result.certificates || result.certificates.length === 0) {
6134
- log$7.debug('No certificate information available');
4247
+ log$5.debug('No certificate information available');
6135
4248
  return null;
6136
4249
  }
6137
4250
  // Get the first certificate (primary client certificate)
6138
4251
  const cert = result.certificates[0];
6139
4252
  if (!cert) {
6140
- log$7.debug('Certificate data is empty');
4253
+ log$5.debug('Certificate data is empty');
6141
4254
  return null;
6142
4255
  }
6143
- log$7.debug('Retrieved certificate info:', {
4256
+ log$5.debug('Retrieved certificate info:', {
6144
4257
  subject: cert.subject.commonName,
6145
4258
  issuer: cert.issuer.commonName,
6146
4259
  validFrom: new Date(cert.validFrom),
@@ -6159,7 +4272,7 @@ class ReactNativeMTLSAdapter {
6159
4272
  };
6160
4273
  }
6161
4274
  catch (error) {
6162
- log$7.error('Failed to get certificate info:', error);
4275
+ log$5.error('Failed to get certificate info:', error);
6163
4276
  return null;
6164
4277
  }
6165
4278
  }
@@ -6170,7 +4283,7 @@ class ReactNativeMTLSAdapter {
6170
4283
  */
6171
4284
  async parseCertificateData(certificateData) {
6172
4285
  if (!this.expoMTLS) {
6173
- log$7.debug('Parse certificate: Module not available');
4286
+ log$5.debug('Parse certificate: Module not available');
6174
4287
  return null;
6175
4288
  }
6176
4289
  try {
@@ -6187,14 +4300,14 @@ class ReactNativeMTLSAdapter {
6187
4300
  else {
6188
4301
  throw new MTLSError(MTLSErrorType$1.CERTIFICATE_INVALID, `Unsupported certificate format: ${certificateData.format}`);
6189
4302
  }
6190
- log$7.debug('Certificate parsed successfully:', {
4303
+ log$5.debug('Certificate parsed successfully:', {
6191
4304
  certificateCount: result.certificates.length,
6192
4305
  subjects: result.certificates.map((cert) => cert.subject.commonName),
6193
4306
  });
6194
4307
  return result;
6195
4308
  }
6196
4309
  catch (error) {
6197
- log$7.error('Failed to parse certificate:', error);
4310
+ log$5.error('Failed to parse certificate:', error);
6198
4311
  if (error instanceof MTLSError) {
6199
4312
  throw error;
6200
4313
  }
@@ -6209,7 +4322,7 @@ class ReactNativeMTLSAdapter {
6209
4322
  if (!hasCert) {
6210
4323
  throw new MTLSError(MTLSErrorType$1.CERTIFICATE_NOT_FOUND, 'No certificate configured');
6211
4324
  }
6212
- log$7.debug('Making mTLS request:', {
4325
+ log$5.debug('Making mTLS request:', {
6213
4326
  method: requestConfig.method || 'GET',
6214
4327
  url: requestConfig.url,
6215
4328
  headers: requestConfig.headers,
@@ -6217,7 +4330,7 @@ class ReactNativeMTLSAdapter {
6217
4330
  responseType: requestConfig.responseType,
6218
4331
  });
6219
4332
  if (requestConfig.data) {
6220
- log$7.debug('mTLS request body:', requestConfig.data);
4333
+ log$5.debug('mTLS request body:', requestConfig.data);
6221
4334
  }
6222
4335
  try {
6223
4336
  const response = await this.expoMTLS.request(requestConfig.url, {
@@ -6226,7 +4339,7 @@ class ReactNativeMTLSAdapter {
6226
4339
  body: requestConfig.data ? JSON.stringify(requestConfig.data) : undefined,
6227
4340
  responseType: requestConfig.responseType,
6228
4341
  });
6229
- log$7.debug('mTLS request successful:', response);
4342
+ log$5.debug('mTLS request successful:', response);
6230
4343
  if (!response.success) {
6231
4344
  throw new MTLSError(MTLSErrorType$1.CONNECTION_FAILED, `mTLS request failed: ${response.statusMessage} (${response.statusCode})`, undefined, response.statusCode);
6232
4345
  }
@@ -6238,7 +4351,7 @@ class ReactNativeMTLSAdapter {
6238
4351
  data = JSON.parse(response.body);
6239
4352
  }
6240
4353
  catch (parseError) {
6241
- log$7.warn('Failed to parse JSON response:', parseError);
4354
+ log$5.warn('Failed to parse JSON response:', parseError);
6242
4355
  // If parsing fails, keep raw body
6243
4356
  }
6244
4357
  }
@@ -6255,7 +4368,7 @@ class ReactNativeMTLSAdapter {
6255
4368
  };
6256
4369
  }
6257
4370
  catch (error) {
6258
- log$7.error('mTLS request failed:', error);
4371
+ log$5.error('mTLS request failed:', error);
6259
4372
  throw new MTLSError(MTLSErrorType$1.CONNECTION_FAILED, 'mTLS request failed', error);
6260
4373
  }
6261
4374
  }
@@ -6268,18 +4381,18 @@ class ReactNativeMTLSAdapter {
6268
4381
  */
6269
4382
  async testConnection() {
6270
4383
  if (!this.expoMTLS || !this.config) {
6271
- log$7.debug('Diagnostic test: No mTLS module or config available');
4384
+ log$5.debug('Diagnostic test: No mTLS module or config available');
6272
4385
  return false;
6273
4386
  }
6274
4387
  try {
6275
4388
  const hasCert = await this.hasCertificate();
6276
4389
  if (!hasCert) {
6277
- log$7.debug('Diagnostic test: No certificate configured');
4390
+ log$5.debug('Diagnostic test: No certificate configured');
6278
4391
  return false;
6279
4392
  }
6280
- log$7.debug('Running diagnostic test (may fail even if mTLS works):', this.config.baseUrl);
4393
+ log$5.debug('Running diagnostic test (may fail even if mTLS works):', this.config.baseUrl);
6281
4394
  const result = await this.expoMTLS.testConnection(this.config.baseUrl);
6282
- log$7.debug('Diagnostic test result (NOT validation):', {
4395
+ log$5.debug('Diagnostic test result (NOT validation):', {
6283
4396
  success: result.success,
6284
4397
  statusCode: result.statusCode,
6285
4398
  statusMessage: result.statusMessage,
@@ -6290,13 +4403,13 @@ class ReactNativeMTLSAdapter {
6290
4403
  return result.success;
6291
4404
  }
6292
4405
  catch (error) {
6293
- log$7.warn('Diagnostic test failed (this is expected):', error);
4406
+ log$5.warn('Diagnostic test failed (this is expected):', error);
6294
4407
  return false;
6295
4408
  }
6296
4409
  }
6297
4410
  async removeCertificate() {
6298
4411
  if (!this.expoMTLS) {
6299
- log$7.debug('Remove certificate: Module not available');
4412
+ log$5.debug('Remove certificate: Module not available');
6300
4413
  return;
6301
4414
  }
6302
4415
  try {
@@ -6305,10 +4418,10 @@ class ReactNativeMTLSAdapter {
6305
4418
  this.isConfigured = false;
6306
4419
  // Cleanup event listeners
6307
4420
  this.cleanupEventListeners();
6308
- log$7.debug('Certificate removed successfully');
4421
+ log$5.debug('Certificate removed successfully');
6309
4422
  }
6310
4423
  catch (error) {
6311
- log$7.error('Failed to remove certificate:', error);
4424
+ log$5.error('Failed to remove certificate:', error);
6312
4425
  throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, 'Failed to remove certificate', error);
6313
4426
  }
6314
4427
  }
@@ -6317,7 +4430,7 @@ class ReactNativeMTLSAdapter {
6317
4430
  */
6318
4431
  cleanupEventListeners() {
6319
4432
  if (this.eventListeners.length > 0) {
6320
- log$7.debug(`Cleaning up ${this.eventListeners.length} event listeners`);
4433
+ log$5.debug(`Cleaning up ${this.eventListeners.length} event listeners`);
6321
4434
  }
6322
4435
  // Remove individual listeners if they have remove methods
6323
4436
  this.eventListeners.forEach((listener) => {
@@ -6354,7 +4467,7 @@ class ReactNativeMTLSAdapter {
6354
4467
  }
6355
4468
  }
6356
4469
 
6357
- const log$6 = createPrefixedLogger('WEB-MTLS');
4470
+ const log$4 = createPrefixedLogger('WEB-MTLS');
6358
4471
  /**
6359
4472
  * Web mTLS Adapter - Graceful fallback for web browsers
6360
4473
  *
@@ -6368,13 +4481,13 @@ const log$6 = createPrefixedLogger('WEB-MTLS');
6368
4481
  */
6369
4482
  class WebMTLSAdapter {
6370
4483
  constructor() {
6371
- log$6.warn('Web browsers do not support programmatic mTLS configuration');
6372
- log$6.info('Use JWT authentication or configure client certificates in browser settings');
4484
+ log$4.warn('Web browsers do not support programmatic mTLS configuration');
4485
+ log$4.info('Use JWT authentication or configure client certificates in browser settings');
6373
4486
  }
6374
4487
  async isMTLSSupported() {
6375
4488
  // mTLS is not supported programmatically in web browsers
6376
4489
  const supported = false;
6377
- log$6.debug('mTLS support check:', {
4490
+ log$4.debug('mTLS support check:', {
6378
4491
  supported,
6379
4492
  platform: this.getPlatformInfo().platform,
6380
4493
  reason: 'Browser security model prevents programmatic certificate configuration',
@@ -6383,14 +4496,14 @@ class WebMTLSAdapter {
6383
4496
  return supported;
6384
4497
  }
6385
4498
  async initialize(config) {
6386
- log$6.warn('Initialized but mTLS not available in web browsers:', {
4499
+ log$4.warn('Initialized but mTLS not available in web browsers:', {
6387
4500
  baseUrl: config.baseUrl,
6388
4501
  port: config.port,
6389
4502
  recommendation: 'Use standard HTTPS with JWT authentication',
6390
4503
  });
6391
4504
  }
6392
4505
  async configureCertificate(certificateData) {
6393
- log$6.error('Certificate configuration attempted:', {
4506
+ log$4.error('Certificate configuration attempted:', {
6394
4507
  format: certificateData.format,
6395
4508
  reason: 'Not supported in web browsers',
6396
4509
  alternatives: [
@@ -6405,15 +4518,15 @@ class WebMTLSAdapter {
6405
4518
  }
6406
4519
  async hasCertificate() {
6407
4520
  // We cannot detect if the browser has certificates configured
6408
- log$6.debug('Certificate availability check: Cannot detect browser certificates programmatically');
4521
+ log$4.debug('Certificate availability check: Cannot detect browser certificates programmatically');
6409
4522
  return false;
6410
4523
  }
6411
4524
  async getCertificateInfo() {
6412
- log$6.debug('Certificate info requested: Not accessible in web browsers');
4525
+ log$4.debug('Certificate info requested: Not accessible in web browsers');
6413
4526
  return null;
6414
4527
  }
6415
4528
  async request(requestConfig) {
6416
- log$6.error('mTLS request attempted:', {
4529
+ log$4.error('mTLS request attempted:', {
6417
4530
  method: requestConfig.method,
6418
4531
  url: requestConfig.url,
6419
4532
  reason: 'Not supported in web browsers',
@@ -6428,11 +4541,11 @@ class WebMTLSAdapter {
6428
4541
  'are properly configured in the browser certificate store.');
6429
4542
  }
6430
4543
  async testConnection() {
6431
- log$6.debug('Connection test: mTLS not available in web browsers');
4544
+ log$4.debug('Connection test: mTLS not available in web browsers');
6432
4545
  return false;
6433
4546
  }
6434
4547
  async removeCertificate() {
6435
- log$6.debug('Remove certificate: No certificates to remove (not supported in web browsers)');
4548
+ log$4.debug('Remove certificate: No certificates to remove (not supported in web browsers)');
6436
4549
  // No-op - cannot remove certificates programmatically in browsers
6437
4550
  }
6438
4551
  /**
@@ -6440,7 +4553,7 @@ class WebMTLSAdapter {
6440
4553
  * Always returns null for web browsers as mTLS is not supported
6441
4554
  */
6442
4555
  getBaseUrl() {
6443
- log$6.debug('Base URL requested: Not supported in web browsers');
4556
+ log$4.debug('Base URL requested: Not supported in web browsers');
6444
4557
  return null;
6445
4558
  }
6446
4559
  getPlatformInfo() {
@@ -6473,7 +4586,7 @@ class WebMTLSAdapter {
6473
4586
  }
6474
4587
  }
6475
4588
 
6476
- const log$5 = createPrefixedLogger('MTLS-LOADER');
4589
+ const log$3 = createPrefixedLogger('MTLS-LOADER');
6477
4590
  function loadMTLSAdapter(platform, config) {
6478
4591
  try {
6479
4592
  let adapter;
@@ -6507,7 +4620,7 @@ function loadMTLSAdapter(platform, config) {
6507
4620
  return adapter;
6508
4621
  }
6509
4622
  catch (error) {
6510
- log$5.warn(`mTLS adapter not available for platform ${platform}:`, error);
4623
+ log$3.warn(`mTLS adapter not available for platform ${platform}:`, error);
6511
4624
  return null;
6512
4625
  }
6513
4626
  }
@@ -6517,7 +4630,7 @@ async function initializeAdapterAsync(adapter, config) {
6517
4630
  if (isSupported) {
6518
4631
  await adapter.initialize(config);
6519
4632
  const platformInfo = adapter.getPlatformInfo();
6520
- log$5.debug('mTLS adapter initialized:', {
4633
+ log$3.debug('mTLS adapter initialized:', {
6521
4634
  platform: platformInfo.platform,
6522
4635
  mtlsSupported: platformInfo.mtlsSupported,
6523
4636
  certificateStorage: platformInfo.certificateStorage,
@@ -6526,31 +4639,28 @@ async function initializeAdapterAsync(adapter, config) {
6526
4639
  }
6527
4640
  }
6528
4641
  catch (error) {
6529
- log$5.warn('Failed to initialize mTLS adapter:', error);
4642
+ log$3.warn('Failed to initialize mTLS adapter:', error);
6530
4643
  }
6531
4644
  }
6532
4645
 
6533
- const log$4 = createPrefixedLogger('ADAPTER-LOADER');
4646
+ const log$2 = createPrefixedLogger('ADAPTER-LOADER');
6534
4647
  function loadPlatformAdapters(options = {}) {
6535
4648
  const { mtlsConfig } = options;
6536
4649
  const { platform } = detectPlatform();
6537
- log$4.debug('Loading adapters for platform:', platform);
4650
+ log$2.debug('Loading adapters for platform:', platform);
6538
4651
  const storageAdapters = loadStorageAdapters(platform);
6539
4652
  const networkMonitor = loadNetworkMonitor(platform);
6540
- const cache = loadCacheAdapter(platform);
6541
4653
  const mtls = loadMTLSAdapter(platform, mtlsConfig);
6542
- log$4.debug('Adapters loaded:', {
4654
+ log$2.debug('Adapters loaded:', {
6543
4655
  platform,
6544
4656
  hasStorage: !!storageAdapters.storage,
6545
4657
  hasSecureStorage: !!storageAdapters.secureStorage,
6546
4658
  hasNetworkMonitor: !!networkMonitor,
6547
- hasCache: !!cache,
6548
4659
  hasMTLS: !!mtls,
6549
4660
  });
6550
4661
  return {
6551
4662
  ...storageAdapters,
6552
4663
  networkMonitor,
6553
- cache,
6554
4664
  mtls: mtls || undefined,
6555
4665
  };
6556
4666
  }
@@ -6567,12 +4677,9 @@ function createACubeMTLSConfig(baseUrl, timeout, autoInitialize = true, forcePor
6567
4677
 
6568
4678
  const DI_TOKENS = {
6569
4679
  HTTP_PORT: Symbol('HTTP_PORT'),
6570
- BASE_HTTP_PORT: Symbol('BASE_HTTP_PORT'),
6571
4680
  STORAGE_PORT: Symbol('STORAGE_PORT'),
6572
4681
  SECURE_STORAGE_PORT: Symbol('SECURE_STORAGE_PORT'),
6573
4682
  NETWORK_PORT: Symbol('NETWORK_PORT'),
6574
- CACHE_PORT: Symbol('CACHE_PORT'),
6575
- CACHE_KEY_GENERATOR: Symbol('CACHE_KEY_GENERATOR'),
6576
4683
  MTLS_PORT: Symbol('MTLS_PORT'),
6577
4684
  TOKEN_STORAGE_PORT: Symbol('TOKEN_STORAGE_PORT'),
6578
4685
  RECEIPT_REPOSITORY: Symbol('RECEIPT_REPOSITORY'),
@@ -6590,7 +4697,6 @@ const DI_TOKENS = {
6590
4697
  AUTH_SERVICE: Symbol('AUTH_SERVICE'),
6591
4698
  AUTHENTICATION_SERVICE: Symbol('AUTHENTICATION_SERVICE'),
6592
4699
  CERTIFICATE_SERVICE: Symbol('CERTIFICATE_SERVICE'),
6593
- OFFLINE_SERVICE: Symbol('OFFLINE_SERVICE'),
6594
4700
  NOTIFICATION_SERVICE: Symbol('NOTIFICATION_SERVICE'),
6595
4701
  TELEMETRY_SERVICE: Symbol('TELEMETRY_SERVICE'),
6596
4702
  };
@@ -7565,468 +5671,6 @@ class TelemetryRepositoryImpl {
7565
5671
  }
7566
5672
  }
7567
5673
 
7568
- const log$3 = createPrefixedLogger('CACHE-KEY');
7569
- const URL_PATTERNS = [
7570
- // Receipt (mf1) - specific patterns first
7571
- {
7572
- pattern: /^\/mf1\/receipts\/([^/]+)\/returnable-items$/,
7573
- resource: 'receipt',
7574
- action: 'returnable',
7575
- },
7576
- {
7577
- pattern: /^\/mf1\/receipts\/([^/]+)\/details$/,
7578
- resource: 'receipt',
7579
- action: 'details',
7580
- },
7581
- { pattern: /^\/mf1\/receipts\/([^/]+)$/, resource: 'receipt' },
7582
- {
7583
- pattern: /^\/mf1\/pems\/([^/]+)\/receipts$/,
7584
- resource: 'receipt',
7585
- parent: 'point-of-sale',
7586
- isList: true,
7587
- },
7588
- { pattern: /^\/mf1\/receipts$/, resource: 'receipt', isList: true },
7589
- // Merchant (mf2)
7590
- { pattern: /^\/mf2\/merchants\/([^/]+)$/, resource: 'merchant' },
7591
- { pattern: /^\/mf2\/merchants$/, resource: 'merchant', isList: true },
7592
- // Cashier (mf1)
7593
- { pattern: /^\/mf1\/cashiers\/me$/, resource: 'cashier', action: 'me' },
7594
- { pattern: /^\/mf1\/cashiers\/([^/]+)$/, resource: 'cashier' },
7595
- { pattern: /^\/mf1\/cashiers$/, resource: 'cashier', isList: true },
7596
- // Cash Register (mf1)
7597
- { pattern: /^\/mf1\/cash-registers\/([^/]+)$/, resource: 'cash-register' },
7598
- { pattern: /^\/mf1\/cash-registers$/, resource: 'cash-register', isList: true },
7599
- // Point of Sale (mf1)
7600
- { pattern: /^\/mf1\/pems\/([^/]+)$/, resource: 'point-of-sale' },
7601
- { pattern: /^\/mf1\/pems$/, resource: 'point-of-sale', isList: true },
7602
- // Nested resources under merchant (mf2)
7603
- {
7604
- pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers\/([^/]+)$/,
7605
- resource: 'supplier',
7606
- parent: 'merchant',
7607
- },
7608
- {
7609
- pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers$/,
7610
- resource: 'supplier',
7611
- parent: 'merchant',
7612
- isList: true,
7613
- },
7614
- {
7615
- pattern: /^\/mf2\/merchants\/([^/]+)\/daily-reports/,
7616
- resource: 'daily-report',
7617
- parent: 'merchant',
7618
- isList: true,
7619
- },
7620
- {
7621
- pattern: /^\/mf2\/merchants\/([^/]+)\/journals/,
7622
- resource: 'journal',
7623
- parent: 'merchant',
7624
- isList: true,
7625
- },
7626
- // PEM (mf2)
7627
- {
7628
- pattern: /^\/mf2\/pems\/([^/]+)\/certificates$/,
7629
- resource: 'pem',
7630
- action: 'certificates',
7631
- },
7632
- { pattern: /^\/mf2\/pems\/([^/]+)$/, resource: 'pem' },
7633
- // Others
7634
- { pattern: /^\/mf1\/notifications/, resource: 'notification', isList: true },
7635
- {
7636
- pattern: /^\/mf1\/pems\/([^/]+)\/telemetry$/,
7637
- resource: 'telemetry',
7638
- },
7639
- ];
7640
- const DEFAULT_TTL_CONFIG = {
7641
- // Data that rarely changes - 30 min TTL for items only
7642
- merchant: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
7643
- 'point-of-sale': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
7644
- 'cash-register': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
7645
- pem: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: false },
7646
- // Data that changes moderately - 10 min TTL for items only
7647
- cashier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
7648
- supplier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
7649
- // Data that can change - 5 min TTL for items only
7650
- receipt: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
7651
- 'daily-report': { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
7652
- journal: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
7653
- // Real-time data - 1 min TTL
7654
- notification: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
7655
- telemetry: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
7656
- };
7657
- const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
7658
- class CacheKeyGenerator {
7659
- constructor(customConfig) {
7660
- this.config = { ...DEFAULT_TTL_CONFIG, ...customConfig };
7661
- log$3.info('CacheKeyGenerator initialized with config:', {
7662
- resources: Object.keys(this.config),
7663
- });
7664
- }
7665
- generate(url, params) {
7666
- const parsed = this.parseUrl(url);
7667
- if (!parsed) {
7668
- // Fallback: use URL as key
7669
- const paramStr = params ? this.serializeParams(params) : '';
7670
- const key = paramStr ? `${url}?${paramStr}` : url;
7671
- log$3.debug('URL not matched, using fallback key:', { url, key });
7672
- return key;
7673
- }
7674
- const { resource, ids, action, isList, parent } = parsed;
7675
- if (isList) {
7676
- const paramStr = params ? this.serializeParams(params) : '';
7677
- const parentPart = parent && ids.length > 0 ? `${parent}=${ids[0]}&` : '';
7678
- const key = `${resource}:list:${parentPart}${paramStr}`;
7679
- log$3.debug('Generated list cache key:', { url, key, resource });
7680
- return key;
7681
- }
7682
- // Single item
7683
- if (ids.length === 0 && action) {
7684
- // Special case for endpoints like /cashiers/me
7685
- const key = `${resource}:${action}`;
7686
- log$3.debug('Generated special action cache key:', { url, key, resource, action });
7687
- return key;
7688
- }
7689
- let key = `${resource}:${ids.join(':')}`;
7690
- if (action) {
7691
- key += `:${action}`;
7692
- }
7693
- log$3.debug('Generated item cache key:', { url, key, resource, ids, action });
7694
- return key;
7695
- }
7696
- parseResource(url) {
7697
- const parsed = this.parseUrl(url);
7698
- return parsed?.resource;
7699
- }
7700
- getTTL(url) {
7701
- const resource = this.parseResource(url);
7702
- if (!resource) {
7703
- log$3.debug('No resource found for URL, using default TTL:', { url, ttl: DEFAULT_TTL });
7704
- return DEFAULT_TTL;
7705
- }
7706
- const ttl = this.config[resource].ttlMs;
7707
- log$3.debug('TTL for resource:', { url, resource, ttlMs: ttl, ttlMin: ttl / 60000 });
7708
- return ttl;
7709
- }
7710
- shouldCache(url) {
7711
- const parsed = this.parseUrl(url);
7712
- if (!parsed) {
7713
- log$3.debug('URL not recognized, should not cache:', { url });
7714
- return false;
7715
- }
7716
- const { resource, isList } = parsed;
7717
- const config = this.config[resource];
7718
- if (isList) {
7719
- log$3.debug('List endpoint cache decision:', {
7720
- url,
7721
- resource,
7722
- isList: true,
7723
- shouldCache: config.cacheList,
7724
- });
7725
- return config.cacheList;
7726
- }
7727
- log$3.debug('Item endpoint cache decision:', {
7728
- url,
7729
- resource,
7730
- isList: false,
7731
- shouldCache: config.cacheItem,
7732
- });
7733
- return config.cacheItem;
7734
- }
7735
- getInvalidationPatterns(url, method) {
7736
- const parsed = this.parseUrl(url);
7737
- if (!parsed) {
7738
- log$3.debug('No patterns to invalidate for URL:', { url, method });
7739
- return [];
7740
- }
7741
- const { resource, ids, parent } = parsed;
7742
- const patterns = [];
7743
- // Always invalidate list on mutations
7744
- if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
7745
- if (parent && ids.length > 0) {
7746
- patterns.push(`${resource}:list:${parent}=${ids[0]}*`);
7747
- }
7748
- patterns.push(`${resource}:list:*`);
7749
- }
7750
- // Invalidate specific item on PUT/PATCH/DELETE
7751
- if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
7752
- if (ids.length > 0) {
7753
- patterns.push(`${resource}:${ids.join(':')}*`);
7754
- }
7755
- }
7756
- // Special cases
7757
- if (resource === 'cashier' && (method === 'PUT' || method === 'DELETE')) {
7758
- patterns.push('cashier:me');
7759
- }
7760
- log$3.debug('Invalidation patterns:', { url, method, patterns });
7761
- return patterns;
7762
- }
7763
- parseUrl(url) {
7764
- // Remove query string for pattern matching
7765
- const urlPath = url.split('?')[0];
7766
- for (const pattern of URL_PATTERNS) {
7767
- const match = urlPath?.match(pattern.pattern);
7768
- if (match) {
7769
- // Extract IDs from capture groups
7770
- const ids = match.slice(1).filter(Boolean);
7771
- return {
7772
- resource: pattern.resource,
7773
- ids,
7774
- action: pattern.action,
7775
- isList: pattern.isList,
7776
- parent: pattern.parent,
7777
- };
7778
- }
7779
- }
7780
- return null;
7781
- }
7782
- serializeParams(params) {
7783
- const sortedKeys = Object.keys(params).sort();
7784
- const parts = [];
7785
- for (const key of sortedKeys) {
7786
- const value = params[key];
7787
- if (value !== undefined && value !== null) {
7788
- parts.push(`${key}=${String(value)}`);
7789
- }
7790
- }
7791
- return parts.join('&');
7792
- }
7793
- }
7794
-
7795
- const log$2 = createPrefixedLogger('CACHE');
7796
- class CachingHttpDecorator {
7797
- constructor(http, cache, keyGenerator, networkMonitor, config = {}) {
7798
- this.http = http;
7799
- this.cache = cache;
7800
- this.keyGenerator = keyGenerator;
7801
- this.networkMonitor = networkMonitor;
7802
- this.config = config;
7803
- this.currentOnlineState = true;
7804
- this.authToken = null;
7805
- log$2.info('CachingHttpDecorator initialized', {
7806
- enabled: config.enabled !== false,
7807
- hasNetworkMonitor: !!networkMonitor,
7808
- });
7809
- this.setupNetworkMonitoring();
7810
- }
7811
- setupNetworkMonitoring() {
7812
- if (this.networkMonitor) {
7813
- this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
7814
- if (this.currentOnlineState !== online) {
7815
- log$2.info('Network state changed:', { online });
7816
- }
7817
- this.currentOnlineState = online;
7818
- });
7819
- }
7820
- }
7821
- isOnline() {
7822
- if (this.networkMonitor) {
7823
- return this.currentOnlineState;
7824
- }
7825
- if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
7826
- return navigator.onLine;
7827
- }
7828
- return true;
7829
- }
7830
- async get(url, config) {
7831
- const startTime = Date.now();
7832
- // Check if caching is disabled globally
7833
- if (this.config.enabled === false) {
7834
- log$2.debug('GET (cache disabled globally):', { url });
7835
- return this.http.get(url, config);
7836
- }
7837
- // Check if this URL should be cached
7838
- const shouldCache = this.keyGenerator.shouldCache(url);
7839
- if (!shouldCache) {
7840
- log$2.debug('GET (not cacheable - likely a list endpoint):', { url });
7841
- return this.http.get(url, config);
7842
- }
7843
- const cacheKey = this.keyGenerator.generate(url, config?.params);
7844
- const ttl = this.keyGenerator.getTTL(url);
7845
- const resource = this.keyGenerator.parseResource(url);
7846
- log$2.info('GET request starting:', {
7847
- url,
7848
- resource,
7849
- cacheKey,
7850
- ttlMs: ttl,
7851
- ttlMin: Math.round(ttl / 60000),
7852
- params: config?.params,
7853
- });
7854
- // Check cache
7855
- let cached = null;
7856
- try {
7857
- cached = await this.cache.get(cacheKey);
7858
- if (cached) {
7859
- log$2.debug('Cache entry found:', {
7860
- cacheKey,
7861
- timestamp: new Date(cached.timestamp).toISOString(),
7862
- ageMs: Date.now() - cached.timestamp,
7863
- });
7864
- }
7865
- else {
7866
- log$2.debug('Cache entry not found:', { cacheKey });
7867
- }
7868
- }
7869
- catch (error) {
7870
- log$2.warn('Cache lookup failed:', {
7871
- cacheKey,
7872
- error: error instanceof Error ? error.message : error,
7873
- });
7874
- }
7875
- if (cached) {
7876
- const age = Date.now() - cached.timestamp;
7877
- const isExpired = age >= ttl;
7878
- log$2.debug('Cache analysis:', {
7879
- cacheKey,
7880
- ageMs: age,
7881
- ageSec: Math.round(age / 1000),
7882
- ttlMs: ttl,
7883
- isExpired,
7884
- isOnline: this.isOnline(),
7885
- });
7886
- // If within TTL, return cached data
7887
- if (!isExpired) {
7888
- const duration = Date.now() - startTime;
7889
- log$2.info('CACHE HIT:', {
7890
- url,
7891
- cacheKey,
7892
- ageMs: age,
7893
- durationMs: duration,
7894
- });
7895
- return {
7896
- data: cached.data,
7897
- status: 200,
7898
- headers: { 'x-cache': 'HIT' },
7899
- };
7900
- }
7901
- // If offline and cache is stale, return stale data
7902
- if (!this.isOnline()) {
7903
- const duration = Date.now() - startTime;
7904
- log$2.info('CACHE STALE (offline):', {
7905
- url,
7906
- cacheKey,
7907
- ageMs: age,
7908
- durationMs: duration,
7909
- });
7910
- return {
7911
- data: cached.data,
7912
- status: 200,
7913
- headers: { 'x-cache': 'STALE' },
7914
- };
7915
- }
7916
- log$2.debug('Cache expired, fetching fresh data:', { cacheKey, ageMs: age, ttlMs: ttl });
7917
- }
7918
- // Fetch fresh data
7919
- try {
7920
- log$2.debug('Fetching from network:', { url });
7921
- const response = await this.http.get(url, config);
7922
- // Cache the response
7923
- try {
7924
- await this.cache.set(cacheKey, response.data);
7925
- log$2.debug('Response cached successfully:', { cacheKey });
7926
- }
7927
- catch (error) {
7928
- log$2.error('Failed to cache response:', {
7929
- cacheKey,
7930
- error: error instanceof Error ? error.message : error,
7931
- });
7932
- }
7933
- const duration = Date.now() - startTime;
7934
- log$2.info('CACHE MISS (fetched fresh):', {
7935
- url,
7936
- cacheKey,
7937
- status: response.status,
7938
- durationMs: duration,
7939
- });
7940
- return {
7941
- ...response,
7942
- headers: { ...response.headers, 'x-cache': 'MISS' },
7943
- };
7944
- }
7945
- catch (error) {
7946
- // On error, return stale cache if available
7947
- if (cached) {
7948
- const duration = Date.now() - startTime;
7949
- log$2.warn('CACHE STALE (network error):', {
7950
- url,
7951
- cacheKey,
7952
- error: error instanceof Error ? error.message : 'Unknown error',
7953
- durationMs: duration,
7954
- });
7955
- return {
7956
- data: cached.data,
7957
- status: 200,
7958
- headers: { 'x-cache': 'STALE' },
7959
- };
7960
- }
7961
- log$2.error('Network error with no cache fallback:', {
7962
- url,
7963
- error: error instanceof Error ? error.message : error,
7964
- });
7965
- throw error;
7966
- }
7967
- }
7968
- async post(url, data, config) {
7969
- log$2.info('POST request:', { url });
7970
- const response = await this.http.post(url, data, config);
7971
- await this.invalidateRelated(url, 'POST');
7972
- return response;
7973
- }
7974
- async put(url, data, config) {
7975
- log$2.info('PUT request:', { url });
7976
- const response = await this.http.put(url, data, config);
7977
- await this.invalidateRelated(url, 'PUT');
7978
- return response;
7979
- }
7980
- async patch(url, data, config) {
7981
- log$2.info('PATCH request:', { url });
7982
- const response = await this.http.patch(url, data, config);
7983
- await this.invalidateRelated(url, 'PATCH');
7984
- return response;
7985
- }
7986
- async delete(url, config) {
7987
- log$2.info('DELETE request:', { url });
7988
- const response = await this.http.delete(url, config);
7989
- await this.invalidateRelated(url, 'DELETE');
7990
- return response;
7991
- }
7992
- setAuthToken(token) {
7993
- log$2.debug('CachingHttpDecorator.setAuthToken called:', {
7994
- hasToken: !!token,
7995
- tokenPrefix: token?.substring(0, 20),
7996
- underlyingHttpType: this.http.constructor.name,
7997
- });
7998
- this.authToken = token;
7999
- this.http.setAuthToken(token);
8000
- }
8001
- getAuthToken() {
8002
- return this.authToken;
8003
- }
8004
- async invalidateRelated(url, method) {
8005
- const patterns = this.keyGenerator.getInvalidationPatterns(url, method);
8006
- if (patterns.length === 0) {
8007
- log$2.debug('No cache patterns to invalidate:', { url, method });
8008
- return;
8009
- }
8010
- log$2.info('Invalidating cache patterns:', { url, method, patterns });
8011
- for (const pattern of patterns) {
8012
- try {
8013
- await this.cache.invalidate(pattern);
8014
- log$2.debug('Cache pattern invalidated:', { pattern });
8015
- }
8016
- catch (error) {
8017
- log$2.error('Failed to invalidate pattern:', {
8018
- pattern,
8019
- error: error instanceof Error ? error.message : error,
8020
- });
8021
- }
8022
- }
8023
- }
8024
- destroy() {
8025
- log$2.debug('CachingHttpDecorator destroyed');
8026
- this.networkSubscription?.unsubscribe();
8027
- }
8028
- }
8029
-
8030
5674
  const logJwt = createPrefixedLogger('HTTP-JWT');
8031
5675
  const logMtls = createPrefixedLogger('HTTP-MTLS');
8032
5676
  class AxiosHttpAdapter {
@@ -8296,7 +5940,6 @@ class SDKFactory {
8296
5940
  baseUrl: config.baseUrl,
8297
5941
  timeout: config.timeout,
8298
5942
  });
8299
- container.register(DI_TOKENS.BASE_HTTP_PORT, httpAdapter);
8300
5943
  container.register(DI_TOKENS.HTTP_PORT, httpAdapter);
8301
5944
  container.registerFactory(DI_TOKENS.RECEIPT_REPOSITORY, () => {
8302
5945
  const http = container.get(DI_TOKENS.HTTP_PORT);
@@ -8344,23 +5987,6 @@ class SDKFactory {
8344
5987
  });
8345
5988
  return container;
8346
5989
  }
8347
- static registerCacheServices(container, cache, network) {
8348
- container.register(DI_TOKENS.CACHE_PORT, cache);
8349
- if (network) {
8350
- container.register(DI_TOKENS.NETWORK_PORT, network);
8351
- }
8352
- const keyGenerator = new CacheKeyGenerator();
8353
- container.register(DI_TOKENS.CACHE_KEY_GENERATOR, keyGenerator);
8354
- const baseHttp = container.get(DI_TOKENS.BASE_HTTP_PORT);
8355
- const cachingHttp = new CachingHttpDecorator(baseHttp, cache, keyGenerator, network);
8356
- container.register(DI_TOKENS.HTTP_PORT, cachingHttp);
8357
- }
8358
- static getCacheKeyGenerator(container) {
8359
- if (container.has(DI_TOKENS.CACHE_KEY_GENERATOR)) {
8360
- return container.get(DI_TOKENS.CACHE_KEY_GENERATOR);
8361
- }
8362
- return undefined;
8363
- }
8364
5990
  static registerAuthServices(container, secureStorage, config) {
8365
5991
  const tokenStorage = new TokenStorageAdapter(secureStorage);
8366
5992
  container.register(DI_TOKENS.TOKEN_STORAGE_PORT, tokenStorage);
@@ -8428,7 +6054,6 @@ class ACubeSDK {
8428
6054
  mtlsConfig,
8429
6055
  });
8430
6056
  log$1.info('Platform adapters loaded', {
8431
- hasCache: !!this.adapters.cache,
8432
6057
  hasNetworkMonitor: !!this.adapters.networkMonitor,
8433
6058
  hasMtls: !!this.adapters.mtls,
8434
6059
  hasSecureStorage: !!this.adapters.secureStorage,
@@ -8444,25 +6069,10 @@ class ACubeSDK {
8444
6069
  this.container = SDKFactory.createContainer(factoryConfig);
8445
6070
  log$1.debug('Registering auth services');
8446
6071
  SDKFactory.registerAuthServices(this.container, this.adapters.secureStorage, factoryConfig);
8447
- if (this.adapters.cache) {
8448
- log$1.info('Registering cache services', {
8449
- hasNetworkMonitor: !!this.adapters.networkMonitor,
8450
- });
8451
- SDKFactory.registerCacheServices(this.container, this.adapters.cache, this.adapters.networkMonitor);
8452
- }
8453
- else {
8454
- log$1.debug('No cache adapter available, caching disabled');
8455
- }
8456
6072
  log$1.debug('Initializing certificate service');
8457
6073
  this.certificateService = new CertificateService(this.adapters.secureStorage);
8458
6074
  const tokenStorage = this.container.get(DI_TOKENS.TOKEN_STORAGE_PORT);
8459
6075
  const httpPort = this.container.get(DI_TOKENS.HTTP_PORT);
8460
- const baseHttpPort = this.container.get(DI_TOKENS.BASE_HTTP_PORT);
8461
- log$1.debug('HTTP ports initialized', {
8462
- httpPortType: httpPort.constructor.name,
8463
- baseHttpPortType: baseHttpPort.constructor.name,
8464
- areSameInstance: httpPort === baseHttpPort,
8465
- });
8466
6076
  log$1.debug('Initializing authentication service');
8467
6077
  this.authService = new AuthenticationService(httpPort, tokenStorage, {
8468
6078
  authUrl: this.config.getAuthUrl(),
@@ -8473,27 +6083,9 @@ class ACubeSDK {
8473
6083
  this.events.onAuthError?.(new ACubeSDKError('AUTH_ERROR', error.message, error));
8474
6084
  },
8475
6085
  });
8476
- log$1.debug('Initializing offline manager');
8477
- const queueEvents = {
8478
- onOperationAdded: (operation) => {
8479
- this.events.onOfflineOperationAdded?.(operation.id);
8480
- },
8481
- onOperationCompleted: (result) => {
8482
- this.events.onOfflineOperationCompleted?.(result.operation.id, result.success);
8483
- },
8484
- onOperationFailed: (result) => {
8485
- this.events.onOfflineOperationCompleted?.(result.operation.id, false);
8486
- },
8487
- };
8488
- this.offlineManager = new OfflineManager(this.adapters.storage, httpPort, this.adapters.networkMonitor, {
8489
- syncInterval: 30000,
8490
- }, queueEvents);
8491
6086
  this.networkSubscription = this.adapters.networkMonitor.online$.subscribe((online) => {
8492
6087
  this.currentOnlineState = online;
8493
6088
  this.events.onNetworkStatusChanged?.(online);
8494
- if (online && this.offlineManager) {
8495
- this.offlineManager.sync().catch(() => { });
8496
- }
8497
6089
  });
8498
6090
  const isAuth = await this.authService.isAuthenticated();
8499
6091
  log$1.debug('Checking authentication status during init', { isAuthenticated: isAuth });
@@ -8511,12 +6103,12 @@ class ACubeSDK {
8511
6103
  else {
8512
6104
  log$1.warn('User not authenticated during SDK init - token will be set after login');
8513
6105
  }
8514
- if (this.adapters?.mtls && 'setMTLSAdapter' in baseHttpPort) {
6106
+ if (this.adapters?.mtls && 'setMTLSAdapter' in httpPort) {
8515
6107
  log$1.debug('Connecting mTLS adapter to HTTP port');
8516
- const httpWithMtls = baseHttpPort;
6108
+ const httpWithMtls = httpPort;
8517
6109
  httpWithMtls.setMTLSAdapter(this.adapters.mtls);
8518
6110
  }
8519
- if ('setAuthStrategy' in baseHttpPort) {
6111
+ if ('setAuthStrategy' in httpPort) {
8520
6112
  log$1.debug('Configuring auth strategy');
8521
6113
  const jwtHandler = new JwtAuthHandler(tokenStorage);
8522
6114
  const certificatePort = this.certificateService
@@ -8548,7 +6140,7 @@ class ACubeSDK {
8548
6140
  },
8549
6141
  };
8550
6142
  const authStrategy = new AuthStrategy(jwtHandler, mtlsHandler, userProvider, this.adapters?.mtls || null);
8551
- const httpWithStrategy = baseHttpPort;
6143
+ const httpWithStrategy = httpPort;
8552
6144
  httpWithStrategy.setAuthStrategy(authStrategy);
8553
6145
  }
8554
6146
  if (this.adapters?.mtls && this.certificateService) {
@@ -8573,7 +6165,6 @@ class ACubeSDK {
8573
6165
  }
8574
6166
  this.isInitialized = true;
8575
6167
  log$1.info('SDK initialized successfully', {
8576
- hasCache: !!this.adapters.cache,
8577
6168
  hasMtls: !!this.adapters.mtls,
8578
6169
  });
8579
6170
  }
@@ -8664,10 +6255,6 @@ class ACubeSDK {
8664
6255
  this.ensureInitialized();
8665
6256
  return await this.authService.isAuthenticated();
8666
6257
  }
8667
- getOfflineManager() {
8668
- this.ensureInitialized();
8669
- return this.offlineManager;
8670
- }
8671
6258
  isOnline() {
8672
6259
  this.ensureInitialized();
8673
6260
  return this.currentOnlineState;
@@ -8795,7 +6382,6 @@ class ACubeSDK {
8795
6382
  }
8796
6383
  destroy() {
8797
6384
  this.networkSubscription?.unsubscribe();
8798
- this.offlineManager?.destroy();
8799
6385
  this.container?.clear();
8800
6386
  this.isInitialized = false;
8801
6387
  }
@@ -8820,6 +6406,7 @@ const INITIAL_STATE = {
8820
6406
  remainingMs: 0,
8821
6407
  },
8822
6408
  lastNotification: null,
6409
+ certificateMissing: false,
8823
6410
  };
8824
6411
  class AppStateService {
8825
6412
  get state$() {
@@ -8834,28 +6421,33 @@ class AppStateService {
8834
6421
  get warning$() {
8835
6422
  return this.state$.pipe(map((s) => s.warning), distinctUntilChanged((a, b) => a.active === b.active && a.remainingMs === b.remainingMs));
8836
6423
  }
8837
- constructor(notifications$, networkPort) {
6424
+ get certificateMissing$() {
6425
+ return this.state$.pipe(map((s) => s.certificateMissing), distinctUntilChanged());
6426
+ }
6427
+ constructor(notifications$, networkPort, certificateMissingInput$ = of(false)) {
8838
6428
  this.notifications$ = notifications$;
8839
6429
  this.networkPort = networkPort;
6430
+ this.certificateMissingInput$ = certificateMissingInput$;
8840
6431
  this.stateSubject = new BehaviorSubject(INITIAL_STATE);
8841
6432
  this.destroy$ = new Subject();
8842
6433
  this.warningTimerId = null;
8843
6434
  this.setupSubscriptions();
8844
6435
  }
8845
6436
  setupSubscriptions() {
8846
- combineLatest([this.notifications$, this.networkPort.online$])
6437
+ combineLatest([this.notifications$, this.networkPort.online$, this.certificateMissingInput$])
8847
6438
  .pipe(takeUntil(this.destroy$))
8848
- .subscribe(([notifications, isOnline]) => {
8849
- this.processState(notifications, isOnline);
6439
+ .subscribe(([notifications, isOnline, certificateMissing]) => {
6440
+ this.processState(notifications, isOnline, certificateMissing);
8850
6441
  });
8851
6442
  }
8852
- processState(notifications, isOnline) {
6443
+ processState(notifications, isOnline, certificateMissing) {
8853
6444
  if (!isOnline) {
8854
6445
  this.updateState({
8855
6446
  mode: 'OFFLINE',
8856
6447
  isOnline: false,
8857
6448
  warning: { active: false, blockAt: null, remainingMs: 0 },
8858
6449
  lastNotification: null,
6450
+ certificateMissing,
8859
6451
  });
8860
6452
  this.stopWarningTimer();
8861
6453
  return;
@@ -8917,6 +6509,7 @@ class AppStateService {
8917
6509
  isOnline: true,
8918
6510
  warning: warningState,
8919
6511
  lastNotification,
6512
+ certificateMissing,
8920
6513
  });
8921
6514
  }
8922
6515
  getLatestNotificationByCode(notifications) {
@@ -9074,7 +6667,6 @@ class TelemetryService {
9074
6667
  this.events = events;
9075
6668
  this.stateSubject = new BehaviorSubject({
9076
6669
  data: null,
9077
- isCached: false,
9078
6670
  isLoading: false,
9079
6671
  lastFetchedAt: null,
9080
6672
  });
@@ -9144,7 +6736,6 @@ class TelemetryService {
9144
6736
  const data = await this.repository.getTelemetry(this.currentPemId);
9145
6737
  const newState = {
9146
6738
  data,
9147
- isCached: false,
9148
6739
  isLoading: false,
9149
6740
  lastFetchedAt: Date.now(),
9150
6741
  };
@@ -9169,7 +6760,6 @@ class TelemetryService {
9169
6760
  clearTelemetry() {
9170
6761
  this.stateSubject.next({
9171
6762
  data: null,
9172
- isCached: false,
9173
6763
  isLoading: false,
9174
6764
  lastFetchedAt: null,
9175
6765
  });
@@ -9182,6 +6772,7 @@ class TelemetryService {
9182
6772
  }
9183
6773
  }
9184
6774
 
6775
+ const log = createPrefixedLogger('SDK-MANAGER');
9185
6776
  /**
9186
6777
  * SDKManager - Singleton wrapper for ACubeSDK with simplified API
9187
6778
  *
@@ -9232,6 +6823,7 @@ class SDKManager {
9232
6823
  this.appStateService = null;
9233
6824
  this.isInitialized = false;
9234
6825
  this.isPollingActive = false;
6826
+ this.certificateMissingSubject = new BehaviorSubject(false);
9235
6827
  /**
9236
6828
  * Handle user state changes (login/logout/token expiration)
9237
6829
  * Manages polling lifecycle based on user role
@@ -9242,9 +6834,14 @@ class SDKManager {
9242
6834
  if (!this.isInitialized)
9243
6835
  return;
9244
6836
  if (user) {
9245
- // User logged in - check role and start polling if allowed
6837
+ // User logged in - check role and certificate before starting polling
9246
6838
  const canPoll = hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
9247
6839
  if (canPoll && !this.isPollingActive) {
6840
+ const hasCert = await this.checkCertificate();
6841
+ if (!hasCert) {
6842
+ log.warn('Certificate missing — polling blocked until certificate is installed');
6843
+ return;
6844
+ }
9248
6845
  this.notificationService?.startPolling();
9249
6846
  await this.startTelemetryPollingAuto();
9250
6847
  this.isPollingActive = true;
@@ -9258,6 +6855,7 @@ class SDKManager {
9258
6855
  this.telemetryService?.clearTelemetry();
9259
6856
  this.isPollingActive = false;
9260
6857
  }
6858
+ this.certificateMissingSubject.next(false);
9261
6859
  }
9262
6860
  };
9263
6861
  }
@@ -9324,7 +6922,7 @@ class SDKManager {
9324
6922
  this.telemetryService = new TelemetryService(telemetryRepo, networkPort, {
9325
6923
  pollIntervalMs: this.config.telemetryPollIntervalMs ?? 60000,
9326
6924
  });
9327
- this.appStateService = new AppStateService(this.notificationService.notifications$, networkPort);
6925
+ this.appStateService = new AppStateService(this.notificationService.notifications$, networkPort, this.certificateMissingSubject.asObservable());
9328
6926
  if (this.events?.onAppStateChanged) {
9329
6927
  this.appStateService.state$.subscribe(this.events.onAppStateChanged);
9330
6928
  }
@@ -9336,9 +6934,15 @@ class SDKManager {
9336
6934
  const user = await this.sdk.getCurrentUser();
9337
6935
  const canPoll = user && hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
9338
6936
  if (canPoll) {
9339
- this.notificationService.startPolling();
9340
- await this.startTelemetryPollingAuto();
9341
- this.isPollingActive = true;
6937
+ const hasCert = await this.checkCertificate();
6938
+ if (hasCert) {
6939
+ this.notificationService.startPolling();
6940
+ await this.startTelemetryPollingAuto();
6941
+ this.isPollingActive = true;
6942
+ }
6943
+ else {
6944
+ log.warn('Certificate missing at init — polling blocked until certificate is installed');
6945
+ }
9342
6946
  }
9343
6947
  // AppStateService remains active for all users (handles OFFLINE network state)
9344
6948
  }
@@ -9373,7 +6977,14 @@ class SDKManager {
9373
6977
  return this.appStateService.warning$;
9374
6978
  }
9375
6979
  /**
9376
- * Observable stream of telemetry state (data, isLoading, isCached, error)
6980
+ * Observable stream indicating if certificate is missing
6981
+ * When true, polling is blocked and the user should install a certificate
6982
+ */
6983
+ get certificateMissing$() {
6984
+ return this.certificateMissingSubject.asObservable();
6985
+ }
6986
+ /**
6987
+ * Observable stream of telemetry state (data, isLoading, error)
9377
6988
  */
9378
6989
  get telemetryState$() {
9379
6990
  this.ensureInitialized();
@@ -9449,9 +7060,37 @@ class SDKManager {
9449
7060
  logout: () => sdk.logout(),
9450
7061
  getCurrentUser: () => sdk.getCurrentUser(),
9451
7062
  isAuthenticated: () => sdk.isAuthenticated(),
9452
- storeCertificate: (certificate, privateKey, options) => sdk.storeCertificate(certificate, privateKey, options),
7063
+ storeCertificate: async (certificate, privateKey, options) => {
7064
+ await sdk.storeCertificate(certificate, privateKey, options);
7065
+ this.certificateMissingSubject.next(false);
7066
+ // Start polling if user can poll and polling is not active
7067
+ if (!this.isPollingActive) {
7068
+ const user = await sdk.getCurrentUser();
7069
+ const canPoll = user && hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
7070
+ if (canPoll) {
7071
+ log.info('Certificate installed — starting polling');
7072
+ this.notificationService?.startPolling();
7073
+ await this.startTelemetryPollingAuto();
7074
+ this.isPollingActive = true;
7075
+ }
7076
+ }
7077
+ },
9453
7078
  hasCertificate: () => sdk.hasCertificate(),
9454
- clearCertificate: () => sdk.clearCertificate(),
7079
+ clearCertificate: async () => {
7080
+ await sdk.clearCertificate();
7081
+ this.certificateMissingSubject.next(true);
7082
+ // Stop polling since certificate is required
7083
+ if (this.isPollingActive) {
7084
+ log.info('Certificate removed — stopping polling');
7085
+ this.notificationService?.stopPolling();
7086
+ this.telemetryService?.stopPolling();
7087
+ this.telemetryService?.clearTelemetry();
7088
+ this.isPollingActive = false;
7089
+ }
7090
+ },
7091
+ getCertificate: () => sdk.getCertificate(),
7092
+ getCertificatesInfo: () => sdk.getCertificatesInfo(),
7093
+ notifications: sdk.notifications,
9455
7094
  isOnline: () => sdk.isOnline(),
9456
7095
  };
9457
7096
  }
@@ -9482,6 +7121,21 @@ class SDKManager {
9482
7121
  this.ensureInitialized();
9483
7122
  return this.sdk;
9484
7123
  }
7124
+ /**
7125
+ * Check certificate availability and update certificateMissing state.
7126
+ * Returns true if certificate is available, false otherwise.
7127
+ */
7128
+ async checkCertificate() {
7129
+ try {
7130
+ const hasCert = await this.sdk.hasCertificate();
7131
+ this.certificateMissingSubject.next(!hasCert);
7132
+ return hasCert;
7133
+ }
7134
+ catch {
7135
+ this.certificateMissingSubject.next(true);
7136
+ return false;
7137
+ }
7138
+ }
9485
7139
  cleanup() {
9486
7140
  this.notificationService?.destroy();
9487
7141
  this.telemetryService?.destroy();
@@ -9507,91 +7161,6 @@ const RECEIPT_SENT = 'sent';
9507
7161
  const RECEIPT_SORT_DESCENDING = 'descending';
9508
7162
  const RECEIPT_SORT_ASCENDING = 'ascending';
9509
7163
 
9510
- const log = createPrefixedLogger('CACHE-HANDLER');
9511
- class CacheHandler {
9512
- constructor(cache, networkMonitor) {
9513
- this.cache = cache;
9514
- this.networkMonitor = networkMonitor;
9515
- this.currentOnlineState = true;
9516
- this.setupNetworkMonitoring();
9517
- }
9518
- setupNetworkMonitoring() {
9519
- if (this.networkMonitor) {
9520
- this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
9521
- this.currentOnlineState = online;
9522
- });
9523
- }
9524
- }
9525
- isOnline() {
9526
- if (this.networkMonitor) {
9527
- return this.currentOnlineState;
9528
- }
9529
- if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
9530
- return navigator.onLine;
9531
- }
9532
- return false;
9533
- }
9534
- async handleCachedRequest(url, requestFn, config) {
9535
- if (!this.cache || config?.useCache === false) {
9536
- const response = await requestFn();
9537
- return response.data;
9538
- }
9539
- const cacheKey = this.generateCacheKey(url);
9540
- const online = this.isOnline();
9541
- log.debug('Request:', { url, cacheKey, online });
9542
- if (online) {
9543
- try {
9544
- const response = await requestFn();
9545
- if (this.cache) {
9546
- await this.cache.set(cacheKey, response.data).catch((error) => {
9547
- log.error('Failed to cache:', error instanceof Error ? error.message : error);
9548
- });
9549
- }
9550
- return response.data;
9551
- }
9552
- catch (error) {
9553
- const cached = await this.cache.get(cacheKey).catch(() => null);
9554
- if (cached) {
9555
- log.debug('Network failed, using cache fallback');
9556
- return cached.data;
9557
- }
9558
- throw error;
9559
- }
9560
- }
9561
- else {
9562
- const cached = await this.cache.get(cacheKey).catch(() => null);
9563
- if (cached) {
9564
- log.debug('Offline, returning cached data');
9565
- return cached.data;
9566
- }
9567
- throw new Error('Offline: No cached data available');
9568
- }
9569
- }
9570
- generateCacheKey(url) {
9571
- return url;
9572
- }
9573
- async invalidateCache(pattern) {
9574
- if (!this.cache)
9575
- return;
9576
- try {
9577
- await this.cache.invalidate(pattern);
9578
- }
9579
- catch (error) {
9580
- log.error('Invalidation failed:', error instanceof Error ? error.message : error);
9581
- }
9582
- }
9583
- getCacheStatus() {
9584
- return {
9585
- available: !!this.cache,
9586
- networkMonitorAvailable: !!this.networkMonitor,
9587
- isOnline: this.isOnline(),
9588
- };
9589
- }
9590
- destroy() {
9591
- this.networkSubscription?.unsubscribe();
9592
- }
9593
- }
9594
-
9595
7164
  function transformError(error) {
9596
7165
  if (axios.isAxiosError(error)) {
9597
7166
  const response = error.response;
@@ -9770,5 +7339,5 @@ class PlatformDetector {
9770
7339
  }
9771
7340
  }
9772
7341
 
9773
- export { ACubeSDK, ACubeSDKError, ActivationRequestSchema, AddressMapper, AddressSchema, AppStateService, AuthStrategy, AuthenticationService, AxiosHttpAdapter, CacheHandler, CashRegisterCreateSchema, CashRegisterMapper, CashRegisterRepositoryImpl, CashierCreateInputSchema, CashierMapper, CashierRepositoryImpl, CertificateService, CertificateValidator, ConfigManager, DAILY_REPORT_STATUS_OPTIONS, DEFAULT_QUEUE_CONFIG, DIContainer, DI_TOKENS, DailyReportMapper, DailyReportRepositoryImpl, DailyReportStatusSchema, DailyReportsParamsSchema, EXEMPT_VAT_CODES, ErrorCategory, GOOD_OR_SERVICE_OPTIONS, GoodOrServiceSchema, JournalCloseInputSchema, JournalMapper, JournalRepositoryImpl, JwtAuthHandler, LotterySchema, LotterySecretRequestSchema, MTLSError, MTLSErrorType$1 as MTLSErrorType, MerchantCreateInputSchema, MerchantMapper, MerchantRepositoryImpl, MerchantUpdateInputSchema, MessageSchema, MtlsAuthHandler, NOTIFICATION_CODES, NOTIFICATION_LEVELS, NOTIFICATION_SCOPES, NOTIFICATION_SOURCES, NOTIFICATION_TYPES, NotificationDataBlockAtSchema, NotificationDataPemStatusSchema, NotificationListResponseSchema, NotificationMapper, NotificationMf2UnreachableSchema, NotificationPemBackOnlineSchema, NotificationPemsBlockedSchema, NotificationRepositoryImpl, NotificationSchema, NotificationScopeSchema, NotificationService, OfflineManager, OperationQueue, PEMStatusOfflineRequestSchema, PEMStatusSchema, PEM_STATUS_OPTIONS, PEM_TYPE_OPTIONS, PemCreateInputSchema, PemDataSchema, PemMapper, PemRepositoryImpl, PemStatusSchema, PendingReceiptsSchema, PlatformDetector, PointOfSaleMapper, PointOfSaleRepositoryImpl, RECEIPT_PROOF_TYPE_OPTIONS, RECEIPT_READY, RECEIPT_SENT, RECEIPT_SORT_ASCENDING, RECEIPT_SORT_DESCENDING, ReceiptInputSchema, ReceiptItemSchema, ReceiptMapper, ReceiptProofTypeSchema, ReceiptRepositoryImpl, ReceiptReturnInputSchema, ReceiptReturnItemSchema, ReceiptReturnOrVoidViaPEMInputSchema, ReceiptReturnOrVoidWithProofInputSchema, SDKFactory, SDKManager, STANDARD_VAT_RATES, SupplierCreateInputSchema, SupplierMapper, SupplierRepositoryImpl, SupplierUpdateInputSchema, SyncManager, TelemetryMapper, TelemetryMerchantSchema, TelemetryRepositoryImpl, TelemetrySchema, TelemetryService, TelemetrySoftwareSchema, TelemetrySoftwareVersionSchema, TelemetrySupplierSchema, TransmissionSchema, VAT_RATE_CODES, VAT_RATE_CODE_OPTIONS, ValidationMessages, VatRateCodeSchema, VoidReceiptInputSchema, classifyError, clearObject, clearObjectShallow, createACubeMTLSConfig, createACubeSDK, createPrefixedLogger, createACubeSDK as default, detectPlatform, extractRoles, formatDecimal, getUserFriendlyMessage, hasAnyRole, hasNonEmptyValues, hasRole, isEmpty, isTokenExpired, loadPlatformAdapters, logger, parseJwt, shouldReconfigureCertificate, shouldRetryRequest, transformError, validateInput };
7342
+ export { ACubeSDK, ACubeSDKError, ActivationRequestSchema, AddressMapper, AddressSchema, AppStateService, AuthStrategy, AuthenticationService, AxiosHttpAdapter, CashRegisterCreateSchema, CashRegisterMapper, CashRegisterRepositoryImpl, CashierCreateInputSchema, CashierMapper, CashierRepositoryImpl, CertificateService, CertificateValidator, ConfigManager, DAILY_REPORT_STATUS_OPTIONS, DIContainer, DI_TOKENS, DailyReportMapper, DailyReportRepositoryImpl, DailyReportStatusSchema, DailyReportsParamsSchema, EXEMPT_VAT_CODES, ErrorCategory, GOOD_OR_SERVICE_OPTIONS, GoodOrServiceSchema, JournalCloseInputSchema, JournalMapper, JournalRepositoryImpl, JwtAuthHandler, LotterySchema, LotterySecretRequestSchema, MTLSError, MTLSErrorType$1 as MTLSErrorType, MerchantCreateInputSchema, MerchantMapper, MerchantRepositoryImpl, MerchantUpdateInputSchema, MessageSchema, MtlsAuthHandler, NOTIFICATION_CODES, NOTIFICATION_LEVELS, NOTIFICATION_SCOPES, NOTIFICATION_SOURCES, NOTIFICATION_TYPES, NotificationDataBlockAtSchema, NotificationDataPemStatusSchema, NotificationListResponseSchema, NotificationMapper, NotificationMf2UnreachableSchema, NotificationPemBackOnlineSchema, NotificationPemsBlockedSchema, NotificationRepositoryImpl, NotificationSchema, NotificationScopeSchema, NotificationService, PEMStatusOfflineRequestSchema, PEMStatusSchema, PEM_STATUS_OPTIONS, PEM_TYPE_OPTIONS, PemCreateInputSchema, PemDataSchema, PemMapper, PemRepositoryImpl, PemStatusSchema, PendingReceiptsSchema, PlatformDetector, PointOfSaleMapper, PointOfSaleRepositoryImpl, RECEIPT_PROOF_TYPE_OPTIONS, RECEIPT_READY, RECEIPT_SENT, RECEIPT_SORT_ASCENDING, RECEIPT_SORT_DESCENDING, ReceiptInputSchema, ReceiptItemSchema, ReceiptMapper, ReceiptProofTypeSchema, ReceiptRepositoryImpl, ReceiptReturnInputSchema, ReceiptReturnItemSchema, ReceiptReturnOrVoidViaPEMInputSchema, ReceiptReturnOrVoidWithProofInputSchema, SDKFactory, SDKManager, STANDARD_VAT_RATES, SupplierCreateInputSchema, SupplierMapper, SupplierRepositoryImpl, SupplierUpdateInputSchema, TelemetryMapper, TelemetryMerchantSchema, TelemetryRepositoryImpl, TelemetrySchema, TelemetryService, TelemetrySoftwareSchema, TelemetrySoftwareVersionSchema, TelemetrySupplierSchema, TransmissionSchema, VAT_RATE_CODES, VAT_RATE_CODE_OPTIONS, ValidationMessages, VatRateCodeSchema, VoidReceiptInputSchema, classifyError, clearObject, clearObjectShallow, createACubeMTLSConfig, createACubeSDK, createPrefixedLogger, createACubeSDK as default, detectPlatform, extractRoles, formatDecimal, getUserFriendlyMessage, hasAnyRole, hasNonEmptyValues, hasRole, isEmpty, isTokenExpired, loadPlatformAdapters, logger, parseJwt, shouldReconfigureCertificate, shouldRetryRequest, transformError, validateInput };
9774
7343
  //# sourceMappingURL=index.esm.js.map