@virtualdisplay.io/client 3.4.1 → 3.5.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.
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +142 -1
- package/dist/virtualdisplay.client.cjs.js +3 -3
- package/dist/virtualdisplay.client.cjs.js.map +1 -1
- package/dist/virtualdisplay.client.es.js +243 -15
- package/dist/virtualdisplay.client.es.js.map +1 -1
- package/dist/virtualdisplay.client.umd.js +3 -3
- package/dist/virtualdisplay.client.umd.js.map +1 -1
- package/package.json +10 -10
|
@@ -4,7 +4,8 @@ const ERROR_CODES = {
|
|
|
4
4
|
VALUE_NOT_FOUND: "VALUE_NOT_FOUND",
|
|
5
5
|
INVALID_MAPPING: "INVALID_MAPPING",
|
|
6
6
|
PARENT_NOT_FOUND: "PARENT_NOT_FOUND",
|
|
7
|
-
INVALID_MUTATIONS: "INVALID_MUTATIONS"
|
|
7
|
+
INVALID_MUTATIONS: "INVALID_MUTATIONS",
|
|
8
|
+
INVALID_PARAMETER: "INVALID_PARAMETER"
|
|
8
9
|
};
|
|
9
10
|
class VirtualdisplayError extends Error {
|
|
10
11
|
code;
|
|
@@ -56,10 +57,15 @@ const EVENT_NAMES = {
|
|
|
56
57
|
MUTATION_MESSAGE: "mutation:message",
|
|
57
58
|
// Config flow (Client → Server)
|
|
58
59
|
CONFIG_MESSAGE: "config:message",
|
|
60
|
+
// Camera flow (Client → Server)
|
|
61
|
+
CAMERA_MESSAGE: "camera:message",
|
|
59
62
|
// State flow (Server → Client)
|
|
60
63
|
MESSAGE_RECEIVED: "message:received",
|
|
61
64
|
STATE_CHANGED: "state:changed",
|
|
62
|
-
INITIAL_STATE_RECEIVED: "state:initial-received"
|
|
65
|
+
INITIAL_STATE_RECEIVED: "state:initial-received",
|
|
66
|
+
// Snapshot flow
|
|
67
|
+
SNAPSHOT_MESSAGE: "snapshot:take",
|
|
68
|
+
SNAPSHOT_DEVELOPED: "snapshot:developed"
|
|
63
69
|
};
|
|
64
70
|
const g = Object.freeze({
|
|
65
71
|
debug: 0,
|
|
@@ -6928,7 +6934,9 @@ class MappingValidator {
|
|
|
6928
6934
|
const MESSAGE_TYPES = {
|
|
6929
6935
|
MUTATION: "mutation",
|
|
6930
6936
|
STATE: "state",
|
|
6931
|
-
CONFIG: "config"
|
|
6937
|
+
CONFIG: "config",
|
|
6938
|
+
CAMERA: "camera",
|
|
6939
|
+
SNAPSHOT: "snapshot"
|
|
6932
6940
|
};
|
|
6933
6941
|
function createMutationMessage(mutations) {
|
|
6934
6942
|
if (!Array.isArray(mutations)) {
|
|
@@ -6940,14 +6948,7 @@ function createMutationMessage(mutations) {
|
|
|
6940
6948
|
};
|
|
6941
6949
|
}
|
|
6942
6950
|
function isValidMessage(data) {
|
|
6943
|
-
return
|
|
6944
|
-
}
|
|
6945
|
-
function isMutationMessage(data) {
|
|
6946
|
-
if (typeof data !== "object" || data === null) {
|
|
6947
|
-
return false;
|
|
6948
|
-
}
|
|
6949
|
-
const obj = data;
|
|
6950
|
-
return obj.type === MESSAGE_TYPES.MUTATION && Array.isArray(obj.mutations);
|
|
6951
|
+
return isStateMessage(data) || isSnapshotDevelopedMessage(data);
|
|
6951
6952
|
}
|
|
6952
6953
|
function isStateMessage(data) {
|
|
6953
6954
|
if (typeof data !== "object" || data === null) {
|
|
@@ -6956,12 +6957,12 @@ function isStateMessage(data) {
|
|
|
6956
6957
|
const obj = data;
|
|
6957
6958
|
return obj.type === MESSAGE_TYPES.STATE && Array.isArray(obj.nodes);
|
|
6958
6959
|
}
|
|
6959
|
-
function
|
|
6960
|
+
function isSnapshotDevelopedMessage(data) {
|
|
6960
6961
|
if (typeof data !== "object" || data === null) {
|
|
6961
6962
|
return false;
|
|
6962
6963
|
}
|
|
6963
6964
|
const obj = data;
|
|
6964
|
-
return obj.type === MESSAGE_TYPES.
|
|
6965
|
+
return obj.type === MESSAGE_TYPES.SNAPSHOT && typeof obj.filename === "string" && typeof obj.data === "string";
|
|
6965
6966
|
}
|
|
6966
6967
|
class AttributeService {
|
|
6967
6968
|
attributes = /* @__PURE__ */ new Map();
|
|
@@ -7052,6 +7053,61 @@ class AttributeService {
|
|
|
7052
7053
|
this.isInitialized = false;
|
|
7053
7054
|
}
|
|
7054
7055
|
}
|
|
7056
|
+
class Camera {
|
|
7057
|
+
constructor(eventBus) {
|
|
7058
|
+
this.eventBus = eventBus;
|
|
7059
|
+
}
|
|
7060
|
+
commands = [];
|
|
7061
|
+
/**
|
|
7062
|
+
* Rotate camera horizontally
|
|
7063
|
+
* @param degrees - Absolute rotation in degrees (0 = front view)
|
|
7064
|
+
*/
|
|
7065
|
+
rotate(degrees) {
|
|
7066
|
+
this.commands.push({ action: "rotate", value: degrees });
|
|
7067
|
+
return this;
|
|
7068
|
+
}
|
|
7069
|
+
/**
|
|
7070
|
+
* Tilt camera vertically
|
|
7071
|
+
* @param degrees - Absolute tilt in degrees (0 = top view, 90 = horizontal)
|
|
7072
|
+
*/
|
|
7073
|
+
tilt(degrees) {
|
|
7074
|
+
this.commands.push({ action: "tilt", value: degrees });
|
|
7075
|
+
return this;
|
|
7076
|
+
}
|
|
7077
|
+
/**
|
|
7078
|
+
* Zoom camera
|
|
7079
|
+
* @param percentage - Zoom percentage (100 = normal, >100 = zoom in, <100 = zoom out)
|
|
7080
|
+
*/
|
|
7081
|
+
zoom(percentage) {
|
|
7082
|
+
this.commands.push({ action: "zoom", value: percentage });
|
|
7083
|
+
return this;
|
|
7084
|
+
}
|
|
7085
|
+
/**
|
|
7086
|
+
* Execute accumulated camera commands
|
|
7087
|
+
*/
|
|
7088
|
+
set() {
|
|
7089
|
+
if (this.commands.length === 0) {
|
|
7090
|
+
return;
|
|
7091
|
+
}
|
|
7092
|
+
const message = {
|
|
7093
|
+
type: MESSAGE_TYPES.CAMERA,
|
|
7094
|
+
commands: this.commands
|
|
7095
|
+
};
|
|
7096
|
+
this.eventBus.emit(EVENT_NAMES.CAMERA_MESSAGE, { message });
|
|
7097
|
+
this.commands = [];
|
|
7098
|
+
}
|
|
7099
|
+
/**
|
|
7100
|
+
* Reset camera to initial position
|
|
7101
|
+
* This is not chainable and executes immediately
|
|
7102
|
+
*/
|
|
7103
|
+
reset() {
|
|
7104
|
+
const message = {
|
|
7105
|
+
type: MESSAGE_TYPES.CAMERA,
|
|
7106
|
+
commands: [{ action: "reset" }]
|
|
7107
|
+
};
|
|
7108
|
+
this.eventBus.emit(EVENT_NAMES.CAMERA_MESSAGE, { message });
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7055
7111
|
class EventBus {
|
|
7056
7112
|
listeners = /* @__PURE__ */ new Map();
|
|
7057
7113
|
on(event, listener) {
|
|
@@ -7168,11 +7224,14 @@ class MessageHandler {
|
|
|
7168
7224
|
setupOutgoingMessages() {
|
|
7169
7225
|
const outgoingEvents = [
|
|
7170
7226
|
EVENT_NAMES.MUTATION_MESSAGE,
|
|
7171
|
-
EVENT_NAMES.CONFIG_MESSAGE
|
|
7227
|
+
EVENT_NAMES.CONFIG_MESSAGE,
|
|
7228
|
+
EVENT_NAMES.CAMERA_MESSAGE,
|
|
7229
|
+
EVENT_NAMES.SNAPSHOT_MESSAGE
|
|
7172
7230
|
];
|
|
7173
7231
|
outgoingEvents.forEach((eventName) => {
|
|
7174
7232
|
this.eventBus.on(eventName, (event) => {
|
|
7175
|
-
|
|
7233
|
+
const messageEvent = event;
|
|
7234
|
+
this.queueOrSend(messageEvent.message);
|
|
7176
7235
|
});
|
|
7177
7236
|
});
|
|
7178
7237
|
}
|
|
@@ -7360,6 +7419,156 @@ class StateService {
|
|
|
7360
7419
|
this.hasReceivedInitialSync = false;
|
|
7361
7420
|
}
|
|
7362
7421
|
}
|
|
7422
|
+
class Photo {
|
|
7423
|
+
constructor(filename) {
|
|
7424
|
+
this.filename = filename;
|
|
7425
|
+
}
|
|
7426
|
+
callbacks = /* @__PURE__ */ new Set();
|
|
7427
|
+
developed = false;
|
|
7428
|
+
photoData = null;
|
|
7429
|
+
/**
|
|
7430
|
+
* Register a callback for when the photo is developed
|
|
7431
|
+
* If the photo is already developed, the callback is called immediately
|
|
7432
|
+
*
|
|
7433
|
+
* @param callback Function to call with photo data
|
|
7434
|
+
* @returns This Photo instance for chaining
|
|
7435
|
+
*/
|
|
7436
|
+
onDeveloped(callback) {
|
|
7437
|
+
if (this.developed && this.photoData !== null) {
|
|
7438
|
+
callback(this.photoData);
|
|
7439
|
+
return this;
|
|
7440
|
+
}
|
|
7441
|
+
this.callbacks.add(callback);
|
|
7442
|
+
return this;
|
|
7443
|
+
}
|
|
7444
|
+
/**
|
|
7445
|
+
* Get the filename for this photo
|
|
7446
|
+
*/
|
|
7447
|
+
getFilename() {
|
|
7448
|
+
return this.filename;
|
|
7449
|
+
}
|
|
7450
|
+
/**
|
|
7451
|
+
* Mark the photo as developed and notify all callbacks
|
|
7452
|
+
* This method is called by the SnapshotMessageHandler when the photo data arrives
|
|
7453
|
+
*/
|
|
7454
|
+
develop(photoData) {
|
|
7455
|
+
if (this.developed) {
|
|
7456
|
+
return;
|
|
7457
|
+
}
|
|
7458
|
+
this.photoData = photoData;
|
|
7459
|
+
this.developed = true;
|
|
7460
|
+
this.callbacks.forEach((callback) => {
|
|
7461
|
+
callback(this.photoData);
|
|
7462
|
+
});
|
|
7463
|
+
this.callbacks.clear();
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
class SnapshotMessageHandler {
|
|
7467
|
+
constructor(eventBus) {
|
|
7468
|
+
this.eventBus = eventBus;
|
|
7469
|
+
logger.debug("SnapshotMessageHandler initialized");
|
|
7470
|
+
this.setupMessageListener();
|
|
7471
|
+
}
|
|
7472
|
+
pendingPhotos = /* @__PURE__ */ new Map();
|
|
7473
|
+
/**
|
|
7474
|
+
* Register a photo to be developed when its data arrives
|
|
7475
|
+
*/
|
|
7476
|
+
registerPhoto(photo) {
|
|
7477
|
+
logger.debug("Registering photo for development", { filename: photo.getFilename() });
|
|
7478
|
+
this.pendingPhotos.set(photo.getFilename(), photo);
|
|
7479
|
+
logger.debug("Pending photos count", { count: this.pendingPhotos.size });
|
|
7480
|
+
}
|
|
7481
|
+
setupMessageListener() {
|
|
7482
|
+
logger.debug("Setting up message listener for snapshot messages");
|
|
7483
|
+
this.eventBus.on(EVENT_NAMES.MESSAGE_RECEIVED, (event) => {
|
|
7484
|
+
const { message } = event;
|
|
7485
|
+
if (message.type === MESSAGE_TYPES.SNAPSHOT) {
|
|
7486
|
+
logger.debug("Processing snapshot message");
|
|
7487
|
+
this.handleSnapshotDeveloped(message);
|
|
7488
|
+
}
|
|
7489
|
+
});
|
|
7490
|
+
}
|
|
7491
|
+
handleSnapshotDeveloped(message) {
|
|
7492
|
+
logger.debug("Handling snapshot developed message", {
|
|
7493
|
+
filename: message.filename,
|
|
7494
|
+
dataLength: message.data.length,
|
|
7495
|
+
pendingCount: this.pendingPhotos.size
|
|
7496
|
+
});
|
|
7497
|
+
const photo = this.pendingPhotos.get(message.filename);
|
|
7498
|
+
if (photo !== void 0) {
|
|
7499
|
+
logger.debug("Found pending photo, developing...", { filename: message.filename });
|
|
7500
|
+
const photoData = {
|
|
7501
|
+
filename: message.filename,
|
|
7502
|
+
data: message.data
|
|
7503
|
+
};
|
|
7504
|
+
photo.develop(photoData);
|
|
7505
|
+
this.pendingPhotos.delete(message.filename);
|
|
7506
|
+
logger.debug("Photo developed and removed from pending", {
|
|
7507
|
+
filename: message.filename,
|
|
7508
|
+
remainingCount: this.pendingPhotos.size
|
|
7509
|
+
});
|
|
7510
|
+
return;
|
|
7511
|
+
}
|
|
7512
|
+
logger.warn("No pending photo found for filename", {
|
|
7513
|
+
filename: message.filename,
|
|
7514
|
+
pendingFilenames: Array.from(this.pendingPhotos.keys())
|
|
7515
|
+
});
|
|
7516
|
+
}
|
|
7517
|
+
}
|
|
7518
|
+
class Snapshot {
|
|
7519
|
+
constructor(eventBus) {
|
|
7520
|
+
this.eventBus = eventBus;
|
|
7521
|
+
this.messageHandler = new SnapshotMessageHandler(eventBus);
|
|
7522
|
+
}
|
|
7523
|
+
validExtensions = [".png", ".jpg", ".jpeg", ".webp"];
|
|
7524
|
+
messageHandler;
|
|
7525
|
+
/**
|
|
7526
|
+
* Take a snapshot of the current 3D model view
|
|
7527
|
+
*
|
|
7528
|
+
* @param filename The filename for the snapshot (must include valid extension)
|
|
7529
|
+
* @returns Photo object for registering callbacks
|
|
7530
|
+
* @throws VirtualdisplayError if filename is invalid
|
|
7531
|
+
*/
|
|
7532
|
+
take(filename) {
|
|
7533
|
+
this.validateFilename(filename);
|
|
7534
|
+
logger.debug("Taking snapshot", { filename });
|
|
7535
|
+
const photo = new Photo(filename);
|
|
7536
|
+
this.messageHandler.registerPhoto(photo);
|
|
7537
|
+
this.eventBus.emit(EVENT_NAMES.SNAPSHOT_MESSAGE, {
|
|
7538
|
+
message: {
|
|
7539
|
+
type: MESSAGE_TYPES.SNAPSHOT,
|
|
7540
|
+
filename
|
|
7541
|
+
}
|
|
7542
|
+
});
|
|
7543
|
+
return photo;
|
|
7544
|
+
}
|
|
7545
|
+
/**
|
|
7546
|
+
* Validate filename has a valid image extension
|
|
7547
|
+
*/
|
|
7548
|
+
validateFilename(filename) {
|
|
7549
|
+
if (filename === "" || typeof filename !== "string") {
|
|
7550
|
+
throw new VirtualdisplayError(
|
|
7551
|
+
"Filename must be a non-empty string",
|
|
7552
|
+
ERROR_CODES.INVALID_PARAMETER
|
|
7553
|
+
);
|
|
7554
|
+
}
|
|
7555
|
+
const hasValidExtension = this.validExtensions.some(
|
|
7556
|
+
(ext) => filename.toLowerCase().endsWith(ext)
|
|
7557
|
+
);
|
|
7558
|
+
if (!hasValidExtension) {
|
|
7559
|
+
throw new VirtualdisplayError(
|
|
7560
|
+
`Invalid filename: "${filename}". Filename must end with one of: ${this.validExtensions.join(", ")}`,
|
|
7561
|
+
ERROR_CODES.INVALID_PARAMETER
|
|
7562
|
+
);
|
|
7563
|
+
}
|
|
7564
|
+
if (filename.includes("..") || filename.includes("/") || filename.includes("\\")) {
|
|
7565
|
+
throw new VirtualdisplayError(
|
|
7566
|
+
'Filename cannot contain path separators or ".."',
|
|
7567
|
+
ERROR_CODES.INVALID_PARAMETER
|
|
7568
|
+
);
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
}
|
|
7363
7572
|
class VirtualdisplayViewerService {
|
|
7364
7573
|
eventBus;
|
|
7365
7574
|
constructor(eventBus) {
|
|
@@ -7452,6 +7661,8 @@ class VirtualdisplayClient {
|
|
|
7452
7661
|
stateService;
|
|
7453
7662
|
messageHandler;
|
|
7454
7663
|
viewerService;
|
|
7664
|
+
cameraApi;
|
|
7665
|
+
snapshotApi;
|
|
7455
7666
|
options;
|
|
7456
7667
|
iframe;
|
|
7457
7668
|
constructor(options) {
|
|
@@ -7461,6 +7672,8 @@ class VirtualdisplayClient {
|
|
|
7461
7672
|
this.stateService = new StateService(this.eventBus);
|
|
7462
7673
|
this.messageHandler = new MessageHandler(this.eventBus);
|
|
7463
7674
|
this.viewerService = new VirtualdisplayViewerService(this.eventBus);
|
|
7675
|
+
this.cameraApi = new Camera(this.eventBus);
|
|
7676
|
+
this.snapshotApi = new Snapshot(this.eventBus);
|
|
7464
7677
|
this.initializeLogging();
|
|
7465
7678
|
this.createAndConnectIframe();
|
|
7466
7679
|
this.setupInitialConfig();
|
|
@@ -7554,6 +7767,18 @@ class VirtualdisplayClient {
|
|
|
7554
7767
|
get viewer() {
|
|
7555
7768
|
return this.viewerService;
|
|
7556
7769
|
}
|
|
7770
|
+
/**
|
|
7771
|
+
* Get the camera control API
|
|
7772
|
+
*/
|
|
7773
|
+
get camera() {
|
|
7774
|
+
return this.cameraApi;
|
|
7775
|
+
}
|
|
7776
|
+
/**
|
|
7777
|
+
* Get the snapshot API for taking screenshots
|
|
7778
|
+
*/
|
|
7779
|
+
get snapshot() {
|
|
7780
|
+
return this.snapshotApi;
|
|
7781
|
+
}
|
|
7557
7782
|
destroy() {
|
|
7558
7783
|
logger.info("Destroying Virtualdisplay client");
|
|
7559
7784
|
this.attributeService.clear();
|
|
@@ -7568,6 +7793,7 @@ class VirtualdisplayClient {
|
|
|
7568
7793
|
}
|
|
7569
7794
|
export {
|
|
7570
7795
|
AttributeSelector,
|
|
7796
|
+
Camera,
|
|
7571
7797
|
ERROR_CODES,
|
|
7572
7798
|
EVENT_NAMES,
|
|
7573
7799
|
MESSAGE_TYPES,
|
|
@@ -7577,6 +7803,8 @@ export {
|
|
|
7577
7803
|
Mutation,
|
|
7578
7804
|
NODE_TYPES,
|
|
7579
7805
|
NodeSelector,
|
|
7806
|
+
Photo,
|
|
7807
|
+
Snapshot,
|
|
7580
7808
|
VirtualdisplayClient,
|
|
7581
7809
|
VirtualdisplayError,
|
|
7582
7810
|
VirtualdisplayViewerService,
|