@virtualdisplay.io/client 3.4.0 → 3.5.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.
@@ -56,6 +56,8 @@ const EVENT_NAMES = {
56
56
  MUTATION_MESSAGE: "mutation:message",
57
57
  // Config flow (Client → Server)
58
58
  CONFIG_MESSAGE: "config:message",
59
+ // Camera flow (Client → Server)
60
+ CAMERA_MESSAGE: "camera:message",
59
61
  // State flow (Server → Client)
60
62
  MESSAGE_RECEIVED: "message:received",
61
63
  STATE_CHANGED: "state:changed",
@@ -396,7 +398,6 @@ class IframeBuilder {
396
398
  height: "100%",
397
399
  border: "none"
398
400
  });
399
- iframe.setAttribute("allowfullscreen", "true");
400
401
  iframe.setAttribute("allow", "xr-spatial-tracking; fullscreen");
401
402
  iframe.addEventListener("load", () => {
402
403
  logger.info("Virtualdisplay iframe loaded");
@@ -6929,7 +6930,8 @@ class MappingValidator {
6929
6930
  const MESSAGE_TYPES = {
6930
6931
  MUTATION: "mutation",
6931
6932
  STATE: "state",
6932
- CONFIG: "config"
6933
+ CONFIG: "config",
6934
+ CAMERA: "camera"
6933
6935
  };
6934
6936
  function createMutationMessage(mutations) {
6935
6937
  if (!Array.isArray(mutations)) {
@@ -7053,6 +7055,61 @@ class AttributeService {
7053
7055
  this.isInitialized = false;
7054
7056
  }
7055
7057
  }
7058
+ class Camera {
7059
+ constructor(eventBus) {
7060
+ this.eventBus = eventBus;
7061
+ }
7062
+ commands = [];
7063
+ /**
7064
+ * Rotate camera horizontally
7065
+ * @param degrees - Absolute rotation in degrees (0 = front view)
7066
+ */
7067
+ rotate(degrees) {
7068
+ this.commands.push({ action: "rotate", value: degrees });
7069
+ return this;
7070
+ }
7071
+ /**
7072
+ * Tilt camera vertically
7073
+ * @param degrees - Absolute tilt in degrees (0 = top view, 90 = horizontal)
7074
+ */
7075
+ tilt(degrees) {
7076
+ this.commands.push({ action: "tilt", value: degrees });
7077
+ return this;
7078
+ }
7079
+ /**
7080
+ * Zoom camera
7081
+ * @param percentage - Zoom percentage (100 = normal, >100 = zoom in, <100 = zoom out)
7082
+ */
7083
+ zoom(percentage) {
7084
+ this.commands.push({ action: "zoom", value: percentage });
7085
+ return this;
7086
+ }
7087
+ /**
7088
+ * Execute accumulated camera commands
7089
+ */
7090
+ set() {
7091
+ if (this.commands.length === 0) {
7092
+ return;
7093
+ }
7094
+ const message = {
7095
+ type: MESSAGE_TYPES.CAMERA,
7096
+ commands: this.commands
7097
+ };
7098
+ this.eventBus.emit(EVENT_NAMES.CAMERA_MESSAGE, { message });
7099
+ this.commands = [];
7100
+ }
7101
+ /**
7102
+ * Reset camera to initial position
7103
+ * This is not chainable and executes immediately
7104
+ */
7105
+ reset() {
7106
+ const message = {
7107
+ type: MESSAGE_TYPES.CAMERA,
7108
+ commands: [{ action: "reset" }]
7109
+ };
7110
+ this.eventBus.emit(EVENT_NAMES.CAMERA_MESSAGE, { message });
7111
+ }
7112
+ }
7056
7113
  class EventBus {
7057
7114
  listeners = /* @__PURE__ */ new Map();
7058
7115
  on(event, listener) {
@@ -7061,8 +7118,19 @@ class EventBus {
7061
7118
  this.listeners.set(event, handlers);
7062
7119
  }
7063
7120
  emit(event, payload) {
7064
- this.listeners.get(event)?.forEach((listener) => listener(payload));
7121
+ const handlers = this.listeners.get(event);
7122
+ if (handlers === void 0 || handlers.length === 0) {
7123
+ return;
7124
+ }
7125
+ const handlersCopy = [...handlers];
7126
+ handlersCopy.forEach((listener) => {
7127
+ listener(payload);
7128
+ });
7065
7129
  }
7130
+ /**
7131
+ * Register a listener that will be called only once
7132
+ * The listener is automatically removed after the first call
7133
+ */
7066
7134
  once(event, listener) {
7067
7135
  const onceWrapper = (payload) => {
7068
7136
  this.off(event, onceWrapper);
@@ -7070,6 +7138,9 @@ class EventBus {
7070
7138
  };
7071
7139
  this.on(event, onceWrapper);
7072
7140
  }
7141
+ /**
7142
+ * Remove a specific listener for an event
7143
+ */
7073
7144
  off(event, listener) {
7074
7145
  const handlers = this.listeners.get(event);
7075
7146
  if (handlers === void 0) {
@@ -7094,16 +7165,11 @@ class MessageHandler {
7094
7165
  }
7095
7166
  setIframe(iframe) {
7096
7167
  this.iframe = iframe;
7097
- logger.debug("Message handler configured with iframe", {
7098
- src: iframe.src,
7099
- loaded: iframe.contentWindow !== null
7100
- });
7101
7168
  }
7102
7169
  destroy() {
7103
7170
  window.removeEventListener("message", this.handleWindowMessage);
7104
7171
  this.queue.length = 0;
7105
7172
  this.isReady = false;
7106
- logger.debug("Message handler destroyed");
7107
7173
  }
7108
7174
  queueOrSend(message) {
7109
7175
  if (!this.isReady) {
@@ -7128,7 +7194,7 @@ class MessageHandler {
7128
7194
  payload: message
7129
7195
  });
7130
7196
  this.iframe.contentWindow.postMessage(message, targetOrigin);
7131
- logger.info("Message sent to server", message);
7197
+ logger.debug("Message sent to server", message);
7132
7198
  }
7133
7199
  canSend() {
7134
7200
  return this.iframe?.contentWindow !== null && this.iframe?.contentWindow !== void 0;
@@ -7156,12 +7222,12 @@ class MessageHandler {
7156
7222
  };
7157
7223
  setupIncomingMessages() {
7158
7224
  window.addEventListener("message", this.handleWindowMessage);
7159
- logger.debug("Message handler started listening");
7160
7225
  }
7161
7226
  setupOutgoingMessages() {
7162
7227
  const outgoingEvents = [
7163
7228
  EVENT_NAMES.MUTATION_MESSAGE,
7164
- EVENT_NAMES.CONFIG_MESSAGE
7229
+ EVENT_NAMES.CONFIG_MESSAGE,
7230
+ EVENT_NAMES.CAMERA_MESSAGE
7165
7231
  ];
7166
7232
  outgoingEvents.forEach((eventName) => {
7167
7233
  this.eventBus.on(eventName, (event) => {
@@ -7374,7 +7440,6 @@ class VirtualdisplayViewerService {
7374
7440
  config.loadingIndicatorEnabled = false;
7375
7441
  }
7376
7442
  if (Object.keys(config).length > 0) {
7377
- logger.debug("Sending initial UI configuration", config);
7378
7443
  this.sendUIConfig(config);
7379
7444
  }
7380
7445
  }
@@ -7437,7 +7502,6 @@ class VirtualdisplayViewerService {
7437
7502
  type: MESSAGE_TYPES.CONFIG,
7438
7503
  config
7439
7504
  };
7440
- logger.debug("Emitting CONFIG message", message);
7441
7505
  this.eventBus.emit(EVENT_NAMES.CONFIG_MESSAGE, { message });
7442
7506
  }
7443
7507
  }
@@ -7447,6 +7511,7 @@ class VirtualdisplayClient {
7447
7511
  stateService;
7448
7512
  messageHandler;
7449
7513
  viewerService;
7514
+ cameraApi;
7450
7515
  options;
7451
7516
  iframe;
7452
7517
  constructor(options) {
@@ -7456,6 +7521,7 @@ class VirtualdisplayClient {
7456
7521
  this.stateService = new StateService(this.eventBus);
7457
7522
  this.messageHandler = new MessageHandler(this.eventBus);
7458
7523
  this.viewerService = new VirtualdisplayViewerService(this.eventBus);
7524
+ this.cameraApi = new Camera(this.eventBus);
7459
7525
  this.initializeLogging();
7460
7526
  this.createAndConnectIframe();
7461
7527
  this.setupInitialConfig();
@@ -7549,6 +7615,12 @@ class VirtualdisplayClient {
7549
7615
  get viewer() {
7550
7616
  return this.viewerService;
7551
7617
  }
7618
+ /**
7619
+ * Get the camera control API
7620
+ */
7621
+ get camera() {
7622
+ return this.cameraApi;
7623
+ }
7552
7624
  destroy() {
7553
7625
  logger.info("Destroying Virtualdisplay client");
7554
7626
  this.attributeService.clear();
@@ -7563,6 +7635,7 @@ class VirtualdisplayClient {
7563
7635
  }
7564
7636
  export {
7565
7637
  AttributeSelector,
7638
+ Camera,
7566
7639
  ERROR_CODES,
7567
7640
  EVENT_NAMES,
7568
7641
  MESSAGE_TYPES,