@virtualdisplay.io/client 3.2.2 → 3.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.
@@ -54,9 +54,12 @@ const EVENT_NAMES = {
54
54
  IFRAME_READY: "iframe:ready",
55
55
  // Mutation flow (Client → Server)
56
56
  MUTATION_MESSAGE: "mutation:message",
57
+ // Config flow (Client → Server)
58
+ CONFIG_MESSAGE: "config:message",
57
59
  // State flow (Server → Client)
58
60
  MESSAGE_RECEIVED: "message:received",
59
- STATE_CHANGED: "state:changed"
61
+ STATE_CHANGED: "state:changed",
62
+ INITIAL_STATE_RECEIVED: "state:initial-received"
60
63
  };
61
64
  const g = Object.freeze({
62
65
  debug: 0,
@@ -6925,7 +6928,8 @@ class MappingValidator {
6925
6928
  }
6926
6929
  const MESSAGE_TYPES = {
6927
6930
  MUTATION: "mutation",
6928
- STATE: "state"
6931
+ STATE: "state",
6932
+ CONFIG: "config"
6929
6933
  };
6930
6934
  function createMutationMessage(mutations) {
6931
6935
  if (!Array.isArray(mutations)) {
@@ -6937,7 +6941,7 @@ function createMutationMessage(mutations) {
6937
6941
  };
6938
6942
  }
6939
6943
  function isValidMessage(data) {
6940
- return isMutationMessage(data) || isStateMessage(data);
6944
+ return isMutationMessage(data) || isStateMessage(data) || isConfigMessage(data);
6941
6945
  }
6942
6946
  function isMutationMessage(data) {
6943
6947
  if (typeof data !== "object" || data === null) {
@@ -6953,6 +6957,13 @@ function isStateMessage(data) {
6953
6957
  const obj = data;
6954
6958
  return obj.type === MESSAGE_TYPES.STATE && Array.isArray(obj.nodes);
6955
6959
  }
6960
+ function isConfigMessage(data) {
6961
+ if (typeof data !== "object" || data === null) {
6962
+ return false;
6963
+ }
6964
+ const obj = data;
6965
+ return obj.type === MESSAGE_TYPES.CONFIG && typeof obj.config === "object" && obj.config !== null;
6966
+ }
6956
6967
  class AttributeService {
6957
6968
  attributes = /* @__PURE__ */ new Map();
6958
6969
  eventBus;
@@ -7148,8 +7159,14 @@ class MessageHandler {
7148
7159
  logger.debug("Message handler started listening");
7149
7160
  }
7150
7161
  setupOutgoingMessages() {
7151
- this.eventBus.on(EVENT_NAMES.MUTATION_MESSAGE, (event) => {
7152
- this.queueOrSend(event.message);
7162
+ const outgoingEvents = [
7163
+ EVENT_NAMES.MUTATION_MESSAGE,
7164
+ EVENT_NAMES.CONFIG_MESSAGE
7165
+ ];
7166
+ outgoingEvents.forEach((eventName) => {
7167
+ this.eventBus.on(eventName, (event) => {
7168
+ this.queueOrSend(event.message);
7169
+ });
7153
7170
  });
7154
7171
  }
7155
7172
  setupReadyListener() {
@@ -7186,7 +7203,7 @@ class NodeSelector {
7186
7203
  logger.warn(`Cannot toggle node ${this.nodeId}: node not found`);
7187
7204
  return;
7188
7205
  }
7189
- if (node.isVisible === true) {
7206
+ if (node.isVisible) {
7190
7207
  this.hide();
7191
7208
  return;
7192
7209
  }
@@ -7224,6 +7241,7 @@ class ModelNode {
7224
7241
  name;
7225
7242
  type;
7226
7243
  visible;
7244
+ onChange;
7227
7245
  constructor(params) {
7228
7246
  this.id = params.id;
7229
7247
  this.name = params.name;
@@ -7243,6 +7261,7 @@ class ModelNode {
7243
7261
  class StateService {
7244
7262
  nodes = /* @__PURE__ */ new Map();
7245
7263
  eventBus;
7264
+ hasReceivedInitialSync = false;
7246
7265
  constructor(eventBus) {
7247
7266
  this.eventBus = eventBus;
7248
7267
  this.eventBus.on(EVENT_NAMES.MESSAGE_RECEIVED, (event) => {
@@ -7255,28 +7274,32 @@ class StateService {
7255
7274
  currentNodeCount: this.nodeCount
7256
7275
  });
7257
7276
  if (isStateMessage(message)) {
7258
- this.updateState(message.nodes);
7277
+ this.updateState(message.nodes, message.isInitial);
7259
7278
  }
7260
7279
  }
7261
- updateState(newNodes) {
7262
- const wasInitial = !this.hasInitialState;
7280
+ updateState(newNodes, isInitial) {
7263
7281
  logger.debug("Updating state with nodes from server", {
7264
7282
  incomingNodes: newNodes.length,
7265
- isInitialState: wasInitial,
7283
+ isInitialState: isInitial,
7266
7284
  currentNodes: this.nodeCount
7267
7285
  });
7268
7286
  for (const nodeData of newNodes) {
7269
- this.updateOrCreateNode(nodeData);
7287
+ const shouldProcessNode = !isInitial || !this.nodes.has(nodeData.id);
7288
+ if (shouldProcessNode) {
7289
+ this.updateOrCreateNode(nodeData);
7290
+ }
7270
7291
  }
7271
7292
  this.eventBus.emit(EVENT_NAMES.STATE_CHANGED, {
7272
7293
  nodes: this.getAllNodes()
7273
7294
  });
7274
7295
  logger.info("State updated", {
7275
7296
  totalNodes: this.nodeCount,
7276
- wasInitialState: wasInitial
7297
+ wasInitialState: isInitial
7277
7298
  });
7278
- if (wasInitial) {
7299
+ if (isInitial && !this.hasReceivedInitialSync) {
7300
+ this.hasReceivedInitialSync = true;
7279
7301
  logger.info("Initial state received - client is now ready");
7302
+ this.eventBus.emit(EVENT_NAMES.INITIAL_STATE_RECEIVED, {});
7280
7303
  }
7281
7304
  }
7282
7305
  updateOrCreateNode(newNodeData) {
@@ -7285,8 +7308,12 @@ class StateService {
7285
7308
  this.createNewNode(newNodeData);
7286
7309
  return;
7287
7310
  }
7311
+ const previousVisibility = existingNode.isVisible;
7288
7312
  const visibilityMethod = newNodeData.visible ? "show" : "hide";
7289
7313
  existingNode[visibilityMethod]();
7314
+ if (previousVisibility !== existingNode.isVisible && existingNode.onChange !== void 0) {
7315
+ existingNode.onChange();
7316
+ }
7290
7317
  logger.debug("Updated existing node", {
7291
7318
  id: existingNode.id,
7292
7319
  name: existingNode.name,
@@ -7316,16 +7343,102 @@ class StateService {
7316
7343
  return Array.from(this.nodes.values());
7317
7344
  }
7318
7345
  get isReady() {
7319
- return this.hasInitialState;
7346
+ return this.hasReceivedInitialSync;
7320
7347
  }
7321
7348
  get nodeCount() {
7322
7349
  return this.nodes.size;
7323
7350
  }
7324
- get hasInitialState() {
7325
- return this.nodeCount > 0;
7326
- }
7327
7351
  clear() {
7328
7352
  this.nodes.clear();
7353
+ this.hasReceivedInitialSync = false;
7354
+ }
7355
+ }
7356
+ class VirtualdisplayViewerService {
7357
+ eventBus;
7358
+ constructor(eventBus) {
7359
+ this.eventBus = eventBus;
7360
+ }
7361
+ /**
7362
+ * Send initial configuration based on client options
7363
+ * Only sends config when values are false (server defaults to true)
7364
+ */
7365
+ sendInitialConfig(options) {
7366
+ const config = {};
7367
+ if (options.arEnabled === false) {
7368
+ config.arEnabled = false;
7369
+ }
7370
+ if (options.fullscreenEnabled === false) {
7371
+ config.fullscreenEnabled = false;
7372
+ }
7373
+ if (options.loadingIndicatorEnabled === false) {
7374
+ config.loadingIndicatorEnabled = false;
7375
+ }
7376
+ if (Object.keys(config).length > 0) {
7377
+ logger.debug("Sending initial UI configuration", config);
7378
+ this.sendUIConfig(config);
7379
+ }
7380
+ }
7381
+ /**
7382
+ * Update AR button visibility
7383
+ */
7384
+ setArEnabled(enabled) {
7385
+ logger.debug(`Setting AR button enabled: ${enabled}`);
7386
+ this.sendUIConfig({ arEnabled: enabled });
7387
+ }
7388
+ /**
7389
+ * Update fullscreen button visibility
7390
+ */
7391
+ setFullscreenEnabled(enabled) {
7392
+ logger.debug(`Setting fullscreen button enabled: ${enabled}`);
7393
+ this.sendUIConfig({ fullscreenEnabled: enabled });
7394
+ }
7395
+ /**
7396
+ * Update loading indicator visibility
7397
+ */
7398
+ setLoadingIndicatorEnabled(enabled) {
7399
+ logger.debug(`Setting loading indicator enabled: ${enabled}`);
7400
+ this.sendUIConfig({ loadingIndicatorEnabled: enabled });
7401
+ }
7402
+ /**
7403
+ * Update multiple UI settings at once
7404
+ */
7405
+ updateUIConfig(config) {
7406
+ logger.debug("Updating UI configuration", config);
7407
+ this.sendUIConfig(config);
7408
+ }
7409
+ /**
7410
+ * Hide all UI elements
7411
+ */
7412
+ hideAllUI() {
7413
+ logger.debug("Hiding all UI elements");
7414
+ this.sendUIConfig({
7415
+ arEnabled: false,
7416
+ fullscreenEnabled: false,
7417
+ loadingIndicatorEnabled: false
7418
+ });
7419
+ }
7420
+ /**
7421
+ * Show all UI elements
7422
+ */
7423
+ showAllUI() {
7424
+ logger.debug("Showing all UI elements");
7425
+ this.sendUIConfig({
7426
+ arEnabled: true,
7427
+ fullscreenEnabled: true,
7428
+ loadingIndicatorEnabled: true
7429
+ });
7430
+ }
7431
+ /**
7432
+ * Send UI configuration to server
7433
+ */
7434
+ sendUIConfig(uiConfig) {
7435
+ const config = { ui: uiConfig };
7436
+ const message = {
7437
+ type: MESSAGE_TYPES.CONFIG,
7438
+ config
7439
+ };
7440
+ logger.debug("Emitting CONFIG message", message);
7441
+ this.eventBus.emit(EVENT_NAMES.CONFIG_MESSAGE, { message });
7329
7442
  }
7330
7443
  }
7331
7444
  class VirtualdisplayClient {
@@ -7333,6 +7446,7 @@ class VirtualdisplayClient {
7333
7446
  attributeService;
7334
7447
  stateService;
7335
7448
  messageHandler;
7449
+ viewerService;
7336
7450
  options;
7337
7451
  iframe;
7338
7452
  constructor(options) {
@@ -7341,8 +7455,10 @@ class VirtualdisplayClient {
7341
7455
  this.attributeService = new AttributeService(this.eventBus);
7342
7456
  this.stateService = new StateService(this.eventBus);
7343
7457
  this.messageHandler = new MessageHandler(this.eventBus);
7458
+ this.viewerService = new VirtualdisplayViewerService(this.eventBus);
7344
7459
  this.initializeLogging();
7345
7460
  this.createAndConnectIframe();
7461
+ this.setupInitialConfig();
7346
7462
  }
7347
7463
  initializeLogging() {
7348
7464
  if (this.options.debug === true) {
@@ -7361,6 +7477,11 @@ class VirtualdisplayClient {
7361
7477
  logger.debug("Iframe created, setting up messaging");
7362
7478
  this.messageHandler.setIframe(this.iframe);
7363
7479
  }
7480
+ setupInitialConfig() {
7481
+ this.eventBus.once(EVENT_NAMES.IFRAME_READY, () => {
7482
+ this.viewerService.sendInitialConfig(this.options);
7483
+ });
7484
+ }
7364
7485
  /**
7365
7486
  * Load attribute mapping configuration
7366
7487
  * @throws {VirtualdisplayError} When mapping configuration is invalid
@@ -7405,6 +7526,29 @@ class VirtualdisplayClient {
7405
7526
  getNodes() {
7406
7527
  return this.stateService.getAllNodes();
7407
7528
  }
7529
+ /**
7530
+ * Check if the client has received initial state and is ready
7531
+ */
7532
+ get isReady() {
7533
+ return this.stateService.isReady;
7534
+ }
7535
+ /**
7536
+ * Register a callback to be called when the client is ready
7537
+ * If already ready, the callback is called immediately
7538
+ */
7539
+ onReady(callback) {
7540
+ if (this.stateService.isReady) {
7541
+ callback();
7542
+ return;
7543
+ }
7544
+ this.eventBus.once(EVENT_NAMES.INITIAL_STATE_RECEIVED, callback);
7545
+ }
7546
+ /**
7547
+ * Get the viewer service for UI configuration
7548
+ */
7549
+ get viewer() {
7550
+ return this.viewerService;
7551
+ }
7408
7552
  destroy() {
7409
7553
  logger.info("Destroying Virtualdisplay client");
7410
7554
  this.attributeService.clear();
@@ -7430,6 +7574,7 @@ export {
7430
7574
  NodeSelector,
7431
7575
  VirtualdisplayClient,
7432
7576
  VirtualdisplayError,
7577
+ VirtualdisplayViewerService,
7433
7578
  enableDebugMode,
7434
7579
  isDebugMode,
7435
7580
  logger,