@xiboplayer/cache 0.3.6 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/cache",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Offline caching and download management with parallel chunk downloads",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "spark-md5": "^3.0.2",
15
- "@xiboplayer/utils": "0.3.6"
15
+ "@xiboplayer/utils": "0.4.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "vitest": "^2.0.0",
package/src/cache.js CHANGED
@@ -8,6 +8,9 @@
8
8
  * - Full cache clearing
9
9
  */
10
10
 
11
+ import { createLogger } from '@xiboplayer/utils';
12
+
13
+ const log = createLogger('Cache');
11
14
  const CACHE_NAME = 'xibo-media-v1';
12
15
 
13
16
  // Dynamic base path for multi-variant deployment (pwa, pwa-xmds, pwa-xlr)
@@ -61,7 +64,7 @@ export class CacheManager {
61
64
  }
62
65
 
63
66
  if (orphaned.length > 0) {
64
- console.log(`[Cache] ${orphaned.length} media files orphaned after layout ${layoutId} removed:`, orphaned);
67
+ log.info(`${orphaned.length} media files orphaned after layout ${layoutId} removed:`, orphaned);
65
68
  }
66
69
  return orphaned;
67
70
  }
@@ -25,6 +25,9 @@
25
25
  * const blob = await file.wait();
26
26
  */
27
27
 
28
+ import { createLogger } from '@xiboplayer/utils';
29
+
30
+ const log = createLogger('Download');
28
31
  const DEFAULT_CONCURRENCY = 6; // Max concurrent HTTP connections (matches Chromium per-host limit)
29
32
  const DEFAULT_CHUNK_SIZE = 50 * 1024 * 1024; // 50MB chunks
30
33
  const DEFAULT_MAX_CHUNKS_PER_FILE = 3; // Max parallel chunk downloads per file
