@xiboplayer/xmds 0.4.0 → 0.4.1

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 CHANGED
@@ -43,9 +43,9 @@ const schedule = await client.schedule();
43
43
  | `getResource(regionId, mediaId)` | Get rendered widget HTML |
44
44
  | `notifyStatus(status)` | Report display status to CMS |
45
45
  | `mediaInventory(inventory)` | Report cached media inventory |
46
- | `submitStats(stats)` | Submit proof of play statistics |
46
+ | `submitStats(stats, hardwareKeyOverride?)` | Submit proof of play statistics (optional `hardwareKeyOverride` for delegated submissions on behalf of another display) |
47
47
  | `submitScreenShot(base64)` | Upload a screenshot to the CMS |
48
- | `submitLog(logs)` | Submit display logs |
48
+ | `submitLog(logs, hardwareKeyOverride?)` | Submit display logs (optional `hardwareKeyOverride` for delegated submissions on behalf of another display) |
49
49
 
50
50
  ## Dependencies
51
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/xmds",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "XMDS SOAP client for Xibo CMS communication",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -9,7 +9,7 @@
9
9
  "./xmds": "./src/xmds.js"
10
10
  },
11
11
  "dependencies": {
12
- "@xiboplayer/utils": "0.4.0"
12
+ "@xiboplayer/utils": "0.4.1"
13
13
  },
14
14
  "devDependencies": {
15
15
  "vitest": "^2.0.0"
@@ -35,6 +35,32 @@ export class RestClient {
35
35
  return base.replace(/\/+$/, '');
36
36
  }
37
37
 
38
+ /**
39
+ * Check if running behind the local proxy (Electron or Chromium kiosk).
40
+ */
41
+ _isProxyMode() {
42
+ return typeof window !== 'undefined' &&
43
+ (window.electronAPI?.isElectron ||
44
+ (window.location.hostname === 'localhost' && window.location.port === '8765'));
45
+ }
46
+
47
+ /**
48
+ * Rewrite an absolute REST URL to go through /rest-proxy.
49
+ * Preserves all query params from the original URL.
50
+ */
51
+ _rewriteForProxy(urlString) {
52
+ if (!this._isProxyMode() || !urlString.startsWith('http')) return urlString;
53
+ const parsed = new URL(urlString);
54
+ const proxyUrl = new URL('/rest-proxy', window.location.origin);
55
+ proxyUrl.searchParams.set('cms', parsed.origin);
56
+ proxyUrl.searchParams.set('path', parsed.pathname);
57
+ // Forward all original query params
58
+ for (const [key, value] of parsed.searchParams) {
59
+ proxyUrl.searchParams.set(key, value);
60
+ }
61
+ return proxyUrl.toString();
62
+ }
63
+
38
64
  /**
39
65
  * Make a REST GET request with optional ETag caching.
40
66
  * Returns the parsed JSON body, or cached data on 304.
@@ -57,7 +83,7 @@ export class RestClient {
57
83
 
58
84
  log.debug(`GET ${path}`, queryParams);
59
85
 
60
- const response = await fetchWithRetry(url.toString(), {
86
+ const response = await fetchWithRetry(this._rewriteForProxy(url.toString()), {
61
87
  method: 'GET',
62
88
  headers,
63
89
  }, this.retryOptions);
@@ -107,7 +133,7 @@ export class RestClient {
107
133
 
108
134
  log.debug(`${method} ${path}`);
109
135
 
110
- const response = await fetchWithRetry(url.toString(), {
136
+ const response = await fetchWithRetry(this._rewriteForProxy(url.toString()), {
111
137
  method,
112
138
  headers: { 'Content-Type': 'application/json' },
113
139
  body: JSON.stringify({
@@ -185,9 +211,20 @@ export class RestClient {
185
211
  continue;
186
212
  }
187
213
  if (key === 'tags') {
188
- // Parse tags: array of strings, or array of {tag: "value"} objects
214
+ // Parse tags from CMS JSON (SimpleXMLElement serialization varies):
215
+ // Array of strings: ["geoApiKey|AIzaSy..."]
216
+ // Array of objects: [{tag: "geoApiKey|AIzaSy..."}]
217
+ // Single-tag object: {tag: "geoApiKey|AIzaSy..."} (SimpleXMLElement collapses single-element arrays)
218
+ // String: "geoApiKey|AIzaSy..."
219
+ const extractTag = (t) => typeof t === 'object' ? (t.tag || t.value || '') : String(t);
189
220
  if (Array.isArray(value)) {
190
- tags = value.map(t => typeof t === 'object' ? (t.tag || t.value || '') : String(t)).filter(Boolean);
221
+ tags = value.map(extractTag).filter(Boolean);
222
+ } else if (value && typeof value === 'object') {
223
+ // Single tag: {tag: "value"} — wrap in array
224
+ const t = extractTag(value);
225
+ if (t) tags = [t];
226
+ } else if (typeof value === 'string' && value) {
227
+ tags = [value];
191
228
  }
192
229
  continue;
193
230
  }
@@ -364,9 +401,10 @@ export class RestClient {
364
401
  * SubmitLog - submit player logs to CMS
365
402
  * POST /log → JSON acknowledgement
366
403
  */
367
- async submitLog(logXml) {
404
+ async submitLog(logXml, hardwareKeyOverride = null) {
368
405
  // Accept array (JSON-native) or string (XML) — send under the right key
369
406
  const body = Array.isArray(logXml) ? { logs: logXml } : { logXml };
407
+ if (hardwareKeyOverride) body.hardwareKey = hardwareKeyOverride;
370
408
  const result = await this.restSend('POST', '/log', body);
371
409
  return result?.success === true;
372
410
  }
@@ -406,10 +444,11 @@ export class RestClient {
406
444
  return this.restGet('/weather');
407
445
  }
408
446
 
409
- async submitStats(statsXml) {
447
+ async submitStats(statsXml, hardwareKeyOverride = null) {
410
448
  try {
411
449
  // Accept array (JSON-native) or string (XML) — send under the right key
412
450
  const body = Array.isArray(statsXml) ? { stats: statsXml } : { statXml: statsXml };
451
+ if (hardwareKeyOverride) body.hardwareKey = hardwareKeyOverride;
413
452
  const result = await this.restSend('POST', '/stats', body);
414
453
  const success = result?.success === true;
415
454
  log.info(`SubmitStats result: ${success}`);
@@ -382,10 +382,10 @@ export class XmdsClient {
382
382
  * @param {string} logXml - XML string containing log entries
383
383
  * @returns {Promise<boolean>} - true if logs were successfully submitted
384
384
  */
385
- async submitLog(logXml) {
385
+ async submitLog(logXml, hardwareKeyOverride = null) {
386
386
  const xml = await this.call('SubmitLog', {
387
387
  serverKey: this.config.cmsKey,
388
- hardwareKey: this.config.hardwareKey,
388
+ hardwareKey: hardwareKeyOverride || this.config.hardwareKey,
389
389
  logXml: logXml
390
390
  });
391
391
 
@@ -436,11 +436,11 @@ export class XmdsClient {
436
436
  });
437
437
  }
438
438
 
439
- async submitStats(statsXml) {
439
+ async submitStats(statsXml, hardwareKeyOverride = null) {
440
440
  try {
441
441
  const xml = await this.call('SubmitStats', {
442
442
  serverKey: this.config.cmsKey,
443
- hardwareKey: this.config.hardwareKey,
443
+ hardwareKey: hardwareKeyOverride || this.config.hardwareKey,
444
444
  statXml: statsXml
445
445
  });
446
446