@xiboplayer/xmr 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/xmr",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "XMR WebSocket client for real-time Xibo CMS commands",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "@xibosignage/xibo-communication-framework": "^0.0.6",
12
- "@xiboplayer/utils": "0.3.6"
12
+ "@xiboplayer/utils": "0.4.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "vitest": "^2.0.0"
@@ -156,10 +156,18 @@ export class XmrWrapper {
156
156
  });
157
157
 
158
158
  // CMS command: Change Layout
159
- this.xmr.on('changeLayout', async (layoutId) => {
160
- log.info('Received changeLayout command:', layoutId);
159
+ // Payload may be a layoutId string or an object with { layoutId, duration, downloadRequired, changeMode }
160
+ this.xmr.on('changeLayout', async (data) => {
161
+ const layoutId = typeof data === 'object' ? (data.layoutId || data) : data;
162
+ const duration = typeof data === 'object' ? (parseInt(data.duration) || 0) : 0;
163
+ const changeMode = typeof data === 'object' ? (data.changeMode || 'replace') : 'replace';
164
+ log.info('Received changeLayout command:', layoutId, duration ? `duration=${duration}s` : '', changeMode !== 'replace' ? `mode=${changeMode}` : '');
161
165
  try {
162
- await this.player.changeLayout(layoutId);
166
+ if (typeof data === 'object' && data.downloadRequired === true) {
167
+ log.info('changeLayout: downloadRequired — triggering collection first');
168
+ await this.player.collect();
169
+ }
170
+ await this.player.changeLayout(layoutId, { duration, changeMode });
163
171
  log.debug('changeLayout completed successfully');
164
172
  } catch (error) {
165
173
  log.error('changeLayout failed:', error);
@@ -167,10 +175,17 @@ export class XmrWrapper {
167
175
  });
168
176
 
169
177
  // CMS command: Overlay Layout
170
- this.xmr.on('overlayLayout', async (layoutId) => {
171
- log.info('Received overlayLayout command:', layoutId);
178
+ // Payload may be a layoutId string or an object with { layoutId, duration, downloadRequired }
179
+ this.xmr.on('overlayLayout', async (data) => {
180
+ const layoutId = typeof data === 'object' ? (data.layoutId || data) : data;
181
+ const duration = typeof data === 'object' ? (parseInt(data.duration) || 0) : 0;
182
+ log.info('Received overlayLayout command:', layoutId, duration ? `duration=${duration}s` : '');
172
183
  try {
173
- await this.player.overlayLayout(layoutId);
184
+ if (typeof data === 'object' && data.downloadRequired === true) {
185
+ log.info('overlayLayout: downloadRequired — triggering collection first');
186
+ await this.player.collect();
187
+ }
188
+ await this.player.overlayLayout(layoutId, { duration });
174
189
  log.debug('overlayLayout completed successfully');
175
190
  } catch (error) {
176
191
  log.error('overlayLayout failed:', error);
@@ -200,10 +215,14 @@ export class XmrWrapper {
200
215
  });
201
216
 
202
217
  // CMS command: Execute Command
218
+ // Resolve command from local display settings (from RegisterDisplay), not from XMR payload
203
219
  this.xmr.on('commandAction', async (data) => {
204
- log.info('Received commandAction command:', data);
220
+ const commandCode = data?.commandCode || data;
221
+ log.info('Received commandAction command:', commandCode);
205
222
  try {
206
- await this.player.executeCommand(data?.commandCode || data, data?.commands);
223
+ // Use local commands from RegisterDisplay (stored on player), not XMR payload commands
224
+ const localCommands = this.player.displayCommands || data?.commands;
225
+ await this.player.executeCommand(commandCode, localCommands);
207
226
  log.debug('commandAction completed successfully');
208
227
  } catch (error) {
209
228
  log.error('commandAction failed:', error);
@@ -232,9 +251,9 @@ export class XmrWrapper {
232
251
  }
233
252
  });
234
253
 
235
- // CMS command: Rekey (RSA key pair rotation)
236
- this.xmr.on('rekey', async () => {
237
- log.info('Received rekey command - rotating RSA key pair');
254
+ // CMS command: Rekey (RSA key pair rotation) — spec event name is 'rekeyAction'
255
+ this.xmr.on('rekeyAction', async () => {
256
+ log.info('Received rekeyAction command - rotating RSA key pair');
238
257
  try {
239
258
  this.config.data.xmrPubKey = '';
240
259
  this.config.data.xmrPrivKey = '';
@@ -287,7 +287,7 @@ describe('XmrWrapper', () => {
287
287
  xmrInstance.simulateCommand('changeLayout', 'layout-123');
288
288
  await vi.runAllTimersAsync();
289
289
 
290
- expect(mockPlayer.changeLayout).toHaveBeenCalledWith('layout-123');
290
+ expect(mockPlayer.changeLayout).toHaveBeenCalledWith('layout-123', { duration: 0, changeMode: 'replace' });
291
291
  });
292
292
 
293
293
  it('should handle changeLayout failure gracefully', async () => {
@@ -310,7 +310,7 @@ describe('XmrWrapper', () => {
310
310
  mockConfig.data.xmrPubKey = 'old-pub-key';
311
311
  mockConfig.data.xmrPrivKey = 'old-priv-key';
312
312
 
313
- xmrInstance.simulateCommand('rekey');
313
+ xmrInstance.simulateCommand('rekeyAction');
314
314
  await vi.runAllTimersAsync();
315
315
 
316
316
  // Should clear old keys before regenerating
@@ -326,7 +326,7 @@ describe('XmrWrapper', () => {
326
326
  mockConfig.ensureXmrKeyPair.mockRejectedValue(new Error('Key generation failed'));
327
327
  const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
328
328
 
329
- xmrInstance.simulateCommand('rekey');
329
+ xmrInstance.simulateCommand('rekeyAction');
330
330
  await vi.runAllTimersAsync();
331
331
 
332
332
  expect(consoleErrorSpy).toHaveBeenCalledWith(
@@ -435,7 +435,7 @@ describe('XmrWrapper', () => {
435
435
  xmrInstance.simulateCommand('overlayLayout', 'overlay-layout-456');
436
436
  await vi.runAllTimersAsync();
437
437
 
438
- expect(mockPlayer.overlayLayout).toHaveBeenCalledWith('overlay-layout-456');
438
+ expect(mockPlayer.overlayLayout).toHaveBeenCalledWith('overlay-layout-456', { duration: 0 });
439
439
  });
440
440
 
441
441
  it('should handle overlayLayout failure gracefully', async () => {
@@ -745,7 +745,7 @@ describe('XmrWrapper', () => {
745
745
 
746
746
  expect(mockPlayer.collect).toHaveBeenCalled();
747
747
  expect(mockPlayer.captureScreenshot).toHaveBeenCalled();
748
- expect(mockPlayer.changeLayout).toHaveBeenCalledWith('layout-123');
748
+ expect(mockPlayer.changeLayout).toHaveBeenCalledWith('layout-123', { duration: 0, changeMode: 'replace' });
749
749
  });
750
750
 
751
751
  it('should handle rapid connect/disconnect cycles', async () => {