@@ -149,7 +152,7 @@ export class DownloadTask {
149
152
  if (attempt < MAX_RETRIES) {
150
153
  const delay = RETRY_DELAY_MS * attempt;
151
154
  const chunkLabel = this.chunkIndex != null ? ` chunk ${this.chunkIndex}` : '';
152
- console.warn(`[DownloadTask] ${this.fileInfo.type}/${this.fileInfo.id}${chunkLabel} attempt ${attempt}/${MAX_RETRIES} failed: ${msg}. Retrying in ${delay / 1000}s...`);
155
+ log.warn(`[DownloadTask] ${this.fileInfo.type}/${this.fileInfo.id}${chunkLabel} attempt ${attempt}/${MAX_RETRIES} failed: ${msg}. Retrying in ${delay / 1000}s...`);
153
156
  await new Promise(resolve => setTimeout(resolve, delay));
154
157
  } else {
155
158
  this.state = 'failed';
@@ -220,7 +223,7 @@ export class FileDownload {
220
223
  try {
221
224
  this.state = 'preparing';
222
225
  const { id, type, size } = this.fileInfo;
223
- console.log('[FileDownload] Starting:', `${type}/${id}`);
226
+ log.info('[FileDownload] Starting:', `${type}/${id}`);
224
227
 
225
228
  // Use declared size from RequiredFiles — no HEAD needed for queue building
226
229
  this.totalBytes = (size && size > 0) ? parseInt(size) : 0;
@@ -242,7 +245,7 @@ export class FileDownload {
242
245
  }
243
246
  }
244
247
 
245
- console.log('[FileDownload] File size:', (this.totalBytes / 1024 / 1024).toFixed(1), 'MB');
248
+ log.info('[FileDownload] File size:', (this.totalBytes / 1024 / 1024).toFixed(1), 'MB');
246
249
 
247
250
  const chunkSize = this.options.chunkSize || DEFAULT_CHUNK_SIZE;
248
251
 
@@ -267,14 +270,14 @@ export class FileDownload {
267
270
  }
268
271
 
269
272
  if (needed.length === 0) {
270
- console.log('[FileDownload] All chunks already cached, nothing to download');
273
+ log.info('[FileDownload] All chunks already cached, nothing to download');
271
274
  this.state = 'complete';
272
275
  this._resolve(new Blob([], { type: this._contentType }));
273
276
  return;
274
277
  }
275
278
 
276
279
  if (skippedCount > 0) {
277
- console.log(`[FileDownload] Resuming: ${skippedCount} chunks cached, ${needed.length} to download`);
280
+ log.info(`[FileDownload] Resuming: ${skippedCount} chunks cached, ${needed.length} to download`);
278
281
  }
279
282
 
280
283
  const isResume = skippedCount > 0;
@@ -301,7 +304,7 @@ export class FileDownload {
301
304
  }
302
305
 
303
306
  const highCount = this.tasks.filter(t => t._priority >= PRIORITY.high).length;
304
- console.log(`[FileDownload] ${type}/${id}: ${this.tasks.length} chunks` +
307
+ log.info(`[FileDownload] ${type}/${id}: ${this.tasks.length} chunks` +
305
308
  (highCount > 0 ? ` (${highCount} priority)` : '') +
306
309
  (isResume ? ' (resume)' : ''));
307
310
 
@@ -316,7 +319,7 @@ export class FileDownload {
316
319
  this.state = 'downloading';
317
320
 
318
321
  } catch (error) {
319
- console.error('[FileDownload] Prepare failed:', `${this.fileInfo.type}/${this.fileInfo.id}`, error);
322
+ log.error('[FileDownload] Prepare failed:', `${this.fileInfo.type}/${this.fileInfo.id}`, error);
320
323
  this.state = 'failed';
321
324
  this._reject(error);
322
325
  }
@@ -339,7 +342,7 @@ export class FileDownload {
339
342
  try {
340
343
  await this.onChunkDownloaded(task.chunkIndex, task.blob, this.totalChunks);
341
344
  } catch (e) {
342
- console.warn('[FileDownload] onChunkDownloaded callback error:', e);
345
+ log.warn('[FileDownload] onChunkDownloaded callback error:', e);
343
346
  }
344
347
  }
345
348
 
@@ -348,10 +351,10 @@ export class FileDownload {
348
351
  const { type, id } = this.fileInfo;
349
352
 
350
353
  if (task.chunkIndex == null) {
351
- console.log('[FileDownload] Complete:', `${type}/${id}`, `(${task.blob.size} bytes)`);
354
+ log.info('[FileDownload] Complete:', `${type}/${id}`, `(${task.blob.size} bytes)`);
352
355
  this._resolve(task.blob);
353
356
  } else if (this.onChunkDownloaded) {
354
- console.log('[FileDownload] Complete:', `${type}/${id}`, `(progressive, ${this.totalChunks} chunks)`);
357
+ log.info('[FileDownload] Complete:', `${type}/${id}`, `(progressive, ${this.totalChunks} chunks)`);
355
358
  this._resolve(new Blob([], { type: this._contentType }));
356
359
  } else {
357
360
  const ordered = [];
@@ -360,7 +363,7 @@ export class FileDownload {
360
363
  if (blob) ordered.push(blob);
361
364
  }
362
365
  const assembled = new Blob(ordered, { type: this._contentType });
363
- console.log('[FileDownload] Complete:', `${type}/${id}`, `(${assembled.size} bytes, reassembled)`);
366
+ log.info('[FileDownload] Complete:', `${type}/${id}`, `(${assembled.size} bytes, reassembled)`);
364
367
  this._resolve(assembled);
365
368
  }
366
369
 
@@ -376,7 +379,7 @@ export class FileDownload {
376
379
  // provides fresh URLs and the resume logic (skipChunks) fills the gaps.
377
380
  if (error.message?.includes('URL expired')) {
378
381
  const chunkLabel = task.chunkIndex != null ? ` chunk ${task.chunkIndex}` : '';
379
- console.warn(`[FileDownload] URL expired, dropping${chunkLabel}:`, `${this.fileInfo.type}/${this.fileInfo.id}`);
382
+ log.warn(`[FileDownload] URL expired, dropping${chunkLabel}:`, `${this.fileInfo.type}/${this.fileInfo.id}`);
380
383
  this.tasks = this.tasks.filter(t => t !== task);
381
384
  // If all remaining tasks completed, resolve as partial
382
385
  if (this.tasks.length === 0 || this.completedChunks >= this.tasks.length) {
@@ -387,7 +390,7 @@ export class FileDownload {
387
390
  return;
388
391
  }
389
392
 
390
- console.error('[FileDownload] Failed:', `${this.fileInfo.type}/${this.fileInfo.id}`, error);
393
+ log.error('[FileDownload] Failed:', `${this.fileInfo.type}/${this.fileInfo.id}`, error);
391
394
  this.state = 'failed';
392
395
  this._reject(error);
393
396
  }
@@ -564,7 +567,7 @@ export class DownloadQueue {
564
567
  const oldExpiry = getUrlExpiry(existing.fileInfo.path);
565
568
  const newExpiry = getUrlExpiry(fileInfo.path);
566
569
  if (newExpiry > oldExpiry) {
567
- console.log('[DownloadQueue] Refreshing URL for', key);
570
+ log.info('[DownloadQueue] Refreshing URL for', key);
568
571
  existing.fileInfo.path = fileInfo.path;
569
572
  }
570
573
  }
@@ -578,7 +581,7 @@ export class DownloadQueue {
578
581
  });
579
582
 
580
583
  this.active.set(key, file);
581
- console.log('[DownloadQueue] Enqueued:', key);
584
+ log.info('[DownloadQueue] Enqueued:', key);
582
585
 
583
586
  // Throttled prepare: HEAD requests are limited to avoid flooding connections
584
587
  this._schedulePrepare(file);
@@ -612,7 +615,7 @@ export class DownloadQueue {
612
615
  }
613
616
  this._sortQueue();
614
617
 
615
- console.log(`[DownloadQueue] ${tasks.length} tasks added (${this.queue.length} pending, ${this.running} active)`);
618
+ log.info(`[DownloadQueue] ${tasks.length} tasks added (${this.queue.length} pending, ${this.running} active)`);
616
619
  this.processQueue();
617
620
  }
618
621
 
@@ -638,7 +641,7 @@ export class DownloadQueue {
638
641
  }
639
642
  }
640
643
 
641
- console.log(`[DownloadQueue] Ordered queue: ${taskCount} tasks, ${barrierCount} barriers (${this.queue.length} pending, ${this.running} active)`);
644
+ log.info(`[DownloadQueue] Ordered queue: ${taskCount} tasks, ${barrierCount} barriers (${this.queue.length} pending, ${this.running} active)`);
642
645
  this.processQueue();
643
646
  }
644
647
 
@@ -652,7 +655,7 @@ export class DownloadQueue {
652
655
  const file = this.active.get(key);
653
656
 
654
657
  if (!file) {
655
- console.log('[DownloadQueue] Not found:', key);
658
+ log.info('[DownloadQueue] Not found:', key);
656
659
  return false;
657
660
  }
658
661
 
@@ -665,7 +668,7 @@ export class DownloadQueue {
665
668
  }
666
669
  this._sortQueue();
667
670
 
668
- console.log('[DownloadQueue] Prioritized:', key, `(${boosted} tasks boosted)`);
671
+ log.info('[DownloadQueue] Prioritized:', key, `(${boosted} tasks boosted)`);
669
672
  return true;
670
673
  }
671
674
 
@@ -691,7 +694,7 @@ export class DownloadQueue {
691
694
  }
692
695
  this._sortQueue();
693
696
 
694
- console.log('[DownloadQueue] Layout files prioritized:', idSet.size, 'files,', boosted, 'tasks boosted to', priority);
697
+ log.info('[DownloadQueue] Layout files prioritized:', idSet.size, 'files,', boosted, 'tasks boosted to', priority);
695
698
  }
696
699
 
697
700
  /**
@@ -704,7 +707,7 @@ export class DownloadQueue {
704
707
  const file = this.active.get(key);
705
708
 
706
709
  if (!file) {
707
- console.log('[DownloadQueue] urgentChunk: file not active:', key, 'chunk', chunkIndex);
710
+ log.info('[DownloadQueue] urgentChunk: file not active:', key, 'chunk', chunkIndex);
708
711
  return false;
709
712
  }
710
713
 
@@ -719,13 +722,13 @@ export class DownloadQueue {
719
722
  );
720
723
  if (activeTask && activeTask._priority < PRIORITY.urgent) {
721
724
  activeTask._priority = PRIORITY.urgent;
722
- console.log(`[DownloadQueue] URGENT: ${key} chunk ${chunkIndex} (already in-flight, limiting slots)`);
725
+ log.info(`[DownloadQueue] URGENT: ${key} chunk ${chunkIndex} (already in-flight, limiting slots)`);
723
726
  // Don't call processQueue() — can't stop in-flight tasks, but next
724
727
  // processQueue() call (when any task completes) will see hasUrgent
725
728
  // and limit new starts to URGENT_CONCURRENCY.
726
729
  return true;
727
730
  }
728
- console.log('[DownloadQueue] urgentChunk: already urgent:', key, 'chunk', chunkIndex);
731
+ log.info('[DownloadQueue] urgentChunk: already urgent:', key, 'chunk', chunkIndex);
729
732
  return false;
730
733
  }
731
734
 
@@ -735,7 +738,7 @@ export class DownloadQueue {
735
738
  );
736
739
 
737
740
  if (idx === -1) {
738
- console.log('[DownloadQueue] urgentChunk: chunk not in queue:', key, 'chunk', chunkIndex);
741
+ log.info('[DownloadQueue] urgentChunk: chunk not in queue:', key, 'chunk', chunkIndex);
739
742
  return false;
740
743
  }
741
744
 
@@ -744,7 +747,7 @@ export class DownloadQueue {
744
747
  // Move to front of queue (past any barriers)
745
748
  this.queue.unshift(task);
746
749
 
747
- console.log(`[DownloadQueue] URGENT: ${key} chunk ${chunkIndex} (moved to front)`);
750
+ log.info(`[DownloadQueue] URGENT: ${key} chunk ${chunkIndex} (moved to front)`);
748
751
  this.processQueue();
749
752
  return true;
750
753
  }
@@ -809,7 +812,7 @@ export class DownloadQueue {
809
812
  }
810
813
 
811
814
  if (this.queue.length === 0 && this.running === 0) {
812
- console.log('[DownloadQueue] All downloads complete');
815
+ log.info('[DownloadQueue] All downloads complete');
813
816
  }
814
817
  }
815
818
 
@@ -827,14 +830,14 @@ export class DownloadQueue {
827
830
  this._activeTasks.push(task);
828
831
  const key = `${task.fileInfo.type}/${task.fileInfo.id}`;
829
832
  const chunkLabel = task.chunkIndex != null ? ` chunk ${task.chunkIndex}` : '';
830
- console.log(`[DownloadQueue] Starting: ${key}${chunkLabel} (${this.running}/${this.concurrency} active)`);
833
+ log.info(`[DownloadQueue] Starting: ${key}${chunkLabel} (${this.running}/${this.concurrency} active)`);
831
834
 
832
835
  task.start()
833
836
  .then(() => {
834
837
  this.running--;
835
838
  task._parentFile._runningCount--;
836
839
  this._activeTasks = this._activeTasks.filter(t => t !== task);
837
- console.log(`[DownloadQueue] Fetched: ${key}${chunkLabel} (${this.running} active, ${this.queue.length} pending)`);
840
+ log.info(`[DownloadQueue] Fetched: ${key}${chunkLabel} (${this.running} active, ${this.queue.length} pending)`);
838
841
  this.processQueue();
839
842
  return task._parentFile.onTaskComplete(task);
840
843
  })
@@ -12,6 +12,9 @@
12
12
  * Uses Cache API directly — the SW also serves from the same cache.
13
13
  */
14
14
 
15
+ import { createLogger } from '@xiboplayer/utils';
16
+
17
+ const log = createLogger('Cache');
15
18
  const CACHE_NAME = 'xibo-media-v1';
16
19
 
17
20
  // Dynamic base path for multi-variant deployment (pwa, pwa-xmds, pwa-xlr)
@@ -54,7 +57,7 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
54
57
  modifiedHtml = modifiedHtml.replace(cmsUrlRegex, (match, filename) => {
55
58
  const localPath = `${BASE}/cache/static/${filename}`;
56
59
  staticResources.push({ filename, originalUrl: match });
57
- console.log(`[Cache] Rewrote widget URL: ${filename} → ${localPath}`);
60
+ log.info(`Rewrote widget URL: ${filename} → ${localPath}`);
58
61
  return localPath;
59
62
  });
60
63
 
@@ -77,7 +80,7 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
77
80
  `hostAddress: "${BASE}/ic"`
78
81
  );
79
82
 
80
- console.log(`[Cache] Injected base tag and rewrote CMS URLs in widget HTML`);
83
+ log.info('Injected base tag and rewrote CMS URLs in widget HTML');
81
84
 
82
85
  // Construct full URL for cache storage
83
86
  const cacheUrl = new URL(cacheKey, window.location.origin);
@@ -90,7 +93,7 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
90
93
  });
