@xiboplayer/xmds 0.3.4 → 0.3.6

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/xmds",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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.3.4"
12
+ "@xiboplayer/utils": "0.3.6"
13
13
  },
14
14
  "devDependencies": {
15
15
  "vitest": "^2.0.0"
@@ -326,6 +326,17 @@ export class RestClient {
326
326
  * SubmitStats - submit proof of play statistics
327
327
  * POST /stats → JSON acknowledgement
328
328
  */
329
+ /**
330
+ * ReportFaults - submit fault data to CMS for dashboard alerts
331
+ * POST /fault → JSON acknowledgement
332
+ * @param {string} faultJson - JSON-encoded fault data
333
+ * @returns {Promise<boolean>}
334
+ */
335
+ async reportFaults(faultJson) {
336
+ const result = await this.restSend('POST', '/fault', { fault: faultJson });
337
+ return result?.success === true;
338
+ }
339
+
329
340
  async submitStats(statsXml) {
330
341
  try {
331
342
  // Accept array (JSON-native) or string (XML) — send under the right key
@@ -73,7 +73,7 @@ export class XmdsClient {
73
73
  */
74
74
  async call(method, params = {}) {
75
75
  const xmdsUrl = this.rewriteXmdsUrl(this.config.cmsAddress);
76
- const url = `${xmdsUrl}${xmdsUrl.includes('?') ? '&' : '?'}v=${this.schemaVersion}`;
76
+ const url = `${xmdsUrl}${xmdsUrl.includes('?') ? '&' : '?'}v=${this.schemaVersion}&method=${method}`;
77
77
  const body = this.buildEnvelope(method, params);
78
78
 
79
79
  log.debug(`${method}`, params);
@@ -354,6 +354,19 @@ export class XmdsClient {
354
354
  * @param {string} statsXml - XML-encoded stats string
355
355
  * @returns {Promise<boolean>} - true if stats were successfully submitted
356
356
  */
357
+ /**
358
+ * ReportFaults - submit fault data to CMS for dashboard alerts
359
+ * @param {string} faultJson - JSON-encoded fault data
360
+ * @returns {Promise<boolean>}
361
+ */
362
+ async reportFaults(faultJson) {
363
+ return this.call('ReportFaults', {
364
+ serverKey: this.config.cmsKey,
365
+ hardwareKey: this.config.hardwareKey,
366
+ fault: faultJson
367
+ });
368
+ }
369
+
357
370
  async submitStats(statsXml) {
358
371
  try {
359
372
  const xml = await this.call('SubmitStats', {
package/src/xmds.test.js CHANGED
@@ -70,6 +70,68 @@ describe('XmdsClient - RegisterDisplay', () => {
70
70
  });
71
71
  });
72
72
 
73
+ describe('XmdsClient - URL construction', () => {
74
+ let client;
75
+ let mockFetch;
76
+
77
+ beforeEach(() => {
78
+ client = new XmdsClient({
79
+ cmsAddress: 'https://cms.example.com',
80
+ cmsKey: 'test-server-key',
81
+ hardwareKey: 'test-hardware-key',
82
+ retryOptions: { maxRetries: 0 }
83
+ });
84
+
85
+ mockFetch = vi.fn();
86
+ global.fetch = mockFetch;
87
+ });
88
+
89
+ it('should include &method= query parameter in SOAP URLs', async () => {
90
+ mockFetch.mockResolvedValue({
91
+ ok: true,
92
+ text: async () => `<?xml version="1.0"?>
93
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
94
+ <soap:Body>
95
+ <RegisterDisplayResponse>
96
+ <display code="READY" message="ok"></display>
97
+ </RegisterDisplayResponse>
98
+ </soap:Body>
99
+ </soap:Envelope>`
100
+ });
101
+
102
+ await client.call('RegisterDisplay', {
103
+ serverKey: 'test-server-key',
104
+ hardwareKey: 'test-hardware-key'
105
+ });
106
+
107
+ const url = mockFetch.mock.calls[0][0];
108
+ expect(url).toBe('https://cms.example.com/xmds.php?v=5&method=RegisterDisplay');
109
+ });
110
+
111
+ it('should append method to proxy URLs with existing query params', async () => {
112
+ // Simulate Electron proxy URL that already has ?cms=...
113
+ client.rewriteXmdsUrl = () => '/xmds-proxy?cms=https%3A%2F%2Fcms.example.com';
114
+
115
+ mockFetch.mockResolvedValue({
116
+ ok: true,
117
+ text: async () => `<?xml version="1.0"?>
118
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
119
+ <soap:Body>
120
+ <ScheduleResponse><result></result></ScheduleResponse>
121
+ </soap:Body>
122
+ </soap:Envelope>`
123
+ });
124
+
125
+ await client.call('Schedule', {
126
+ serverKey: 'test-server-key',
127
+ hardwareKey: 'test-hardware-key'
128
+ });
129
+
130
+ const url = mockFetch.mock.calls[0][0];
131
+ expect(url).toContain('&v=5&method=Schedule');
132
+ });
133
+ });
134
+
73
135
  describe('XmdsClient - SubmitLog', () => {
74
136
  let client;
75
137
  let mockFetch;
@@ -104,7 +166,7 @@ describe('XmdsClient - SubmitLog', () => {
104
166
  await client.submitLog(logXml);
105
167
 
106
168
  expect(mockFetch).toHaveBeenCalledWith(
107
- 'https://cms.example.com/xmds.php?v=5',
169
+ 'https://cms.example.com/xmds.php?v=5&method=SubmitLog',
108
170
  expect.objectContaining({
109
171
  method: 'POST',
110
172
  headers: {
@@ -245,7 +307,7 @@ describe('XmdsClient - SubmitScreenShot', () => {
245
307
  await client.submitScreenShot(base64Image);
246
308
 
247
309
  expect(mockFetch).toHaveBeenCalledWith(
248
- 'https://cms.example.com/xmds.php?v=5',
310
+ 'https://cms.example.com/xmds.php?v=5&method=SubmitScreenShot',
249
311
  expect.objectContaining({
250
312
  method: 'POST',
251
313
  headers: {
@@ -389,7 +451,7 @@ describe('XmdsClient - BlackList', () => {
389
451
  await client.blackList('42', 'media', 'Corrupt file');
390
452
 
391
453
  expect(mockFetch).toHaveBeenCalledWith(
392
- 'https://cms.example.com/xmds.php?v=5',
454
+ 'https://cms.example.com/xmds.php?v=5&method=BlackList',
393
455
  expect.objectContaining({
394
456
  method: 'POST',
395
457
  headers: {
@@ -517,6 +579,80 @@ describe('XmdsClient - BlackList', () => {
517
579
  });
518
580
  });
519
581
 
582
+ describe('XmdsClient - ReportFaults', () => {
583
+ let client;
584
+ let mockFetch;
585
+
586
+ beforeEach(() => {
587
+ client = new XmdsClient({
588
+ cmsAddress: 'https://cms.example.com',
589
+ cmsKey: 'test-server-key',
590
+ hardwareKey: 'test-hardware-key',
591
+ retryOptions: { maxRetries: 0 }
592
+ });
593
+
594
+ mockFetch = vi.fn();
595
+ global.fetch = mockFetch;
596
+ });
597
+
598
+ it('should build correct SOAP envelope for ReportFaults with JSON fault data', async () => {
599
+ mockFetch.mockResolvedValue({
600
+ ok: true,
601
+ text: async () => `<?xml version="1.0"?>
602
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
603
+ <soap:Body>
604
+ <ReportFaultsResponse>
605
+ <success>true</success>
606
+ </ReportFaultsResponse>
607
+ </soap:Body>
608
+ </soap:Envelope>`
609
+ });
610
+
611
+ const fault = JSON.stringify([{
612
+ code: 'LAYOUT_LOAD_FAILED',
613
+ reason: 'Missing resource',
614
+ date: '2026-02-21 10:00:00',
615
+ layoutId: 5
616
+ }]);
617
+
618
+ await client.reportFaults(fault);
619
+
620
+ expect(mockFetch).toHaveBeenCalledWith(
621
+ 'https://cms.example.com/xmds.php?v=5&method=ReportFaults',
622
+ expect.objectContaining({
623
+ method: 'POST',
624
+ headers: {
625
+ 'Content-Type': 'text/xml; charset=utf-8'
626
+ }
627
+ })
628
+ );
629
+
630
+ const body = mockFetch.mock.calls[0][1].body;
631
+ expect(body).toContain('<tns:ReportFaults>');
632
+ expect(body).toContain('<serverKey xsi:type="xsd:string">test-server-key</serverKey>');
633
+ expect(body).toContain('<hardwareKey xsi:type="xsd:string">test-hardware-key</hardwareKey>');
634
+ expect(body).toContain('<fault xsi:type="xsd:string">');
635
+ expect(body).toContain('LAYOUT_LOAD_FAILED');
636
+ });
637
+
638
+ it('should handle SOAP fault', async () => {
639
+ mockFetch.mockResolvedValue({
640
+ ok: true,
641
+ text: async () => `<?xml version="1.0"?>
642
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
643
+ <soap:Body>
644
+ <soap:Fault>
645
+ <faultcode>Server</faultcode>
646
+ <faultstring>Display not licensed</faultstring>
647
+ </soap:Fault>
648
+ </soap:Body>
649
+ </soap:Envelope>`
650
+ });
651
+
652
+ await expect(client.reportFaults('[]')).rejects.toThrow('SOAP Fault: Display not licensed');
653
+ });
654
+ });
655
+
520
656
  describe('XmdsClient - NotifyStatus', () => {
521
657
  let client;
522
658
  let mockFetch;
@@ -565,7 +701,7 @@ describe('XmdsClient - NotifyStatus', () => {
565
701
  await client.notifyStatus(status);
566
702
 
567
703
  expect(mockFetch).toHaveBeenCalledWith(
568
- 'https://cms.example.com/xmds.php?v=5',
704
+ 'https://cms.example.com/xmds.php?v=5&method=NotifyStatus',
569
705
  expect.objectContaining({
570
706
  method: 'POST',
571
707
  headers: {
@@ -781,7 +917,7 @@ describe('XmdsClient - MediaInventory', () => {
781
917
  await client.mediaInventory(inventoryXml);
782
918
 
783
919
  expect(mockFetch).toHaveBeenCalledWith(
784
- 'https://cms.example.com/xmds.php?v=5',
920
+ 'https://cms.example.com/xmds.php?v=5&method=MediaInventory',
785
921
  expect.objectContaining({
786
922
  method: 'POST',
787
923
  headers: {