@a-cube-io/ereceipts-js-sdk 2.0.9 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -7
- package/dist/index.cjs.js +135 -2651
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +8 -251
- package/dist/index.esm.js +136 -2647
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.js +136 -2647
- package/dist/index.native.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2112,7 +2112,7 @@ function formatDecimal(value, decimals = 2) {
|
|
|
2112
2112
|
return num.toFixed(decimals);
|
|
2113
2113
|
}
|
|
2114
2114
|
|
|
2115
|
-
const log$
|
|
2115
|
+
const log$b = createPrefixedLogger('AUTH-SERVICE');
|
|
2116
2116
|
class AuthenticationService {
|
|
2117
2117
|
get user$() {
|
|
2118
2118
|
return this.userSubject.asObservable();
|
|
@@ -2134,7 +2134,7 @@ class AuthenticationService {
|
|
|
2134
2134
|
}
|
|
2135
2135
|
async login(credentials) {
|
|
2136
2136
|
this.authStateSubject.next('authenticating');
|
|
2137
|
-
log$
|
|
2137
|
+
log$b.info('Login attempt', {
|
|
2138
2138
|
authUrl: this.config.authUrl,
|
|
2139
2139
|
email: credentials.email,
|
|
2140
2140
|
});
|
|
@@ -2145,7 +2145,7 @@ class AuthenticationService {
|
|
|
2145
2145
|
});
|
|
2146
2146
|
const jwtPayload = parseJwt(response.data.token);
|
|
2147
2147
|
const expiresAt = jwtPayload.exp * 1000;
|
|
2148
|
-
log$
|
|
2148
|
+
log$b.info('Login successful', {
|
|
2149
2149
|
authUrl: this.config.authUrl,
|
|
2150
2150
|
tokenPrefix: response.data.token.substring(0, 30) + '...',
|
|
2151
2151
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
@@ -2174,21 +2174,21 @@ class AuthenticationService {
|
|
|
2174
2174
|
const token = await this.tokenStorage.getAccessToken();
|
|
2175
2175
|
if (!token) {
|
|
2176
2176
|
// No token - clear any stale user state
|
|
2177
|
-
log$
|
|
2177
|
+
log$b.debug('getCurrentUser: No token in storage');
|
|
2178
2178
|
if (this.userSubject.value) {
|
|
2179
2179
|
this.userSubject.next(null);
|
|
2180
2180
|
this.authStateSubject.next('idle');
|
|
2181
2181
|
}
|
|
2182
2182
|
return null;
|
|
2183
2183
|
}
|
|
2184
|
-
log$
|
|
2184
|
+
log$b.debug('getCurrentUser: Token found', {
|
|
2185
2185
|
tokenPrefix: token.substring(0, 30) + '...',
|
|
2186
2186
|
tokenLength: token.length,
|
|
2187
2187
|
});
|
|
2188
2188
|
const jwtPayload = parseJwt(token);
|
|
2189
2189
|
if (isTokenExpired(jwtPayload)) {
|
|
2190
2190
|
// Token expired - clear everything
|
|
2191
|
-
log$
|
|
2191
|
+
log$b.warn('getCurrentUser: Token expired');
|
|
2192
2192
|
await this.tokenStorage.clearTokens();
|
|
2193
2193
|
this.userSubject.next(null);
|
|
2194
2194
|
this.authStateSubject.next('idle');
|
|
@@ -2198,7 +2198,7 @@ class AuthenticationService {
|
|
|
2198
2198
|
// Token is valid - return cached user if available
|
|
2199
2199
|
const currentUser = this.userSubject.value;
|
|
2200
2200
|
if (currentUser) {
|
|
2201
|
-
log$
|
|
2201
|
+
log$b.debug('getCurrentUser: Returning cached user', {
|
|
2202
2202
|
email: currentUser.email,
|
|
2203
2203
|
roles: currentUser.roles,
|
|
2204
2204
|
});
|
|
@@ -2221,12 +2221,12 @@ class AuthenticationService {
|
|
|
2221
2221
|
async isAuthenticated() {
|
|
2222
2222
|
const token = await this.tokenStorage.getAccessToken();
|
|
2223
2223
|
if (!token) {
|
|
2224
|
-
log$
|
|
2224
|
+
log$b.debug('isAuthenticated: No token in storage');
|
|
2225
2225
|
return false;
|
|
2226
2226
|
}
|
|
2227
2227
|
const jwtPayload = parseJwt(token);
|
|
2228
2228
|
const expired = isTokenExpired(jwtPayload);
|
|
2229
|
-
log$
|
|
2229
|
+
log$b.debug('isAuthenticated: Token check', {
|
|
2230
2230
|
hasToken: true,
|
|
2231
2231
|
expired,
|
|
2232
2232
|
expiresAt: new Date(jwtPayload.exp * 1000).toISOString(),
|
|
@@ -2898,7 +2898,7 @@ class ACubeSDKError extends Error {
|
|
|
2898
2898
|
}
|
|
2899
2899
|
}
|
|
2900
2900
|
|
|
2901
|
-
const log$
|
|
2901
|
+
const log$a = createPrefixedLogger('AUTH-STRATEGY');
|
|
2902
2902
|
class AuthStrategy {
|
|
2903
2903
|
constructor(jwtHandler, mtlsHandler, userProvider, mtlsAdapter) {
|
|
2904
2904
|
this.jwtHandler = jwtHandler;
|
|
@@ -2913,7 +2913,7 @@ class AuthStrategy {
|
|
|
2913
2913
|
const platform = this.detectPlatform();
|
|
2914
2914
|
const userRole = await this.getUserRole();
|
|
2915
2915
|
const isReceiptEndpoint = this.isReceiptEndpoint(url);
|
|
2916
|
-
log$
|
|
2916
|
+
log$a.debug('Determining auth config', {
|
|
2917
2917
|
url,
|
|
2918
2918
|
method,
|
|
2919
2919
|
platform,
|
|
@@ -3044,7 +3044,7 @@ class JwtAuthHandler {
|
|
|
3044
3044
|
}
|
|
3045
3045
|
}
|
|
3046
3046
|
|
|
3047
|
-
const log$
|
|
3047
|
+
const log$9 = createPrefixedLogger('MTLS-HANDLER');
|
|
3048
3048
|
class MtlsAuthHandler {
|
|
3049
3049
|
constructor(mtlsAdapter, certificatePort) {
|
|
3050
3050
|
this.mtlsAdapter = mtlsAdapter;
|
|
@@ -3086,7 +3086,7 @@ class MtlsAuthHandler {
|
|
|
3086
3086
|
async makeRequest(url, config, jwtToken) {
|
|
3087
3087
|
const requestKey = this.generateRequestKey(url, config, jwtToken);
|
|
3088
3088
|
if (this.pendingRequests.has(requestKey)) {
|
|
3089
|
-
log$
|
|
3089
|
+
log$9.debug('Deduplicating concurrent request:', url);
|
|
3090
3090
|
return this.pendingRequests.get(requestKey);
|
|
3091
3091
|
}
|
|
3092
3092
|
const requestPromise = this.executeRequest(url, config, jwtToken, false);
|
|
@@ -3110,10 +3110,10 @@ class MtlsAuthHandler {
|
|
|
3110
3110
|
};
|
|
3111
3111
|
if (jwtToken) {
|
|
3112
3112
|
headers['Authorization'] = `Bearer ${jwtToken}`;
|
|
3113
|
-
log$
|
|
3113
|
+
log$9.debug('JWT token present:', jwtToken.substring(0, 20) + '...');
|
|
3114
3114
|
}
|
|
3115
3115
|
else {
|
|
3116
|
-
log$
|
|
3116
|
+
log$9.warn('No JWT token provided for mTLS request');
|
|
3117
3117
|
}
|
|
3118
3118
|
const fullUrl = this.constructMtlsUrl(url);
|
|
3119
3119
|
const mtlsConfig = {
|
|
@@ -3124,25 +3124,25 @@ class MtlsAuthHandler {
|
|
|
3124
3124
|
timeout: config.timeout,
|
|
3125
3125
|
responseType: config.responseType,
|
|
3126
3126
|
};
|
|
3127
|
-
log$
|
|
3128
|
-
log$
|
|
3127
|
+
log$9.debug('header-mtls', headers);
|
|
3128
|
+
log$9.debug(`${config.method} ${fullUrl}`);
|
|
3129
3129
|
if (config.data) {
|
|
3130
|
-
log$
|
|
3130
|
+
log$9.debug('Request body:', config.data);
|
|
3131
3131
|
}
|
|
3132
3132
|
try {
|
|
3133
3133
|
const response = await this.mtlsAdapter.request(mtlsConfig);
|
|
3134
|
-
log$
|
|
3134
|
+
log$9.debug(`Response ${response.status} from ${fullUrl}`);
|
|
3135
3135
|
if (response.data) {
|
|
3136
|
-
log$
|
|
3136
|
+
log$9.debug('Response body:', response.data);
|
|
3137
3137
|
}
|
|
3138
3138
|
return response.data;
|
|
3139
3139
|
}
|
|
3140
3140
|
catch (error) {
|
|
3141
|
-
log$
|
|
3141
|
+
log$9.error(`Response error from ${fullUrl}:`, error);
|
|
3142
3142
|
if (error && typeof error === 'object' && 'response' in error) {
|
|
3143
3143
|
const axiosError = error;
|
|
3144
3144
|
if (axiosError.response?.data) {
|
|
3145
|
-
log$
|
|
3145
|
+
log$9.error('Response body:', axiosError.response.data);
|
|
3146
3146
|
}
|
|
3147
3147
|
}
|
|
3148
3148
|
if (isRetryAttempt) {
|
|
@@ -3152,7 +3152,7 @@ class MtlsAuthHandler {
|
|
|
3152
3152
|
if (!shouldRetry) {
|
|
3153
3153
|
throw error;
|
|
3154
3154
|
}
|
|
3155
|
-
log$
|
|
3155
|
+
log$9.debug('Request failed, reconfiguring certificate and retrying...');
|
|
3156
3156
|
try {
|
|
3157
3157
|
await this.configureCertificate(certificate);
|
|
3158
3158
|
return await this.executeRequest(url, config, jwtToken, true);
|
|
@@ -3260,1902 +3260,6 @@ class MtlsAuthHandler {
|
|
|
3260
3260
|
}
|
|
3261
3261
|
}
|
|
3262
3262
|
|
|
3263
|
-
const DEFAULT_QUEUE_CONFIG = {
|
|
3264
|
-
maxRetries: 3,
|
|
3265
|
-
retryDelay: 1000,
|
|
3266
|
-
maxRetryDelay: 30000,
|
|
3267
|
-
backoffMultiplier: 2,
|
|
3268
|
-
maxQueueSize: 1000,
|
|
3269
|
-
batchSize: 10,
|
|
3270
|
-
syncInterval: 30000,
|
|
3271
|
-
};
|
|
3272
|
-
|
|
3273
|
-
class OperationQueue {
|
|
3274
|
-
constructor(storage, config = DEFAULT_QUEUE_CONFIG, events = {}) {
|
|
3275
|
-
this.storage = storage;
|
|
3276
|
-
this.config = config;
|
|
3277
|
-
this.events = events;
|
|
3278
|
-
this.queue = [];
|
|
3279
|
-
this.processing = false;
|
|
3280
|
-
this.config = { ...DEFAULT_QUEUE_CONFIG, ...config };
|
|
3281
|
-
this.loadQueue();
|
|
3282
|
-
if (this.config.syncInterval > 0) {
|
|
3283
|
-
this.startAutoSync();
|
|
3284
|
-
}
|
|
3285
|
-
}
|
|
3286
|
-
async addOperation(type, resource, endpoint, method, data, priority = 1) {
|
|
3287
|
-
if (this.queue.length >= this.config.maxQueueSize) {
|
|
3288
|
-
const lowPriorityIndex = this.queue.findIndex((op) => op.priority === 1);
|
|
3289
|
-
if (lowPriorityIndex !== -1) {
|
|
3290
|
-
this.queue.splice(lowPriorityIndex, 1);
|
|
3291
|
-
}
|
|
3292
|
-
else {
|
|
3293
|
-
throw new Error('Queue is full');
|
|
3294
|
-
}
|
|
3295
|
-
}
|
|
3296
|
-
const operation = {
|
|
3297
|
-
id: this.generateId(),
|
|
3298
|
-
type,
|
|
3299
|
-
resource,
|
|
3300
|
-
endpoint,
|
|
3301
|
-
method,
|
|
3302
|
-
data,
|
|
3303
|
-
status: 'pending',
|
|
3304
|
-
createdAt: Date.now(),
|
|
3305
|
-
updatedAt: Date.now(),
|
|
3306
|
-
retryCount: 0,
|
|
3307
|
-
maxRetries: this.config.maxRetries,
|
|
3308
|
-
priority,
|
|
3309
|
-
};
|
|
3310
|
-
const insertIndex = this.queue.findIndex((op) => op.priority < priority);
|
|
3311
|
-
if (insertIndex === -1) {
|
|
3312
|
-
this.queue.push(operation);
|
|
3313
|
-
}
|
|
3314
|
-
else {
|
|
3315
|
-
this.queue.splice(insertIndex, 0, operation);
|
|
3316
|
-
}
|
|
3317
|
-
await this.saveQueue();
|
|
3318
|
-
this.events.onOperationAdded?.(operation);
|
|
3319
|
-
return operation.id;
|
|
3320
|
-
}
|
|
3321
|
-
getPendingOperations() {
|
|
3322
|
-
return this.queue.filter((op) => op.status === 'pending' || op.status === 'failed');
|
|
3323
|
-
}
|
|
3324
|
-
getOperation(id) {
|
|
3325
|
-
return this.queue.find((op) => op.id === id);
|
|
3326
|
-
}
|
|
3327
|
-
async removeOperation(id) {
|
|
3328
|
-
const index = this.queue.findIndex((op) => op.id === id);
|
|
3329
|
-
if (index === -1)
|
|
3330
|
-
return false;
|
|
3331
|
-
this.queue.splice(index, 1);
|
|
3332
|
-
await this.saveQueue();
|
|
3333
|
-
return true;
|
|
3334
|
-
}
|
|
3335
|
-
async updateOperation(id, updates) {
|
|
3336
|
-
const operation = this.queue.find((op) => op.id === id);
|
|
3337
|
-
if (!operation)
|
|
3338
|
-
return false;
|
|
3339
|
-
Object.assign(operation, { ...updates, updatedAt: Date.now() });
|
|
3340
|
-
await this.saveQueue();
|
|
3341
|
-
return true;
|
|
3342
|
-
}
|
|
3343
|
-
getStats() {
|
|
3344
|
-
return {
|
|
3345
|
-
total: this.queue.length,
|
|
3346
|
-
pending: this.queue.filter((op) => op.status === 'pending').length,
|
|
3347
|
-
processing: this.queue.filter((op) => op.status === 'processing').length,
|
|
3348
|
-
completed: this.queue.filter((op) => op.status === 'completed').length,
|
|
3349
|
-
failed: this.queue.filter((op) => op.status === 'failed').length,
|
|
3350
|
-
};
|
|
3351
|
-
}
|
|
3352
|
-
async clearQueue() {
|
|
3353
|
-
this.queue = [];
|
|
3354
|
-
await this.saveQueue();
|
|
3355
|
-
}
|
|
3356
|
-
async clearCompleted() {
|
|
3357
|
-
this.queue = this.queue.filter((op) => op.status !== 'completed');
|
|
3358
|
-
await this.saveQueue();
|
|
3359
|
-
}
|
|
3360
|
-
async clearFailed() {
|
|
3361
|
-
this.queue = this.queue.filter((op) => op.status !== 'failed');
|
|
3362
|
-
await this.saveQueue();
|
|
3363
|
-
}
|
|
3364
|
-
async retryFailed() {
|
|
3365
|
-
for (const operation of this.queue.filter((op) => op.status === 'failed')) {
|
|
3366
|
-
if (operation.retryCount < operation.maxRetries) {
|
|
3367
|
-
operation.status = 'pending';
|
|
3368
|
-
operation.retryCount++;
|
|
3369
|
-
operation.updatedAt = Date.now();
|
|
3370
|
-
delete operation.error;
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
await this.saveQueue();
|
|
3374
|
-
}
|
|
3375
|
-
getNextBatch() {
|
|
3376
|
-
return this.queue
|
|
3377
|
-
.filter((op) => op.status === 'pending')
|
|
3378
|
-
.sort((a, b) => b.priority - a.priority || a.createdAt - b.createdAt)
|
|
3379
|
-
.slice(0, this.config.batchSize);
|
|
3380
|
-
}
|
|
3381
|
-
isEmpty() {
|
|
3382
|
-
return this.getPendingOperations().length === 0;
|
|
3383
|
-
}
|
|
3384
|
-
startAutoSync() {
|
|
3385
|
-
if (this.syncIntervalId)
|
|
3386
|
-
return;
|
|
3387
|
-
this.syncIntervalId = setInterval(() => {
|
|
3388
|
-
if (!this.isEmpty() && !this.processing) {
|
|
3389
|
-
this.events.onQueueEmpty?.();
|
|
3390
|
-
}
|
|
3391
|
-
}, this.config.syncInterval);
|
|
3392
|
-
}
|
|
3393
|
-
stopAutoSync() {
|
|
3394
|
-
if (this.syncIntervalId) {
|
|
3395
|
-
clearInterval(this.syncIntervalId);
|
|
3396
|
-
this.syncIntervalId = undefined;
|
|
3397
|
-
}
|
|
3398
|
-
}
|
|
3399
|
-
setProcessing(value) {
|
|
3400
|
-
this.processing = value;
|
|
3401
|
-
}
|
|
3402
|
-
isCurrentlyProcessing() {
|
|
3403
|
-
return this.processing;
|
|
3404
|
-
}
|
|
3405
|
-
async loadQueue() {
|
|
3406
|
-
try {
|
|
3407
|
-
const queueData = await this.storage.get(OperationQueue.QUEUE_KEY);
|
|
3408
|
-
if (queueData) {
|
|
3409
|
-
this.queue = JSON.parse(queueData);
|
|
3410
|
-
this.queue.forEach((op) => {
|
|
3411
|
-
if (op.status === 'processing') {
|
|
3412
|
-
op.status = 'pending';
|
|
3413
|
-
}
|
|
3414
|
-
});
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
catch {
|
|
3418
|
-
this.queue = [];
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
async saveQueue() {
|
|
3422
|
-
try {
|
|
3423
|
-
await this.storage.set(OperationQueue.QUEUE_KEY, JSON.stringify(this.queue));
|
|
3424
|
-
}
|
|
3425
|
-
catch (error) {
|
|
3426
|
-
this.events.onError?.(new Error(`Failed to save queue: ${error}`));
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
generateId() {
|
|
3430
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3431
|
-
}
|
|
3432
|
-
destroy() {
|
|
3433
|
-
this.stopAutoSync();
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
OperationQueue.QUEUE_KEY = 'acube_operation_queue';
|
|
3437
|
-
|
|
3438
|
-
class SyncManager {
|
|
3439
|
-
constructor(queue, httpPort, networkMonitor, config, events = {}) {
|
|
3440
|
-
this.queue = queue;
|
|
3441
|
-
this.httpPort = httpPort;
|
|
3442
|
-
this.networkMonitor = networkMonitor;
|
|
3443
|
-
this.config = config;
|
|
3444
|
-
this.events = events;
|
|
3445
|
-
this.isOnline = true;
|
|
3446
|
-
this.destroy$ = new Subject();
|
|
3447
|
-
this.setupNetworkMonitoring();
|
|
3448
|
-
}
|
|
3449
|
-
setupNetworkMonitoring() {
|
|
3450
|
-
// Subscribe to online$ to track current state
|
|
3451
|
-
this.networkSubscription = this.networkMonitor.online$
|
|
3452
|
-
.pipe(startWith(true), // Assume online initially
|
|
3453
|
-
pairwise(), filter(([wasOnline, isNowOnline]) => !wasOnline && isNowOnline), takeUntil(this.destroy$))
|
|
3454
|
-
.subscribe(() => {
|
|
3455
|
-
// Offline → Online transition detected
|
|
3456
|
-
this.syncPendingOperations();
|
|
3457
|
-
});
|
|
3458
|
-
// Track current online state
|
|
3459
|
-
this.networkMonitor.online$.pipe(takeUntil(this.destroy$)).subscribe((online) => {
|
|
3460
|
-
this.isOnline = online;
|
|
3461
|
-
});
|
|
3462
|
-
}
|
|
3463
|
-
async syncPendingOperations() {
|
|
3464
|
-
if (!this.isOnline) {
|
|
3465
|
-
throw new Error('Cannot sync while offline');
|
|
3466
|
-
}
|
|
3467
|
-
if (this.queue.isCurrentlyProcessing()) {
|
|
3468
|
-
throw new Error('Sync already in progress');
|
|
3469
|
-
}
|
|
3470
|
-
this.queue.setProcessing(true);
|
|
3471
|
-
try {
|
|
3472
|
-
const results = [];
|
|
3473
|
-
let successCount = 0;
|
|
3474
|
-
let failureCount = 0;
|
|
3475
|
-
while (!this.queue.isEmpty()) {
|
|
3476
|
-
const batch = this.queue.getNextBatch();
|
|
3477
|
-
if (batch.length === 0)
|
|
3478
|
-
break;
|
|
3479
|
-
const batchPromises = batch.map((operation) => this.processOperation(operation));
|
|
3480
|
-
const batchResults = await Promise.allSettled(batchPromises);
|
|
3481
|
-
batchResults.forEach((result, index) => {
|
|
3482
|
-
const operation = batch[index];
|
|
3483
|
-
if (!operation)
|
|
3484
|
-
return;
|
|
3485
|
-
if (result.status === 'fulfilled') {
|
|
3486
|
-
const syncResult = result.value;
|
|
3487
|
-
results.push(syncResult);
|
|
3488
|
-
if (syncResult.success) {
|
|
3489
|
-
successCount++;
|
|
3490
|
-
this.events.onOperationCompleted?.(syncResult);
|
|
3491
|
-
}
|
|
3492
|
-
else {
|
|
3493
|
-
failureCount++;
|
|
3494
|
-
this.events.onOperationFailed?.(syncResult);
|
|
3495
|
-
}
|
|
3496
|
-
}
|
|
3497
|
-
else {
|
|
3498
|
-
const syncResult = {
|
|
3499
|
-
operation,
|
|
3500
|
-
success: false,
|
|
3501
|
-
error: result.reason?.message || 'Unknown error',
|
|
3502
|
-
};
|
|
3503
|
-
results.push(syncResult);
|
|
3504
|
-
failureCount++;
|
|
3505
|
-
this.events.onOperationFailed?.(syncResult);
|
|
3506
|
-
this.queue.updateOperation(operation.id, {
|
|
3507
|
-
status: 'failed',
|
|
3508
|
-
error: syncResult.error,
|
|
3509
|
-
});
|
|
3510
|
-
}
|
|
3511
|
-
});
|
|
3512
|
-
if (!this.queue.isEmpty()) {
|
|
3513
|
-
await this.delay(500);
|
|
3514
|
-
}
|
|
3515
|
-
}
|
|
3516
|
-
const batchResult = {
|
|
3517
|
-
totalOperations: results.length,
|
|
3518
|
-
successCount,
|
|
3519
|
-
failureCount,
|
|
3520
|
-
results,
|
|
3521
|
-
};
|
|
3522
|
-
this.events.onBatchSyncCompleted?.(batchResult);
|
|
3523
|
-
if (this.queue.isEmpty()) {
|
|
3524
|
-
this.events.onQueueEmpty?.();
|
|
3525
|
-
}
|
|
3526
|
-
return batchResult;
|
|
3527
|
-
}
|
|
3528
|
-
finally {
|
|
3529
|
-
this.queue.setProcessing(false);
|
|
3530
|
-
}
|
|
3531
|
-
}
|
|
3532
|
-
async processOperation(operation) {
|
|
3533
|
-
await this.queue.updateOperation(operation.id, { status: 'processing' });
|
|
3534
|
-
try {
|
|
3535
|
-
const response = await this.executeOperation(operation);
|
|
3536
|
-
await this.queue.updateOperation(operation.id, { status: 'completed' });
|
|
3537
|
-
return { operation, success: true, response };
|
|
3538
|
-
}
|
|
3539
|
-
catch (error) {
|
|
3540
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
3541
|
-
if (operation.retryCount < operation.maxRetries && this.isRetryableError(error)) {
|
|
3542
|
-
const delay = this.calculateRetryDelay(operation.retryCount);
|
|
3543
|
-
await this.queue.updateOperation(operation.id, {
|
|
3544
|
-
status: 'pending',
|
|
3545
|
-
retryCount: operation.retryCount + 1,
|
|
3546
|
-
error: errorMessage,
|
|
3547
|
-
});
|
|
3548
|
-
setTimeout(() => {
|
|
3549
|
-
if (this.isOnline && !this.queue.isCurrentlyProcessing()) {
|
|
3550
|
-
this.syncPendingOperations();
|
|
3551
|
-
}
|
|
3552
|
-
}, delay);
|
|
3553
|
-
return { operation, success: false, error: `Retrying: ${errorMessage}` };
|
|
3554
|
-
}
|
|
3555
|
-
else {
|
|
3556
|
-
await this.queue.updateOperation(operation.id, {
|
|
3557
|
-
status: 'failed',
|
|
3558
|
-
error: errorMessage,
|
|
3559
|
-
});
|
|
3560
|
-
return { operation, success: false, error: errorMessage };
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3563
|
-
}
|
|
3564
|
-
async executeOperation(operation) {
|
|
3565
|
-
const { method, endpoint, data, headers } = operation;
|
|
3566
|
-
const config = headers ? { headers } : undefined;
|
|
3567
|
-
switch (method) {
|
|
3568
|
-
case 'GET':
|
|
3569
|
-
return (await this.httpPort.get(endpoint, config)).data;
|
|
3570
|
-
case 'POST':
|
|
3571
|
-
return (await this.httpPort.post(endpoint, data, config)).data;
|
|
3572
|
-
case 'PUT':
|
|
3573
|
-
return (await this.httpPort.put(endpoint, data, config)).data;
|
|
3574
|
-
case 'PATCH':
|
|
3575
|
-
return (await this.httpPort.patch(endpoint, data, config)).data;
|
|
3576
|
-
case 'DELETE':
|
|
3577
|
-
return (await this.httpPort.delete(endpoint, config)).data;
|
|
3578
|
-
default:
|
|
3579
|
-
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
isRetryableError(error) {
|
|
3583
|
-
const errorObj = error;
|
|
3584
|
-
if (errorObj.code === 'NETWORK_ERROR')
|
|
3585
|
-
return true;
|
|
3586
|
-
if (errorObj.statusCode && errorObj.statusCode >= 500)
|
|
3587
|
-
return true;
|
|
3588
|
-
if (errorObj.statusCode === 429)
|
|
3589
|
-
return true;
|
|
3590
|
-
const errorMessage = error?.message;
|
|
3591
|
-
if (errorObj.code === 'ECONNABORTED' || errorMessage?.includes('timeout'))
|
|
3592
|
-
return true;
|
|
3593
|
-
return false;
|
|
3594
|
-
}
|
|
3595
|
-
calculateRetryDelay(retryCount) {
|
|
3596
|
-
const delay = this.config.retryDelay * Math.pow(this.config.backoffMultiplier, retryCount);
|
|
3597
|
-
return Math.min(delay, this.config.maxRetryDelay);
|
|
3598
|
-
}
|
|
3599
|
-
delay(ms) {
|
|
3600
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3601
|
-
}
|
|
3602
|
-
isCurrentlyOnline() {
|
|
3603
|
-
return this.isOnline;
|
|
3604
|
-
}
|
|
3605
|
-
async triggerSync() {
|
|
3606
|
-
if (!this.isOnline)
|
|
3607
|
-
return null;
|
|
3608
|
-
if (this.queue.isEmpty()) {
|
|
3609
|
-
return { totalOperations: 0, successCount: 0, failureCount: 0, results: [] };
|
|
3610
|
-
}
|
|
3611
|
-
return await this.syncPendingOperations();
|
|
3612
|
-
}
|
|
3613
|
-
getSyncStatus() {
|
|
3614
|
-
return {
|
|
3615
|
-
isOnline: this.isOnline,
|
|
3616
|
-
isProcessing: this.queue.isCurrentlyProcessing(),
|
|
3617
|
-
queueStats: this.queue.getStats(),
|
|
3618
|
-
};
|
|
3619
|
-
}
|
|
3620
|
-
destroy() {
|
|
3621
|
-
this.destroy$.next();
|
|
3622
|
-
this.destroy$.complete();
|
|
3623
|
-
this.networkSubscription?.unsubscribe();
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
|
|
3627
|
-
class OfflineManager {
|
|
3628
|
-
get queue$() {
|
|
3629
|
-
return this.queueSubject.asObservable();
|
|
3630
|
-
}
|
|
3631
|
-
get syncStatus$() {
|
|
3632
|
-
return this.syncStatusSubject.asObservable();
|
|
3633
|
-
}
|
|
3634
|
-
constructor(storage, httpPort, networkMonitor, config = {}, events = {}, _cache) {
|
|
3635
|
-
this.queueSubject = new BehaviorSubject([]);
|
|
3636
|
-
this.syncStatusSubject = new BehaviorSubject({
|
|
3637
|
-
isOnline: true,
|
|
3638
|
-
isProcessing: false,
|
|
3639
|
-
queueStats: { total: 0, pending: 0, processing: 0, completed: 0, failed: 0 },
|
|
3640
|
-
});
|
|
3641
|
-
this.destroy$ = new Subject();
|
|
3642
|
-
const finalConfig = { ...DEFAULT_QUEUE_CONFIG, ...config };
|
|
3643
|
-
const wrappedEvents = {
|
|
3644
|
-
...events,
|
|
3645
|
-
onOperationAdded: (op) => {
|
|
3646
|
-
this.updateQueueState();
|
|
3647
|
-
events.onOperationAdded?.(op);
|
|
3648
|
-
},
|
|
3649
|
-
onOperationCompleted: (result) => {
|
|
3650
|
-
this.updateQueueState();
|
|
3651
|
-
events.onOperationCompleted?.(result);
|
|
3652
|
-
},
|
|
3653
|
-
onOperationFailed: (result) => {
|
|
3654
|
-
this.updateQueueState();
|
|
3655
|
-
events.onOperationFailed?.(result);
|
|
3656
|
-
},
|
|
3657
|
-
onBatchSyncCompleted: (result) => {
|
|
3658
|
-
this.updateQueueState();
|
|
3659
|
-
events.onBatchSyncCompleted?.(result);
|
|
3660
|
-
},
|
|
3661
|
-
};
|
|
3662
|
-
this.queue = new OperationQueue(storage, finalConfig, wrappedEvents);
|
|
3663
|
-
this.syncManager = new SyncManager(this.queue, httpPort, networkMonitor, finalConfig, wrappedEvents);
|
|
3664
|
-
this.updateQueueState();
|
|
3665
|
-
}
|
|
3666
|
-
updateQueueState() {
|
|
3667
|
-
this.queueSubject.next(this.queue.getPendingOperations());
|
|
3668
|
-
this.syncStatusSubject.next(this.syncManager.getSyncStatus());
|
|
3669
|
-
}
|
|
3670
|
-
async queueOperation(type, resource, endpoint, method, data, priority = 1) {
|
|
3671
|
-
const id = await this.queue.addOperation(type, resource, endpoint, method, data, priority);
|
|
3672
|
-
this.updateQueueState();
|
|
3673
|
-
return id;
|
|
3674
|
-
}
|
|
3675
|
-
async queueReceiptCreation(receiptData, priority = 2) {
|
|
3676
|
-
return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts', 'POST', receiptData, priority);
|
|
3677
|
-
}
|
|
3678
|
-
async queueReceiptVoid(voidData, priority = 3) {
|
|
3679
|
-
return await this.queueOperation('DELETE', 'receipt', '/mf1/receipts', 'DELETE', voidData, priority);
|
|
3680
|
-
}
|
|
3681
|
-
async queueReceiptReturn(returnData, priority = 3) {
|
|
3682
|
-
return await this.queueOperation('CREATE', 'receipt', '/mf1/receipts/return', 'POST', returnData, priority);
|
|
3683
|
-
}
|
|
3684
|
-
async queueCashierCreation(cashierData, priority = 1) {
|
|
3685
|
-
return await this.queueOperation('CREATE', 'cashier', '/mf1/cashiers', 'POST', cashierData, priority);
|
|
3686
|
-
}
|
|
3687
|
-
isOnline() {
|
|
3688
|
-
return this.syncManager.isCurrentlyOnline();
|
|
3689
|
-
}
|
|
3690
|
-
getStatus() {
|
|
3691
|
-
return this.syncManager.getSyncStatus();
|
|
3692
|
-
}
|
|
3693
|
-
getPendingCount() {
|
|
3694
|
-
return this.queue.getPendingOperations().length;
|
|
3695
|
-
}
|
|
3696
|
-
isEmpty() {
|
|
3697
|
-
return this.queue.isEmpty();
|
|
3698
|
-
}
|
|
3699
|
-
async sync() {
|
|
3700
|
-
const result = await this.syncManager.triggerSync();
|
|
3701
|
-
this.updateQueueState();
|
|
3702
|
-
return result;
|
|
3703
|
-
}
|
|
3704
|
-
async retryFailed() {
|
|
3705
|
-
await this.queue.retryFailed();
|
|
3706
|
-
this.updateQueueState();
|
|
3707
|
-
if (this.isOnline()) {
|
|
3708
|
-
await this.sync();
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
async clearCompleted() {
|
|
3712
|
-
await this.queue.clearCompleted();
|
|
3713
|
-
this.updateQueueState();
|
|
3714
|
-
}
|
|
3715
|
-
async clearFailed() {
|
|
3716
|
-
await this.queue.clearFailed();
|
|
3717
|
-
this.updateQueueState();
|
|
3718
|
-
}
|
|
3719
|
-
async clearAll() {
|
|
3720
|
-
await this.queue.clearQueue();
|
|
3721
|
-
this.updateQueueState();
|
|
3722
|
-
}
|
|
3723
|
-
getOperation(id) {
|
|
3724
|
-
return this.queue.getOperation(id);
|
|
3725
|
-
}
|
|
3726
|
-
async removeOperation(id) {
|
|
3727
|
-
const result = await this.queue.removeOperation(id);
|
|
3728
|
-
this.updateQueueState();
|
|
3729
|
-
return result;
|
|
3730
|
-
}
|
|
3731
|
-
getQueueStats() {
|
|
3732
|
-
return this.queue.getStats();
|
|
3733
|
-
}
|
|
3734
|
-
startAutoSync() {
|
|
3735
|
-
this.queue.startAutoSync();
|
|
3736
|
-
}
|
|
3737
|
-
stopAutoSync() {
|
|
3738
|
-
this.queue.stopAutoSync();
|
|
3739
|
-
}
|
|
3740
|
-
destroy() {
|
|
3741
|
-
this.destroy$.next();
|
|
3742
|
-
this.destroy$.complete();
|
|
3743
|
-
this.queue.destroy();
|
|
3744
|
-
this.syncManager.destroy();
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
|
|
3748
|
-
class CompressionAdapter {
|
|
3749
|
-
compress(data, threshold = 1024) {
|
|
3750
|
-
const originalSize = data.length * 2;
|
|
3751
|
-
if (originalSize < threshold) {
|
|
3752
|
-
return {
|
|
3753
|
-
data,
|
|
3754
|
-
compressed: false,
|
|
3755
|
-
originalSize,
|
|
3756
|
-
compressedSize: originalSize,
|
|
3757
|
-
};
|
|
3758
|
-
}
|
|
3759
|
-
try {
|
|
3760
|
-
const compressed = this.compressString(data);
|
|
3761
|
-
const compressedSize = compressed.length * 2;
|
|
3762
|
-
if (compressedSize < originalSize) {
|
|
3763
|
-
return {
|
|
3764
|
-
data: compressed,
|
|
3765
|
-
compressed: true,
|
|
3766
|
-
originalSize,
|
|
3767
|
-
compressedSize,
|
|
3768
|
-
};
|
|
3769
|
-
}
|
|
3770
|
-
return {
|
|
3771
|
-
data,
|
|
3772
|
-
compressed: false,
|
|
3773
|
-
originalSize,
|
|
3774
|
-
compressedSize: originalSize,
|
|
3775
|
-
};
|
|
3776
|
-
}
|
|
3777
|
-
catch {
|
|
3778
|
-
return {
|
|
3779
|
-
data,
|
|
3780
|
-
compressed: false,
|
|
3781
|
-
originalSize,
|
|
3782
|
-
compressedSize: originalSize,
|
|
3783
|
-
};
|
|
3784
|
-
}
|
|
3785
|
-
}
|
|
3786
|
-
decompress(data, compressed) {
|
|
3787
|
-
if (!compressed) {
|
|
3788
|
-
return { data, wasCompressed: false };
|
|
3789
|
-
}
|
|
3790
|
-
try {
|
|
3791
|
-
const decompressed = this.decompressString(data);
|
|
3792
|
-
return { data: decompressed, wasCompressed: true };
|
|
3793
|
-
}
|
|
3794
|
-
catch {
|
|
3795
|
-
return { data, wasCompressed: false };
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
estimateSavings(data) {
|
|
3799
|
-
const repeated = data.match(/(.)\1{3,}/g);
|
|
3800
|
-
if (!repeated)
|
|
3801
|
-
return 0;
|
|
3802
|
-
let savings = 0;
|
|
3803
|
-
for (const match of repeated) {
|
|
3804
|
-
const originalBytes = match.length * 2;
|
|
3805
|
-
const compressedBytes = 6;
|
|
3806
|
-
if (originalBytes > compressedBytes) {
|
|
3807
|
-
savings += originalBytes - compressedBytes;
|
|
3808
|
-
}
|
|
3809
|
-
}
|
|
3810
|
-
return Math.min(savings, data.length * 2 * 0.5);
|
|
3811
|
-
}
|
|
3812
|
-
compressString(input) {
|
|
3813
|
-
let compressed = '';
|
|
3814
|
-
let i = 0;
|
|
3815
|
-
while (i < input.length) {
|
|
3816
|
-
let count = 1;
|
|
3817
|
-
const char = input[i];
|
|
3818
|
-
while (i + count < input.length && input[i + count] === char && count < 255) {
|
|
3819
|
-
count++;
|
|
3820
|
-
}
|
|
3821
|
-
if (count > 3) {
|
|
3822
|
-
compressed += `~${count}${char}`;
|
|
3823
|
-
}
|
|
3824
|
-
else {
|
|
3825
|
-
for (let j = 0; j < count; j++) {
|
|
3826
|
-
compressed += char;
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
i += count;
|
|
3830
|
-
}
|
|
3831
|
-
return `COMP:${btoa(compressed)}`;
|
|
3832
|
-
}
|
|
3833
|
-
decompressString(input) {
|
|
3834
|
-
if (!input.startsWith('COMP:')) {
|
|
3835
|
-
return input;
|
|
3836
|
-
}
|
|
3837
|
-
const encodedData = input.substring(5);
|
|
3838
|
-
if (!encodedData) {
|
|
3839
|
-
return input;
|
|
3840
|
-
}
|
|
3841
|
-
const compressed = atob(encodedData);
|
|
3842
|
-
let decompressed = '';
|
|
3843
|
-
let i = 0;
|
|
3844
|
-
while (i < compressed.length) {
|
|
3845
|
-
if (compressed[i] === '~' && i + 2 < compressed.length) {
|
|
3846
|
-
let countStr = '';
|
|
3847
|
-
i++;
|
|
3848
|
-
while (i < compressed.length) {
|
|
3849
|
-
const char = compressed[i];
|
|
3850
|
-
if (char && /\d/.test(char)) {
|
|
3851
|
-
countStr += char;
|
|
3852
|
-
i++;
|
|
3853
|
-
}
|
|
3854
|
-
else {
|
|
3855
|
-
break;
|
|
3856
|
-
}
|
|
3857
|
-
}
|
|
3858
|
-
if (countStr && i < compressed.length) {
|
|
3859
|
-
const count = parseInt(countStr, 10);
|
|
3860
|
-
const char = compressed[i];
|
|
3861
|
-
for (let j = 0; j < count; j++) {
|
|
3862
|
-
decompressed += char;
|
|
3863
|
-
}
|
|
3864
|
-
i++;
|
|
3865
|
-
}
|
|
3866
|
-
}
|
|
3867
|
-
else {
|
|
3868
|
-
decompressed += compressed[i];
|
|
3869
|
-
i++;
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
return decompressed;
|
|
3873
|
-
}
|
|
3874
|
-
}
|
|
3875
|
-
function compressData(data, threshold = 1024) {
|
|
3876
|
-
return new CompressionAdapter().compress(data, threshold);
|
|
3877
|
-
}
|
|
3878
|
-
function decompressData(data, compressed) {
|
|
3879
|
-
return new CompressionAdapter().decompress(data, compressed);
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
|
-
const log$e = createPrefixedLogger('CACHE-RN');
|
|
3883
|
-
/**
|
|
3884
|
-
* React Native cache adapter using SQLite (Expo or react-native-sqlite-storage)
|
|
3885
|
-
* Cache never expires - data persists until explicitly invalidated
|
|
3886
|
-
*/
|
|
3887
|
-
class ReactNativeCacheAdapter {
|
|
3888
|
-
constructor(options = {}) {
|
|
3889
|
-
this.db = null;
|
|
3890
|
-
this.initPromise = null;
|
|
3891
|
-
this.isExpo = false;
|
|
3892
|
-
this.hasCompressedColumn = false;
|
|
3893
|
-
this.options = {
|
|
3894
|
-
maxSize: 50 * 1024 * 1024, // 50MB
|
|
3895
|
-
maxEntries: 10000,
|
|
3896
|
-
compression: false,
|
|
3897
|
-
compressionThreshold: 1024,
|
|
3898
|
-
...options,
|
|
3899
|
-
};
|
|
3900
|
-
this.initPromise = this.initialize();
|
|
3901
|
-
}
|
|
3902
|
-
normalizeResults(results) {
|
|
3903
|
-
if (this.isExpo) {
|
|
3904
|
-
const expoResults = results;
|
|
3905
|
-
if (Array.isArray(expoResults)) {
|
|
3906
|
-
return expoResults;
|
|
3907
|
-
}
|
|
3908
|
-
return expoResults.results || [];
|
|
3909
|
-
}
|
|
3910
|
-
else {
|
|
3911
|
-
const rnResults = results;
|
|
3912
|
-
const rows = rnResults.rows;
|
|
3913
|
-
if (!rows || rows.length === 0)
|
|
3914
|
-
return [];
|
|
3915
|
-
const normalizedRows = [];
|
|
3916
|
-
for (let i = 0; i < rows.length; i++) {
|
|
3917
|
-
normalizedRows.push(rows.item(i));
|
|
3918
|
-
}
|
|
3919
|
-
return normalizedRows;
|
|
3920
|
-
}
|
|
3921
|
-
}
|
|
3922
|
-
async initialize() {
|
|
3923
|
-
if (this.db)
|
|
3924
|
-
return;
|
|
3925
|
-
try {
|
|
3926
|
-
// Try Expo SQLite first
|
|
3927
|
-
const ExpoSQLite = require('expo-sqlite');
|
|
3928
|
-
this.db = await ExpoSQLite.openDatabaseAsync(ReactNativeCacheAdapter.DB_NAME);
|
|
3929
|
-
this.isExpo = true;
|
|
3930
|
-
await this.createTables();
|
|
3931
|
-
}
|
|
3932
|
-
catch (expoError) {
|
|
3933
|
-
try {
|
|
3934
|
-
// Fallback to react-native-sqlite-storage
|
|
3935
|
-
const SQLite = require('react-native-sqlite-storage');
|
|
3936
|
-
this.db = await new Promise((resolve, reject) => {
|
|
3937
|
-
SQLite.openDatabase({
|
|
3938
|
-
name: ReactNativeCacheAdapter.DB_NAME,
|
|
3939
|
-
location: 'default',
|
|
3940
|
-
}, resolve, reject);
|
|
3941
|
-
});
|
|
3942
|
-
this.isExpo = false;
|
|
3943
|
-
await this.createTables();
|
|
3944
|
-
}
|
|
3945
|
-
catch (rnError) {
|
|
3946
|
-
throw new Error(`Failed to initialize SQLite: Expo error: ${expoError}, RN error: ${rnError}`);
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
|
-
}
|
|
3950
|
-
async createTables() {
|
|
3951
|
-
// Create table with simplified schema (no TTL)
|
|
3952
|
-
const createTableSQL = `
|
|
3953
|
-
CREATE TABLE IF NOT EXISTS ${ReactNativeCacheAdapter.TABLE_NAME} (
|
|
3954
|
-
cache_key TEXT PRIMARY KEY,
|
|
3955
|
-
data TEXT NOT NULL,
|
|
3956
|
-
timestamp INTEGER NOT NULL
|
|
3957
|
-
);
|
|
3958
|
-
|
|
3959
|
-
CREATE INDEX IF NOT EXISTS idx_timestamp ON ${ReactNativeCacheAdapter.TABLE_NAME}(timestamp);
|
|
3960
|
-
`;
|
|
3961
|
-
await this.executeSql(createTableSQL);
|
|
3962
|
-
// Then, run migrations to add new columns if they don't exist
|
|
3963
|
-
await this.runMigrations();
|
|
3964
|
-
}
|
|
3965
|
-
async runMigrations() {
|
|
3966
|
-
log$e.debug('Running database migrations...');
|
|
3967
|
-
try {
|
|
3968
|
-
// Check if compressed column exists
|
|
3969
|
-
this.hasCompressedColumn = await this.checkColumnExists('compressed');
|
|
3970
|
-
if (!this.hasCompressedColumn) {
|
|
3971
|
-
log$e.debug('Adding compressed column to cache table');
|
|
3972
|
-
const addColumnSQL = `ALTER TABLE ${ReactNativeCacheAdapter.TABLE_NAME} ADD COLUMN compressed INTEGER DEFAULT 0`;
|
|
3973
|
-
await this.executeSql(addColumnSQL);
|
|
3974
|
-
this.hasCompressedColumn = true;
|
|
3975
|
-
log$e.debug('Successfully added compressed column');
|
|
3976
|
-
}
|
|
3977
|
-
else {
|
|
3978
|
-
log$e.debug('Compressed column already exists');
|
|
3979
|
-
}
|
|
3980
|
-
log$e.debug('Database migrations completed', {
|
|
3981
|
-
hasCompressedColumn: this.hasCompressedColumn,
|
|
3982
|
-
});
|
|
3983
|
-
}
|
|
3984
|
-
catch (error) {
|
|
3985
|
-
log$e.debug('Migration failed, disabling compression features', error);
|
|
3986
|
-
this.hasCompressedColumn = false;
|
|
3987
|
-
// Don't throw - allow the app to continue even if migration fails
|
|
3988
|
-
// The compressed feature will just be disabled
|
|
3989
|
-
}
|
|
3990
|
-
}
|
|
3991
|
-
async checkColumnExists(columnName) {
|
|
3992
|
-
try {
|
|
3993
|
-
const pragmaSQL = `PRAGMA table_info(${ReactNativeCacheAdapter.TABLE_NAME})`;
|
|
3994
|
-
const results = await this.executeSql(pragmaSQL);
|
|
3995
|
-
const columns = this.normalizeResults(results);
|
|
3996
|
-
log$e.debug('Table columns found', { columns: columns.map((c) => c.name) });
|
|
3997
|
-
return columns.some((column) => column.name === columnName);
|
|
3998
|
-
}
|
|
3999
|
-
catch (error) {
|
|
4000
|
-
log$e.debug('Error checking column existence', error);
|
|
4001
|
-
return false;
|
|
4002
|
-
}
|
|
4003
|
-
}
|
|
4004
|
-
async get(key) {
|
|
4005
|
-
await this.ensureInitialized();
|
|
4006
|
-
const sql = `SELECT * FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key = ?`;
|
|
4007
|
-
log$e.debug('Executing get query', { sql, key });
|
|
4008
|
-
const results = await this.executeSql(sql, [key]);
|
|
4009
|
-
log$e.debug('Get query results', { key, hasResults: !!results });
|
|
4010
|
-
// Normalize results from different SQLite implementations
|
|
4011
|
-
const rows = this.normalizeResults(results);
|
|
4012
|
-
if (!rows || rows.length === 0) {
|
|
4013
|
-
return null;
|
|
4014
|
-
}
|
|
4015
|
-
const row = rows[0];
|
|
4016
|
-
if (!row) {
|
|
4017
|
-
return null;
|
|
4018
|
-
}
|
|
4019
|
-
const isCompressed = this.hasCompressedColumn ? !!row.compressed : false;
|
|
4020
|
-
const rawData = isCompressed ? decompressData(row.data, true).data : row.data;
|
|
4021
|
-
return {
|
|
4022
|
-
data: JSON.parse(rawData),
|
|
4023
|
-
timestamp: row.timestamp,
|
|
4024
|
-
compressed: isCompressed,
|
|
4025
|
-
};
|
|
4026
|
-
}
|
|
4027
|
-
async set(key, data) {
|
|
4028
|
-
const item = {
|
|
4029
|
-
data,
|
|
4030
|
-
timestamp: Date.now(),
|
|
4031
|
-
};
|
|
4032
|
-
log$e.debug('Setting cache item', { key });
|
|
4033
|
-
return this.setItem(key, item);
|
|
4034
|
-
}
|
|
4035
|
-
async setItem(key, item) {
|
|
4036
|
-
await this.ensureInitialized();
|
|
4037
|
-
// Handle compression if enabled and compressed column is available
|
|
4038
|
-
const serializedData = JSON.stringify(item.data);
|
|
4039
|
-
let finalData = serializedData;
|
|
4040
|
-
let isCompressed = false;
|
|
4041
|
-
if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
|
|
4042
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4043
|
-
finalData = compressionResult.data;
|
|
4044
|
-
isCompressed = compressionResult.compressed;
|
|
4045
|
-
log$e.debug('Compression result', {
|
|
4046
|
-
key,
|
|
4047
|
-
originalSize: compressionResult.originalSize,
|
|
4048
|
-
compressedSize: compressionResult.compressedSize,
|
|
4049
|
-
compressed: isCompressed,
|
|
4050
|
-
savings: compressionResult.originalSize - compressionResult.compressedSize,
|
|
4051
|
-
});
|
|
4052
|
-
}
|
|
4053
|
-
log$e.debug('Setting item with metadata', {
|
|
4054
|
-
key,
|
|
4055
|
-
timestamp: item.timestamp,
|
|
4056
|
-
compressed: isCompressed,
|
|
4057
|
-
hasCompressedColumn: this.hasCompressedColumn,
|
|
4058
|
-
});
|
|
4059
|
-
// Build SQL and parameters based on available columns
|
|
4060
|
-
let sql;
|
|
4061
|
-
let params;
|
|
4062
|
-
if (this.hasCompressedColumn) {
|
|
4063
|
-
sql = `
|
|
4064
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4065
|
-
(cache_key, data, timestamp, compressed)
|
|
4066
|
-
VALUES (?, ?, ?, ?)
|
|
4067
|
-
`;
|
|
4068
|
-
params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
|
|
4069
|
-
}
|
|
4070
|
-
else {
|
|
4071
|
-
// Fallback for databases without compressed column
|
|
4072
|
-
sql = `
|
|
4073
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4074
|
-
(cache_key, data, timestamp)
|
|
4075
|
-
VALUES (?, ?, ?)
|
|
4076
|
-
`;
|
|
4077
|
-
params = [key, finalData, item.timestamp];
|
|
4078
|
-
}
|
|
4079
|
-
log$e.debug('Executing setItem SQL', { key, paramsCount: params.length });
|
|
4080
|
-
await this.executeSql(sql, params);
|
|
4081
|
-
}
|
|
4082
|
-
async setBatch(items) {
|
|
4083
|
-
if (items.length === 0)
|
|
4084
|
-
return;
|
|
4085
|
-
await this.ensureInitialized();
|
|
4086
|
-
log$e.debug('Batch setting items', { count: items.length });
|
|
4087
|
-
if (this.isExpo) {
|
|
4088
|
-
await this.db.withTransactionAsync(async () => {
|
|
4089
|
-
for (const [key, item] of items) {
|
|
4090
|
-
await this.setBatchItem(key, item);
|
|
4091
|
-
}
|
|
4092
|
-
});
|
|
4093
|
-
}
|
|
4094
|
-
else {
|
|
4095
|
-
return new Promise((resolve, reject) => {
|
|
4096
|
-
this.db.transaction((tx) => {
|
|
4097
|
-
const promises = items.map(([key, item]) => this.setBatchItemRN(tx, key, item));
|
|
4098
|
-
Promise.all(promises)
|
|
4099
|
-
.then(() => resolve())
|
|
4100
|
-
.catch(reject);
|
|
4101
|
-
}, reject, () => resolve());
|
|
4102
|
-
});
|
|
4103
|
-
}
|
|
4104
|
-
log$e.debug('Batch operation completed', { count: items.length });
|
|
4105
|
-
}
|
|
4106
|
-
async setBatchItem(key, item) {
|
|
4107
|
-
// Handle compression if enabled and compressed column is available
|
|
4108
|
-
const serializedData = JSON.stringify(item.data);
|
|
4109
|
-
let finalData = serializedData;
|
|
4110
|
-
let isCompressed = false;
|
|
4111
|
-
if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
|
|
4112
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4113
|
-
finalData = compressionResult.data;
|
|
4114
|
-
isCompressed = compressionResult.compressed;
|
|
4115
|
-
}
|
|
4116
|
-
// Build SQL and parameters based on available columns
|
|
4117
|
-
let sql;
|
|
4118
|
-
let params;
|
|
4119
|
-
if (this.hasCompressedColumn) {
|
|
4120
|
-
sql = `
|
|
4121
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4122
|
-
(cache_key, data, timestamp, compressed)
|
|
4123
|
-
VALUES (?, ?, ?, ?)
|
|
4124
|
-
`;
|
|
4125
|
-
params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
|
|
4126
|
-
}
|
|
4127
|
-
else {
|
|
4128
|
-
sql = `
|
|
4129
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4130
|
-
(cache_key, data, timestamp)
|
|
4131
|
-
VALUES (?, ?, ?)
|
|
4132
|
-
`;
|
|
4133
|
-
params = [key, finalData, item.timestamp];
|
|
4134
|
-
}
|
|
4135
|
-
await this.db.runAsync(sql, params);
|
|
4136
|
-
}
|
|
4137
|
-
async setBatchItemRN(tx, key, item) {
|
|
4138
|
-
// Handle compression if enabled and compressed column is available
|
|
4139
|
-
const serializedData = JSON.stringify(item.data);
|
|
4140
|
-
let finalData = serializedData;
|
|
4141
|
-
let isCompressed = false;
|
|
4142
|
-
if (this.options.compression && this.options.compressionThreshold && this.hasCompressedColumn) {
|
|
4143
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4144
|
-
finalData = compressionResult.data;
|
|
4145
|
-
isCompressed = compressionResult.compressed;
|
|
4146
|
-
}
|
|
4147
|
-
// Build SQL and parameters based on available columns
|
|
4148
|
-
let sql;
|
|
4149
|
-
let params;
|
|
4150
|
-
if (this.hasCompressedColumn) {
|
|
4151
|
-
sql = `
|
|
4152
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4153
|
-
(cache_key, data, timestamp, compressed)
|
|
4154
|
-
VALUES (?, ?, ?, ?)
|
|
4155
|
-
`;
|
|
4156
|
-
params = [key, finalData, item.timestamp, isCompressed ? 1 : 0];
|
|
4157
|
-
}
|
|
4158
|
-
else {
|
|
4159
|
-
sql = `
|
|
4160
|
-
INSERT OR REPLACE INTO ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4161
|
-
(cache_key, data, timestamp)
|
|
4162
|
-
VALUES (?, ?, ?)
|
|
4163
|
-
`;
|
|
4164
|
-
params = [key, finalData, item.timestamp];
|
|
4165
|
-
}
|
|
4166
|
-
return new Promise((resolve, reject) => {
|
|
4167
|
-
tx.executeSql(sql, params, () => resolve(), (_, error) => {
|
|
4168
|
-
reject(error);
|
|
4169
|
-
return false;
|
|
4170
|
-
});
|
|
4171
|
-
});
|
|
4172
|
-
}
|
|
4173
|
-
async invalidate(pattern) {
|
|
4174
|
-
await this.ensureInitialized();
|
|
4175
|
-
const keys = await this.getKeys(pattern);
|
|
4176
|
-
if (keys.length === 0)
|
|
4177
|
-
return;
|
|
4178
|
-
const placeholders = keys.map(() => '?').join(',');
|
|
4179
|
-
const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME} WHERE cache_key IN (${placeholders})`;
|
|
4180
|
-
await this.executeSql(sql, keys);
|
|
4181
|
-
}
|
|
4182
|
-
async clear() {
|
|
4183
|
-
await this.ensureInitialized();
|
|
4184
|
-
const sql = `DELETE FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
|
|
4185
|
-
await this.executeSql(sql);
|
|
4186
|
-
}
|
|
4187
|
-
async getSize() {
|
|
4188
|
-
await this.ensureInitialized();
|
|
4189
|
-
const sql = `
|
|
4190
|
-
SELECT
|
|
4191
|
-
COUNT(*) as entries,
|
|
4192
|
-
SUM(LENGTH(data)) as bytes
|
|
4193
|
-
FROM ${ReactNativeCacheAdapter.TABLE_NAME}
|
|
4194
|
-
`;
|
|
4195
|
-
const results = await this.executeSql(sql);
|
|
4196
|
-
const rows = this.normalizeResults(results);
|
|
4197
|
-
const row = rows[0] || { entries: 0, bytes: 0 };
|
|
4198
|
-
return {
|
|
4199
|
-
entries: row.entries || 0,
|
|
4200
|
-
bytes: (row.bytes || 0) * 2,
|
|
4201
|
-
lastCleanup: Date.now(),
|
|
4202
|
-
};
|
|
4203
|
-
}
|
|
4204
|
-
async cleanup() {
|
|
4205
|
-
// No cleanup needed - cache never expires
|
|
4206
|
-
return 0;
|
|
4207
|
-
}
|
|
4208
|
-
async getKeys(pattern) {
|
|
4209
|
-
await this.ensureInitialized();
|
|
4210
|
-
let sql = `SELECT cache_key FROM ${ReactNativeCacheAdapter.TABLE_NAME}`;
|
|
4211
|
-
const params = [];
|
|
4212
|
-
if (pattern) {
|
|
4213
|
-
// Simple pattern matching with LIKE
|
|
4214
|
-
const likePattern = pattern.replace(/\*/g, '%').replace(/\?/g, '_');
|
|
4215
|
-
sql += ' WHERE cache_key LIKE ?';
|
|
4216
|
-
params.push(likePattern);
|
|
4217
|
-
}
|
|
4218
|
-
const results = await this.executeSql(sql, params);
|
|
4219
|
-
const keys = [];
|
|
4220
|
-
const rows = this.normalizeResults(results);
|
|
4221
|
-
for (const row of rows) {
|
|
4222
|
-
keys.push(row.cache_key);
|
|
4223
|
-
}
|
|
4224
|
-
return keys;
|
|
4225
|
-
}
|
|
4226
|
-
async executeSql(sql, params = []) {
|
|
4227
|
-
if (this.isExpo) {
|
|
4228
|
-
const expoDB = this.db;
|
|
4229
|
-
if (sql.toLowerCase().includes('select') || sql.toLowerCase().includes('pragma')) {
|
|
4230
|
-
const result = await expoDB.getAllAsync(sql, params);
|
|
4231
|
-
return Array.isArray(result) ? { results: result } : result;
|
|
4232
|
-
}
|
|
4233
|
-
else {
|
|
4234
|
-
return await expoDB.runAsync(sql, params);
|
|
4235
|
-
}
|
|
4236
|
-
}
|
|
4237
|
-
else {
|
|
4238
|
-
// react-native-sqlite-storage
|
|
4239
|
-
return new Promise((resolve, reject) => {
|
|
4240
|
-
this.db.transaction((tx) => {
|
|
4241
|
-
tx.executeSql(sql, params, (_, results) => resolve(results), (_, error) => {
|
|
4242
|
-
reject(error);
|
|
4243
|
-
return false;
|
|
4244
|
-
});
|
|
4245
|
-
});
|
|
4246
|
-
});
|
|
4247
|
-
}
|
|
4248
|
-
}
|
|
4249
|
-
async ensureInitialized() {
|
|
4250
|
-
if (!this.initPromise) {
|
|
4251
|
-
this.initPromise = this.initialize();
|
|
4252
|
-
}
|
|
4253
|
-
await this.initPromise;
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
ReactNativeCacheAdapter.DB_NAME = 'acube_cache.db';
|
|
4257
|
-
ReactNativeCacheAdapter.TABLE_NAME = 'cache_entries';
|
|
4258
|
-
/**
|
|
4259
|
-
* Memory-based fallback cache adapter for environments without SQLite
|
|
4260
|
-
* Cache never expires - data persists until explicitly invalidated
|
|
4261
|
-
*/
|
|
4262
|
-
class MemoryCacheAdapter {
|
|
4263
|
-
constructor(options = {}) {
|
|
4264
|
-
this.cache = new Map();
|
|
4265
|
-
this.totalBytes = 0;
|
|
4266
|
-
this.options = {
|
|
4267
|
-
maxEntries: 1000,
|
|
4268
|
-
...options,
|
|
4269
|
-
};
|
|
4270
|
-
}
|
|
4271
|
-
calculateItemSize(key, item) {
|
|
4272
|
-
// Calculate rough size estimation for memory usage
|
|
4273
|
-
const keySize = key.length * 2; // UTF-16 estimation
|
|
4274
|
-
const itemSize = JSON.stringify(item).length * 2; // UTF-16 estimation
|
|
4275
|
-
return keySize + itemSize;
|
|
4276
|
-
}
|
|
4277
|
-
async get(key) {
|
|
4278
|
-
log$e.debug('Getting cache item', { key });
|
|
4279
|
-
const item = this.cache.get(key);
|
|
4280
|
-
if (!item) {
|
|
4281
|
-
log$e.debug('Cache miss', { key });
|
|
4282
|
-
return null;
|
|
4283
|
-
}
|
|
4284
|
-
// Handle decompression if needed
|
|
4285
|
-
const isCompressed = !!item.compressed;
|
|
4286
|
-
let finalData = item.data;
|
|
4287
|
-
if (isCompressed) {
|
|
4288
|
-
const decompressed = decompressData(item.data, true);
|
|
4289
|
-
finalData = JSON.parse(decompressed.data);
|
|
4290
|
-
}
|
|
4291
|
-
log$e.debug('Cache hit', { key, compressed: isCompressed });
|
|
4292
|
-
return {
|
|
4293
|
-
...item,
|
|
4294
|
-
data: finalData,
|
|
4295
|
-
compressed: isCompressed,
|
|
4296
|
-
};
|
|
4297
|
-
}
|
|
4298
|
-
async set(key, data) {
|
|
4299
|
-
log$e.debug('Setting cache item', { key });
|
|
4300
|
-
// Handle compression if enabled
|
|
4301
|
-
let finalData = data;
|
|
4302
|
-
let isCompressed = false;
|
|
4303
|
-
if (this.options.compression && this.options.compressionThreshold) {
|
|
4304
|
-
const serializedData = JSON.stringify(data);
|
|
4305
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4306
|
-
if (compressionResult.compressed) {
|
|
4307
|
-
finalData = compressionResult.data;
|
|
4308
|
-
isCompressed = true;
|
|
4309
|
-
log$e.debug('Compression result', {
|
|
4310
|
-
key,
|
|
4311
|
-
originalSize: compressionResult.originalSize,
|
|
4312
|
-
compressedSize: compressionResult.compressedSize,
|
|
4313
|
-
savings: compressionResult.originalSize - compressionResult.compressedSize,
|
|
4314
|
-
});
|
|
4315
|
-
}
|
|
4316
|
-
}
|
|
4317
|
-
const item = {
|
|
4318
|
-
data: finalData,
|
|
4319
|
-
timestamp: Date.now(),
|
|
4320
|
-
compressed: isCompressed,
|
|
4321
|
-
};
|
|
4322
|
-
return this.setItem(key, item);
|
|
4323
|
-
}
|
|
4324
|
-
async setItem(key, item) {
|
|
4325
|
-
// Calculate size of new item
|
|
4326
|
-
const newItemSize = this.calculateItemSize(key, item);
|
|
4327
|
-
// If item already exists, subtract old size
|
|
4328
|
-
if (this.cache.has(key)) {
|
|
4329
|
-
const oldItem = this.cache.get(key);
|
|
4330
|
-
const oldItemSize = this.calculateItemSize(key, oldItem);
|
|
4331
|
-
this.totalBytes -= oldItemSize;
|
|
4332
|
-
}
|
|
4333
|
-
// Enforce max entries limit
|
|
4334
|
-
if (this.cache.size >= (this.options.maxEntries || 1000) && !this.cache.has(key)) {
|
|
4335
|
-
const oldestKey = this.cache.keys().next().value;
|
|
4336
|
-
if (oldestKey) {
|
|
4337
|
-
const oldestItem = this.cache.get(oldestKey);
|
|
4338
|
-
const oldestItemSize = this.calculateItemSize(oldestKey, oldestItem);
|
|
4339
|
-
this.totalBytes -= oldestItemSize;
|
|
4340
|
-
this.cache.delete(oldestKey);
|
|
4341
|
-
log$e.debug('Removed oldest item for capacity', { oldestKey, freedBytes: oldestItemSize });
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
// Set new item and update total size
|
|
4345
|
-
this.cache.set(key, item);
|
|
4346
|
-
this.totalBytes += newItemSize;
|
|
4347
|
-
log$e.debug('Updated cache size', {
|
|
4348
|
-
entries: this.cache.size,
|
|
4349
|
-
totalBytes: this.totalBytes,
|
|
4350
|
-
newItemSize,
|
|
4351
|
-
});
|
|
4352
|
-
}
|
|
4353
|
-
async setBatch(items) {
|
|
4354
|
-
if (items.length === 0)
|
|
4355
|
-
return;
|
|
4356
|
-
log$e.debug('Batch setting items', { count: items.length });
|
|
4357
|
-
let totalNewBytes = 0;
|
|
4358
|
-
let totalOldBytes = 0;
|
|
4359
|
-
const itemsToRemove = [];
|
|
4360
|
-
// First pass: calculate size changes and identify capacity issues
|
|
4361
|
-
for (const [key, item] of items) {
|
|
4362
|
-
const newItemSize = this.calculateItemSize(key, item);
|
|
4363
|
-
totalNewBytes += newItemSize;
|
|
4364
|
-
// If item already exists, track old size for removal
|
|
4365
|
-
if (this.cache.has(key)) {
|
|
4366
|
-
const oldItem = this.cache.get(key);
|
|
4367
|
-
const oldItemSize = this.calculateItemSize(key, oldItem);
|
|
4368
|
-
totalOldBytes += oldItemSize;
|
|
4369
|
-
}
|
|
4370
|
-
}
|
|
4371
|
-
// Handle capacity limits - remove oldest items if needed
|
|
4372
|
-
const projectedEntries = this.cache.size + items.filter(([key]) => !this.cache.has(key)).length;
|
|
4373
|
-
const maxEntries = this.options.maxEntries || 1000;
|
|
4374
|
-
if (projectedEntries > maxEntries) {
|
|
4375
|
-
const entriesToRemove = projectedEntries - maxEntries;
|
|
4376
|
-
const oldestKeys = Array.from(this.cache.keys()).slice(0, entriesToRemove);
|
|
4377
|
-
for (const oldKey of oldestKeys) {
|
|
4378
|
-
const oldItem = this.cache.get(oldKey);
|
|
4379
|
-
const oldItemSize = this.calculateItemSize(oldKey, oldItem);
|
|
4380
|
-
this.totalBytes -= oldItemSize;
|
|
4381
|
-
this.cache.delete(oldKey);
|
|
4382
|
-
itemsToRemove.push(oldKey);
|
|
4383
|
-
}
|
|
4384
|
-
if (itemsToRemove.length > 0) {
|
|
4385
|
-
log$e.debug('Removed items for batch capacity', {
|
|
4386
|
-
removedCount: itemsToRemove.length,
|
|
4387
|
-
removedKeys: itemsToRemove,
|
|
4388
|
-
});
|
|
4389
|
-
}
|
|
4390
|
-
}
|
|
4391
|
-
// Update total bytes accounting
|
|
4392
|
-
this.totalBytes = this.totalBytes - totalOldBytes + totalNewBytes;
|
|
4393
|
-
// Second pass: set all items
|
|
4394
|
-
for (const [key, item] of items) {
|
|
4395
|
-
this.cache.set(key, item);
|
|
4396
|
-
}
|
|
4397
|
-
log$e.debug('Batch operation completed', {
|
|
4398
|
-
count: items.length,
|
|
4399
|
-
totalBytes: this.totalBytes,
|
|
4400
|
-
entries: this.cache.size,
|
|
4401
|
-
bytesAdded: totalNewBytes - totalOldBytes,
|
|
4402
|
-
});
|
|
4403
|
-
}
|
|
4404
|
-
async invalidate(pattern) {
|
|
4405
|
-
const regex = this.patternToRegex(pattern);
|
|
4406
|
-
let removed = 0;
|
|
4407
|
-
let bytesFreed = 0;
|
|
4408
|
-
for (const key of this.cache.keys()) {
|
|
4409
|
-
if (regex.test(key)) {
|
|
4410
|
-
const item = this.cache.get(key);
|
|
4411
|
-
const itemSize = this.calculateItemSize(key, item);
|
|
4412
|
-
this.cache.delete(key);
|
|
4413
|
-
this.totalBytes -= itemSize;
|
|
4414
|
-
bytesFreed += itemSize;
|
|
4415
|
-
removed++;
|
|
4416
|
-
}
|
|
4417
|
-
}
|
|
4418
|
-
if (removed > 0) {
|
|
4419
|
-
log$e.debug('Invalidation completed', {
|
|
4420
|
-
pattern,
|
|
4421
|
-
entriesRemoved: removed,
|
|
4422
|
-
bytesFreed,
|
|
4423
|
-
remainingEntries: this.cache.size,
|
|
4424
|
-
remainingBytes: this.totalBytes,
|
|
4425
|
-
});
|
|
4426
|
-
}
|
|
4427
|
-
}
|
|
4428
|
-
async clear() {
|
|
4429
|
-
this.cache.clear();
|
|
4430
|
-
this.totalBytes = 0;
|
|
4431
|
-
log$e.debug('Cache cleared', { entries: 0, bytes: 0 });
|
|
4432
|
-
}
|
|
4433
|
-
async getSize() {
|
|
4434
|
-
return {
|
|
4435
|
-
entries: this.cache.size,
|
|
4436
|
-
bytes: this.totalBytes,
|
|
4437
|
-
lastCleanup: Date.now(),
|
|
4438
|
-
};
|
|
4439
|
-
}
|
|
4440
|
-
async cleanup() {
|
|
4441
|
-
// No cleanup needed - cache never expires
|
|
4442
|
-
return 0;
|
|
4443
|
-
}
|
|
4444
|
-
async getKeys(pattern) {
|
|
4445
|
-
const keys = Array.from(this.cache.keys());
|
|
4446
|
-
if (!pattern)
|
|
4447
|
-
return keys;
|
|
4448
|
-
const regex = this.patternToRegex(pattern);
|
|
4449
|
-
return keys.filter((key) => regex.test(key));
|
|
4450
|
-
}
|
|
4451
|
-
patternToRegex(pattern) {
|
|
4452
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
4453
|
-
const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
4454
|
-
return new RegExp(`^${regexPattern}$`);
|
|
4455
|
-
}
|
|
4456
|
-
}
|
|
4457
|
-
|
|
4458
|
-
const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
|
|
4459
|
-
|
|
4460
|
-
let idbProxyableTypes;
|
|
4461
|
-
let cursorAdvanceMethods;
|
|
4462
|
-
// This is a function to prevent it throwing up in node environments.
|
|
4463
|
-
function getIdbProxyableTypes() {
|
|
4464
|
-
return (idbProxyableTypes ||
|
|
4465
|
-
(idbProxyableTypes = [
|
|
4466
|
-
IDBDatabase,
|
|
4467
|
-
IDBObjectStore,
|
|
4468
|
-
IDBIndex,
|
|
4469
|
-
IDBCursor,
|
|
4470
|
-
IDBTransaction,
|
|
4471
|
-
]));
|
|
4472
|
-
}
|
|
4473
|
-
// This is a function to prevent it throwing up in node environments.
|
|
4474
|
-
function getCursorAdvanceMethods() {
|
|
4475
|
-
return (cursorAdvanceMethods ||
|
|
4476
|
-
(cursorAdvanceMethods = [
|
|
4477
|
-
IDBCursor.prototype.advance,
|
|
4478
|
-
IDBCursor.prototype.continue,
|
|
4479
|
-
IDBCursor.prototype.continuePrimaryKey,
|
|
4480
|
-
]));
|
|
4481
|
-
}
|
|
4482
|
-
const transactionDoneMap = new WeakMap();
|
|
4483
|
-
const transformCache = new WeakMap();
|
|
4484
|
-
const reverseTransformCache = new WeakMap();
|
|
4485
|
-
function promisifyRequest(request) {
|
|
4486
|
-
const promise = new Promise((resolve, reject) => {
|
|
4487
|
-
const unlisten = () => {
|
|
4488
|
-
request.removeEventListener('success', success);
|
|
4489
|
-
request.removeEventListener('error', error);
|
|
4490
|
-
};
|
|
4491
|
-
const success = () => {
|
|
4492
|
-
resolve(wrap(request.result));
|
|
4493
|
-
unlisten();
|
|
4494
|
-
};
|
|
4495
|
-
const error = () => {
|
|
4496
|
-
reject(request.error);
|
|
4497
|
-
unlisten();
|
|
4498
|
-
};
|
|
4499
|
-
request.addEventListener('success', success);
|
|
4500
|
-
request.addEventListener('error', error);
|
|
4501
|
-
});
|
|
4502
|
-
// This mapping exists in reverseTransformCache but doesn't exist in transformCache. This
|
|
4503
|
-
// is because we create many promises from a single IDBRequest.
|
|
4504
|
-
reverseTransformCache.set(promise, request);
|
|
4505
|
-
return promise;
|
|
4506
|
-
}
|
|
4507
|
-
function cacheDonePromiseForTransaction(tx) {
|
|
4508
|
-
// Early bail if we've already created a done promise for this transaction.
|
|
4509
|
-
if (transactionDoneMap.has(tx))
|
|
4510
|
-
return;
|
|
4511
|
-
const done = new Promise((resolve, reject) => {
|
|
4512
|
-
const unlisten = () => {
|
|
4513
|
-
tx.removeEventListener('complete', complete);
|
|
4514
|
-
tx.removeEventListener('error', error);
|
|
4515
|
-
tx.removeEventListener('abort', error);
|
|
4516
|
-
};
|
|
4517
|
-
const complete = () => {
|
|
4518
|
-
resolve();
|
|
4519
|
-
unlisten();
|
|
4520
|
-
};
|
|
4521
|
-
const error = () => {
|
|
4522
|
-
reject(tx.error || new DOMException('AbortError', 'AbortError'));
|
|
4523
|
-
unlisten();
|
|
4524
|
-
};
|
|
4525
|
-
tx.addEventListener('complete', complete);
|
|
4526
|
-
tx.addEventListener('error', error);
|
|
4527
|
-
tx.addEventListener('abort', error);
|
|
4528
|
-
});
|
|
4529
|
-
// Cache it for later retrieval.
|
|
4530
|
-
transactionDoneMap.set(tx, done);
|
|
4531
|
-
}
|
|
4532
|
-
let idbProxyTraps = {
|
|
4533
|
-
get(target, prop, receiver) {
|
|
4534
|
-
if (target instanceof IDBTransaction) {
|
|
4535
|
-
// Special handling for transaction.done.
|
|
4536
|
-
if (prop === 'done')
|
|
4537
|
-
return transactionDoneMap.get(target);
|
|
4538
|
-
// Make tx.store return the only store in the transaction, or undefined if there are many.
|
|
4539
|
-
if (prop === 'store') {
|
|
4540
|
-
return receiver.objectStoreNames[1]
|
|
4541
|
-
? undefined
|
|
4542
|
-
: receiver.objectStore(receiver.objectStoreNames[0]);
|
|
4543
|
-
}
|
|
4544
|
-
}
|
|
4545
|
-
// Else transform whatever we get back.
|
|
4546
|
-
return wrap(target[prop]);
|
|
4547
|
-
},
|
|
4548
|
-
set(target, prop, value) {
|
|
4549
|
-
target[prop] = value;
|
|
4550
|
-
return true;
|
|
4551
|
-
},
|
|
4552
|
-
has(target, prop) {
|
|
4553
|
-
if (target instanceof IDBTransaction &&
|
|
4554
|
-
(prop === 'done' || prop === 'store')) {
|
|
4555
|
-
return true;
|
|
4556
|
-
}
|
|
4557
|
-
return prop in target;
|
|
4558
|
-
},
|
|
4559
|
-
};
|
|
4560
|
-
function replaceTraps(callback) {
|
|
4561
|
-
idbProxyTraps = callback(idbProxyTraps);
|
|
4562
|
-
}
|
|
4563
|
-
function wrapFunction(func) {
|
|
4564
|
-
// Due to expected object equality (which is enforced by the caching in `wrap`), we
|
|
4565
|
-
// only create one new func per func.
|
|
4566
|
-
// Cursor methods are special, as the behaviour is a little more different to standard IDB. In
|
|
4567
|
-
// IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
|
|
4568
|
-
// cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
|
|
4569
|
-
// with real promises, so each advance methods returns a new promise for the cursor object, or
|
|
4570
|
-
// undefined if the end of the cursor has been reached.
|
|
4571
|
-
if (getCursorAdvanceMethods().includes(func)) {
|
|
4572
|
-
return function (...args) {
|
|
4573
|
-
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
|
|
4574
|
-
// the original object.
|
|
4575
|
-
func.apply(unwrap(this), args);
|
|
4576
|
-
return wrap(this.request);
|
|
4577
|
-
};
|
|
4578
|
-
}
|
|
4579
|
-
return function (...args) {
|
|
4580
|
-
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
|
|
4581
|
-
// the original object.
|
|
4582
|
-
return wrap(func.apply(unwrap(this), args));
|
|
4583
|
-
};
|
|
4584
|
-
}
|
|
4585
|
-
function transformCachableValue(value) {
|
|
4586
|
-
if (typeof value === 'function')
|
|
4587
|
-
return wrapFunction(value);
|
|
4588
|
-
// This doesn't return, it just creates a 'done' promise for the transaction,
|
|
4589
|
-
// which is later returned for transaction.done (see idbObjectHandler).
|
|
4590
|
-
if (value instanceof IDBTransaction)
|
|
4591
|
-
cacheDonePromiseForTransaction(value);
|
|
4592
|
-
if (instanceOfAny(value, getIdbProxyableTypes()))
|
|
4593
|
-
return new Proxy(value, idbProxyTraps);
|
|
4594
|
-
// Return the same value back if we're not going to transform it.
|
|
4595
|
-
return value;
|
|
4596
|
-
}
|
|
4597
|
-
function wrap(value) {
|
|
4598
|
-
// We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
|
|
4599
|
-
// IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
|
|
4600
|
-
if (value instanceof IDBRequest)
|
|
4601
|
-
return promisifyRequest(value);
|
|
4602
|
-
// If we've already transformed this value before, reuse the transformed value.
|
|
4603
|
-
// This is faster, but it also provides object equality.
|
|
4604
|
-
if (transformCache.has(value))
|
|
4605
|
-
return transformCache.get(value);
|
|
4606
|
-
const newValue = transformCachableValue(value);
|
|
4607
|
-
// Not all types are transformed.
|
|
4608
|
-
// These may be primitive types, so they can't be WeakMap keys.
|
|
4609
|
-
if (newValue !== value) {
|
|
4610
|
-
transformCache.set(value, newValue);
|
|
4611
|
-
reverseTransformCache.set(newValue, value);
|
|
4612
|
-
}
|
|
4613
|
-
return newValue;
|
|
4614
|
-
}
|
|
4615
|
-
const unwrap = (value) => reverseTransformCache.get(value);
|
|
4616
|
-
|
|
4617
|
-
/**
|
|
4618
|
-
* Open a database.
|
|
4619
|
-
*
|
|
4620
|
-
* @param name Name of the database.
|
|
4621
|
-
* @param version Schema version.
|
|
4622
|
-
* @param callbacks Additional callbacks.
|
|
4623
|
-
*/
|
|
4624
|
-
function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
|
|
4625
|
-
const request = indexedDB.open(name, version);
|
|
4626
|
-
const openPromise = wrap(request);
|
|
4627
|
-
if (upgrade) {
|
|
4628
|
-
request.addEventListener('upgradeneeded', (event) => {
|
|
4629
|
-
upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
|
|
4630
|
-
});
|
|
4631
|
-
}
|
|
4632
|
-
if (blocked) {
|
|
4633
|
-
request.addEventListener('blocked', (event) => blocked(
|
|
4634
|
-
// Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
|
|
4635
|
-
event.oldVersion, event.newVersion, event));
|
|
4636
|
-
}
|
|
4637
|
-
openPromise
|
|
4638
|
-
.then((db) => {
|
|
4639
|
-
if (terminated)
|
|
4640
|
-
db.addEventListener('close', () => terminated());
|
|
4641
|
-
if (blocking) {
|
|
4642
|
-
db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));
|
|
4643
|
-
}
|
|
4644
|
-
})
|
|
4645
|
-
.catch(() => { });
|
|
4646
|
-
return openPromise;
|
|
4647
|
-
}
|
|
4648
|
-
/**
|
|
4649
|
-
* Delete a database.
|
|
4650
|
-
*
|
|
4651
|
-
* @param name Name of the database.
|
|
4652
|
-
*/
|
|
4653
|
-
function deleteDB(name, { blocked } = {}) {
|
|
4654
|
-
const request = indexedDB.deleteDatabase(name);
|
|
4655
|
-
if (blocked) {
|
|
4656
|
-
request.addEventListener('blocked', (event) => blocked(
|
|
4657
|
-
// Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
|
|
4658
|
-
event.oldVersion, event));
|
|
4659
|
-
}
|
|
4660
|
-
return wrap(request).then(() => undefined);
|
|
4661
|
-
}
|
|
4662
|
-
|
|
4663
|
-
const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
|
|
4664
|
-
const writeMethods = ['put', 'add', 'delete', 'clear'];
|
|
4665
|
-
const cachedMethods = new Map();
|
|
4666
|
-
function getMethod(target, prop) {
|
|
4667
|
-
if (!(target instanceof IDBDatabase &&
|
|
4668
|
-
!(prop in target) &&
|
|
4669
|
-
typeof prop === 'string')) {
|
|
4670
|
-
return;
|
|
4671
|
-
}
|
|
4672
|
-
if (cachedMethods.get(prop))
|
|
4673
|
-
return cachedMethods.get(prop);
|
|
4674
|
-
const targetFuncName = prop.replace(/FromIndex$/, '');
|
|
4675
|
-
const useIndex = prop !== targetFuncName;
|
|
4676
|
-
const isWrite = writeMethods.includes(targetFuncName);
|
|
4677
|
-
if (
|
|
4678
|
-
// Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
|
|
4679
|
-
!(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
|
|
4680
|
-
!(isWrite || readMethods.includes(targetFuncName))) {
|
|
4681
|
-
return;
|
|
4682
|
-
}
|
|
4683
|
-
const method = async function (storeName, ...args) {
|
|
4684
|
-
// isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
|
|
4685
|
-
const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
|
|
4686
|
-
let target = tx.store;
|
|
4687
|
-
if (useIndex)
|
|
4688
|
-
target = target.index(args.shift());
|
|
4689
|
-
// Must reject if op rejects.
|
|
4690
|
-
// If it's a write operation, must reject if tx.done rejects.
|
|
4691
|
-
// Must reject with op rejection first.
|
|
4692
|
-
// Must resolve with op value.
|
|
4693
|
-
// Must handle both promises (no unhandled rejections)
|
|
4694
|
-
return (await Promise.all([
|
|
4695
|
-
target[targetFuncName](...args),
|
|
4696
|
-
isWrite && tx.done,
|
|
4697
|
-
]))[0];
|
|
4698
|
-
};
|
|
4699
|
-
cachedMethods.set(prop, method);
|
|
4700
|
-
return method;
|
|
4701
|
-
}
|
|
4702
|
-
replaceTraps((oldTraps) => ({
|
|
4703
|
-
...oldTraps,
|
|
4704
|
-
get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
|
|
4705
|
-
has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
|
|
4706
|
-
}));
|
|
4707
|
-
|
|
4708
|
-
const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
|
|
4709
|
-
const methodMap = {};
|
|
4710
|
-
const advanceResults = new WeakMap();
|
|
4711
|
-
const ittrProxiedCursorToOriginalProxy = new WeakMap();
|
|
4712
|
-
const cursorIteratorTraps = {
|
|
4713
|
-
get(target, prop) {
|
|
4714
|
-
if (!advanceMethodProps.includes(prop))
|
|
4715
|
-
return target[prop];
|
|
4716
|
-
let cachedFunc = methodMap[prop];
|
|
4717
|
-
if (!cachedFunc) {
|
|
4718
|
-
cachedFunc = methodMap[prop] = function (...args) {
|
|
4719
|
-
advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
|
|
4720
|
-
};
|
|
4721
|
-
}
|
|
4722
|
-
return cachedFunc;
|
|
4723
|
-
},
|
|
4724
|
-
};
|
|
4725
|
-
async function* iterate(...args) {
|
|
4726
|
-
// tslint:disable-next-line:no-this-assignment
|
|
4727
|
-
let cursor = this;
|
|
4728
|
-
if (!(cursor instanceof IDBCursor)) {
|
|
4729
|
-
cursor = await cursor.openCursor(...args);
|
|
4730
|
-
}
|
|
4731
|
-
if (!cursor)
|
|
4732
|
-
return;
|
|
4733
|
-
cursor = cursor;
|
|
4734
|
-
const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
|
|
4735
|
-
ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
|
|
4736
|
-
// Map this double-proxy back to the original, so other cursor methods work.
|
|
4737
|
-
reverseTransformCache.set(proxiedCursor, unwrap(cursor));
|
|
4738
|
-
while (cursor) {
|
|
4739
|
-
yield proxiedCursor;
|
|
4740
|
-
// If one of the advancing methods was not called, call continue().
|
|
4741
|
-
cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
|
|
4742
|
-
advanceResults.delete(proxiedCursor);
|
|
4743
|
-
}
|
|
4744
|
-
}
|
|
4745
|
-
function isIteratorProp(target, prop) {
|
|
4746
|
-
return ((prop === Symbol.asyncIterator &&
|
|
4747
|
-
instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||
|
|
4748
|
-
(prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));
|
|
4749
|
-
}
|
|
4750
|
-
replaceTraps((oldTraps) => ({
|
|
4751
|
-
...oldTraps,
|
|
4752
|
-
get(target, prop, receiver) {
|
|
4753
|
-
if (isIteratorProp(target, prop))
|
|
4754
|
-
return iterate;
|
|
4755
|
-
return oldTraps.get(target, prop, receiver);
|
|
4756
|
-
},
|
|
4757
|
-
has(target, prop) {
|
|
4758
|
-
return isIteratorProp(target, prop) || oldTraps.has(target, prop);
|
|
4759
|
-
},
|
|
4760
|
-
}));
|
|
4761
|
-
|
|
4762
|
-
const log$d = createPrefixedLogger('CACHE-WEB');
|
|
4763
|
-
/**
|
|
4764
|
-
* Web cache adapter using IndexedDB with automatic error recovery
|
|
4765
|
-
* Cache never expires - data persists until explicitly invalidated
|
|
4766
|
-
*/
|
|
4767
|
-
class WebCacheAdapter {
|
|
4768
|
-
constructor(options = {}) {
|
|
4769
|
-
this.db = null;
|
|
4770
|
-
this.initPromise = null;
|
|
4771
|
-
this.retryCount = 0;
|
|
4772
|
-
this.maxRetries = 3;
|
|
4773
|
-
this.options = {
|
|
4774
|
-
maxSize: 50 * 1024 * 1024, // 50MB
|
|
4775
|
-
maxEntries: 10000,
|
|
4776
|
-
compression: false,
|
|
4777
|
-
compressionThreshold: 1024,
|
|
4778
|
-
...options,
|
|
4779
|
-
};
|
|
4780
|
-
this.initPromise = this.initialize();
|
|
4781
|
-
}
|
|
4782
|
-
async initialize() {
|
|
4783
|
-
if (this.db)
|
|
4784
|
-
return;
|
|
4785
|
-
log$d.debug('Initializing IndexedDB cache', {
|
|
4786
|
-
dbName: WebCacheAdapter.DB_NAME,
|
|
4787
|
-
version: WebCacheAdapter.DB_VERSION,
|
|
4788
|
-
});
|
|
4789
|
-
try {
|
|
4790
|
-
this.db = await this.openDatabase();
|
|
4791
|
-
log$d.debug('IndexedDB cache initialized successfully');
|
|
4792
|
-
this.retryCount = 0; // Reset retry count on success
|
|
4793
|
-
}
|
|
4794
|
-
catch (error) {
|
|
4795
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
4796
|
-
log$d.debug('Failed to initialize IndexedDB', { error: errorMessage });
|
|
4797
|
-
// Check if this is a version conflict error
|
|
4798
|
-
if (this.isVersionConflictError(errorMessage)) {
|
|
4799
|
-
await this.handleVersionConflict();
|
|
4800
|
-
}
|
|
4801
|
-
else {
|
|
4802
|
-
throw new Error(`Failed to initialize IndexedDB: ${errorMessage}`);
|
|
4803
|
-
}
|
|
4804
|
-
}
|
|
4805
|
-
}
|
|
4806
|
-
async openDatabase() {
|
|
4807
|
-
return await openDB(WebCacheAdapter.DB_NAME, WebCacheAdapter.DB_VERSION, {
|
|
4808
|
-
upgrade: (db, oldVersion, newVersion, transaction) => {
|
|
4809
|
-
log$d.debug('Database upgrade needed', { oldVersion, newVersion });
|
|
4810
|
-
this.handleUpgrade(db, oldVersion, newVersion, transaction);
|
|
4811
|
-
},
|
|
4812
|
-
blocked: () => {
|
|
4813
|
-
log$d.debug('Database blocked - another tab may be open');
|
|
4814
|
-
},
|
|
4815
|
-
blocking: () => {
|
|
4816
|
-
log$d.debug('Database blocking - will close connection');
|
|
4817
|
-
if (this.db) {
|
|
4818
|
-
this.db.close();
|
|
4819
|
-
this.db = null;
|
|
4820
|
-
}
|
|
4821
|
-
},
|
|
4822
|
-
terminated: () => {
|
|
4823
|
-
log$d.debug('Database connection terminated unexpectedly');
|
|
4824
|
-
this.db = null;
|
|
4825
|
-
},
|
|
4826
|
-
});
|
|
4827
|
-
}
|
|
4828
|
-
handleUpgrade(db, oldVersion, newVersion, transaction) {
|
|
4829
|
-
log$d.debug('Handling database upgrade', { oldVersion, newVersion });
|
|
4830
|
-
// Create cache store if it doesn't exist (initial setup)
|
|
4831
|
-
if (!db.objectStoreNames.contains(WebCacheAdapter.STORE_NAME)) {
|
|
4832
|
-
const store = db.createObjectStore(WebCacheAdapter.STORE_NAME, { keyPath: 'key' });
|
|
4833
|
-
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
4834
|
-
log$d.debug('Created cache store and timestamp index');
|
|
4835
|
-
}
|
|
4836
|
-
// Handle migration from version 1 to 2
|
|
4837
|
-
if (oldVersion < 2) {
|
|
4838
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
4839
|
-
// Remove unused indexes from simplified cache structure
|
|
4840
|
-
const indexesToRemove = ['tags', 'source', 'syncStatus'];
|
|
4841
|
-
indexesToRemove.forEach((indexName) => {
|
|
4842
|
-
try {
|
|
4843
|
-
if (store.indexNames.contains(indexName)) {
|
|
4844
|
-
store.deleteIndex(indexName);
|
|
4845
|
-
log$d.debug(`Removed unused index: ${indexName}`);
|
|
4846
|
-
}
|
|
4847
|
-
}
|
|
4848
|
-
catch (error) {
|
|
4849
|
-
// Ignore errors if indexes don't exist
|
|
4850
|
-
log$d.debug(`Warning: Could not remove index ${indexName}`, error);
|
|
4851
|
-
}
|
|
4852
|
-
});
|
|
4853
|
-
}
|
|
4854
|
-
log$d.debug('Database upgrade completed');
|
|
4855
|
-
}
|
|
4856
|
-
isVersionConflictError(errorMessage) {
|
|
4857
|
-
return (errorMessage.includes('less than the existing version') ||
|
|
4858
|
-
errorMessage.includes('version conflict') ||
|
|
4859
|
-
errorMessage.includes('VersionError'));
|
|
4860
|
-
}
|
|
4861
|
-
async handleVersionConflict() {
|
|
4862
|
-
log$d.debug('Handling version conflict, attempting recovery...');
|
|
4863
|
-
if (this.retryCount >= this.maxRetries) {
|
|
4864
|
-
throw new Error('Failed to resolve IndexedDB version conflict after multiple attempts');
|
|
4865
|
-
}
|
|
4866
|
-
this.retryCount++;
|
|
4867
|
-
try {
|
|
4868
|
-
// Close any existing connection
|
|
4869
|
-
if (this.db) {
|
|
4870
|
-
this.db.close();
|
|
4871
|
-
this.db = null;
|
|
4872
|
-
}
|
|
4873
|
-
// Delete the problematic database
|
|
4874
|
-
log$d.debug('Deleting problematic database to resolve version conflict');
|
|
4875
|
-
await deleteDB(WebCacheAdapter.DB_NAME);
|
|
4876
|
-
// Wait a bit for the deletion to complete
|
|
4877
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
4878
|
-
// Try to open the database again
|
|
4879
|
-
log$d.debug(`Retrying database initialization (attempt ${this.retryCount}/${this.maxRetries})`);
|
|
4880
|
-
this.db = await this.openDatabase();
|
|
4881
|
-
log$d.debug('Successfully recovered from version conflict');
|
|
4882
|
-
this.retryCount = 0; // Reset retry count on success
|
|
4883
|
-
}
|
|
4884
|
-
catch (retryError) {
|
|
4885
|
-
const retryErrorMessage = retryError instanceof Error ? retryError.message : 'Unknown error';
|
|
4886
|
-
log$d.debug('Recovery attempt failed', { attempt: this.retryCount, error: retryErrorMessage });
|
|
4887
|
-
if (this.retryCount < this.maxRetries) {
|
|
4888
|
-
// Try again
|
|
4889
|
-
await this.handleVersionConflict();
|
|
4890
|
-
}
|
|
4891
|
-
else {
|
|
4892
|
-
throw new Error(`Failed to recover from IndexedDB version conflict: ${retryErrorMessage}`);
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
}
|
|
4896
|
-
async get(key) {
|
|
4897
|
-
await this.ensureInitialized();
|
|
4898
|
-
log$d.debug('Getting cache item', { key });
|
|
4899
|
-
try {
|
|
4900
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
|
|
4901
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
4902
|
-
const result = await store.get(key);
|
|
4903
|
-
if (!result) {
|
|
4904
|
-
return null;
|
|
4905
|
-
}
|
|
4906
|
-
const item = result;
|
|
4907
|
-
// Handle decompression if needed
|
|
4908
|
-
const isCompressed = !!item.compressed;
|
|
4909
|
-
let finalData;
|
|
4910
|
-
if (isCompressed) {
|
|
4911
|
-
const decompressed = decompressData(item.data, true);
|
|
4912
|
-
finalData = JSON.parse(decompressed.data);
|
|
4913
|
-
}
|
|
4914
|
-
else {
|
|
4915
|
-
finalData = item.data;
|
|
4916
|
-
}
|
|
4917
|
-
return {
|
|
4918
|
-
data: finalData,
|
|
4919
|
-
timestamp: item.timestamp,
|
|
4920
|
-
compressed: isCompressed,
|
|
4921
|
-
};
|
|
4922
|
-
}
|
|
4923
|
-
catch (error) {
|
|
4924
|
-
log$d.debug('Error getting cache item', { key, error });
|
|
4925
|
-
return null; // Return null on error instead of throwing
|
|
4926
|
-
}
|
|
4927
|
-
}
|
|
4928
|
-
async set(key, data) {
|
|
4929
|
-
const item = {
|
|
4930
|
-
data,
|
|
4931
|
-
timestamp: Date.now(),
|
|
4932
|
-
};
|
|
4933
|
-
return this.setItem(key, item);
|
|
4934
|
-
}
|
|
4935
|
-
async setItem(key, item) {
|
|
4936
|
-
await this.ensureInitialized();
|
|
4937
|
-
// Handle compression if enabled
|
|
4938
|
-
let finalData = item.data;
|
|
4939
|
-
let isCompressed = false;
|
|
4940
|
-
if (this.options.compression && this.options.compressionThreshold) {
|
|
4941
|
-
const serializedData = JSON.stringify(item.data);
|
|
4942
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
4943
|
-
if (compressionResult.compressed) {
|
|
4944
|
-
finalData = compressionResult.data;
|
|
4945
|
-
isCompressed = true;
|
|
4946
|
-
log$d.debug('Compression result', {
|
|
4947
|
-
key,
|
|
4948
|
-
originalSize: compressionResult.originalSize,
|
|
4949
|
-
compressedSize: compressionResult.compressedSize,
|
|
4950
|
-
compressed: isCompressed,
|
|
4951
|
-
savings: compressionResult.originalSize - compressionResult.compressedSize,
|
|
4952
|
-
});
|
|
4953
|
-
}
|
|
4954
|
-
}
|
|
4955
|
-
log$d.debug('Setting cache item', { key, timestamp: item.timestamp, compressed: isCompressed });
|
|
4956
|
-
const storedItem = {
|
|
4957
|
-
key,
|
|
4958
|
-
data: finalData,
|
|
4959
|
-
timestamp: item.timestamp,
|
|
4960
|
-
compressed: isCompressed,
|
|
4961
|
-
};
|
|
4962
|
-
try {
|
|
4963
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
4964
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
4965
|
-
await store.put(storedItem);
|
|
4966
|
-
}
|
|
4967
|
-
catch (error) {
|
|
4968
|
-
log$d.debug('Error setting cache item', { key, error });
|
|
4969
|
-
// Silently fail for cache writes
|
|
4970
|
-
}
|
|
4971
|
-
}
|
|
4972
|
-
async setBatch(items) {
|
|
4973
|
-
if (items.length === 0)
|
|
4974
|
-
return;
|
|
4975
|
-
await this.ensureInitialized();
|
|
4976
|
-
log$d.debug('Batch setting items', { count: items.length });
|
|
4977
|
-
try {
|
|
4978
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
4979
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
4980
|
-
// Process all items in the transaction
|
|
4981
|
-
const promises = items.map(([key, item]) => {
|
|
4982
|
-
const storedItem = this.prepareBatchItem(key, item);
|
|
4983
|
-
return store.put(storedItem);
|
|
4984
|
-
});
|
|
4985
|
-
await Promise.all(promises);
|
|
4986
|
-
log$d.debug('Batch operation completed', { count: items.length });
|
|
4987
|
-
}
|
|
4988
|
-
catch (error) {
|
|
4989
|
-
log$d.debug('Error in batch operation', { count: items.length, error });
|
|
4990
|
-
// Silently fail for batch writes
|
|
4991
|
-
}
|
|
4992
|
-
}
|
|
4993
|
-
prepareBatchItem(key, item) {
|
|
4994
|
-
// Handle compression if enabled (same logic as setItem)
|
|
4995
|
-
let finalData = item.data;
|
|
4996
|
-
let isCompressed = false;
|
|
4997
|
-
if (this.options.compression && this.options.compressionThreshold) {
|
|
4998
|
-
const serializedData = JSON.stringify(item.data);
|
|
4999
|
-
const compressionResult = compressData(serializedData, this.options.compressionThreshold);
|
|
5000
|
-
if (compressionResult.compressed) {
|
|
5001
|
-
finalData = compressionResult.data;
|
|
5002
|
-
isCompressed = true;
|
|
5003
|
-
}
|
|
5004
|
-
}
|
|
5005
|
-
return {
|
|
5006
|
-
key,
|
|
5007
|
-
data: finalData,
|
|
5008
|
-
timestamp: item.timestamp,
|
|
5009
|
-
compressed: isCompressed,
|
|
5010
|
-
};
|
|
5011
|
-
}
|
|
5012
|
-
async invalidate(pattern) {
|
|
5013
|
-
await this.ensureInitialized();
|
|
5014
|
-
const keys = await this.getKeys(pattern);
|
|
5015
|
-
const deletePromises = keys.map((key) => this.delete(key));
|
|
5016
|
-
await Promise.all(deletePromises);
|
|
5017
|
-
}
|
|
5018
|
-
async clear() {
|
|
5019
|
-
await this.ensureInitialized();
|
|
5020
|
-
try {
|
|
5021
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
5022
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5023
|
-
await store.clear();
|
|
5024
|
-
log$d.debug('Cache cleared successfully');
|
|
5025
|
-
}
|
|
5026
|
-
catch (error) {
|
|
5027
|
-
log$d.debug('Error clearing cache', error);
|
|
5028
|
-
// Silently fail for cache clear
|
|
5029
|
-
}
|
|
5030
|
-
}
|
|
5031
|
-
async getSize() {
|
|
5032
|
-
await this.ensureInitialized();
|
|
5033
|
-
try {
|
|
5034
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
|
|
5035
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5036
|
-
let entries = 0;
|
|
5037
|
-
let bytes = 0;
|
|
5038
|
-
// Use cursor for efficient iteration
|
|
5039
|
-
let cursor = await store.openCursor();
|
|
5040
|
-
while (cursor) {
|
|
5041
|
-
entries++;
|
|
5042
|
-
// Rough estimation of size
|
|
5043
|
-
bytes += JSON.stringify(cursor.value).length * 2; // UTF-16 encoding
|
|
5044
|
-
cursor = await cursor.continue();
|
|
5045
|
-
}
|
|
5046
|
-
return {
|
|
5047
|
-
entries,
|
|
5048
|
-
bytes,
|
|
5049
|
-
lastCleanup: Date.now(),
|
|
5050
|
-
};
|
|
5051
|
-
}
|
|
5052
|
-
catch (error) {
|
|
5053
|
-
log$d.debug('Error getting cache size', error);
|
|
5054
|
-
return {
|
|
5055
|
-
entries: 0,
|
|
5056
|
-
bytes: 0,
|
|
5057
|
-
lastCleanup: Date.now(),
|
|
5058
|
-
};
|
|
5059
|
-
}
|
|
5060
|
-
}
|
|
5061
|
-
async cleanup() {
|
|
5062
|
-
// No cleanup needed - cache never expires
|
|
5063
|
-
return 0;
|
|
5064
|
-
}
|
|
5065
|
-
async getKeys(pattern) {
|
|
5066
|
-
await this.ensureInitialized();
|
|
5067
|
-
try {
|
|
5068
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readonly');
|
|
5069
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5070
|
-
const allKeys = (await store.getAllKeys());
|
|
5071
|
-
if (!pattern) {
|
|
5072
|
-
return allKeys;
|
|
5073
|
-
}
|
|
5074
|
-
const regex = this.patternToRegex(pattern);
|
|
5075
|
-
return allKeys.filter((key) => regex.test(key));
|
|
5076
|
-
}
|
|
5077
|
-
catch (error) {
|
|
5078
|
-
log$d.debug('Error getting cache keys', error);
|
|
5079
|
-
return [];
|
|
5080
|
-
}
|
|
5081
|
-
}
|
|
5082
|
-
async delete(key) {
|
|
5083
|
-
await this.ensureInitialized();
|
|
5084
|
-
try {
|
|
5085
|
-
const transaction = this.db.transaction([WebCacheAdapter.STORE_NAME], 'readwrite');
|
|
5086
|
-
const store = transaction.objectStore(WebCacheAdapter.STORE_NAME);
|
|
5087
|
-
await store.delete(key);
|
|
5088
|
-
return true;
|
|
5089
|
-
}
|
|
5090
|
-
catch (error) {
|
|
5091
|
-
log$d.debug('Error deleting cache item', { key, error });
|
|
5092
|
-
return false;
|
|
5093
|
-
}
|
|
5094
|
-
}
|
|
5095
|
-
async ensureInitialized() {
|
|
5096
|
-
if (!this.initPromise) {
|
|
5097
|
-
this.initPromise = this.initialize();
|
|
5098
|
-
}
|
|
5099
|
-
try {
|
|
5100
|
-
await this.initPromise;
|
|
5101
|
-
}
|
|
5102
|
-
catch (error) {
|
|
5103
|
-
log$d.debug('Failed to ensure initialization', error);
|
|
5104
|
-
// Reset and try once more
|
|
5105
|
-
this.initPromise = null;
|
|
5106
|
-
this.db = null;
|
|
5107
|
-
this.initPromise = this.initialize();
|
|
5108
|
-
await this.initPromise;
|
|
5109
|
-
}
|
|
5110
|
-
}
|
|
5111
|
-
patternToRegex(pattern) {
|
|
5112
|
-
// Convert simple glob patterns to regex
|
|
5113
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
5114
|
-
const regexPattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
5115
|
-
return new RegExp(`^${regexPattern}$`);
|
|
5116
|
-
}
|
|
5117
|
-
}
|
|
5118
|
-
WebCacheAdapter.DB_NAME = 'acube_cache';
|
|
5119
|
-
WebCacheAdapter.DB_VERSION = 2;
|
|
5120
|
-
WebCacheAdapter.STORE_NAME = 'cache_entries';
|
|
5121
|
-
|
|
5122
|
-
const log$c = createPrefixedLogger('CACHE-LOADER');
|
|
5123
|
-
function loadCacheAdapter(platform) {
|
|
5124
|
-
try {
|
|
5125
|
-
switch (platform) {
|
|
5126
|
-
case 'web':
|
|
5127
|
-
return new WebCacheAdapter({
|
|
5128
|
-
maxSize: 50 * 1024 * 1024,
|
|
5129
|
-
maxEntries: 10000,
|
|
5130
|
-
compression: false,
|
|
5131
|
-
});
|
|
5132
|
-
case 'react-native':
|
|
5133
|
-
try {
|
|
5134
|
-
return new ReactNativeCacheAdapter({
|
|
5135
|
-
maxSize: 100 * 1024 * 1024,
|
|
5136
|
-
maxEntries: 15000,
|
|
5137
|
-
});
|
|
5138
|
-
}
|
|
5139
|
-
catch {
|
|
5140
|
-
return new MemoryCacheAdapter({
|
|
5141
|
-
maxSize: 10 * 1024 * 1024,
|
|
5142
|
-
maxEntries: 5000,
|
|
5143
|
-
});
|
|
5144
|
-
}
|
|
5145
|
-
case 'node':
|
|
5146
|
-
default:
|
|
5147
|
-
return new MemoryCacheAdapter({
|
|
5148
|
-
maxSize: 10 * 1024 * 1024,
|
|
5149
|
-
maxEntries: 5000,
|
|
5150
|
-
});
|
|
5151
|
-
}
|
|
5152
|
-
}
|
|
5153
|
-
catch (error) {
|
|
5154
|
-
log$c.warn(`Cache adapter not available for platform ${platform}:`, error);
|
|
5155
|
-
return undefined;
|
|
5156
|
-
}
|
|
5157
|
-
}
|
|
5158
|
-
|
|
5159
3263
|
/**
|
|
5160
3264
|
* Mixin that adds multiGet, multiSet, multiRemove to any storage adapter
|
|
5161
3265
|
* Eliminates duplicate code across Node, Web, and React Native adapters
|
|
@@ -5208,7 +3312,7 @@ class BaseSecureStorageAdapter {
|
|
|
5208
3312
|
}
|
|
5209
3313
|
}
|
|
5210
3314
|
|
|
5211
|
-
const log$
|
|
3315
|
+
const log$8 = createPrefixedLogger('NETWORK-BASE');
|
|
5212
3316
|
class NetworkBase {
|
|
5213
3317
|
constructor(initialOnline = true, debounceMs = 300) {
|
|
5214
3318
|
this.destroy$ = new Subject();
|
|
@@ -5228,14 +3332,14 @@ class NetworkBase {
|
|
|
5228
3332
|
const current = this.statusSubject.getValue();
|
|
5229
3333
|
if (current.online !== online) {
|
|
5230
3334
|
this.statusSubject.next({ online, timestamp: Date.now() });
|
|
5231
|
-
log$
|
|
3335
|
+
log$8.debug(`Network status changed: ${online ? 'online' : 'offline'}`);
|
|
5232
3336
|
}
|
|
5233
3337
|
}
|
|
5234
3338
|
destroy() {
|
|
5235
3339
|
this.destroy$.next();
|
|
5236
3340
|
this.destroy$.complete();
|
|
5237
3341
|
this.statusSubject.complete();
|
|
5238
|
-
log$
|
|
3342
|
+
log$8.debug('Network monitor destroyed');
|
|
5239
3343
|
}
|
|
5240
3344
|
}
|
|
5241
3345
|
|
|
@@ -5295,7 +3399,7 @@ class NodeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5295
3399
|
}
|
|
5296
3400
|
}
|
|
5297
3401
|
|
|
5298
|
-
const log$
|
|
3402
|
+
const log$7 = createPrefixedLogger('RN-STORAGE');
|
|
5299
3403
|
/**
|
|
5300
3404
|
* React Native storage adapter using AsyncStorage
|
|
5301
3405
|
* Note: Uses native batch operations for better performance (not base class)
|
|
@@ -5327,7 +3431,7 @@ class ReactNativeStorageAdapter {
|
|
|
5327
3431
|
return await this.AsyncStorage.getItem(key);
|
|
5328
3432
|
}
|
|
5329
3433
|
catch (error) {
|
|
5330
|
-
log$
|
|
3434
|
+
log$7.error('Failed to get item from AsyncStorage:', error);
|
|
5331
3435
|
return null;
|
|
5332
3436
|
}
|
|
5333
3437
|
}
|
|
@@ -5368,7 +3472,7 @@ class ReactNativeStorageAdapter {
|
|
|
5368
3472
|
return await this.AsyncStorage.getAllKeys();
|
|
5369
3473
|
}
|
|
5370
3474
|
catch (error) {
|
|
5371
|
-
log$
|
|
3475
|
+
log$7.error('Failed to get all keys:', error);
|
|
5372
3476
|
return [];
|
|
5373
3477
|
}
|
|
5374
3478
|
}
|
|
@@ -5385,7 +3489,7 @@ class ReactNativeStorageAdapter {
|
|
|
5385
3489
|
return result;
|
|
5386
3490
|
}
|
|
5387
3491
|
catch (error) {
|
|
5388
|
-
log$
|
|
3492
|
+
log$7.error('Failed to get multiple items:', error);
|
|
5389
3493
|
const result = {};
|
|
5390
3494
|
keys.forEach((key) => {
|
|
5391
3495
|
result[key] = null;
|
|
@@ -5435,7 +3539,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5435
3539
|
return;
|
|
5436
3540
|
}
|
|
5437
3541
|
catch {
|
|
5438
|
-
log$
|
|
3542
|
+
log$7.debug('expo-secure-store not available, trying react-native-keychain');
|
|
5439
3543
|
}
|
|
5440
3544
|
try {
|
|
5441
3545
|
const Keychain = require('react-native-keychain');
|
|
@@ -5444,7 +3548,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5444
3548
|
return;
|
|
5445
3549
|
}
|
|
5446
3550
|
catch {
|
|
5447
|
-
log$
|
|
3551
|
+
log$7.error('react-native-keychain not available');
|
|
5448
3552
|
}
|
|
5449
3553
|
throw new Error('No secure storage available. Please install expo-secure-store or react-native-keychain');
|
|
5450
3554
|
}
|
|
@@ -5462,7 +3566,7 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5462
3566
|
}
|
|
5463
3567
|
}
|
|
5464
3568
|
catch (error) {
|
|
5465
|
-
log$
|
|
3569
|
+
log$7.error('Failed to get secure item:', error);
|
|
5466
3570
|
}
|
|
5467
3571
|
return null;
|
|
5468
3572
|
}
|
|
@@ -5499,10 +3603,10 @@ class ReactNativeSecureStorageAdapter extends BaseSecureStorageAdapter {
|
|
|
5499
3603
|
}
|
|
5500
3604
|
}
|
|
5501
3605
|
async clear() {
|
|
5502
|
-
log$
|
|
3606
|
+
log$7.warn('Clear all secure items not fully implemented for React Native');
|
|
5503
3607
|
}
|
|
5504
3608
|
async getAllKeys() {
|
|
5505
|
-
log$
|
|
3609
|
+
log$7.warn('Get all secure keys not implemented for React Native');
|
|
5506
3610
|
return [];
|
|
5507
3611
|
}
|
|
5508
3612
|
async isAvailable() {
|
|
@@ -5720,7 +3824,7 @@ class NodeNetworkMonitor extends NetworkBase {
|
|
|
5720
3824
|
}
|
|
5721
3825
|
}
|
|
5722
3826
|
|
|
5723
|
-
const log$
|
|
3827
|
+
const log$6 = createPrefixedLogger('RN-NETWORK');
|
|
5724
3828
|
/**
|
|
5725
3829
|
* React Native network monitor using RxJS
|
|
5726
3830
|
* Supports both @react-native-community/netinfo and expo-network
|
|
@@ -5733,7 +3837,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5733
3837
|
this.moduleReady$ = new Subject();
|
|
5734
3838
|
this.isExpo = detectPlatform().isExpo;
|
|
5735
3839
|
this.init().catch((error) => {
|
|
5736
|
-
log$
|
|
3840
|
+
log$6.error('Network monitor initialization failed:', error);
|
|
5737
3841
|
});
|
|
5738
3842
|
}
|
|
5739
3843
|
async init() {
|
|
@@ -5752,10 +3856,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5752
3856
|
try {
|
|
5753
3857
|
const module = require('@react-native-community/netinfo');
|
|
5754
3858
|
this.netInfoModule = module.default || module;
|
|
5755
|
-
log$
|
|
3859
|
+
log$6.debug('Loaded @react-native-community/netinfo module');
|
|
5756
3860
|
}
|
|
5757
3861
|
catch (error) {
|
|
5758
|
-
log$
|
|
3862
|
+
log$6.error('Failed to load React Native NetInfo module:', error);
|
|
5759
3863
|
this.netInfoModule = null;
|
|
5760
3864
|
}
|
|
5761
3865
|
}
|
|
@@ -5763,10 +3867,10 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5763
3867
|
try {
|
|
5764
3868
|
const module = require('expo-network');
|
|
5765
3869
|
this.netInfoModule = module.default || module;
|
|
5766
|
-
log$
|
|
3870
|
+
log$6.debug('Loaded expo-network module');
|
|
5767
3871
|
}
|
|
5768
3872
|
catch (error) {
|
|
5769
|
-
log$
|
|
3873
|
+
log$6.error('Failed to load Expo Network module:', error);
|
|
5770
3874
|
this.netInfoModule = null;
|
|
5771
3875
|
}
|
|
5772
3876
|
}
|
|
@@ -5783,16 +3887,16 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5783
3887
|
}
|
|
5784
3888
|
const online = !!(state.isConnected && state.isInternetReachable !== false);
|
|
5785
3889
|
this.updateStatus(online);
|
|
5786
|
-
log$
|
|
3890
|
+
log$6.debug('Initial network state:', online ? 'online' : 'offline');
|
|
5787
3891
|
}
|
|
5788
3892
|
catch (error) {
|
|
5789
|
-
log$
|
|
3893
|
+
log$6.warn('Could not fetch initial network state:', error);
|
|
5790
3894
|
}
|
|
5791
3895
|
}
|
|
5792
3896
|
subscribeToStateChanges() {
|
|
5793
3897
|
if (!this.netInfoModule)
|
|
5794
3898
|
return;
|
|
5795
|
-
log$
|
|
3899
|
+
log$6.debug('Subscribing to network state changes');
|
|
5796
3900
|
const handleState = (state) => {
|
|
5797
3901
|
const online = !!(state.isConnected && (state.isInternetReachable ?? true));
|
|
5798
3902
|
this.updateStatus(online);
|
|
@@ -5834,7 +3938,7 @@ class ReactNativeNetworkMonitor extends NetworkBase {
|
|
|
5834
3938
|
};
|
|
5835
3939
|
}
|
|
5836
3940
|
catch (error) {
|
|
5837
|
-
log$
|
|
3941
|
+
log$6.error('Failed to fetch detailed network info:', error);
|
|
5838
3942
|
return null;
|
|
5839
3943
|
}
|
|
5840
3944
|
}
|
|
@@ -5970,7 +4074,7 @@ class CertificateValidator {
|
|
|
5970
4074
|
}
|
|
5971
4075
|
}
|
|
5972
4076
|
|
|
5973
|
-
const log$
|
|
4077
|
+
const log$5 = createPrefixedLogger('RN-MTLS');
|
|
5974
4078
|
/**
|
|
5975
4079
|
* React Native mTLS Adapter using @a-cube-io/expo-mutual-tls
|
|
5976
4080
|
*/
|
|
@@ -5992,7 +4096,7 @@ class ReactNativeMTLSAdapter {
|
|
|
5992
4096
|
this.expoMTLS = ExpoMutualTls;
|
|
5993
4097
|
// Set up debug logging with the correct event signature
|
|
5994
4098
|
const debugListener = ExpoMutualTls.onDebugLog((event) => {
|
|
5995
|
-
log$
|
|
4099
|
+
log$5.debug(`${event.type}: ${event.message}`, {
|
|
5996
4100
|
method: event.method,
|
|
5997
4101
|
url: event.url,
|
|
5998
4102
|
statusCode: event.statusCode,
|
|
@@ -6002,28 +4106,28 @@ class ReactNativeMTLSAdapter {
|
|
|
6002
4106
|
this.eventListeners.push(debugListener);
|
|
6003
4107
|
// Set up error logging with the correct event signature
|
|
6004
4108
|
const errorListener = ExpoMutualTls.onError((event) => {
|
|
6005
|
-
log$
|
|
4109
|
+
log$5.error(event.message, {
|
|
6006
4110
|
code: event.code,
|
|
6007
4111
|
});
|
|
6008
4112
|
});
|
|
6009
4113
|
this.eventListeners.push(errorListener);
|
|
6010
4114
|
// Set up certificate expiry monitoring with the correct event signature
|
|
6011
4115
|
const expiryListener = ExpoMutualTls.onCertificateExpiry((event) => {
|
|
6012
|
-
log$
|
|
4116
|
+
log$5.warn(`Certificate ${event.subject} expires at ${new Date(event.expiry)}`, {
|
|
6013
4117
|
alias: event.alias,
|
|
6014
4118
|
warning: event.warning,
|
|
6015
4119
|
});
|
|
6016
4120
|
});
|
|
6017
4121
|
this.eventListeners.push(expiryListener);
|
|
6018
|
-
log$
|
|
4122
|
+
log$5.debug('Expo mTLS module loaded successfully');
|
|
6019
4123
|
}
|
|
6020
4124
|
catch (error) {
|
|
6021
|
-
log$
|
|
4125
|
+
log$5.warn('@a-cube-io/expo-mutual-tls not available:', error);
|
|
6022
4126
|
}
|
|
6023
4127
|
}
|
|
6024
4128
|
async isMTLSSupported() {
|
|
6025
4129
|
const supported = this.expoMTLS !== null;
|
|
6026
|
-
log$
|
|
4130
|
+
log$5.debug('mTLS support check:', {
|
|
6027
4131
|
supported,
|
|
6028
4132
|
platform: this.getPlatformInfo().platform,
|
|
6029
4133
|
moduleAvailable: !!this.expoMTLS,
|
|
@@ -6035,7 +4139,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6035
4139
|
throw new MTLSError(MTLSErrorType$1.NOT_SUPPORTED, 'Expo mTLS module not available');
|
|
6036
4140
|
}
|
|
6037
4141
|
this.config = config;
|
|
6038
|
-
log$
|
|
4142
|
+
log$5.debug('Initialized with config:', {
|
|
6039
4143
|
baseUrl: config.baseUrl,
|
|
6040
4144
|
port: config.port,
|
|
6041
4145
|
timeout: config.timeout,
|
|
@@ -6049,7 +4153,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6049
4153
|
if (!this.config) {
|
|
6050
4154
|
throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, 'Adapter not initialized. Call initialize() first.');
|
|
6051
4155
|
}
|
|
6052
|
-
log$
|
|
4156
|
+
log$5.debug('Configuring certificate:', {
|
|
6053
4157
|
format: certificateData.format,
|
|
6054
4158
|
hasPassword: !!certificateData.password,
|
|
6055
4159
|
certificateLength: certificateData.certificate.length,
|
|
@@ -6066,14 +4170,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6066
4170
|
'client-key-service', // keyService
|
|
6067
4171
|
true // enableLogging - let the native module handle its own debug logging
|
|
6068
4172
|
);
|
|
6069
|
-
log$
|
|
4173
|
+
log$5.debug('PEM services configured:', configResult);
|
|
6070
4174
|
if (!configResult.success) {
|
|
6071
4175
|
throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, `PEM configuration failed: ${configResult.state}`);
|
|
6072
4176
|
}
|
|
6073
4177
|
// Step 2: Store the actual PEM certificate and private key
|
|
6074
4178
|
const storeResult = await this.expoMTLS.storePEM(certificateData.certificate, certificateData.privateKey, certificateData.password // passphrase (optional)
|
|
6075
4179
|
);
|
|
6076
|
-
log$
|
|
4180
|
+
log$5.debug('PEM certificate store result:', storeResult);
|
|
6077
4181
|
if (!storeResult) {
|
|
6078
4182
|
throw new MTLSError(MTLSErrorType$1.CERTIFICATE_INVALID, 'Failed to store PEM certificate');
|
|
6079
4183
|
}
|
|
@@ -6086,14 +4190,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6086
4190
|
const configResult = await this.expoMTLS.configureP12('client-p12-service', // keychainService
|
|
6087
4191
|
true // enableLogging - let the native module handle its own debug logging
|
|
6088
4192
|
);
|
|
6089
|
-
log$
|
|
4193
|
+
log$5.debug('P12 service configured:', configResult);
|
|
6090
4194
|
if (!configResult.success) {
|
|
6091
4195
|
throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, `P12 configuration failed: ${configResult.state}`);
|
|
6092
4196
|
}
|
|
6093
4197
|
// Step 2: Store the P12 certificate data
|
|
6094
4198
|
const storeResult = await this.expoMTLS.storeP12(certificateData.certificate, // P12 data in certificate field
|
|
6095
4199
|
certificateData.password);
|
|
6096
|
-
log$
|
|
4200
|
+
log$5.debug('P12 certificate store result:', storeResult);
|
|
6097
4201
|
if (!storeResult) {
|
|
6098
4202
|
throw new MTLSError(MTLSErrorType$1.CERTIFICATE_INVALID, 'Failed to store P12 certificate');
|
|
6099
4203
|
}
|
|
@@ -6107,7 +4211,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6107
4211
|
if (error instanceof MTLSError) {
|
|
6108
4212
|
throw error;
|
|
6109
4213
|
}
|
|
6110
|
-
log$
|
|
4214
|
+
log$5.error('Certificate configuration failed:', error);
|
|
6111
4215
|
throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, 'Failed to configure certificate', error);
|
|
6112
4216
|
}
|
|
6113
4217
|
}
|
|
@@ -6118,38 +4222,38 @@ class ReactNativeMTLSAdapter {
|
|
|
6118
4222
|
try {
|
|
6119
4223
|
// Use static method call
|
|
6120
4224
|
const hasCert = await this.expoMTLS.hasCertificate();
|
|
6121
|
-
log$
|
|
4225
|
+
log$5.debug('Certificate availability check:', hasCert);
|
|
6122
4226
|
return hasCert;
|
|
6123
4227
|
}
|
|
6124
4228
|
catch (error) {
|
|
6125
|
-
log$
|
|
4229
|
+
log$5.error('Certificate check failed:', error);
|
|
6126
4230
|
return false;
|
|
6127
4231
|
}
|
|
6128
4232
|
}
|
|
6129
4233
|
async getCertificateInfo() {
|
|
6130
4234
|
if (!this.expoMTLS) {
|
|
6131
|
-
log$
|
|
4235
|
+
log$5.debug('Certificate info requested but module not available');
|
|
6132
4236
|
return null;
|
|
6133
4237
|
}
|
|
6134
4238
|
try {
|
|
6135
4239
|
const hasCert = await this.hasCertificate();
|
|
6136
4240
|
if (!hasCert) {
|
|
6137
|
-
log$
|
|
4241
|
+
log$5.debug('No certificate stored');
|
|
6138
4242
|
return null;
|
|
6139
4243
|
}
|
|
6140
4244
|
// Use getCertificatesInfo to retrieve information about stored certificates
|
|
6141
4245
|
const result = await this.expoMTLS.getCertificatesInfo();
|
|
6142
4246
|
if (!result || !result.certificates || result.certificates.length === 0) {
|
|
6143
|
-
log$
|
|
4247
|
+
log$5.debug('No certificate information available');
|
|
6144
4248
|
return null;
|
|
6145
4249
|
}
|
|
6146
4250
|
// Get the first certificate (primary client certificate)
|
|
6147
4251
|
const cert = result.certificates[0];
|
|
6148
4252
|
if (!cert) {
|
|
6149
|
-
log$
|
|
4253
|
+
log$5.debug('Certificate data is empty');
|
|
6150
4254
|
return null;
|
|
6151
4255
|
}
|
|
6152
|
-
log$
|
|
4256
|
+
log$5.debug('Retrieved certificate info:', {
|
|
6153
4257
|
subject: cert.subject.commonName,
|
|
6154
4258
|
issuer: cert.issuer.commonName,
|
|
6155
4259
|
validFrom: new Date(cert.validFrom),
|
|
@@ -6168,7 +4272,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6168
4272
|
};
|
|
6169
4273
|
}
|
|
6170
4274
|
catch (error) {
|
|
6171
|
-
log$
|
|
4275
|
+
log$5.error('Failed to get certificate info:', error);
|
|
6172
4276
|
return null;
|
|
6173
4277
|
}
|
|
6174
4278
|
}
|
|
@@ -6179,7 +4283,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6179
4283
|
*/
|
|
6180
4284
|
async parseCertificateData(certificateData) {
|
|
6181
4285
|
if (!this.expoMTLS) {
|
|
6182
|
-
log$
|
|
4286
|
+
log$5.debug('Parse certificate: Module not available');
|
|
6183
4287
|
return null;
|
|
6184
4288
|
}
|
|
6185
4289
|
try {
|
|
@@ -6196,14 +4300,14 @@ class ReactNativeMTLSAdapter {
|
|
|
6196
4300
|
else {
|
|
6197
4301
|
throw new MTLSError(MTLSErrorType$1.CERTIFICATE_INVALID, `Unsupported certificate format: ${certificateData.format}`);
|
|
6198
4302
|
}
|
|
6199
|
-
log$
|
|
4303
|
+
log$5.debug('Certificate parsed successfully:', {
|
|
6200
4304
|
certificateCount: result.certificates.length,
|
|
6201
4305
|
subjects: result.certificates.map((cert) => cert.subject.commonName),
|
|
6202
4306
|
});
|
|
6203
4307
|
return result;
|
|
6204
4308
|
}
|
|
6205
4309
|
catch (error) {
|
|
6206
|
-
log$
|
|
4310
|
+
log$5.error('Failed to parse certificate:', error);
|
|
6207
4311
|
if (error instanceof MTLSError) {
|
|
6208
4312
|
throw error;
|
|
6209
4313
|
}
|
|
@@ -6218,7 +4322,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6218
4322
|
if (!hasCert) {
|
|
6219
4323
|
throw new MTLSError(MTLSErrorType$1.CERTIFICATE_NOT_FOUND, 'No certificate configured');
|
|
6220
4324
|
}
|
|
6221
|
-
log$
|
|
4325
|
+
log$5.debug('Making mTLS request:', {
|
|
6222
4326
|
method: requestConfig.method || 'GET',
|
|
6223
4327
|
url: requestConfig.url,
|
|
6224
4328
|
headers: requestConfig.headers,
|
|
@@ -6226,7 +4330,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6226
4330
|
responseType: requestConfig.responseType,
|
|
6227
4331
|
});
|
|
6228
4332
|
if (requestConfig.data) {
|
|
6229
|
-
log$
|
|
4333
|
+
log$5.debug('mTLS request body:', requestConfig.data);
|
|
6230
4334
|
}
|
|
6231
4335
|
try {
|
|
6232
4336
|
const response = await this.expoMTLS.request(requestConfig.url, {
|
|
@@ -6235,7 +4339,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6235
4339
|
body: requestConfig.data ? JSON.stringify(requestConfig.data) : undefined,
|
|
6236
4340
|
responseType: requestConfig.responseType,
|
|
6237
4341
|
});
|
|
6238
|
-
log$
|
|
4342
|
+
log$5.debug('mTLS request successful:', response);
|
|
6239
4343
|
if (!response.success) {
|
|
6240
4344
|
throw new MTLSError(MTLSErrorType$1.CONNECTION_FAILED, `mTLS request failed: ${response.statusMessage} (${response.statusCode})`, undefined, response.statusCode);
|
|
6241
4345
|
}
|
|
@@ -6247,7 +4351,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6247
4351
|
data = JSON.parse(response.body);
|
|
6248
4352
|
}
|
|
6249
4353
|
catch (parseError) {
|
|
6250
|
-
log$
|
|
4354
|
+
log$5.warn('Failed to parse JSON response:', parseError);
|
|
6251
4355
|
// If parsing fails, keep raw body
|
|
6252
4356
|
}
|
|
6253
4357
|
}
|
|
@@ -6264,7 +4368,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6264
4368
|
};
|
|
6265
4369
|
}
|
|
6266
4370
|
catch (error) {
|
|
6267
|
-
log$
|
|
4371
|
+
log$5.error('mTLS request failed:', error);
|
|
6268
4372
|
throw new MTLSError(MTLSErrorType$1.CONNECTION_FAILED, 'mTLS request failed', error);
|
|
6269
4373
|
}
|
|
6270
4374
|
}
|
|
@@ -6277,18 +4381,18 @@ class ReactNativeMTLSAdapter {
|
|
|
6277
4381
|
*/
|
|
6278
4382
|
async testConnection() {
|
|
6279
4383
|
if (!this.expoMTLS || !this.config) {
|
|
6280
|
-
log$
|
|
4384
|
+
log$5.debug('Diagnostic test: No mTLS module or config available');
|
|
6281
4385
|
return false;
|
|
6282
4386
|
}
|
|
6283
4387
|
try {
|
|
6284
4388
|
const hasCert = await this.hasCertificate();
|
|
6285
4389
|
if (!hasCert) {
|
|
6286
|
-
log$
|
|
4390
|
+
log$5.debug('Diagnostic test: No certificate configured');
|
|
6287
4391
|
return false;
|
|
6288
4392
|
}
|
|
6289
|
-
log$
|
|
4393
|
+
log$5.debug('Running diagnostic test (may fail even if mTLS works):', this.config.baseUrl);
|
|
6290
4394
|
const result = await this.expoMTLS.testConnection(this.config.baseUrl);
|
|
6291
|
-
log$
|
|
4395
|
+
log$5.debug('Diagnostic test result (NOT validation):', {
|
|
6292
4396
|
success: result.success,
|
|
6293
4397
|
statusCode: result.statusCode,
|
|
6294
4398
|
statusMessage: result.statusMessage,
|
|
@@ -6299,13 +4403,13 @@ class ReactNativeMTLSAdapter {
|
|
|
6299
4403
|
return result.success;
|
|
6300
4404
|
}
|
|
6301
4405
|
catch (error) {
|
|
6302
|
-
log$
|
|
4406
|
+
log$5.warn('Diagnostic test failed (this is expected):', error);
|
|
6303
4407
|
return false;
|
|
6304
4408
|
}
|
|
6305
4409
|
}
|
|
6306
4410
|
async removeCertificate() {
|
|
6307
4411
|
if (!this.expoMTLS) {
|
|
6308
|
-
log$
|
|
4412
|
+
log$5.debug('Remove certificate: Module not available');
|
|
6309
4413
|
return;
|
|
6310
4414
|
}
|
|
6311
4415
|
try {
|
|
@@ -6314,10 +4418,10 @@ class ReactNativeMTLSAdapter {
|
|
|
6314
4418
|
this.isConfigured = false;
|
|
6315
4419
|
// Cleanup event listeners
|
|
6316
4420
|
this.cleanupEventListeners();
|
|
6317
|
-
log$
|
|
4421
|
+
log$5.debug('Certificate removed successfully');
|
|
6318
4422
|
}
|
|
6319
4423
|
catch (error) {
|
|
6320
|
-
log$
|
|
4424
|
+
log$5.error('Failed to remove certificate:', error);
|
|
6321
4425
|
throw new MTLSError(MTLSErrorType$1.CONFIGURATION_ERROR, 'Failed to remove certificate', error);
|
|
6322
4426
|
}
|
|
6323
4427
|
}
|
|
@@ -6326,7 +4430,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6326
4430
|
*/
|
|
6327
4431
|
cleanupEventListeners() {
|
|
6328
4432
|
if (this.eventListeners.length > 0) {
|
|
6329
|
-
log$
|
|
4433
|
+
log$5.debug(`Cleaning up ${this.eventListeners.length} event listeners`);
|
|
6330
4434
|
}
|
|
6331
4435
|
// Remove individual listeners if they have remove methods
|
|
6332
4436
|
this.eventListeners.forEach((listener) => {
|
|
@@ -6363,7 +4467,7 @@ class ReactNativeMTLSAdapter {
|
|
|
6363
4467
|
}
|
|
6364
4468
|
}
|
|
6365
4469
|
|
|
6366
|
-
const log$
|
|
4470
|
+
const log$4 = createPrefixedLogger('WEB-MTLS');
|
|
6367
4471
|
/**
|
|
6368
4472
|
* Web mTLS Adapter - Graceful fallback for web browsers
|
|
6369
4473
|
*
|
|
@@ -6377,13 +4481,13 @@ const log$7 = createPrefixedLogger('WEB-MTLS');
|
|
|
6377
4481
|
*/
|
|
6378
4482
|
class WebMTLSAdapter {
|
|
6379
4483
|
constructor() {
|
|
6380
|
-
log$
|
|
6381
|
-
log$
|
|
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');
|
|
6382
4486
|
}
|
|
6383
4487
|
async isMTLSSupported() {
|
|
6384
4488
|
// mTLS is not supported programmatically in web browsers
|
|
6385
4489
|
const supported = false;
|
|
6386
|
-
log$
|
|
4490
|
+
log$4.debug('mTLS support check:', {
|
|
6387
4491
|
supported,
|
|
6388
4492
|
platform: this.getPlatformInfo().platform,
|
|
6389
4493
|
reason: 'Browser security model prevents programmatic certificate configuration',
|
|
@@ -6392,14 +4496,14 @@ class WebMTLSAdapter {
|
|
|
6392
4496
|
return supported;
|
|
6393
4497
|
}
|
|
6394
4498
|
async initialize(config) {
|
|
6395
|
-
log$
|
|
4499
|
+
log$4.warn('Initialized but mTLS not available in web browsers:', {
|
|
6396
4500
|
baseUrl: config.baseUrl,
|
|
6397
4501
|
port: config.port,
|
|
6398
4502
|
recommendation: 'Use standard HTTPS with JWT authentication',
|
|
6399
4503
|
});
|
|
6400
4504
|
}
|
|
6401
4505
|
async configureCertificate(certificateData) {
|
|
6402
|
-
log$
|
|
4506
|
+
log$4.error('Certificate configuration attempted:', {
|
|
6403
4507
|
format: certificateData.format,
|
|
6404
4508
|
reason: 'Not supported in web browsers',
|
|
6405
4509
|
alternatives: [
|
|
@@ -6414,15 +4518,15 @@ class WebMTLSAdapter {
|
|
|
6414
4518
|
}
|
|
6415
4519
|
async hasCertificate() {
|
|
6416
4520
|
// We cannot detect if the browser has certificates configured
|
|
6417
|
-
log$
|
|
4521
|
+
log$4.debug('Certificate availability check: Cannot detect browser certificates programmatically');
|
|
6418
4522
|
return false;
|
|
6419
4523
|
}
|
|
6420
4524
|
async getCertificateInfo() {
|
|
6421
|
-
log$
|
|
4525
|
+
log$4.debug('Certificate info requested: Not accessible in web browsers');
|
|
6422
4526
|
return null;
|
|
6423
4527
|
}
|
|
6424
4528
|
async request(requestConfig) {
|
|
6425
|
-
log$
|
|
4529
|
+
log$4.error('mTLS request attempted:', {
|
|
6426
4530
|
method: requestConfig.method,
|
|
6427
4531
|
url: requestConfig.url,
|
|
6428
4532
|
reason: 'Not supported in web browsers',
|
|
@@ -6437,11 +4541,11 @@ class WebMTLSAdapter {
|
|
|
6437
4541
|
'are properly configured in the browser certificate store.');
|
|
6438
4542
|
}
|
|
6439
4543
|
async testConnection() {
|
|
6440
|
-
log$
|
|
4544
|
+
log$4.debug('Connection test: mTLS not available in web browsers');
|
|
6441
4545
|
return false;
|
|
6442
4546
|
}
|
|
6443
4547
|
async removeCertificate() {
|
|
6444
|
-
log$
|
|
4548
|
+
log$4.debug('Remove certificate: No certificates to remove (not supported in web browsers)');
|
|
6445
4549
|
// No-op - cannot remove certificates programmatically in browsers
|
|
6446
4550
|
}
|
|
6447
4551
|
/**
|
|
@@ -6449,7 +4553,7 @@ class WebMTLSAdapter {
|
|
|
6449
4553
|
* Always returns null for web browsers as mTLS is not supported
|
|
6450
4554
|
*/
|
|
6451
4555
|
getBaseUrl() {
|
|
6452
|
-
log$
|
|
4556
|
+
log$4.debug('Base URL requested: Not supported in web browsers');
|
|
6453
4557
|
return null;
|
|
6454
4558
|
}
|
|
6455
4559
|
getPlatformInfo() {
|
|
@@ -6482,7 +4586,7 @@ class WebMTLSAdapter {
|
|
|
6482
4586
|
}
|
|
6483
4587
|
}
|
|
6484
4588
|
|
|
6485
|
-
const log$
|
|
4589
|
+
const log$3 = createPrefixedLogger('MTLS-LOADER');
|
|
6486
4590
|
function loadMTLSAdapter(platform, config) {
|
|
6487
4591
|
try {
|
|
6488
4592
|
let adapter;
|
|
@@ -6516,7 +4620,7 @@ function loadMTLSAdapter(platform, config) {
|
|
|
6516
4620
|
return adapter;
|
|
6517
4621
|
}
|
|
6518
4622
|
catch (error) {
|
|
6519
|
-
log$
|
|
4623
|
+
log$3.warn(`mTLS adapter not available for platform ${platform}:`, error);
|
|
6520
4624
|
return null;
|
|
6521
4625
|
}
|
|
6522
4626
|
}
|
|
@@ -6526,7 +4630,7 @@ async function initializeAdapterAsync(adapter, config) {
|
|
|
6526
4630
|
if (isSupported) {
|
|
6527
4631
|
await adapter.initialize(config);
|
|
6528
4632
|
const platformInfo = adapter.getPlatformInfo();
|
|
6529
|
-
log$
|
|
4633
|
+
log$3.debug('mTLS adapter initialized:', {
|
|
6530
4634
|
platform: platformInfo.platform,
|
|
6531
4635
|
mtlsSupported: platformInfo.mtlsSupported,
|
|
6532
4636
|
certificateStorage: platformInfo.certificateStorage,
|
|
@@ -6535,31 +4639,28 @@ async function initializeAdapterAsync(adapter, config) {
|
|
|
6535
4639
|
}
|
|
6536
4640
|
}
|
|
6537
4641
|
catch (error) {
|
|
6538
|
-
log$
|
|
4642
|
+
log$3.warn('Failed to initialize mTLS adapter:', error);
|
|
6539
4643
|
}
|
|
6540
4644
|
}
|
|
6541
4645
|
|
|
6542
|
-
const log$
|
|
4646
|
+
const log$2 = createPrefixedLogger('ADAPTER-LOADER');
|
|
6543
4647
|
function loadPlatformAdapters(options = {}) {
|
|
6544
4648
|
const { mtlsConfig } = options;
|
|
6545
4649
|
const { platform } = detectPlatform();
|
|
6546
|
-
log$
|
|
4650
|
+
log$2.debug('Loading adapters for platform:', platform);
|
|
6547
4651
|
const storageAdapters = loadStorageAdapters(platform);
|
|
6548
4652
|
const networkMonitor = loadNetworkMonitor(platform);
|
|
6549
|
-
const cache = loadCacheAdapter(platform);
|
|
6550
4653
|
const mtls = loadMTLSAdapter(platform, mtlsConfig);
|
|
6551
|
-
log$
|
|
4654
|
+
log$2.debug('Adapters loaded:', {
|
|
6552
4655
|
platform,
|
|
6553
4656
|
hasStorage: !!storageAdapters.storage,
|
|
6554
4657
|
hasSecureStorage: !!storageAdapters.secureStorage,
|
|
6555
4658
|
hasNetworkMonitor: !!networkMonitor,
|
|
6556
|
-
hasCache: !!cache,
|
|
6557
4659
|
hasMTLS: !!mtls,
|
|
6558
4660
|
});
|
|
6559
4661
|
return {
|
|
6560
4662
|
...storageAdapters,
|
|
6561
4663
|
networkMonitor,
|
|
6562
|
-
cache,
|
|
6563
4664
|
mtls: mtls || undefined,
|
|
6564
4665
|
};
|
|
6565
4666
|
}
|
|
@@ -6576,12 +4677,9 @@ function createACubeMTLSConfig(baseUrl, timeout, autoInitialize = true, forcePor
|
|
|
6576
4677
|
|
|
6577
4678
|
const DI_TOKENS = {
|
|
6578
4679
|
HTTP_PORT: Symbol('HTTP_PORT'),
|
|
6579
|
-
BASE_HTTP_PORT: Symbol('BASE_HTTP_PORT'),
|
|
6580
4680
|
STORAGE_PORT: Symbol('STORAGE_PORT'),
|
|
6581
4681
|
SECURE_STORAGE_PORT: Symbol('SECURE_STORAGE_PORT'),
|
|
6582
4682
|
NETWORK_PORT: Symbol('NETWORK_PORT'),
|
|
6583
|
-
CACHE_PORT: Symbol('CACHE_PORT'),
|
|
6584
|
-
CACHE_KEY_GENERATOR: Symbol('CACHE_KEY_GENERATOR'),
|
|
6585
4683
|
MTLS_PORT: Symbol('MTLS_PORT'),
|
|
6586
4684
|
TOKEN_STORAGE_PORT: Symbol('TOKEN_STORAGE_PORT'),
|
|
6587
4685
|
RECEIPT_REPOSITORY: Symbol('RECEIPT_REPOSITORY'),
|
|
@@ -6599,7 +4697,6 @@ const DI_TOKENS = {
|
|
|
6599
4697
|
AUTH_SERVICE: Symbol('AUTH_SERVICE'),
|
|
6600
4698
|
AUTHENTICATION_SERVICE: Symbol('AUTHENTICATION_SERVICE'),
|
|
6601
4699
|
CERTIFICATE_SERVICE: Symbol('CERTIFICATE_SERVICE'),
|
|
6602
|
-
OFFLINE_SERVICE: Symbol('OFFLINE_SERVICE'),
|
|
6603
4700
|
NOTIFICATION_SERVICE: Symbol('NOTIFICATION_SERVICE'),
|
|
6604
4701
|
TELEMETRY_SERVICE: Symbol('TELEMETRY_SERVICE'),
|
|
6605
4702
|
};
|
|
@@ -7574,468 +5671,6 @@ class TelemetryRepositoryImpl {
|
|
|
7574
5671
|
}
|
|
7575
5672
|
}
|
|
7576
5673
|
|
|
7577
|
-
const log$4 = createPrefixedLogger('CACHE-KEY');
|
|
7578
|
-
const URL_PATTERNS = [
|
|
7579
|
-
// Receipt (mf1) - specific patterns first
|
|
7580
|
-
{
|
|
7581
|
-
pattern: /^\/mf1\/receipts\/([^/]+)\/returnable-items$/,
|
|
7582
|
-
resource: 'receipt',
|
|
7583
|
-
action: 'returnable',
|
|
7584
|
-
},
|
|
7585
|
-
{
|
|
7586
|
-
pattern: /^\/mf1\/receipts\/([^/]+)\/details$/,
|
|
7587
|
-
resource: 'receipt',
|
|
7588
|
-
action: 'details',
|
|
7589
|
-
},
|
|
7590
|
-
{ pattern: /^\/mf1\/receipts\/([^/]+)$/, resource: 'receipt' },
|
|
7591
|
-
{
|
|
7592
|
-
pattern: /^\/mf1\/pems\/([^/]+)\/receipts$/,
|
|
7593
|
-
resource: 'receipt',
|
|
7594
|
-
parent: 'point-of-sale',
|
|
7595
|
-
isList: true,
|
|
7596
|
-
},
|
|
7597
|
-
{ pattern: /^\/mf1\/receipts$/, resource: 'receipt', isList: true },
|
|
7598
|
-
// Merchant (mf2)
|
|
7599
|
-
{ pattern: /^\/mf2\/merchants\/([^/]+)$/, resource: 'merchant' },
|
|
7600
|
-
{ pattern: /^\/mf2\/merchants$/, resource: 'merchant', isList: true },
|
|
7601
|
-
// Cashier (mf1)
|
|
7602
|
-
{ pattern: /^\/mf1\/cashiers\/me$/, resource: 'cashier', action: 'me' },
|
|
7603
|
-
{ pattern: /^\/mf1\/cashiers\/([^/]+)$/, resource: 'cashier' },
|
|
7604
|
-
{ pattern: /^\/mf1\/cashiers$/, resource: 'cashier', isList: true },
|
|
7605
|
-
// Cash Register (mf1)
|
|
7606
|
-
{ pattern: /^\/mf1\/cash-registers\/([^/]+)$/, resource: 'cash-register' },
|
|
7607
|
-
{ pattern: /^\/mf1\/cash-registers$/, resource: 'cash-register', isList: true },
|
|
7608
|
-
// Point of Sale (mf1)
|
|
7609
|
-
{ pattern: /^\/mf1\/pems\/([^/]+)$/, resource: 'point-of-sale' },
|
|
7610
|
-
{ pattern: /^\/mf1\/pems$/, resource: 'point-of-sale', isList: true },
|
|
7611
|
-
// Nested resources under merchant (mf2)
|
|
7612
|
-
{
|
|
7613
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers\/([^/]+)$/,
|
|
7614
|
-
resource: 'supplier',
|
|
7615
|
-
parent: 'merchant',
|
|
7616
|
-
},
|
|
7617
|
-
{
|
|
7618
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/suppliers$/,
|
|
7619
|
-
resource: 'supplier',
|
|
7620
|
-
parent: 'merchant',
|
|
7621
|
-
isList: true,
|
|
7622
|
-
},
|
|
7623
|
-
{
|
|
7624
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/daily-reports/,
|
|
7625
|
-
resource: 'daily-report',
|
|
7626
|
-
parent: 'merchant',
|
|
7627
|
-
isList: true,
|
|
7628
|
-
},
|
|
7629
|
-
{
|
|
7630
|
-
pattern: /^\/mf2\/merchants\/([^/]+)\/journals/,
|
|
7631
|
-
resource: 'journal',
|
|
7632
|
-
parent: 'merchant',
|
|
7633
|
-
isList: true,
|
|
7634
|
-
},
|
|
7635
|
-
// PEM (mf2)
|
|
7636
|
-
{
|
|
7637
|
-
pattern: /^\/mf2\/pems\/([^/]+)\/certificates$/,
|
|
7638
|
-
resource: 'pem',
|
|
7639
|
-
action: 'certificates',
|
|
7640
|
-
},
|
|
7641
|
-
{ pattern: /^\/mf2\/pems\/([^/]+)$/, resource: 'pem' },
|
|
7642
|
-
// Others
|
|
7643
|
-
{ pattern: /^\/mf1\/notifications/, resource: 'notification', isList: true },
|
|
7644
|
-
{
|
|
7645
|
-
pattern: /^\/mf1\/pems\/([^/]+)\/telemetry$/,
|
|
7646
|
-
resource: 'telemetry',
|
|
7647
|
-
},
|
|
7648
|
-
];
|
|
7649
|
-
const DEFAULT_TTL_CONFIG = {
|
|
7650
|
-
// Data that rarely changes - 30 min TTL for items only
|
|
7651
|
-
merchant: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7652
|
-
'point-of-sale': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7653
|
-
'cash-register': { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7654
|
-
pem: { ttlMs: 30 * 60 * 1000, cacheList: false, cacheItem: false },
|
|
7655
|
-
// Data that changes moderately - 10 min TTL for items only
|
|
7656
|
-
cashier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7657
|
-
supplier: { ttlMs: 10 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7658
|
-
// Data that can change - 5 min TTL for items only
|
|
7659
|
-
receipt: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7660
|
-
'daily-report': { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7661
|
-
journal: { ttlMs: 5 * 60 * 1000, cacheList: false, cacheItem: true },
|
|
7662
|
-
// Real-time data - 1 min TTL
|
|
7663
|
-
notification: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
|
|
7664
|
-
telemetry: { ttlMs: 1 * 60 * 1000, cacheList: false, cacheItem: false },
|
|
7665
|
-
};
|
|
7666
|
-
const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
|
|
7667
|
-
class CacheKeyGenerator {
|
|
7668
|
-
constructor(customConfig) {
|
|
7669
|
-
this.config = { ...DEFAULT_TTL_CONFIG, ...customConfig };
|
|
7670
|
-
log$4.info('CacheKeyGenerator initialized with config:', {
|
|
7671
|
-
resources: Object.keys(this.config),
|
|
7672
|
-
});
|
|
7673
|
-
}
|
|
7674
|
-
generate(url, params) {
|
|
7675
|
-
const parsed = this.parseUrl(url);
|
|
7676
|
-
if (!parsed) {
|
|
7677
|
-
// Fallback: use URL as key
|
|
7678
|
-
const paramStr = params ? this.serializeParams(params) : '';
|
|
7679
|
-
const key = paramStr ? `${url}?${paramStr}` : url;
|
|
7680
|
-
log$4.debug('URL not matched, using fallback key:', { url, key });
|
|
7681
|
-
return key;
|
|
7682
|
-
}
|
|
7683
|
-
const { resource, ids, action, isList, parent } = parsed;
|
|
7684
|
-
if (isList) {
|
|
7685
|
-
const paramStr = params ? this.serializeParams(params) : '';
|
|
7686
|
-
const parentPart = parent && ids.length > 0 ? `${parent}=${ids[0]}&` : '';
|
|
7687
|
-
const key = `${resource}:list:${parentPart}${paramStr}`;
|
|
7688
|
-
log$4.debug('Generated list cache key:', { url, key, resource });
|
|
7689
|
-
return key;
|
|
7690
|
-
}
|
|
7691
|
-
// Single item
|
|
7692
|
-
if (ids.length === 0 && action) {
|
|
7693
|
-
// Special case for endpoints like /cashiers/me
|
|
7694
|
-
const key = `${resource}:${action}`;
|
|
7695
|
-
log$4.debug('Generated special action cache key:', { url, key, resource, action });
|
|
7696
|
-
return key;
|
|
7697
|
-
}
|
|
7698
|
-
let key = `${resource}:${ids.join(':')}`;
|
|
7699
|
-
if (action) {
|
|
7700
|
-
key += `:${action}`;
|
|
7701
|
-
}
|
|
7702
|
-
log$4.debug('Generated item cache key:', { url, key, resource, ids, action });
|
|
7703
|
-
return key;
|
|
7704
|
-
}
|
|
7705
|
-
parseResource(url) {
|
|
7706
|
-
const parsed = this.parseUrl(url);
|
|
7707
|
-
return parsed?.resource;
|
|
7708
|
-
}
|
|
7709
|
-
getTTL(url) {
|
|
7710
|
-
const resource = this.parseResource(url);
|
|
7711
|
-
if (!resource) {
|
|
7712
|
-
log$4.debug('No resource found for URL, using default TTL:', { url, ttl: DEFAULT_TTL });
|
|
7713
|
-
return DEFAULT_TTL;
|
|
7714
|
-
}
|
|
7715
|
-
const ttl = this.config[resource].ttlMs;
|
|
7716
|
-
log$4.debug('TTL for resource:', { url, resource, ttlMs: ttl, ttlMin: ttl / 60000 });
|
|
7717
|
-
return ttl;
|
|
7718
|
-
}
|
|
7719
|
-
shouldCache(url) {
|
|
7720
|
-
const parsed = this.parseUrl(url);
|
|
7721
|
-
if (!parsed) {
|
|
7722
|
-
log$4.debug('URL not recognized, should not cache:', { url });
|
|
7723
|
-
return false;
|
|
7724
|
-
}
|
|
7725
|
-
const { resource, isList } = parsed;
|
|
7726
|
-
const config = this.config[resource];
|
|
7727
|
-
if (isList) {
|
|
7728
|
-
log$4.debug('List endpoint cache decision:', {
|
|
7729
|
-
url,
|
|
7730
|
-
resource,
|
|
7731
|
-
isList: true,
|
|
7732
|
-
shouldCache: config.cacheList,
|
|
7733
|
-
});
|
|
7734
|
-
return config.cacheList;
|
|
7735
|
-
}
|
|
7736
|
-
log$4.debug('Item endpoint cache decision:', {
|
|
7737
|
-
url,
|
|
7738
|
-
resource,
|
|
7739
|
-
isList: false,
|
|
7740
|
-
shouldCache: config.cacheItem,
|
|
7741
|
-
});
|
|
7742
|
-
return config.cacheItem;
|
|
7743
|
-
}
|
|
7744
|
-
getInvalidationPatterns(url, method) {
|
|
7745
|
-
const parsed = this.parseUrl(url);
|
|
7746
|
-
if (!parsed) {
|
|
7747
|
-
log$4.debug('No patterns to invalidate for URL:', { url, method });
|
|
7748
|
-
return [];
|
|
7749
|
-
}
|
|
7750
|
-
const { resource, ids, parent } = parsed;
|
|
7751
|
-
const patterns = [];
|
|
7752
|
-
// Always invalidate list on mutations
|
|
7753
|
-
if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
|
|
7754
|
-
if (parent && ids.length > 0) {
|
|
7755
|
-
patterns.push(`${resource}:list:${parent}=${ids[0]}*`);
|
|
7756
|
-
}
|
|
7757
|
-
patterns.push(`${resource}:list:*`);
|
|
7758
|
-
}
|
|
7759
|
-
// Invalidate specific item on PUT/PATCH/DELETE
|
|
7760
|
-
if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
|
|
7761
|
-
if (ids.length > 0) {
|
|
7762
|
-
patterns.push(`${resource}:${ids.join(':')}*`);
|
|
7763
|
-
}
|
|
7764
|
-
}
|
|
7765
|
-
// Special cases
|
|
7766
|
-
if (resource === 'cashier' && (method === 'PUT' || method === 'DELETE')) {
|
|
7767
|
-
patterns.push('cashier:me');
|
|
7768
|
-
}
|
|
7769
|
-
log$4.debug('Invalidation patterns:', { url, method, patterns });
|
|
7770
|
-
return patterns;
|
|
7771
|
-
}
|
|
7772
|
-
parseUrl(url) {
|
|
7773
|
-
// Remove query string for pattern matching
|
|
7774
|
-
const urlPath = url.split('?')[0];
|
|
7775
|
-
for (const pattern of URL_PATTERNS) {
|
|
7776
|
-
const match = urlPath?.match(pattern.pattern);
|
|
7777
|
-
if (match) {
|
|
7778
|
-
// Extract IDs from capture groups
|
|
7779
|
-
const ids = match.slice(1).filter(Boolean);
|
|
7780
|
-
return {
|
|
7781
|
-
resource: pattern.resource,
|
|
7782
|
-
ids,
|
|
7783
|
-
action: pattern.action,
|
|
7784
|
-
isList: pattern.isList,
|
|
7785
|
-
parent: pattern.parent,
|
|
7786
|
-
};
|
|
7787
|
-
}
|
|
7788
|
-
}
|
|
7789
|
-
return null;
|
|
7790
|
-
}
|
|
7791
|
-
serializeParams(params) {
|
|
7792
|
-
const sortedKeys = Object.keys(params).sort();
|
|
7793
|
-
const parts = [];
|
|
7794
|
-
for (const key of sortedKeys) {
|
|
7795
|
-
const value = params[key];
|
|
7796
|
-
if (value !== undefined && value !== null) {
|
|
7797
|
-
parts.push(`${key}=${String(value)}`);
|
|
7798
|
-
}
|
|
7799
|
-
}
|
|
7800
|
-
return parts.join('&');
|
|
7801
|
-
}
|
|
7802
|
-
}
|
|
7803
|
-
|
|
7804
|
-
const log$3 = createPrefixedLogger('CACHE');
|
|
7805
|
-
class CachingHttpDecorator {
|
|
7806
|
-
constructor(http, cache, keyGenerator, networkMonitor, config = {}) {
|
|
7807
|
-
this.http = http;
|
|
7808
|
-
this.cache = cache;
|
|
7809
|
-
this.keyGenerator = keyGenerator;
|
|
7810
|
-
this.networkMonitor = networkMonitor;
|
|
7811
|
-
this.config = config;
|
|
7812
|
-
this.currentOnlineState = true;
|
|
7813
|
-
this.authToken = null;
|
|
7814
|
-
log$3.info('CachingHttpDecorator initialized', {
|
|
7815
|
-
enabled: config.enabled !== false,
|
|
7816
|
-
hasNetworkMonitor: !!networkMonitor,
|
|
7817
|
-
});
|
|
7818
|
-
this.setupNetworkMonitoring();
|
|
7819
|
-
}
|
|
7820
|
-
setupNetworkMonitoring() {
|
|
7821
|
-
if (this.networkMonitor) {
|
|
7822
|
-
this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
|
|
7823
|
-
if (this.currentOnlineState !== online) {
|
|
7824
|
-
log$3.info('Network state changed:', { online });
|
|
7825
|
-
}
|
|
7826
|
-
this.currentOnlineState = online;
|
|
7827
|
-
});
|
|
7828
|
-
}
|
|
7829
|
-
}
|
|
7830
|
-
isOnline() {
|
|
7831
|
-
if (this.networkMonitor) {
|
|
7832
|
-
return this.currentOnlineState;
|
|
7833
|
-
}
|
|
7834
|
-
if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
|
|
7835
|
-
return navigator.onLine;
|
|
7836
|
-
}
|
|
7837
|
-
return true;
|
|
7838
|
-
}
|
|
7839
|
-
async get(url, config) {
|
|
7840
|
-
const startTime = Date.now();
|
|
7841
|
-
// Check if caching is disabled globally
|
|
7842
|
-
if (this.config.enabled === false) {
|
|
7843
|
-
log$3.debug('GET (cache disabled globally):', { url });
|
|
7844
|
-
return this.http.get(url, config);
|
|
7845
|
-
}
|
|
7846
|
-
// Check if this URL should be cached
|
|
7847
|
-
const shouldCache = this.keyGenerator.shouldCache(url);
|
|
7848
|
-
if (!shouldCache) {
|
|
7849
|
-
log$3.debug('GET (not cacheable - likely a list endpoint):', { url });
|
|
7850
|
-
return this.http.get(url, config);
|
|
7851
|
-
}
|
|
7852
|
-
const cacheKey = this.keyGenerator.generate(url, config?.params);
|
|
7853
|
-
const ttl = this.keyGenerator.getTTL(url);
|
|
7854
|
-
const resource = this.keyGenerator.parseResource(url);
|
|
7855
|
-
log$3.info('GET request starting:', {
|
|
7856
|
-
url,
|
|
7857
|
-
resource,
|
|
7858
|
-
cacheKey,
|
|
7859
|
-
ttlMs: ttl,
|
|
7860
|
-
ttlMin: Math.round(ttl / 60000),
|
|
7861
|
-
params: config?.params,
|
|
7862
|
-
});
|
|
7863
|
-
// Check cache
|
|
7864
|
-
let cached = null;
|
|
7865
|
-
try {
|
|
7866
|
-
cached = await this.cache.get(cacheKey);
|
|
7867
|
-
if (cached) {
|
|
7868
|
-
log$3.debug('Cache entry found:', {
|
|
7869
|
-
cacheKey,
|
|
7870
|
-
timestamp: new Date(cached.timestamp).toISOString(),
|
|
7871
|
-
ageMs: Date.now() - cached.timestamp,
|
|
7872
|
-
});
|
|
7873
|
-
}
|
|
7874
|
-
else {
|
|
7875
|
-
log$3.debug('Cache entry not found:', { cacheKey });
|
|
7876
|
-
}
|
|
7877
|
-
}
|
|
7878
|
-
catch (error) {
|
|
7879
|
-
log$3.warn('Cache lookup failed:', {
|
|
7880
|
-
cacheKey,
|
|
7881
|
-
error: error instanceof Error ? error.message : error,
|
|
7882
|
-
});
|
|
7883
|
-
}
|
|
7884
|
-
if (cached) {
|
|
7885
|
-
const age = Date.now() - cached.timestamp;
|
|
7886
|
-
const isExpired = age >= ttl;
|
|
7887
|
-
log$3.debug('Cache analysis:', {
|
|
7888
|
-
cacheKey,
|
|
7889
|
-
ageMs: age,
|
|
7890
|
-
ageSec: Math.round(age / 1000),
|
|
7891
|
-
ttlMs: ttl,
|
|
7892
|
-
isExpired,
|
|
7893
|
-
isOnline: this.isOnline(),
|
|
7894
|
-
});
|
|
7895
|
-
// If within TTL, return cached data
|
|
7896
|
-
if (!isExpired) {
|
|
7897
|
-
const duration = Date.now() - startTime;
|
|
7898
|
-
log$3.info('CACHE HIT:', {
|
|
7899
|
-
url,
|
|
7900
|
-
cacheKey,
|
|
7901
|
-
ageMs: age,
|
|
7902
|
-
durationMs: duration,
|
|
7903
|
-
});
|
|
7904
|
-
return {
|
|
7905
|
-
data: cached.data,
|
|
7906
|
-
status: 200,
|
|
7907
|
-
headers: { 'x-cache': 'HIT' },
|
|
7908
|
-
};
|
|
7909
|
-
}
|
|
7910
|
-
// If offline and cache is stale, return stale data
|
|
7911
|
-
if (!this.isOnline()) {
|
|
7912
|
-
const duration = Date.now() - startTime;
|
|
7913
|
-
log$3.info('CACHE STALE (offline):', {
|
|
7914
|
-
url,
|
|
7915
|
-
cacheKey,
|
|
7916
|
-
ageMs: age,
|
|
7917
|
-
durationMs: duration,
|
|
7918
|
-
});
|
|
7919
|
-
return {
|
|
7920
|
-
data: cached.data,
|
|
7921
|
-
status: 200,
|
|
7922
|
-
headers: { 'x-cache': 'STALE' },
|
|
7923
|
-
};
|
|
7924
|
-
}
|
|
7925
|
-
log$3.debug('Cache expired, fetching fresh data:', { cacheKey, ageMs: age, ttlMs: ttl });
|
|
7926
|
-
}
|
|
7927
|
-
// Fetch fresh data
|
|
7928
|
-
try {
|
|
7929
|
-
log$3.debug('Fetching from network:', { url });
|
|
7930
|
-
const response = await this.http.get(url, config);
|
|
7931
|
-
// Cache the response
|
|
7932
|
-
try {
|
|
7933
|
-
await this.cache.set(cacheKey, response.data);
|
|
7934
|
-
log$3.debug('Response cached successfully:', { cacheKey });
|
|
7935
|
-
}
|
|
7936
|
-
catch (error) {
|
|
7937
|
-
log$3.error('Failed to cache response:', {
|
|
7938
|
-
cacheKey,
|
|
7939
|
-
error: error instanceof Error ? error.message : error,
|
|
7940
|
-
});
|
|
7941
|
-
}
|
|
7942
|
-
const duration = Date.now() - startTime;
|
|
7943
|
-
log$3.info('CACHE MISS (fetched fresh):', {
|
|
7944
|
-
url,
|
|
7945
|
-
cacheKey,
|
|
7946
|
-
status: response.status,
|
|
7947
|
-
durationMs: duration,
|
|
7948
|
-
});
|
|
7949
|
-
return {
|
|
7950
|
-
...response,
|
|
7951
|
-
headers: { ...response.headers, 'x-cache': 'MISS' },
|
|
7952
|
-
};
|
|
7953
|
-
}
|
|
7954
|
-
catch (error) {
|
|
7955
|
-
// On error, return stale cache if available
|
|
7956
|
-
if (cached) {
|
|
7957
|
-
const duration = Date.now() - startTime;
|
|
7958
|
-
log$3.warn('CACHE STALE (network error):', {
|
|
7959
|
-
url,
|
|
7960
|
-
cacheKey,
|
|
7961
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
7962
|
-
durationMs: duration,
|
|
7963
|
-
});
|
|
7964
|
-
return {
|
|
7965
|
-
data: cached.data,
|
|
7966
|
-
status: 200,
|
|
7967
|
-
headers: { 'x-cache': 'STALE' },
|
|
7968
|
-
};
|
|
7969
|
-
}
|
|
7970
|
-
log$3.error('Network error with no cache fallback:', {
|
|
7971
|
-
url,
|
|
7972
|
-
error: error instanceof Error ? error.message : error,
|
|
7973
|
-
});
|
|
7974
|
-
throw error;
|
|
7975
|
-
}
|
|
7976
|
-
}
|
|
7977
|
-
async post(url, data, config) {
|
|
7978
|
-
log$3.info('POST request:', { url });
|
|
7979
|
-
const response = await this.http.post(url, data, config);
|
|
7980
|
-
await this.invalidateRelated(url, 'POST');
|
|
7981
|
-
return response;
|
|
7982
|
-
}
|
|
7983
|
-
async put(url, data, config) {
|
|
7984
|
-
log$3.info('PUT request:', { url });
|
|
7985
|
-
const response = await this.http.put(url, data, config);
|
|
7986
|
-
await this.invalidateRelated(url, 'PUT');
|
|
7987
|
-
return response;
|
|
7988
|
-
}
|
|
7989
|
-
async patch(url, data, config) {
|
|
7990
|
-
log$3.info('PATCH request:', { url });
|
|
7991
|
-
const response = await this.http.patch(url, data, config);
|
|
7992
|
-
await this.invalidateRelated(url, 'PATCH');
|
|
7993
|
-
return response;
|
|
7994
|
-
}
|
|
7995
|
-
async delete(url, config) {
|
|
7996
|
-
log$3.info('DELETE request:', { url });
|
|
7997
|
-
const response = await this.http.delete(url, config);
|
|
7998
|
-
await this.invalidateRelated(url, 'DELETE');
|
|
7999
|
-
return response;
|
|
8000
|
-
}
|
|
8001
|
-
setAuthToken(token) {
|
|
8002
|
-
log$3.debug('CachingHttpDecorator.setAuthToken called:', {
|
|
8003
|
-
hasToken: !!token,
|
|
8004
|
-
tokenPrefix: token?.substring(0, 20),
|
|
8005
|
-
underlyingHttpType: this.http.constructor.name,
|
|
8006
|
-
});
|
|
8007
|
-
this.authToken = token;
|
|
8008
|
-
this.http.setAuthToken(token);
|
|
8009
|
-
}
|
|
8010
|
-
getAuthToken() {
|
|
8011
|
-
return this.authToken;
|
|
8012
|
-
}
|
|
8013
|
-
async invalidateRelated(url, method) {
|
|
8014
|
-
const patterns = this.keyGenerator.getInvalidationPatterns(url, method);
|
|
8015
|
-
if (patterns.length === 0) {
|
|
8016
|
-
log$3.debug('No cache patterns to invalidate:', { url, method });
|
|
8017
|
-
return;
|
|
8018
|
-
}
|
|
8019
|
-
log$3.info('Invalidating cache patterns:', { url, method, patterns });
|
|
8020
|
-
for (const pattern of patterns) {
|
|
8021
|
-
try {
|
|
8022
|
-
await this.cache.invalidate(pattern);
|
|
8023
|
-
log$3.debug('Cache pattern invalidated:', { pattern });
|
|
8024
|
-
}
|
|
8025
|
-
catch (error) {
|
|
8026
|
-
log$3.error('Failed to invalidate pattern:', {
|
|
8027
|
-
pattern,
|
|
8028
|
-
error: error instanceof Error ? error.message : error,
|
|
8029
|
-
});
|
|
8030
|
-
}
|
|
8031
|
-
}
|
|
8032
|
-
}
|
|
8033
|
-
destroy() {
|
|
8034
|
-
log$3.debug('CachingHttpDecorator destroyed');
|
|
8035
|
-
this.networkSubscription?.unsubscribe();
|
|
8036
|
-
}
|
|
8037
|
-
}
|
|
8038
|
-
|
|
8039
5674
|
const logJwt = createPrefixedLogger('HTTP-JWT');
|
|
8040
5675
|
const logMtls = createPrefixedLogger('HTTP-MTLS');
|
|
8041
5676
|
class AxiosHttpAdapter {
|
|
@@ -8305,7 +5940,6 @@ class SDKFactory {
|
|
|
8305
5940
|
baseUrl: config.baseUrl,
|
|
8306
5941
|
timeout: config.timeout,
|
|
8307
5942
|
});
|
|
8308
|
-
container.register(DI_TOKENS.BASE_HTTP_PORT, httpAdapter);
|
|
8309
5943
|
container.register(DI_TOKENS.HTTP_PORT, httpAdapter);
|
|
8310
5944
|
container.registerFactory(DI_TOKENS.RECEIPT_REPOSITORY, () => {
|
|
8311
5945
|
const http = container.get(DI_TOKENS.HTTP_PORT);
|
|
@@ -8353,23 +5987,6 @@ class SDKFactory {
|
|
|
8353
5987
|
});
|
|
8354
5988
|
return container;
|
|
8355
5989
|
}
|
|
8356
|
-
static registerCacheServices(container, cache, network) {
|
|
8357
|
-
container.register(DI_TOKENS.CACHE_PORT, cache);
|
|
8358
|
-
if (network) {
|
|
8359
|
-
container.register(DI_TOKENS.NETWORK_PORT, network);
|
|
8360
|
-
}
|
|
8361
|
-
const keyGenerator = new CacheKeyGenerator();
|
|
8362
|
-
container.register(DI_TOKENS.CACHE_KEY_GENERATOR, keyGenerator);
|
|
8363
|
-
const baseHttp = container.get(DI_TOKENS.BASE_HTTP_PORT);
|
|
8364
|
-
const cachingHttp = new CachingHttpDecorator(baseHttp, cache, keyGenerator, network);
|
|
8365
|
-
container.register(DI_TOKENS.HTTP_PORT, cachingHttp);
|
|
8366
|
-
}
|
|
8367
|
-
static getCacheKeyGenerator(container) {
|
|
8368
|
-
if (container.has(DI_TOKENS.CACHE_KEY_GENERATOR)) {
|
|
8369
|
-
return container.get(DI_TOKENS.CACHE_KEY_GENERATOR);
|
|
8370
|
-
}
|
|
8371
|
-
return undefined;
|
|
8372
|
-
}
|
|
8373
5990
|
static registerAuthServices(container, secureStorage, config) {
|
|
8374
5991
|
const tokenStorage = new TokenStorageAdapter(secureStorage);
|
|
8375
5992
|
container.register(DI_TOKENS.TOKEN_STORAGE_PORT, tokenStorage);
|
|
@@ -8407,7 +6024,7 @@ class SDKFactory {
|
|
|
8407
6024
|
}
|
|
8408
6025
|
}
|
|
8409
6026
|
|
|
8410
|
-
const log$
|
|
6027
|
+
const log$1 = createPrefixedLogger('SDK');
|
|
8411
6028
|
class ACubeSDK {
|
|
8412
6029
|
constructor(config, customAdapters, events = {}) {
|
|
8413
6030
|
this.events = events;
|
|
@@ -8421,23 +6038,22 @@ class ACubeSDK {
|
|
|
8421
6038
|
}
|
|
8422
6039
|
async initialize() {
|
|
8423
6040
|
if (this.isInitialized) {
|
|
8424
|
-
log$
|
|
6041
|
+
log$1.debug('SDK already initialized, skipping');
|
|
8425
6042
|
return;
|
|
8426
6043
|
}
|
|
8427
|
-
log$
|
|
6044
|
+
log$1.info('Initializing SDK', {
|
|
8428
6045
|
apiUrl: this.config.getApiUrl(),
|
|
8429
6046
|
authUrl: this.config.getAuthUrl(),
|
|
8430
6047
|
debugEnabled: this.config.isDebugEnabled(),
|
|
8431
6048
|
});
|
|
8432
6049
|
try {
|
|
8433
6050
|
if (!this.adapters) {
|
|
8434
|
-
log$
|
|
6051
|
+
log$1.debug('Loading platform adapters');
|
|
8435
6052
|
const mtlsConfig = createACubeMTLSConfig(this.config.getApiUrl(), this.config.getTimeout(), true);
|
|
8436
6053
|
this.adapters = loadPlatformAdapters({
|
|
8437
6054
|
mtlsConfig,
|
|
8438
6055
|
});
|
|
8439
|
-
log$
|
|
8440
|
-
hasCache: !!this.adapters.cache,
|
|
6056
|
+
log$1.info('Platform adapters loaded', {
|
|
8441
6057
|
hasNetworkMonitor: !!this.adapters.networkMonitor,
|
|
8442
6058
|
hasMtls: !!this.adapters.mtls,
|
|
8443
6059
|
hasSecureStorage: !!this.adapters.secureStorage,
|
|
@@ -8449,30 +6065,15 @@ class ACubeSDK {
|
|
|
8449
6065
|
timeout: this.config.getTimeout(),
|
|
8450
6066
|
debugEnabled: this.config.isDebugEnabled(),
|
|
8451
6067
|
};
|
|
8452
|
-
log$
|
|
6068
|
+
log$1.debug('Creating DI container');
|
|
8453
6069
|
this.container = SDKFactory.createContainer(factoryConfig);
|
|
8454
|
-
log$
|
|
6070
|
+
log$1.debug('Registering auth services');
|
|
8455
6071
|
SDKFactory.registerAuthServices(this.container, this.adapters.secureStorage, factoryConfig);
|
|
8456
|
-
|
|
8457
|
-
log$2.info('Registering cache services', {
|
|
8458
|
-
hasNetworkMonitor: !!this.adapters.networkMonitor,
|
|
8459
|
-
});
|
|
8460
|
-
SDKFactory.registerCacheServices(this.container, this.adapters.cache, this.adapters.networkMonitor);
|
|
8461
|
-
}
|
|
8462
|
-
else {
|
|
8463
|
-
log$2.debug('No cache adapter available, caching disabled');
|
|
8464
|
-
}
|
|
8465
|
-
log$2.debug('Initializing certificate service');
|
|
6072
|
+
log$1.debug('Initializing certificate service');
|
|
8466
6073
|
this.certificateService = new CertificateService(this.adapters.secureStorage);
|
|
8467
6074
|
const tokenStorage = this.container.get(DI_TOKENS.TOKEN_STORAGE_PORT);
|
|
8468
6075
|
const httpPort = this.container.get(DI_TOKENS.HTTP_PORT);
|
|
8469
|
-
|
|
8470
|
-
log$2.debug('HTTP ports initialized', {
|
|
8471
|
-
httpPortType: httpPort.constructor.name,
|
|
8472
|
-
baseHttpPortType: baseHttpPort.constructor.name,
|
|
8473
|
-
areSameInstance: httpPort === baseHttpPort,
|
|
8474
|
-
});
|
|
8475
|
-
log$2.debug('Initializing authentication service');
|
|
6076
|
+
log$1.debug('Initializing authentication service');
|
|
8476
6077
|
this.authService = new AuthenticationService(httpPort, tokenStorage, {
|
|
8477
6078
|
authUrl: this.config.getAuthUrl(),
|
|
8478
6079
|
timeout: this.config.getTimeout(),
|
|
@@ -8482,51 +6083,33 @@ class ACubeSDK {
|
|
|
8482
6083
|
this.events.onAuthError?.(new ACubeSDKError('AUTH_ERROR', error.message, error));
|
|
8483
6084
|
},
|
|
8484
6085
|
});
|
|
8485
|
-
log$2.debug('Initializing offline manager');
|
|
8486
|
-
const queueEvents = {
|
|
8487
|
-
onOperationAdded: (operation) => {
|
|
8488
|
-
this.events.onOfflineOperationAdded?.(operation.id);
|
|
8489
|
-
},
|
|
8490
|
-
onOperationCompleted: (result) => {
|
|
8491
|
-
this.events.onOfflineOperationCompleted?.(result.operation.id, result.success);
|
|
8492
|
-
},
|
|
8493
|
-
onOperationFailed: (result) => {
|
|
8494
|
-
this.events.onOfflineOperationCompleted?.(result.operation.id, false);
|
|
8495
|
-
},
|
|
8496
|
-
};
|
|
8497
|
-
this.offlineManager = new OfflineManager(this.adapters.storage, httpPort, this.adapters.networkMonitor, {
|
|
8498
|
-
syncInterval: 30000,
|
|
8499
|
-
}, queueEvents);
|
|
8500
6086
|
this.networkSubscription = this.adapters.networkMonitor.online$.subscribe((online) => {
|
|
8501
6087
|
this.currentOnlineState = online;
|
|
8502
6088
|
this.events.onNetworkStatusChanged?.(online);
|
|
8503
|
-
if (online && this.offlineManager) {
|
|
8504
|
-
this.offlineManager.sync().catch(() => { });
|
|
8505
|
-
}
|
|
8506
6089
|
});
|
|
8507
6090
|
const isAuth = await this.authService.isAuthenticated();
|
|
8508
|
-
log$
|
|
6091
|
+
log$1.debug('Checking authentication status during init', { isAuthenticated: isAuth });
|
|
8509
6092
|
if (isAuth) {
|
|
8510
6093
|
const token = await this.authService.getAccessToken();
|
|
8511
|
-
log$
|
|
6094
|
+
log$1.debug('Token retrieved during init', {
|
|
8512
6095
|
hasToken: !!token,
|
|
8513
6096
|
tokenPrefix: token?.substring(0, 20),
|
|
8514
6097
|
});
|
|
8515
6098
|
if (token) {
|
|
8516
6099
|
httpPort.setAuthToken(token);
|
|
8517
|
-
log$
|
|
6100
|
+
log$1.info('Auth token set on HTTP port during initialization');
|
|
8518
6101
|
}
|
|
8519
6102
|
}
|
|
8520
6103
|
else {
|
|
8521
|
-
log$
|
|
6104
|
+
log$1.warn('User not authenticated during SDK init - token will be set after login');
|
|
8522
6105
|
}
|
|
8523
|
-
if (this.adapters?.mtls && 'setMTLSAdapter' in
|
|
8524
|
-
log$
|
|
8525
|
-
const httpWithMtls =
|
|
6106
|
+
if (this.adapters?.mtls && 'setMTLSAdapter' in httpPort) {
|
|
6107
|
+
log$1.debug('Connecting mTLS adapter to HTTP port');
|
|
6108
|
+
const httpWithMtls = httpPort;
|
|
8526
6109
|
httpWithMtls.setMTLSAdapter(this.adapters.mtls);
|
|
8527
6110
|
}
|
|
8528
|
-
if ('setAuthStrategy' in
|
|
8529
|
-
log$
|
|
6111
|
+
if ('setAuthStrategy' in httpPort) {
|
|
6112
|
+
log$1.debug('Configuring auth strategy');
|
|
8530
6113
|
const jwtHandler = new JwtAuthHandler(tokenStorage);
|
|
8531
6114
|
const certificatePort = this.certificateService
|
|
8532
6115
|
? {
|
|
@@ -8557,7 +6140,7 @@ class ACubeSDK {
|
|
|
8557
6140
|
},
|
|
8558
6141
|
};
|
|
8559
6142
|
const authStrategy = new AuthStrategy(jwtHandler, mtlsHandler, userProvider, this.adapters?.mtls || null);
|
|
8560
|
-
const httpWithStrategy =
|
|
6143
|
+
const httpWithStrategy = httpPort;
|
|
8561
6144
|
httpWithStrategy.setAuthStrategy(authStrategy);
|
|
8562
6145
|
}
|
|
8563
6146
|
if (this.adapters?.mtls && this.certificateService) {
|
|
@@ -8575,19 +6158,18 @@ class ACubeSDK {
|
|
|
8575
6158
|
}
|
|
8576
6159
|
}
|
|
8577
6160
|
catch (certError) {
|
|
8578
|
-
log$
|
|
6161
|
+
log$1.warn('Certificate auto-configuration failed, will retry on demand', {
|
|
8579
6162
|
error: certError instanceof Error ? certError.message : certError,
|
|
8580
6163
|
});
|
|
8581
6164
|
}
|
|
8582
6165
|
}
|
|
8583
6166
|
this.isInitialized = true;
|
|
8584
|
-
log$
|
|
8585
|
-
hasCache: !!this.adapters.cache,
|
|
6167
|
+
log$1.info('SDK initialized successfully', {
|
|
8586
6168
|
hasMtls: !!this.adapters.mtls,
|
|
8587
6169
|
});
|
|
8588
6170
|
}
|
|
8589
6171
|
catch (error) {
|
|
8590
|
-
log$
|
|
6172
|
+
log$1.error('SDK initialization failed', {
|
|
8591
6173
|
error: error instanceof Error ? error.message : error,
|
|
8592
6174
|
});
|
|
8593
6175
|
throw new ACubeSDKError('SDK_INITIALIZATION_ERROR', `Failed to initialize SDK: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
@@ -8643,19 +6225,19 @@ class ACubeSDK {
|
|
|
8643
6225
|
}
|
|
8644
6226
|
async login(credentials) {
|
|
8645
6227
|
this.ensureInitialized();
|
|
8646
|
-
log$
|
|
6228
|
+
log$1.info('Login attempt', { email: credentials.email });
|
|
8647
6229
|
const user = await this.authService.login(credentials);
|
|
8648
|
-
log$
|
|
6230
|
+
log$1.info('Login successful', { roles: user.roles });
|
|
8649
6231
|
const token = await this.authService.getAccessToken();
|
|
8650
6232
|
if (token) {
|
|
8651
6233
|
this.httpPort.setAuthToken(token);
|
|
8652
|
-
log$
|
|
6234
|
+
log$1.debug('Auth token set on HTTP port');
|
|
8653
6235
|
}
|
|
8654
6236
|
return user;
|
|
8655
6237
|
}
|
|
8656
6238
|
async logout() {
|
|
8657
6239
|
this.ensureInitialized();
|
|
8658
|
-
log$
|
|
6240
|
+
log$1.info('Logout');
|
|
8659
6241
|
await this.authService.logout();
|
|
8660
6242
|
this.httpPort.setAuthToken(null);
|
|
8661
6243
|
}
|
|
@@ -8673,10 +6255,6 @@ class ACubeSDK {
|
|
|
8673
6255
|
this.ensureInitialized();
|
|
8674
6256
|
return await this.authService.isAuthenticated();
|
|
8675
6257
|
}
|
|
8676
|
-
getOfflineManager() {
|
|
8677
|
-
this.ensureInitialized();
|
|
8678
|
-
return this.offlineManager;
|
|
8679
|
-
}
|
|
8680
6258
|
isOnline() {
|
|
8681
6259
|
this.ensureInitialized();
|
|
8682
6260
|
return this.currentOnlineState;
|
|
@@ -8804,7 +6382,6 @@ class ACubeSDK {
|
|
|
8804
6382
|
}
|
|
8805
6383
|
destroy() {
|
|
8806
6384
|
this.networkSubscription?.unsubscribe();
|
|
8807
|
-
this.offlineManager?.destroy();
|
|
8808
6385
|
this.container?.clear();
|
|
8809
6386
|
this.isInitialized = false;
|
|
8810
6387
|
}
|
|
@@ -9090,7 +6667,6 @@ class TelemetryService {
|
|
|
9090
6667
|
this.events = events;
|
|
9091
6668
|
this.stateSubject = new BehaviorSubject({
|
|
9092
6669
|
data: null,
|
|
9093
|
-
isCached: false,
|
|
9094
6670
|
isLoading: false,
|
|
9095
6671
|
lastFetchedAt: null,
|
|
9096
6672
|
});
|
|
@@ -9160,7 +6736,6 @@ class TelemetryService {
|
|
|
9160
6736
|
const data = await this.repository.getTelemetry(this.currentPemId);
|
|
9161
6737
|
const newState = {
|
|
9162
6738
|
data,
|
|
9163
|
-
isCached: false,
|
|
9164
6739
|
isLoading: false,
|
|
9165
6740
|
lastFetchedAt: Date.now(),
|
|
9166
6741
|
};
|
|
@@ -9185,7 +6760,6 @@ class TelemetryService {
|
|
|
9185
6760
|
clearTelemetry() {
|
|
9186
6761
|
this.stateSubject.next({
|
|
9187
6762
|
data: null,
|
|
9188
|
-
isCached: false,
|
|
9189
6763
|
isLoading: false,
|
|
9190
6764
|
lastFetchedAt: null,
|
|
9191
6765
|
});
|
|
@@ -9198,7 +6772,7 @@ class TelemetryService {
|
|
|
9198
6772
|
}
|
|
9199
6773
|
}
|
|
9200
6774
|
|
|
9201
|
-
const log
|
|
6775
|
+
const log = createPrefixedLogger('SDK-MANAGER');
|
|
9202
6776
|
/**
|
|
9203
6777
|
* SDKManager - Singleton wrapper for ACubeSDK with simplified API
|
|
9204
6778
|
*
|
|
@@ -9265,7 +6839,7 @@ class SDKManager {
|
|
|
9265
6839
|
if (canPoll && !this.isPollingActive) {
|
|
9266
6840
|
const hasCert = await this.checkCertificate();
|
|
9267
6841
|
if (!hasCert) {
|
|
9268
|
-
log
|
|
6842
|
+
log.warn('Certificate missing — polling blocked until certificate is installed');
|
|
9269
6843
|
return;
|
|
9270
6844
|
}
|
|
9271
6845
|
this.notificationService?.startPolling();
|
|
@@ -9367,7 +6941,7 @@ class SDKManager {
|
|
|
9367
6941
|
this.isPollingActive = true;
|
|
9368
6942
|
}
|
|
9369
6943
|
else {
|
|
9370
|
-
log
|
|
6944
|
+
log.warn('Certificate missing at init — polling blocked until certificate is installed');
|
|
9371
6945
|
}
|
|
9372
6946
|
}
|
|
9373
6947
|
// AppStateService remains active for all users (handles OFFLINE network state)
|
|
@@ -9410,7 +6984,7 @@ class SDKManager {
|
|
|
9410
6984
|
return this.certificateMissingSubject.asObservable();
|
|
9411
6985
|
}
|
|
9412
6986
|
/**
|
|
9413
|
-
* Observable stream of telemetry state (data, isLoading,
|
|
6987
|
+
* Observable stream of telemetry state (data, isLoading, error)
|
|
9414
6988
|
*/
|
|
9415
6989
|
get telemetryState$() {
|
|
9416
6990
|
this.ensureInitialized();
|
|
@@ -9494,7 +7068,7 @@ class SDKManager {
|
|
|
9494
7068
|
const user = await sdk.getCurrentUser();
|
|
9495
7069
|
const canPoll = user && hasAnyRole(user.roles, ['ROLE_MERCHANT', 'ROLE_CASHIER']);
|
|
9496
7070
|
if (canPoll) {
|
|
9497
|
-
log
|
|
7071
|
+
log.info('Certificate installed — starting polling');
|
|
9498
7072
|
this.notificationService?.startPolling();
|
|
9499
7073
|
await this.startTelemetryPollingAuto();
|
|
9500
7074
|
this.isPollingActive = true;
|
|
@@ -9507,7 +7081,7 @@ class SDKManager {
|
|
|
9507
7081
|
this.certificateMissingSubject.next(true);
|
|
9508
7082
|
// Stop polling since certificate is required
|
|
9509
7083
|
if (this.isPollingActive) {
|
|
9510
|
-
log
|
|
7084
|
+
log.info('Certificate removed — stopping polling');
|
|
9511
7085
|
this.notificationService?.stopPolling();
|
|
9512
7086
|
this.telemetryService?.stopPolling();
|
|
9513
7087
|
this.telemetryService?.clearTelemetry();
|
|
@@ -9587,91 +7161,6 @@ const RECEIPT_SENT = 'sent';
|
|
|
9587
7161
|
const RECEIPT_SORT_DESCENDING = 'descending';
|
|
9588
7162
|
const RECEIPT_SORT_ASCENDING = 'ascending';
|
|
9589
7163
|
|
|
9590
|
-
const log = createPrefixedLogger('CACHE-HANDLER');
|
|
9591
|
-
class CacheHandler {
|
|
9592
|
-
constructor(cache, networkMonitor) {
|
|
9593
|
-
this.cache = cache;
|
|
9594
|
-
this.networkMonitor = networkMonitor;
|
|
9595
|
-
this.currentOnlineState = true;
|
|
9596
|
-
this.setupNetworkMonitoring();
|
|
9597
|
-
}
|
|
9598
|
-
setupNetworkMonitoring() {
|
|
9599
|
-
if (this.networkMonitor) {
|
|
9600
|
-
this.networkSubscription = this.networkMonitor.online$.subscribe((online) => {
|
|
9601
|
-
this.currentOnlineState = online;
|
|
9602
|
-
});
|
|
9603
|
-
}
|
|
9604
|
-
}
|
|
9605
|
-
isOnline() {
|
|
9606
|
-
if (this.networkMonitor) {
|
|
9607
|
-
return this.currentOnlineState;
|
|
9608
|
-
}
|
|
9609
|
-
if (typeof navigator !== 'undefined' && 'onLine' in navigator) {
|
|
9610
|
-
return navigator.onLine;
|
|
9611
|
-
}
|
|
9612
|
-
return false;
|
|
9613
|
-
}
|
|
9614
|
-
async handleCachedRequest(url, requestFn, config) {
|
|
9615
|
-
if (!this.cache || config?.useCache === false) {
|
|
9616
|
-
const response = await requestFn();
|
|
9617
|
-
return response.data;
|
|
9618
|
-
}
|
|
9619
|
-
const cacheKey = this.generateCacheKey(url);
|
|
9620
|
-
const online = this.isOnline();
|
|
9621
|
-
log.debug('Request:', { url, cacheKey, online });
|
|
9622
|
-
if (online) {
|
|
9623
|
-
try {
|
|
9624
|
-
const response = await requestFn();
|
|
9625
|
-
if (this.cache) {
|
|
9626
|
-
await this.cache.set(cacheKey, response.data).catch((error) => {
|
|
9627
|
-
log.error('Failed to cache:', error instanceof Error ? error.message : error);
|
|
9628
|
-
});
|
|
9629
|
-
}
|
|
9630
|
-
return response.data;
|
|
9631
|
-
}
|
|
9632
|
-
catch (error) {
|
|
9633
|
-
const cached = await this.cache.get(cacheKey).catch(() => null);
|
|
9634
|
-
if (cached) {
|
|
9635
|
-
log.debug('Network failed, using cache fallback');
|
|
9636
|
-
return cached.data;
|
|
9637
|
-
}
|
|
9638
|
-
throw error;
|
|
9639
|
-
}
|
|
9640
|
-
}
|
|
9641
|
-
else {
|
|
9642
|
-
const cached = await this.cache.get(cacheKey).catch(() => null);
|
|
9643
|
-
if (cached) {
|
|
9644
|
-
log.debug('Offline, returning cached data');
|
|
9645
|
-
return cached.data;
|
|
9646
|
-
}
|
|
9647
|
-
throw new Error('Offline: No cached data available');
|
|
9648
|
-
}
|
|
9649
|
-
}
|
|
9650
|
-
generateCacheKey(url) {
|
|
9651
|
-
return url;
|
|
9652
|
-
}
|
|
9653
|
-
async invalidateCache(pattern) {
|
|
9654
|
-
if (!this.cache)
|
|
9655
|
-
return;
|
|
9656
|
-
try {
|
|
9657
|
-
await this.cache.invalidate(pattern);
|
|
9658
|
-
}
|
|
9659
|
-
catch (error) {
|
|
9660
|
-
log.error('Invalidation failed:', error instanceof Error ? error.message : error);
|
|
9661
|
-
}
|
|
9662
|
-
}
|
|
9663
|
-
getCacheStatus() {
|
|
9664
|
-
return {
|
|
9665
|
-
available: !!this.cache,
|
|
9666
|
-
networkMonitorAvailable: !!this.networkMonitor,
|
|
9667
|
-
isOnline: this.isOnline(),
|
|
9668
|
-
};
|
|
9669
|
-
}
|
|
9670
|
-
destroy() {
|
|
9671
|
-
this.networkSubscription?.unsubscribe();
|
|
9672
|
-
}
|
|
9673
|
-
}
|
|
9674
|
-
|
|
9675
7164
|
function transformError(error) {
|
|
9676
7165
|
if (axios.isAxiosError(error)) {
|
|
9677
7166
|
const response = error.response;
|
|
@@ -9850,5 +7339,5 @@ class PlatformDetector {
|
|
|
9850
7339
|
}
|
|
9851
7340
|
}
|
|
9852
7341
|
|
|
9853
|
-
export { ACubeSDK, ACubeSDKError, ActivationRequestSchema, AddressMapper, AddressSchema, AppStateService, AuthStrategy, AuthenticationService, AxiosHttpAdapter,
|
|
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 };
|
|
9854
7343
|
//# sourceMappingURL=index.esm.js.map
|