91
94
 
92
95
  await cache.put(cacheUrl, response);
93
- console.log(`[Cache] Stored widget HTML at ${cacheKey} (${modifiedHtml.length} bytes)`);
96
+ log.info(`Stored widget HTML at ${cacheKey} (${modifiedHtml.length} bytes)`);
94
97
 
95
98
  // Fetch and cache static resources (shared Cache API - accessible from main thread and SW)
96
99
  if (staticResources.length > 0) {
@@ -105,7 +108,7 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
105
108
  try {
106
109
  const resp = await fetch(originalUrl);
107
110
  if (!resp.ok) {
108
- console.warn(`[Cache] Failed to fetch static resource: ${filename} (HTTP ${resp.status})`);
111
+ log.warn(`Failed to fetch static resource: ${filename} (HTTP ${resp.status})`);
109
112
  return;
110
113
  }
111
114
 
@@ -126,14 +129,14 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
126
129
  const fontUrlRegex = /url\((['"]?)(https?:\/\/[^'")\s]+\?[^'")\s]*file=([^&'")\s]+\.(?:woff2?|ttf|otf|eot|svg))[^'")\s]*)\1\)/gi;
127
130
  cssText = cssText.replace(fontUrlRegex, (_match, quote, fullUrl, fontFilename) => {
128
131
  fontResources.push({ filename: fontFilename, originalUrl: fullUrl });
129
- console.log(`[Cache] Rewrote font URL in CSS: ${fontFilename}`);
132
+ log.info(`Rewrote font URL in CSS: ${fontFilename}`);
130
133
  return `url(${quote}${BASE}/cache/static/${encodeURIComponent(fontFilename)}${quote})`;
131
134
  });
132
135
 
133
136
  await staticCache.put(staticKey, new Response(cssText, {
134
137
  headers: { 'Content-Type': 'text/css' }
135
138
  }));
136
- console.log(`[Cache] Cached CSS with ${fontResources.length} rewritten font URLs: ${filename}`);
139
+ log.info(`Cached CSS with ${fontResources.length} rewritten font URLs: ${filename}`);
137
140
 
138
141
  // Fetch and cache referenced font files
139
142
  await Promise.all(fontResources.map(async ({ filename: fontFile, originalUrl: fontUrl }) => {
@@ -144,7 +147,7 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
144
147
  try {
145
148
  const fontResp = await fetch(fontUrl);
146
149
  if (!fontResp.ok) {
147
- console.warn(`[Cache] Failed to fetch font: ${fontFile} (HTTP ${fontResp.status})`);
150
+ log.warn(`Failed to fetch font: ${fontFile} (HTTP ${fontResp.status})`);
148
151
  return;
149
152
  }
150
153
  const fontBlob = await fontResp.blob();
@@ -159,9 +162,9 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
159
162
  await staticCache.put(fontKey, new Response(fontBlob, {
160
163
  headers: { 'Content-Type': fontContentType }
161
164
  }));
162
- console.log(`[Cache] Cached font: ${fontFile} (${fontContentType}, ${fontBlob.size} bytes)`);
165
+ log.info(`Cached font: ${fontFile} (${fontContentType}, ${fontBlob.size} bytes)`);
163
166
  } catch (fontErr) {
164
- console.warn(`[Cache] Failed to cache font: ${fontFile}`, fontErr);
167
+ log.warn(`Failed to cache font: ${fontFile}`, fontErr);
165
168
  }
166
169
  }));
167
170
  } else {
@@ -169,10 +172,10 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
169
172
  await staticCache.put(staticKey, new Response(blob, {
170
173
  headers: { 'Content-Type': contentType }
171
174
  }));
172
- console.log(`[Cache] Cached static resource: ${filename} (${contentType}, ${blob.size} bytes)`);
175
+ log.info(`Cached static resource: ${filename} (${contentType}, ${blob.size} bytes)`);
173
176
  }
174
177
  } catch (error) {
175
- console.warn(`[Cache] Failed to cache static resource: ${filename}`, error);
178
+ log.warn(`Failed to cache static resource: ${filename}`, error);
176
179
  }
177
180
  }));
178
181
  }