@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 +2 -2
- package/package.json +2 -2
- package/src/rest-client.js +45 -6
- package/src/xmds-client.js +4 -4
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.
|
|
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.
|
|
12
|
+
"@xiboplayer/utils": "0.4.1"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"vitest": "^2.0.0"
|
package/src/rest-client.js
CHANGED
|
@@ -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
|
|
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(
|
|
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}`);
|
package/src/xmds-client.js
CHANGED
|
@@ -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
|
|