@virtualdisplay.io/client 3.3.0 → 3.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.
@@ -54,6 +54,8 @@ 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
61
  STATE_CHANGED: "state:changed",
@@ -394,7 +396,6 @@ class IframeBuilder {
394
396
  height: "100%",
395
397
  border: "none"
396
398
  });
397
- iframe.setAttribute("allowfullscreen", "true");
398
399
  iframe.setAttribute("allow", "xr-spatial-tracking; fullscreen");
399
400
  iframe.addEventListener("load", () => {
400
401
  logger.info("Virtualdisplay iframe loaded");
@@ -6926,7 +6927,8 @@ class MappingValidator {
6926
6927
  }
6927
6928
  const MESSAGE_TYPES = {
6928
6929
  MUTATION: "mutation",
6929
- STATE: "state"
6930
+ STATE: "state",
6931
+ CONFIG: "config"
6930
6932
  };
6931
6933
  function createMutationMessage(mutations) {
6932
6934
  if (!Array.isArray(mutations)) {
@@ -6938,7 +6940,7 @@ function createMutationMessage(mutations) {
6938
6940
  };
6939
6941
  }
6940
6942
  function isValidMessage(data) {
6941
- return isMutationMessage(data) || isStateMessage(data);
6943
+ return isMutationMessage(data) || isStateMessage(data) || isConfigMessage(data);
6942
6944
  }
6943
6945
  function isMutationMessage(data) {
6944
6946
  if (typeof data !== "object" || data === null) {
@@ -6954,6 +6956,13 @@ function isStateMessage(data) {
6954
6956
  const obj = data;
6955
6957
  return obj.type === MESSAGE_TYPES.STATE && Array.isArray(obj.nodes);
6956
6958
  }
6959
+ function isConfigMessage(data) {
6960
+ if (typeof data !== "object" || data === null) {
6961
+ return false;
6962
+ }
6963
+ const obj = data;
6964
+ return obj.type === MESSAGE_TYPES.CONFIG && typeof obj.config === "object" && obj.config !== null;
6965
+ }
6957
6966
  class AttributeService {
6958
6967
  attributes = /* @__PURE__ */ new Map();
6959
6968
  eventBus;
@@ -7051,8 +7060,19 @@ class EventBus {
7051
7060
  this.listeners.set(event, handlers);
7052
7061
  }
7053
7062
  emit(event, payload) {
7054
- this.listeners.get(event)?.forEach((listener) => listener(payload));
7063
+ const handlers = this.listeners.get(event);
7064
+ if (handlers === void 0 || handlers.length === 0) {
7065
+ return;
7066
+ }
7067
+ const handlersCopy = [...handlers];
7068
+ handlersCopy.forEach((listener) => {
7069
+ listener(payload);
7070
+ });
7055
7071
  }
7072
+ /**
7073
+ * Register a listener that will be called only once
7074
+ * The listener is automatically removed after the first call
7075
+ */
7056
7076
  once(event, listener) {
7057
7077
  const onceWrapper = (payload) => {
7058
7078
  this.off(event, onceWrapper);
@@ -7060,6 +7080,9 @@ class EventBus {
7060
7080
  };
7061
7081
  this.on(event, onceWrapper);
7062
7082
  }
7083
+ /**
7084
+ * Remove a specific listener for an event
7085
+ */
7063
7086
  off(event, listener) {
7064
7087
  const handlers = this.listeners.get(event);
7065
7088
  if (handlers === void 0) {
@@ -7084,16 +7107,11 @@ class MessageHandler {
7084
7107
  }
7085
7108
  setIframe(iframe) {
7086
7109
  this.iframe = iframe;
7087
- logger.debug("Message handler configured with iframe", {
7088
- src: iframe.src,
7089
- loaded: iframe.contentWindow !== null
7090
- });
7091
7110
  }
7092
7111
  destroy() {
7093
7112
  window.removeEventListener("message", this.handleWindowMessage);
7094
7113
  this.queue.length = 0;
7095
7114
  this.isReady = false;
7096
- logger.debug("Message handler destroyed");
7097
7115
  }
7098
7116
  queueOrSend(message) {
7099
7117
  if (!this.isReady) {
@@ -7118,7 +7136,7 @@ class MessageHandler {
7118
7136
  payload: message
7119
7137
  });
7120
7138
  this.iframe.contentWindow.postMessage(message, targetOrigin);
7121
- logger.info("Message sent to server", message);
7139
+ logger.debug("Message sent to server", message);
7122
7140
  }
7123
7141
  canSend() {
7124
7142
  return this.iframe?.contentWindow !== null && this.iframe?.contentWindow !== void 0;
@@ -7146,11 +7164,16 @@ class MessageHandler {
7146
7164
  };
7147
7165
  setupIncomingMessages() {
7148
7166
  window.addEventListener("message", this.handleWindowMessage);
7149
- logger.debug("Message handler started listening");
7150
7167
  }
7151
7168
  setupOutgoingMessages() {
7152
- this.eventBus.on(EVENT_NAMES.MUTATION_MESSAGE, (event) => {
7153
- this.queueOrSend(event.message);
7169
+ const outgoingEvents = [
7170
+ EVENT_NAMES.MUTATION_MESSAGE,
7171
+ EVENT_NAMES.CONFIG_MESSAGE
7172
+ ];
7173
+ outgoingEvents.forEach((eventName) => {
7174
+ this.eventBus.on(eventName, (event) => {
7175
+ this.queueOrSend(event.message);
7176
+ });
7154
7177
  });
7155
7178
  }
7156
7179
  setupReadyListener() {
@@ -7187,7 +7210,7 @@ class NodeSelector {
7187
7210
  logger.warn(`Cannot toggle node ${this.nodeId}: node not found`);
7188
7211
  return;
7189
7212
  }
7190
- if (node.isVisible === true) {
7213
+ if (node.isVisible) {
7191
7214
  this.hide();
7192
7215
  return;
7193
7216
  }
@@ -7337,11 +7360,98 @@ class StateService {
7337
7360
  this.hasReceivedInitialSync = false;
7338
7361
  }
7339
7362
  }
7363
+ class VirtualdisplayViewerService {
7364
+ eventBus;
7365
+ constructor(eventBus) {
7366
+ this.eventBus = eventBus;
7367
+ }
7368
+ /**
7369
+ * Send initial configuration based on client options
7370
+ * Only sends config when values are false (server defaults to true)
7371
+ */
7372
+ sendInitialConfig(options) {
7373
+ const config = {};
7374
+ if (options.arEnabled === false) {
7375
+ config.arEnabled = false;
7376
+ }
7377
+ if (options.fullscreenEnabled === false) {
7378
+ config.fullscreenEnabled = false;
7379
+ }
7380
+ if (options.loadingIndicatorEnabled === false) {
7381
+ config.loadingIndicatorEnabled = false;
7382
+ }
7383
+ if (Object.keys(config).length > 0) {
7384
+ this.sendUIConfig(config);
7385
+ }
7386
+ }
7387
+ /**
7388
+ * Update AR button visibility
7389
+ */
7390
+ setArEnabled(enabled) {
7391
+ logger.debug(`Setting AR button enabled: ${enabled}`);
7392
+ this.sendUIConfig({ arEnabled: enabled });
7393
+ }
7394
+ /**
7395
+ * Update fullscreen button visibility
7396
+ */
7397
+ setFullscreenEnabled(enabled) {
7398
+ logger.debug(`Setting fullscreen button enabled: ${enabled}`);
7399
+ this.sendUIConfig({ fullscreenEnabled: enabled });
7400
+ }
7401
+ /**
7402
+ * Update loading indicator visibility
7403
+ */
7404
+ setLoadingIndicatorEnabled(enabled) {
7405
+ logger.debug(`Setting loading indicator enabled: ${enabled}`);
7406
+ this.sendUIConfig({ loadingIndicatorEnabled: enabled });
7407
+ }
7408
+ /**
7409
+ * Update multiple UI settings at once
7410
+ */
7411
+ updateUIConfig(config) {
7412
+ logger.debug("Updating UI configuration", config);
7413
+ this.sendUIConfig(config);
7414
+ }
7415
+ /**
7416
+ * Hide all UI elements
7417
+ */
7418
+ hideAllUI() {
7419
+ logger.debug("Hiding all UI elements");
7420
+ this.sendUIConfig({
7421
+ arEnabled: false,
7422
+ fullscreenEnabled: false,
7423
+ loadingIndicatorEnabled: false
7424
+ });
7425
+ }
7426
+ /**
7427
+ * Show all UI elements
7428
+ */
7429
+ showAllUI() {
7430
+ logger.debug("Showing all UI elements");
7431
+ this.sendUIConfig({
7432
+ arEnabled: true,
7433
+ fullscreenEnabled: true,
7434
+ loadingIndicatorEnabled: true
7435
+ });
7436
+ }
7437
+ /**
7438
+ * Send UI configuration to server
7439
+ */
7440
+ sendUIConfig(uiConfig) {
7441
+ const config = { ui: uiConfig };
7442
+ const message = {
7443
+ type: MESSAGE_TYPES.CONFIG,
7444
+ config
7445
+ };
7446
+ this.eventBus.emit(EVENT_NAMES.CONFIG_MESSAGE, { message });
7447
+ }
7448
+ }
7340
7449
  class VirtualdisplayClient {
7341
7450
  eventBus;
7342
7451
  attributeService;
7343
7452
  stateService;
7344
7453
  messageHandler;
7454
+ viewerService;
7345
7455
  options;
7346
7456
  iframe;
7347
7457
  constructor(options) {
@@ -7350,8 +7460,10 @@ class VirtualdisplayClient {
7350
7460
  this.attributeService = new AttributeService(this.eventBus);
7351
7461
  this.stateService = new StateService(this.eventBus);
7352
7462
  this.messageHandler = new MessageHandler(this.eventBus);
7463
+ this.viewerService = new VirtualdisplayViewerService(this.eventBus);
7353
7464
  this.initializeLogging();
7354
7465
  this.createAndConnectIframe();
7466
+ this.setupInitialConfig();
7355
7467
  }
7356
7468
  initializeLogging() {
7357
7469
  if (this.options.debug === true) {
@@ -7370,6 +7482,11 @@ class VirtualdisplayClient {
7370
7482
  logger.debug("Iframe created, setting up messaging");
7371
7483
  this.messageHandler.setIframe(this.iframe);
7372
7484
  }
7485
+ setupInitialConfig() {
7486
+ this.eventBus.once(EVENT_NAMES.IFRAME_READY, () => {
7487
+ this.viewerService.sendInitialConfig(this.options);
7488
+ });
7489
+ }
7373
7490
  /**
7374
7491
  * Load attribute mapping configuration
7375
7492
  * @throws {VirtualdisplayError} When mapping configuration is invalid
@@ -7431,6 +7548,12 @@ class VirtualdisplayClient {
7431
7548
  }
7432
7549
  this.eventBus.once(EVENT_NAMES.INITIAL_STATE_RECEIVED, callback);
7433
7550
  }
7551
+ /**
7552
+ * Get the viewer service for UI configuration
7553
+ */
7554
+ get viewer() {
7555
+ return this.viewerService;
7556
+ }
7434
7557
  destroy() {
7435
7558
  logger.info("Destroying Virtualdisplay client");
7436
7559
  this.attributeService.clear();
@@ -7456,6 +7579,7 @@ export {
7456
7579
  NodeSelector,
7457
7580
  VirtualdisplayClient,
7458
7581
  VirtualdisplayError,
7582
+ VirtualdisplayViewerService,
7459
7583
  enableDebugMode,
7460
7584
  isDebugMode,
7461
7585
  logger,