@virtualdisplay.io/client 4.0.0-alpha.19 → 4.0.0-alpha.20

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/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * All message types exchanged between client (parent page)
5
5
  * and server (iframe viewer engine) via postMessage.
6
6
  */
7
- declare const MESSAGE_TYPES$1: {
7
+ declare const MESSAGE_TYPES: {
8
8
  readonly HANDSHAKE: "handshake";
9
9
  readonly SESSION: "session";
10
10
  readonly MUTATION: "mutation";
@@ -16,30 +16,28 @@ declare const MESSAGE_TYPES$1: {
16
16
  readonly AR_ACTIVATE: "ar_activate";
17
17
  readonly FULLSCREEN: "fullscreen";
18
18
  readonly CONFIG: "config";
19
- readonly ANNOTATION_PLACE_MODE: "annotation_place_mode";
20
- readonly ANNOTATION_PLACED: "annotation_placed";
21
19
  readonly ANNOTATION_TOGGLE: "annotation_toggle";
22
20
  readonly FOCUS: "focus";
23
21
  readonly PRICING: "pricing";
24
22
  };
25
- interface MutationDto$1 {
23
+ interface MutationDto {
26
24
  readonly name: string;
27
25
  readonly value: string;
28
26
  }
29
- interface MutationResult$1 {
27
+ interface MutationResult {
30
28
  readonly name: string;
31
29
  readonly value: string;
32
30
  readonly success: boolean;
33
31
  readonly error?: string;
34
32
  }
35
- interface MutationMessage$1 {
36
- readonly type: typeof MESSAGE_TYPES$1.MUTATION;
37
- readonly mutations: MutationDto$1[];
33
+ interface MutationMessage {
34
+ readonly type: typeof MESSAGE_TYPES.MUTATION;
35
+ readonly mutations: MutationDto[];
38
36
  readonly isInitial: boolean;
39
37
  }
40
- interface MutationResultMessage$1 {
41
- readonly type: typeof MESSAGE_TYPES$1.MUTATION;
42
- readonly results: MutationResult$1[];
38
+ interface MutationResultMessage {
39
+ readonly type: typeof MESSAGE_TYPES.MUTATION;
40
+ readonly results: MutationResult[];
43
41
  readonly isInitial: boolean;
44
42
  }
45
43
  /**
@@ -61,7 +59,7 @@ interface ViewerSettings$1 {
61
59
  * Used to disable camera control buttons when a limit is reached.
62
60
  * Units match the camera position: zoom = percentage (0–200), tilt/rotate = degrees.
63
61
  */
64
- interface CameraLimits$1 {
62
+ interface CameraLimits {
65
63
  readonly minZoom?: number;
66
64
  readonly maxZoom?: number;
67
65
  readonly minTilt?: number;
@@ -69,47 +67,47 @@ interface CameraLimits$1 {
69
67
  readonly minRotate?: number;
70
68
  readonly maxRotate?: number;
71
69
  }
72
- interface CameraCommand$1 {
70
+ interface CameraCommand {
73
71
  readonly action: 'rotate' | 'tilt' | 'zoom' | 'reset';
74
72
  readonly value?: number;
75
73
  }
76
- interface CameraParameter$1 {
74
+ interface CameraParameter {
77
75
  readonly type: 'rotation' | 'tilt' | 'zoom';
78
76
  readonly value: number;
79
77
  }
80
- interface CameraCommandMessage$1 {
81
- readonly type: typeof MESSAGE_TYPES$1.CAMERA;
82
- readonly commands: CameraCommand$1[];
78
+ interface CameraCommandMessage {
79
+ readonly type: typeof MESSAGE_TYPES.CAMERA;
80
+ readonly commands: CameraCommand[];
83
81
  }
84
- interface CameraPositionMessage$1 {
85
- readonly type: typeof MESSAGE_TYPES$1.CAMERA;
86
- readonly transforms: CameraParameter$1[];
82
+ interface CameraPositionMessage {
83
+ readonly type: typeof MESSAGE_TYPES.CAMERA;
84
+ readonly transforms: CameraParameter[];
87
85
  }
88
- interface SnapshotRequestMessage$1 {
89
- readonly type: typeof MESSAGE_TYPES$1.SNAPSHOT;
86
+ interface SnapshotRequestMessage {
87
+ readonly type: typeof MESSAGE_TYPES.SNAPSHOT;
90
88
  readonly filename: string;
91
89
  }
92
- interface SnapshotResultMessage$1 {
93
- readonly type: typeof MESSAGE_TYPES$1.SNAPSHOT;
90
+ interface SnapshotResultMessage {
91
+ readonly type: typeof MESSAGE_TYPES.SNAPSHOT;
94
92
  readonly filename: string;
95
93
  readonly data?: string;
96
94
  readonly error?: string;
97
95
  }
98
- interface AttributeDefinition$1 {
96
+ interface AttributeDefinition {
99
97
  readonly name: string;
100
98
  readonly values: string[];
101
99
  readonly defaultValue?: string;
102
100
  }
103
- interface MappingMessage$1 {
104
- readonly type: typeof MESSAGE_TYPES$1.MAPPING;
105
- readonly attributes: AttributeDefinition$1[];
101
+ interface MappingMessage {
102
+ readonly type: typeof MESSAGE_TYPES.MAPPING;
103
+ readonly attributes: AttributeDefinition[];
106
104
  }
107
105
  /**
108
106
  * Loading indicator (server → client): progress percentage, phase label, or error.
109
107
  * Sent during model download and parse so the client loading indicator can update.
110
108
  */
111
- interface LoadingIndicatorMessage$1 {
112
- readonly type: typeof MESSAGE_TYPES$1.LOADING_INDICATOR;
109
+ interface LoadingIndicatorMessage {
110
+ readonly type: typeof MESSAGE_TYPES.LOADING_INDICATOR;
113
111
  /** 0–100 */
114
112
  readonly progress: number;
115
113
  /** Human-readable phase label, e.g. "downloading" */
@@ -118,29 +116,29 @@ interface LoadingIndicatorMessage$1 {
118
116
  readonly error?: string;
119
117
  }
120
118
  /** AR activate (client → server): user clicked the AR button. Server handles activation. */
121
- interface ArActivateMessage$1 {
122
- readonly type: typeof MESSAGE_TYPES$1.AR_ACTIVATE;
119
+ interface ArActivateMessage {
120
+ readonly type: typeof MESSAGE_TYPES.AR_ACTIVATE;
123
121
  }
124
122
  /**
125
123
  * Fullscreen (client → server): the user entered fullscreen via the client's
126
124
  * toolbar button. Fullscreen itself is handled client-side; this only lets the
127
125
  * server record the engagement against the tracking session it owns.
128
126
  */
129
- interface FullscreenMessage$1 {
130
- readonly type: typeof MESSAGE_TYPES$1.FULLSCREEN;
127
+ interface FullscreenMessage {
128
+ readonly type: typeof MESSAGE_TYPES.FULLSCREEN;
131
129
  }
132
130
  /**
133
131
  * Config (server → client): viewer settings and camera limits from model metadata.
134
132
  * Sent early — before MAPPING — so the client can show/hide controls and disable
135
133
  * camera buttons at limits as soon as metadata is available.
136
134
  */
137
- interface ConfigMessage$1 {
138
- readonly type: typeof MESSAGE_TYPES$1.CONFIG;
135
+ interface ConfigMessage {
136
+ readonly type: typeof MESSAGE_TYPES.CONFIG;
139
137
  readonly viewerSettings: ViewerSettings$1;
140
- readonly cameraLimits?: CameraLimits$1;
138
+ readonly cameraLimits?: CameraLimits;
141
139
  }
142
- interface HandshakeMessage$1 {
143
- readonly type: typeof MESSAGE_TYPES$1.HANDSHAKE;
140
+ interface HandshakeMessage {
141
+ readonly type: typeof MESSAGE_TYPES.HANDSHAKE;
144
142
  }
145
143
  /**
146
144
  * Session (server → client): the tracking session id, announced once the viewer
@@ -148,61 +146,39 @@ interface HandshakeMessage$1 {
148
146
  * client learns it). The client exposes it so a host page can correlate its own
149
147
  * actions (e.g. a configurator conversion) with the viewer session.
150
148
  */
151
- interface SessionMessage$1 {
152
- readonly type: typeof MESSAGE_TYPES$1.SESSION;
149
+ interface SessionMessage {
150
+ readonly type: typeof MESSAGE_TYPES.SESSION;
153
151
  readonly sessionId: string;
154
152
  }
155
- /**
156
- * Annotation place mode (parent → server): enable or disable click-to-place.
157
- * When enabled, clicking on the model surface sends back an ANNOTATION_PLACED message.
158
- */
159
- interface AnnotationPlaceModeMessage$1 {
160
- readonly type: typeof MESSAGE_TYPES$1.ANNOTATION_PLACE_MODE;
161
- readonly enabled: boolean;
162
- }
163
- /** Position on the model surface returned by raycasting. */
164
- interface AnnotationPosition$1 {
165
- readonly x: number;
166
- readonly y: number;
167
- readonly z: number;
168
- }
169
- /**
170
- * Annotation placed (server → parent): user clicked on the model surface.
171
- * Only sent when place mode is active.
172
- */
173
- interface AnnotationPlacedMessage$1 {
174
- readonly type: typeof MESSAGE_TYPES$1.ANNOTATION_PLACED;
175
- readonly position: AnnotationPosition$1;
176
- }
177
153
  /**
178
154
  * Annotation toggle (parent → server): show or hide annotation markers.
179
155
  * Only works when annotations exist in metadata (grand switch).
180
156
  */
181
- interface AnnotationToggleMessage$1 {
182
- readonly type: typeof MESSAGE_TYPES$1.ANNOTATION_TOGGLE;
157
+ interface AnnotationToggleMessage {
158
+ readonly type: typeof MESSAGE_TYPES.ANNOTATION_TOGGLE;
183
159
  readonly enabled: boolean;
184
160
  }
185
161
  /** Focus actions (client → server). */
186
- declare const FOCUS_ACTIONS$1: {
162
+ declare const FOCUS_ACTIONS: {
187
163
  readonly FOCUS: "focus";
188
164
  readonly UNFOCUS: "unfocus";
189
165
  };
190
166
  /** Focus states (server → client). */
191
- declare const FOCUS_STATES$1: {
167
+ declare const FOCUS_STATES: {
192
168
  readonly FOCUSED: "focused";
193
169
  readonly UNFOCUSED: "unfocused";
194
170
  };
195
- type FocusAction$1 = typeof FOCUS_ACTIONS$1[keyof typeof FOCUS_ACTIONS$1];
196
- type FocusStateValue$1 = typeof FOCUS_STATES$1[keyof typeof FOCUS_STATES$1];
171
+ type FocusAction = typeof FOCUS_ACTIONS[keyof typeof FOCUS_ACTIONS];
172
+ type FocusStateValue = typeof FOCUS_STATES[keyof typeof FOCUS_STATES];
197
173
  /**
198
174
  * Focus (client → server): isolate and frame a component, or return to overview.
199
175
  * Programmatic — the API, the (future) component menu, and the portal editors all
200
176
  * send this. `component` is a name from the `components` metadata, or a raw mesh
201
177
  * name as a fallback (resolved server-side); required when action is `focus`.
202
178
  */
203
- interface FocusMessage$1 {
204
- readonly type: typeof MESSAGE_TYPES$1.FOCUS;
205
- readonly action: FocusAction$1;
179
+ interface FocusMessage {
180
+ readonly type: typeof MESSAGE_TYPES.FOCUS;
181
+ readonly action: FocusAction;
206
182
  readonly component?: string;
207
183
  }
208
184
  /**
@@ -210,9 +186,9 @@ interface FocusMessage$1 {
210
186
  * opacity transition. `attributes` lists which attributes are relevant to the
211
187
  * focused component, so a custom UI can filter its selectors.
212
188
  */
213
- interface FocusStateMessage$1 {
214
- readonly type: typeof MESSAGE_TYPES$1.FOCUS;
215
- readonly state: FocusStateValue$1;
189
+ interface FocusStateMessage {
190
+ readonly type: typeof MESSAGE_TYPES.FOCUS;
191
+ readonly state: FocusStateValue;
216
192
  readonly component?: string;
217
193
  readonly attributes?: string[];
218
194
  }
@@ -267,145 +243,155 @@ interface PricingDocument$1 {
267
243
  * Pricing (server → client): the model's pricing document. Sent once at startup
268
244
  * after MAPPING so the client's resolver can price the current configuration.
269
245
  */
270
- interface PricingMessage$1 {
271
- readonly type: typeof MESSAGE_TYPES$1.PRICING;
246
+ interface PricingMessage {
247
+ readonly type: typeof MESSAGE_TYPES.PRICING;
272
248
  readonly pricing: PricingDocument$1;
273
249
  }
274
- type Message$1 = HandshakeMessage$1 | SessionMessage$1 | MutationMessage$1 | MutationResultMessage$1 | CameraCommandMessage$1 | CameraPositionMessage$1 | SnapshotRequestMessage$1 | SnapshotResultMessage$1 | MappingMessage$1 | LoadingIndicatorMessage$1 | ArActivateMessage$1 | FullscreenMessage$1 | ConfigMessage$1 | AnnotationPlaceModeMessage$1 | AnnotationPlacedMessage$1 | AnnotationToggleMessage$1 | FocusMessage$1 | FocusStateMessage$1 | PricingMessage$1;
275
- type MessageCallback = (message: Message$1) => void | Promise<void>;
250
+ type Message = HandshakeMessage | SessionMessage | MutationMessage | MutationResultMessage | CameraCommandMessage | CameraPositionMessage | SnapshotRequestMessage | SnapshotResultMessage | MappingMessage | LoadingIndicatorMessage | ArActivateMessage | FullscreenMessage | ConfigMessage | AnnotationToggleMessage | FocusMessage | FocusStateMessage | PricingMessage;
251
+ type MessageCallback = (message: Message) => void | Promise<void>;
276
252
 
277
253
  /**
278
- * Configuration options for VirtualdisplayClient
254
+ * Options for IframeMessenger
279
255
  */
280
- interface ClientOptions {
281
- readonly parent: string | HTMLElement;
282
- readonly model: string;
283
- readonly license: string;
284
- readonly debug?: boolean;
285
- readonly language?: string;
286
- /** W3C traceparent string to propagate to the viewer iframe for end-to-end tracing. */
287
- readonly traceparent?: string;
256
+ interface MessengerOptions {
288
257
  /**
289
- * Override viewer settings from metadata. Omitted fields use the metadata value.
290
- * A warning is logged when an override differs from the metadata value.
258
+ * Buffer incoming messages until markConsumerReady().
259
+ * When false (default), incoming messages are processed immediately.
291
260
  */
292
- readonly viewerSettings?: Partial<ViewerSettings$1>;
261
+ bufferIncoming?: boolean;
262
+ /**
263
+ * When buffering, messages where this returns false bypass the buffer
264
+ * and are routed to handlers immediately. Used for urgent messages
265
+ * like CONFIG that must be handled before the model loads.
266
+ */
267
+ shouldBuffer?: (message: Message) => boolean;
268
+ /**
269
+ * Restrict postMessage to a specific origin (e.g. 'https://example.com').
270
+ * Defaults to '*' when not set. Set this when the target origin is known
271
+ * to prevent messages from being intercepted by unintended recipients.
272
+ */
273
+ targetOrigin?: string;
274
+ /**
275
+ * Which protocol to validate incoming messages against — not whether to validate.
276
+ * Both sides always reject browser-extension noise and unknown message types. The
277
+ * default (`isValidMessageData`) accepts the public protocol; the studio side passes
278
+ * `isValidStudioMessageData`, which accepts the public protocol plus the studio
279
+ * messages (e.g. annotation placement), so the public bundle stays lean.
280
+ */
281
+ isValid?: (data: unknown) => boolean;
293
282
  }
294
-
295
283
  /**
296
- * The Virtualdisplay client's DOM event names, in one registry.
284
+ * Bidirectional iframe message port
297
285
  *
298
- * - `ui:*` events dispatch on the container (non-bubbling). They are the typed
299
- * surface of `client.on(...)` — payloads in `VirtualdisplayUiEventMap` (./types).
300
- * - `vd:*` events bubble into the customer's page, so a page can react with
301
- * `addEventListener` without registering a client callback.
302
- */
303
- declare const VD_EVENTS: {
304
- /** Attribute selection requested (cancelable). `detail`: `{ attribute, value }`. */
305
- readonly SELECT: "ui:select";
306
- /** Attribute selection applied. `detail`: `{ attribute, value }`. */
307
- readonly SELECTED: "ui:selected";
308
- /** Focus changed (bubbles). `detail`: `{ component, attributes }`, or `null` for the overview. */
309
- readonly FOCUS_CHANGE: "vd:focus-change";
310
- /** Resolved configuration changed (bubbles). `detail`: the resolved configuration, or `null` when nothing matches. */
311
- readonly CONFIGURATION_CHANGE: "vd:configuration-change";
312
- /** Configuration submitted (bubbles). `detail`: the resolved configuration handed to the host. */
313
- readonly CONFIGURATION_SUBMIT: "vd:configuration-submit";
314
- };
315
- type VdEvent = typeof VD_EVENTS[keyof typeof VD_EVENTS];
316
-
317
- /**
318
- * Error codes for Virtualdisplay client errors
319
- */
320
- declare const ERROR_CODES: {
321
- readonly NO_MAPPING: "NO_MAPPING";
322
- readonly ATTRIBUTE_NOT_FOUND: "ATTRIBUTE_NOT_FOUND";
323
- readonly VALUE_NOT_FOUND: "VALUE_NOT_FOUND";
324
- readonly PARENT_NOT_FOUND: "PARENT_NOT_FOUND";
325
- readonly INVALID_PARAMETER: "INVALID_PARAMETER";
326
- readonly SNAPSHOT_FAILED: "SNAPSHOT_FAILED";
327
- };
328
- type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];
329
- /**
330
- * Custom error class for Virtualdisplay client errors
331
- */
332
- declare class VirtualdisplayError extends Error {
333
- readonly code: ErrorCode;
334
- constructor(message: string, code: ErrorCode);
335
- static valueNotFound(attributeName: string, valueName: string): VirtualdisplayError;
336
- static parentNotFound(selector: string): VirtualdisplayError;
337
- static invalidParameter(reason: string): VirtualdisplayError;
338
- }
339
-
340
- interface MutationFailedEvent {
341
- readonly attribute: string;
342
- readonly value: string;
343
- }
344
- interface VirtualdisplaySelectEvent {
345
- readonly attribute: string;
346
- readonly value: string;
347
- preventDefault(): void;
348
- }
349
- interface VirtualdisplaySelectedEvent {
350
- readonly attribute: string;
351
- readonly value: string;
352
- }
353
- type VirtualdisplayUiEventMap = {
354
- [VD_EVENTS.SELECT]: VirtualdisplaySelectEvent;
355
- [VD_EVENTS.SELECTED]: VirtualdisplaySelectedEvent;
356
- };
357
- type VirtualdisplayUiEventType = keyof VirtualdisplayUiEventMap;
358
-
359
- /**
360
- * Pure domain entity representing a single attribute value
361
- * No side effects - only state
362
- */
363
- declare class AttributeValue {
364
- readonly value: string;
365
- readonly attribute: string;
366
- private isSelectedValue;
367
- constructor(value: string, attribute: string);
368
- get isSelected(): boolean;
369
- setSelected(selected: boolean): void;
370
- }
371
-
372
- interface AttributeOptions {
373
- readonly name: string;
374
- readonly values: string[];
375
- readonly onSelect: (name: string, value: string) => void;
376
- readonly defaultValue?: string;
377
- }
378
- /**
379
- * Represents a single attribute with its available values
380
- * Flat API - no source/target translation, works with server values directly
286
+ * Handles sending and receiving messages via postMessage between
287
+ * a parent page and an iframe. Both client and server use this —
288
+ * the only difference is the target window.
289
+ *
290
+ * Features:
291
+ * - Publisher queue: outgoing messages buffered until markReady()
292
+ * - Consumer queue: incoming messages buffered until markConsumerReady() (opt-in)
293
+ * - Handler registration: route incoming messages by type
294
+ * - Echo prevention: tags outgoing messages to filter self-delivery
295
+ * - Cleanup: removes listener on destroy
296
+ *
297
+ * @example Client (parent page — publisher queue only)
298
+ * ```ts
299
+ * const messenger = new IframeMessenger(() => iframe.contentWindow);
300
+ * messenger.on('mutation', handleMutationResult);
301
+ * messenger.on('mapping', handleMapping);
302
+ * messenger.start();
303
+ * // When iframe loads:
304
+ * messenger.markReady();
305
+ * messenger.send({ type: 'mutation', mutations, isInitial: true });
306
+ * ```
307
+ *
308
+ * @example Server (inside iframe — both queues, CONFIG bypasses consumer)
309
+ * ```ts
310
+ * const messenger = new IframeMessenger(
311
+ * () => window.parent !== window ? window.parent : null,
312
+ * { bufferIncoming: true, shouldBuffer: (msg) => msg.type !== 'config' },
313
+ * );
314
+ * messenger.on('mutation', processMutation);
315
+ * messenger.on('config', applyConfig);
316
+ * messenger.start();
317
+ * messenger.markReady();
318
+ * // When model loads:
319
+ * messenger.markConsumerReady();
320
+ * ```
381
321
  */
382
- declare class Attribute {
383
- readonly name: string;
384
- readonly defaultValue: string | undefined;
385
- private readonly values;
386
- private readonly onSelect;
387
- private onChangeCallback?;
388
- constructor(options: AttributeOptions);
322
+ declare class IframeMessenger {
323
+ private readonly getTarget;
324
+ private readonly handlers;
325
+ private readonly publisher;
326
+ private readonly consumer;
327
+ private readonly shouldBuffer;
328
+ private listener;
329
+ private readonly sourceId;
330
+ private targetOrigin;
331
+ private readonly isValid;
332
+ constructor(getTarget: () => Window | null, options?: MessengerOptions);
389
333
  /**
390
- * Select a value for this attribute
391
- * @throws {VirtualdisplayError} When value not found
334
+ * Register a handler for a specific message type
392
335
  */
393
- select(value: string): void;
394
- getValues(): AttributeValue[];
395
- getValue(value: string): AttributeValue | undefined;
396
- /** The currently selected value, or undefined before any selection lands. */
397
- get selected(): string | undefined;
336
+ on(type: string, handler: MessageCallback): void;
398
337
  /**
399
- * Register callback that fires when selection changes
400
- * Receives the newly selected AttributeValue
338
+ * Remove a handler for a specific message type
401
339
  */
402
- onChange(callback: (selectedValue: AttributeValue) => void): void;
340
+ off(type: string): void;
403
341
  /**
404
- * Update selection state from server mutation result
405
- * Direct value comparison - no source/target translation
342
+ * Update the target origin for outgoing postMessage calls.
343
+ * Use when the target origin is discovered at runtime (e.g. from the first incoming message).
406
344
  */
407
- handleResult(serverValue: string): void;
408
- private createValues;
345
+ setTargetOrigin(origin: string): void;
346
+ /**
347
+ * Send a message to the target window
348
+ * Queued until markReady() is called
349
+ */
350
+ send(message: Message): void;
351
+ /**
352
+ * Enable sending and flush queued outgoing messages
353
+ */
354
+ markReady(): void;
355
+ /**
356
+ * Enable processing of buffered incoming messages
357
+ * Only relevant when constructed with bufferIncoming: true
358
+ */
359
+ markConsumerReady(): void;
360
+ /**
361
+ * Check if an incoming message of a specific type is pending in the consumer buffer
362
+ */
363
+ consumerHasPending(type: string): boolean;
364
+ /**
365
+ * Start listening for incoming messages
366
+ */
367
+ start(): void;
368
+ /**
369
+ * Stop listening and clean up all state
370
+ */
371
+ destroy(): void;
372
+ /**
373
+ * Whether the messenger is ready to send
374
+ */
375
+ get ready(): boolean;
376
+ /**
377
+ * Number of messages waiting in the outgoing queue
378
+ */
379
+ get pendingCount(): number;
380
+ private post;
381
+ private routeToHandler;
382
+ }
383
+
384
+ type SendFn$1 = (message: Message) => void;
385
+ /**
386
+ * Annotation display controls for the viewer: show or hide existing markers.
387
+ *
388
+ * Placement (click-to-place for the editor) is a studio capability and lives in
389
+ * `@virtualdisplay.io/client-studio`, never in the public client or its engine surface.
390
+ */
391
+ declare class Annotations {
392
+ private readonly send;
393
+ constructor(send: SendFn$1);
394
+ setEnabled(enabled: boolean): void;
409
395
  }
410
396
 
411
397
  /**
@@ -486,61 +472,20 @@ declare class CameraPosition {
486
472
  * Current camera movement limits (set from CONFIG message).
487
473
  * Undefined until the server sends limits in its CONFIG payload.
488
474
  */
489
- get limits(): CameraLimits$1 | undefined;
475
+ get limits(): CameraLimits | undefined;
490
476
  /**
491
477
  * Apply camera limits received from the server CONFIG message.
492
478
  * Fires onChange callbacks so button disabled states update immediately.
493
479
  */
494
- applyLimits(limits: CameraLimits$1 | undefined): void;
480
+ applyLimits(limits: CameraLimits | undefined): void;
495
481
  /**
496
482
  * Handle incoming camera position message from server
497
483
  */
498
- handlePositionUpdate(message: CameraPositionMessage$1): void;
484
+ handlePositionUpdate(message: CameraPositionMessage): void;
499
485
  /**
500
486
  * Update camera position from server transforms
501
487
  */
502
- update(transforms: CameraParameter$1[]): void;
503
- }
504
-
505
- /** The focus state reported to `onChange` and the `vd:focus-change` event. */
506
- interface FocusState {
507
- readonly component: string;
508
- readonly attributes: string[];
509
- }
510
- type FocusChange = FocusState | null;
511
- type FocusChangeCallback = (state: FocusChange) => void;
512
- type DispatchFn = (state: FocusChange) => void;
513
- /**
514
- * Focus control API — isolate and frame a component, or return to overview.
515
- *
516
- * Programmatic by design: `set('Seat')` needs no user selection, which is how the
517
- * (future) component menu and the portal/admin editors drive it over the same
518
- * message bus. Alongside `onChange`, the container dispatches a bubbling
519
- * `vd:focus-change` CustomEvent so a page can react without registering a callback.
520
- */
521
- declare class Focus {
522
- private readonly send;
523
- private readonly dispatch;
524
- private focused;
525
- private attributes;
526
- private readonly changeCallbacks;
527
- constructor(send: MessageCallback, dispatch: DispatchFn);
528
- /** The focused component name, or null in overview. */
529
- get current(): string | null;
530
- /**
531
- * Focus a component by name. Sends a focus message to the viewer.
532
- * @throws VirtualdisplayError if component is not a non-empty string
533
- */
534
- set(component: string): void;
535
- /** Return to the overview — unfocus the current component. */
536
- clear(): void;
537
- /**
538
- * Subscribe to focus changes. The callback fires with the focus state when a
539
- * component is focused, and with null when returning to overview.
540
- */
541
- onChange(callback: FocusChangeCallback): void;
542
- /** Called by the client when a FocusStateMessage is received from the viewer. */
543
- handleStateReceived(message: FocusStateMessage$1): void;
488
+ update(transforms: CameraParameter[]): void;
544
489
  }
545
490
 
546
491
  /** The buyer's current configuration as attribute name→value pairs. */
@@ -656,9 +601,73 @@ declare class Configuration {
656
601
  private resolve;
657
602
  }
658
603
 
659
- interface PhotoData {
660
- readonly filename: string;
661
- readonly data: string;
604
+ /** The focus state reported to `onChange` and the `vd:focus-change` event. */
605
+ interface FocusState {
606
+ readonly component: string;
607
+ readonly attributes: string[];
608
+ }
609
+ type FocusChange = FocusState | null;
610
+ type FocusChangeCallback = (state: FocusChange) => void;
611
+ type DispatchFn = (state: FocusChange) => void;
612
+ /**
613
+ * Focus control API — isolate and frame a component, or return to overview.
614
+ *
615
+ * Programmatic by design: `set('Seat')` needs no user selection, which is how the
616
+ * (future) component menu and the portal/admin editors drive it over the same
617
+ * message bus. Alongside `onChange`, the container dispatches a bubbling
618
+ * `vd:focus-change` CustomEvent so a page can react without registering a callback.
619
+ */
620
+ declare class Focus {
621
+ private readonly send;
622
+ private readonly dispatch;
623
+ private focused;
624
+ private attributes;
625
+ private readonly changeCallbacks;
626
+ constructor(send: MessageCallback, dispatch: DispatchFn);
627
+ /** The focused component name, or null in overview. */
628
+ get current(): string | null;
629
+ /**
630
+ * Focus a component by name. Sends a focus message to the viewer.
631
+ * @throws VirtualdisplayError if component is not a non-empty string
632
+ */
633
+ set(component: string): void;
634
+ /** Return to the overview — unfocus the current component. */
635
+ clear(): void;
636
+ /**
637
+ * Subscribe to focus changes. The callback fires with the focus state when a
638
+ * component is focused, and with null when returning to overview.
639
+ */
640
+ onChange(callback: FocusChangeCallback): void;
641
+ /** Called by the client when a FocusStateMessage is received from the viewer. */
642
+ handleStateReceived(message: FocusStateMessage): void;
643
+ }
644
+
645
+ /**
646
+ * Error codes for Virtualdisplay client errors
647
+ */
648
+ declare const ERROR_CODES: {
649
+ readonly NO_MAPPING: "NO_MAPPING";
650
+ readonly ATTRIBUTE_NOT_FOUND: "ATTRIBUTE_NOT_FOUND";
651
+ readonly VALUE_NOT_FOUND: "VALUE_NOT_FOUND";
652
+ readonly PARENT_NOT_FOUND: "PARENT_NOT_FOUND";
653
+ readonly INVALID_PARAMETER: "INVALID_PARAMETER";
654
+ readonly SNAPSHOT_FAILED: "SNAPSHOT_FAILED";
655
+ };
656
+ type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];
657
+ /**
658
+ * Custom error class for Virtualdisplay client errors
659
+ */
660
+ declare class VirtualdisplayError extends Error {
661
+ readonly code: ErrorCode;
662
+ constructor(message: string, code: ErrorCode);
663
+ static valueNotFound(attributeName: string, valueName: string): VirtualdisplayError;
664
+ static parentNotFound(selector: string): VirtualdisplayError;
665
+ static invalidParameter(reason: string): VirtualdisplayError;
666
+ }
667
+
668
+ interface PhotoData {
669
+ readonly filename: string;
670
+ readonly data: string;
662
671
  }
663
672
  /**
664
673
  * Represents a photo being captured
@@ -723,7 +732,7 @@ declare class Snapshot {
723
732
  * Handle incoming snapshot result from server
724
733
  * Matches the message to a pending photo by filename, then develops or fails it
725
734
  */
726
- handleSnapshotResult(message: SnapshotResultMessage$1): void;
735
+ handleSnapshotResult(message: SnapshotResultMessage): void;
727
736
  /**
728
737
  * Fail any pending photos and forget them. Called from client destroy
729
738
  * so callers awaiting onError() see a deterministic outcome instead of
@@ -736,314 +745,140 @@ declare class Snapshot {
736
745
  private validateFilename;
737
746
  }
738
747
 
739
- type Attrs = Record<string, unknown> | unknown;
740
- declare let logger: {
741
- debug: (msg: string, attrs?: Attrs) => void;
742
- info: (msg: string, attrs?: Attrs) => void;
743
- warn: (msg: string, attrs?: Attrs) => void;
744
- error: (msg: string, attrs?: Attrs) => void;
745
- };
746
- declare function enableDebugMode(): void;
747
- declare function isDebugMode(): boolean;
748
-
749
748
  /**
750
- * Virtualdisplay iframe messaging protocol
751
- *
752
- * All message types exchanged between client (parent page)
753
- * and server (iframe viewer engine) via postMessage.
749
+ * Pure domain entity representing a single attribute value
750
+ * No side effects - only state
754
751
  */
755
- declare const MESSAGE_TYPES: {
756
- readonly HANDSHAKE: "handshake";
757
- readonly SESSION: "session";
758
- readonly MUTATION: "mutation";
759
- readonly STATE: "state";
760
- readonly LOADING_INDICATOR: "loading_indicator";
761
- readonly CAMERA: "camera";
762
- readonly SNAPSHOT: "snapshot";
763
- readonly MAPPING: "mapping";
764
- readonly AR_ACTIVATE: "ar_activate";
765
- readonly FULLSCREEN: "fullscreen";
766
- readonly CONFIG: "config";
767
- readonly ANNOTATION_PLACE_MODE: "annotation_place_mode";
768
- readonly ANNOTATION_PLACED: "annotation_placed";
769
- readonly ANNOTATION_TOGGLE: "annotation_toggle";
770
- readonly FOCUS: "focus";
771
- readonly PRICING: "pricing";
772
- };
773
- interface MutationDto {
774
- readonly name: string;
775
- readonly value: string;
776
- }
777
- interface MutationResult {
778
- readonly name: string;
752
+ declare class AttributeValue {
779
753
  readonly value: string;
780
- readonly success: boolean;
781
- readonly error?: string;
782
- }
783
- interface MutationMessage {
784
- readonly type: typeof MESSAGE_TYPES.MUTATION;
785
- readonly mutations: MutationDto[];
786
- readonly isInitial: boolean;
787
- }
788
- interface MutationResultMessage {
789
- readonly type: typeof MESSAGE_TYPES.MUTATION;
790
- readonly results: MutationResult[];
791
- readonly isInitial: boolean;
792
- }
793
- /**
794
- * Viewer settings from model metadata (server → client via MAPPING).
795
- * Customer-configured in the portal.
796
- */
797
- interface ViewerSettings {
798
- readonly ar?: boolean;
799
- readonly fullscreen?: boolean;
800
- readonly screenshot?: boolean;
801
- readonly cameraControls?: boolean;
802
- readonly annotations?: boolean;
803
- readonly language?: string;
804
- /** Where the attribute controls sit: in-frame bottom-left (default) or a docked right panel. */
805
- readonly configuratorLayout?: 'inframe-bottom-left' | 'sidebar-right' | 'hidden';
806
- }
807
- /**
808
- * Camera movement limits sent to the client via CONFIG.
809
- * Used to disable camera control buttons when a limit is reached.
810
- * Units match the camera position: zoom = percentage (0–200), tilt/rotate = degrees.
811
- */
812
- interface CameraLimits {
813
- readonly minZoom?: number;
814
- readonly maxZoom?: number;
815
- readonly minTilt?: number;
816
- readonly maxTilt?: number;
817
- readonly minRotate?: number;
818
- readonly maxRotate?: number;
819
- }
820
- interface CameraCommand {
821
- readonly action: 'rotate' | 'tilt' | 'zoom' | 'reset';
822
- readonly value?: number;
823
- }
824
- interface CameraParameter {
825
- readonly type: 'rotation' | 'tilt' | 'zoom';
826
- readonly value: number;
827
- }
828
- interface CameraCommandMessage {
829
- readonly type: typeof MESSAGE_TYPES.CAMERA;
830
- readonly commands: CameraCommand[];
831
- }
832
- interface CameraPositionMessage {
833
- readonly type: typeof MESSAGE_TYPES.CAMERA;
834
- readonly transforms: CameraParameter[];
835
- }
836
- interface SnapshotRequestMessage {
837
- readonly type: typeof MESSAGE_TYPES.SNAPSHOT;
838
- readonly filename: string;
839
- }
840
- interface SnapshotResultMessage {
841
- readonly type: typeof MESSAGE_TYPES.SNAPSHOT;
842
- readonly filename: string;
843
- readonly data?: string;
844
- readonly error?: string;
754
+ readonly attribute: string;
755
+ private isSelectedValue;
756
+ constructor(value: string, attribute: string);
757
+ get isSelected(): boolean;
758
+ setSelected(selected: boolean): void;
845
759
  }
846
- interface AttributeDefinition {
760
+
761
+ interface AttributeOptions {
847
762
  readonly name: string;
848
763
  readonly values: string[];
764
+ readonly onSelect: (name: string, value: string) => void;
849
765
  readonly defaultValue?: string;
850
766
  }
851
- interface MappingMessage {
852
- readonly type: typeof MESSAGE_TYPES.MAPPING;
853
- readonly attributes: AttributeDefinition[];
854
- }
855
767
  /**
856
- * Loading indicator (server → client): progress percentage, phase label, or error.
857
- * Sent during model download and parse so the client loading indicator can update.
858
- */
859
- interface LoadingIndicatorMessage {
860
- readonly type: typeof MESSAGE_TYPES.LOADING_INDICATOR;
861
- /** 0–100 */
862
- readonly progress: number;
863
- /** Human-readable phase label, e.g. "downloading" */
864
- readonly phase: string;
865
- /** Set when loading failed; client shows error state instead of hiding the overlay. */
866
- readonly error?: string;
867
- }
868
- /** AR activate (client → server): user clicked the AR button. Server handles activation. */
869
- interface ArActivateMessage {
870
- readonly type: typeof MESSAGE_TYPES.AR_ACTIVATE;
871
- }
872
- /**
873
- * Fullscreen (client → server): the user entered fullscreen via the client's
874
- * toolbar button. Fullscreen itself is handled client-side; this only lets the
875
- * server record the engagement against the tracking session it owns.
876
- */
877
- interface FullscreenMessage {
878
- readonly type: typeof MESSAGE_TYPES.FULLSCREEN;
879
- }
880
- /**
881
- * Config (server → client): viewer settings and camera limits from model metadata.
882
- * Sent early — before MAPPING — so the client can show/hide controls and disable
883
- * camera buttons at limits as soon as metadata is available.
884
- */
885
- interface ConfigMessage {
886
- readonly type: typeof MESSAGE_TYPES.CONFIG;
887
- readonly viewerSettings: ViewerSettings;
888
- readonly cameraLimits?: CameraLimits;
889
- }
890
- interface HandshakeMessage {
891
- readonly type: typeof MESSAGE_TYPES.HANDSHAKE;
892
- }
893
- /**
894
- * Session (server → client): the tracking session id, announced once the viewer
895
- * has established it (the id is generated in the iframe, so this is how the
896
- * client learns it). The client exposes it so a host page can correlate its own
897
- * actions (e.g. a configurator conversion) with the viewer session.
898
- */
899
- interface SessionMessage {
900
- readonly type: typeof MESSAGE_TYPES.SESSION;
901
- readonly sessionId: string;
902
- }
903
- /**
904
- * Annotation place mode (parent → server): enable or disable click-to-place.
905
- * When enabled, clicking on the model surface sends back an ANNOTATION_PLACED message.
906
- */
907
- interface AnnotationPlaceModeMessage {
908
- readonly type: typeof MESSAGE_TYPES.ANNOTATION_PLACE_MODE;
909
- readonly enabled: boolean;
910
- }
911
- /** Position on the model surface returned by raycasting. */
912
- interface AnnotationPosition {
913
- readonly x: number;
914
- readonly y: number;
915
- readonly z: number;
916
- }
917
- /**
918
- * Annotation placed (server → parent): user clicked on the model surface.
919
- * Only sent when place mode is active.
920
- */
921
- interface AnnotationPlacedMessage {
922
- readonly type: typeof MESSAGE_TYPES.ANNOTATION_PLACED;
923
- readonly position: AnnotationPosition;
924
- }
925
- /**
926
- * Annotation toggle (parent → server): show or hide annotation markers.
927
- * Only works when annotations exist in metadata (grand switch).
928
- */
929
- interface AnnotationToggleMessage {
930
- readonly type: typeof MESSAGE_TYPES.ANNOTATION_TOGGLE;
931
- readonly enabled: boolean;
932
- }
933
- /** Focus actions (client → server). */
934
- declare const FOCUS_ACTIONS: {
935
- readonly FOCUS: "focus";
936
- readonly UNFOCUS: "unfocus";
937
- };
938
- /** Focus states (server → client). */
939
- declare const FOCUS_STATES: {
940
- readonly FOCUSED: "focused";
941
- readonly UNFOCUSED: "unfocused";
942
- };
943
- type FocusAction = typeof FOCUS_ACTIONS[keyof typeof FOCUS_ACTIONS];
944
- type FocusStateValue = typeof FOCUS_STATES[keyof typeof FOCUS_STATES];
945
- /**
946
- * Focus (client → server): isolate and frame a component, or return to overview.
947
- * Programmatic — the API, the (future) component menu, and the portal editors all
948
- * send this. `component` is a name from the `components` metadata, or a raw mesh
949
- * name as a fallback (resolved server-side); required when action is `focus`.
950
- */
951
- interface FocusMessage {
952
- readonly type: typeof MESSAGE_TYPES.FOCUS;
953
- readonly action: FocusAction;
954
- readonly component?: string;
955
- }
956
- /**
957
- * Focus state (server → client): confirms the focus state after the camera and
958
- * opacity transition. `attributes` lists which attributes are relevant to the
959
- * focused component, so a custom UI can filter its selectors.
768
+ * Represents a single attribute with its available values
769
+ * Flat API - no source/target translation, works with server values directly
960
770
  */
961
- interface FocusStateMessage {
962
- readonly type: typeof MESSAGE_TYPES.FOCUS;
963
- readonly state: FocusStateValue;
964
- readonly component?: string;
965
- readonly attributes?: string[];
771
+ declare class Attribute {
772
+ readonly name: string;
773
+ readonly defaultValue: string | undefined;
774
+ private readonly values;
775
+ private readonly onSelect;
776
+ private onChangeCallback?;
777
+ constructor(options: AttributeOptions);
778
+ /**
779
+ * Select a value for this attribute
780
+ * @throws {VirtualdisplayError} When value not found
781
+ */
782
+ select(value: string): void;
783
+ getValues(): AttributeValue[];
784
+ getValue(value: string): AttributeValue | undefined;
785
+ /** The currently selected value, or undefined before any selection lands. */
786
+ get selected(): string | undefined;
787
+ /**
788
+ * Register callback that fires when selection changes
789
+ * Receives the newly selected AttributeValue
790
+ */
791
+ onChange(callback: (selectedValue: AttributeValue) => void): void;
792
+ /**
793
+ * Update selection state from server mutation result
794
+ * Direct value comparison - no source/target translation
795
+ */
796
+ handleResult(serverValue: string): void;
797
+ private createValues;
966
798
  }
799
+
967
800
  /**
968
- * What the configurator does with a matched configuration. Mirrors the backend
969
- * `availability` enum (pricing rule provenance lives on `PricingRule.source`).
970
- */
971
- declare const AVAILABILITY: {
972
- /** Price and submit button both shown (default). */
973
- readonly AVAILABLE: "available";
974
- /** Price shown, submit hidden (sold out / coming soon). */
975
- readonly VIEW_ONLY: "view_only";
976
- /** Behaves exactly like an unpriced combination: no price, no button. */
977
- readonly HIDDEN: "hidden";
978
- };
979
- type Availability = typeof AVAILABILITY[keyof typeof AVAILABILITY];
980
- /**
981
- * One pricing rule: a subset of attribute values mapped to a reference + price.
982
- * Matches the backend pricing read contract (`GET /api/public/models/.../pricing`).
801
+ * Configuration options for VirtualdisplayClient
983
802
  */
984
- interface PricingRule {
803
+ interface ClientOptions {
804
+ readonly parent: string | HTMLElement;
805
+ readonly model: string;
806
+ readonly license: string;
807
+ readonly debug?: boolean;
808
+ readonly language?: string;
809
+ /** W3C traceparent string to propagate to the viewer iframe for end-to-end tracing. */
810
+ readonly traceparent?: string;
985
811
  /**
986
- * The attribute name→value pairs this rule names. `{}` is a catch-all;
987
- * omitted names are wildcards. A rule matches when every pair equals the
988
- * current selection; the rule naming the most attributes wins.
812
+ * Override viewer settings from metadata. Omitted fields use the metadata value.
813
+ * A warning is logged when an override differs from the metadata value.
989
814
  */
990
- readonly attributes: Record<string, string>;
991
- /** The customer's own identifier (SKU, EAN, artikelnummer, name code). */
992
- readonly reference: string;
993
- /** Price in minor units (cents), exactly as the source displays it. */
994
- readonly priceCents: number;
995
- readonly availability: Availability;
996
- /** Provenance (manual | imported); audit-only, unused by the resolver. */
997
- readonly source: 'manual' | 'imported';
815
+ readonly viewerSettings?: Partial<ViewerSettings$1>;
998
816
  }
817
+
999
818
  /**
1000
- * A model's pricing document. The viewer's fetcher merges it into the model
1001
- * metadata and the server forwards it to the client via {@link PricingMessage}.
819
+ * The Virtualdisplay client's DOM event names, in one registry.
820
+ *
821
+ * - `ui:*` events dispatch on the container (non-bubbling). They are the typed
822
+ * surface of `client.on(...)` — payloads in `VirtualdisplayUiEventMap` (./types).
823
+ * - `vd:*` events bubble into the customer's page, so a page can react with
824
+ * `addEventListener` without registering a client callback.
1002
825
  */
1003
- interface PricingDocument {
1004
- /** Optimistic-concurrency version from the editor; unused by the viewer. */
1005
- readonly version: number;
1006
- /** Customer-authored CTA label; null when the adapter's defaultLabel fills in. */
1007
- readonly buttonLabel: string | null;
1008
- /** ISO 4217 currency code for price formatting. */
1009
- readonly currency: string;
1010
- /** VAT label for bare embeds, e.g. "incl. btw". */
1011
- readonly vatDisplay: string;
1012
- readonly rules: PricingRule[];
826
+ declare const VD_EVENTS: {
827
+ /** Attribute selection requested (cancelable). `detail`: `{ attribute, value }`. */
828
+ readonly SELECT: "ui:select";
829
+ /** Attribute selection applied. `detail`: `{ attribute, value }`. */
830
+ readonly SELECTED: "ui:selected";
831
+ /** Focus changed (bubbles). `detail`: `{ component, attributes }`, or `null` for the overview. */
832
+ readonly FOCUS_CHANGE: "vd:focus-change";
833
+ /** Resolved configuration changed (bubbles). `detail`: the resolved configuration, or `null` when nothing matches. */
834
+ readonly CONFIGURATION_CHANGE: "vd:configuration-change";
835
+ /** Configuration submitted (bubbles). `detail`: the resolved configuration handed to the host. */
836
+ readonly CONFIGURATION_SUBMIT: "vd:configuration-submit";
837
+ };
838
+ type VdEvent = typeof VD_EVENTS[keyof typeof VD_EVENTS];
839
+
840
+ interface MutationFailedEvent {
841
+ readonly attribute: string;
842
+ readonly value: string;
1013
843
  }
1014
- /**
1015
- * Pricing (server → client): the model's pricing document. Sent once at startup
1016
- * after MAPPING so the client's resolver can price the current configuration.
1017
- */
1018
- interface PricingMessage {
1019
- readonly type: typeof MESSAGE_TYPES.PRICING;
1020
- readonly pricing: PricingDocument;
844
+ interface VirtualdisplaySelectEvent {
845
+ readonly attribute: string;
846
+ readonly value: string;
847
+ preventDefault(): void;
1021
848
  }
1022
- type Message = HandshakeMessage | SessionMessage | MutationMessage | MutationResultMessage | CameraCommandMessage | CameraPositionMessage | SnapshotRequestMessage | SnapshotResultMessage | MappingMessage | LoadingIndicatorMessage | ArActivateMessage | FullscreenMessage | ConfigMessage | AnnotationPlaceModeMessage | AnnotationPlacedMessage | AnnotationToggleMessage | FocusMessage | FocusStateMessage | PricingMessage;
849
+ interface VirtualdisplaySelectedEvent {
850
+ readonly attribute: string;
851
+ readonly value: string;
852
+ }
853
+ type VirtualdisplayUiEventMap = {
854
+ [VD_EVENTS.SELECT]: VirtualdisplaySelectEvent;
855
+ [VD_EVENTS.SELECTED]: VirtualdisplaySelectedEvent;
856
+ };
857
+ type VirtualdisplayUiEventType = keyof VirtualdisplayUiEventMap;
1023
858
 
1024
859
  type SendFn = (message: Message) => void;
1025
- type PlacedCallback = (position: AnnotationPosition) => void;
1026
860
  /**
1027
- * Annotation controls for the viewer.
1028
- * Toggle visibility, and manage click-to-place for the annotation editor.
861
+ * Extensions a subclass (the studio client) layers onto the base composition without
862
+ * re-implementing it: a custom incoming-message validator and extra message handlers.
1029
863
  */
1030
- declare class Annotations {
1031
- private readonly send;
1032
- private readonly placedCallbacks;
1033
- constructor(send: SendFn);
1034
- setEnabled(enabled: boolean): void;
1035
- startPlacing(): void;
1036
- stopPlacing(): void;
1037
- onPlaced(callback: PlacedCallback): void;
1038
- /** Called by the client when an ANNOTATION_PLACED message is received. */
1039
- handlePlaced(position: AnnotationPosition): void;
864
+ interface BaseClientExtensions {
865
+ readonly messengerOptions?: MessengerOptions;
866
+ readonly registerHandlers?: (messenger: IframeMessenger, send: SendFn) => void;
867
+ /**
868
+ * The server host this client's viewer iframe targets — a fixed fact per client
869
+ * type, injected like the validator, not a per-instance setting. The studio client
870
+ * fixes it to the studio viewer build; the public client leaves it unset and uses
871
+ * the default public viewer. Both clients are bundled into one portal/admin build,
872
+ * so the host cannot be a single build-time env shared by both.
873
+ */
874
+ readonly serverHost?: string;
1040
875
  }
1041
-
1042
876
  /**
1043
- * Main Virtualdisplay Client — composition root.
1044
- * Wires all services together and exposes the public API.
877
+ * The shared client composition root. Wires the engine (transport, facades, UI) and exposes
878
+ * the buyer-facing API. The public `VirtualdisplayClient` is this base as-is; the studio
879
+ * client extends it with the studio facades via {@link BaseClientExtensions}.
1045
880
  */
1046
- declare class VirtualdisplayClient {
881
+ declare class BaseClient {
1047
882
  private readonly attributes;
1048
883
  private readonly readyStateTracker;
1049
884
  private readonly messenger;
@@ -1057,17 +892,16 @@ declare class VirtualdisplayClient {
1057
892
  private readonly ui;
1058
893
  private readonly qrModal;
1059
894
  private readonly options;
895
+ private readonly serverHost;
1060
896
  private iframe;
1061
897
  private container;
1062
- private viewerSessionId;
1063
898
  private readyCallbacks;
1064
899
  private readonly mutationFailedCallbacks;
1065
- constructor(options: ClientOptions);
1066
- private registerHandlers;
1067
- /** Lifecycle handlers: the handshake (queue flush) and the tracking session id. */
1068
- private registerLifecycleHandlers;
1069
- /** Handlers that feed viewer-reported state into the facades (focus, pricing). */
1070
- private registerFacadeStateHandlers;
900
+ /** The viewer's tracking session id, once the server announces it (SESSION). */
901
+ private viewerSessionId;
902
+ constructor(options: ClientOptions, extensions?: BaseClientExtensions);
903
+ /** Wire the inbound viewer protocol to the facades. */
904
+ private wireIncomingMessages;
1071
905
  /**
1072
906
  * Dispatch a bubbling `vd:focus-change` event on the container so a page can
1073
907
  * react to focus without registering an `onChange` callback.
@@ -1092,7 +926,7 @@ declare class VirtualdisplayClient {
1092
926
  onChange(name: string, callback: (selectedValue: AttributeValue) => void): void;
1093
927
  getAttribute(name: string): Attribute | undefined;
1094
928
  get isReady(): boolean;
1095
- onReady(callback: (client: VirtualdisplayClient) => void): void;
929
+ onReady(callback: (client: this) => void): void;
1096
930
  onMutationFailed(callback: (event: MutationFailedEvent) => void): void;
1097
931
  on<T extends VirtualdisplayUiEventType>(type: T, callback: (event: VirtualdisplayUiEventMap[T]) => void): void;
1098
932
  get annotations(): Annotations;
@@ -1101,16 +935,92 @@ declare class VirtualdisplayClient {
1101
935
  get configuration(): Configuration;
1102
936
  get cameraPosition(): CameraPosition;
1103
937
  get snapshot(): Snapshot;
1104
- get viewerSettings(): ViewerSettings;
1105
- /**
1106
- * The viewer's tracking session id, once the viewer has established it (the id
1107
- * is generated inside the iframe and announced to the client). `undefined`
1108
- * until then. A host page can use it to correlate its own actions, e.g. a
1109
- * configurator conversion, with the viewer session.
1110
- */
938
+ /** The viewer's tracking session id, for a host page to correlate its own actions. */
1111
939
  get sessionId(): string | undefined;
940
+ get viewerSettings(): ViewerSettings$1;
1112
941
  destroy(): void;
1113
942
  }
1114
943
 
944
+ type Attrs = Record<string, unknown> | unknown;
945
+ declare let logger: {
946
+ debug: (msg: string, attrs?: Attrs) => void;
947
+ info: (msg: string, attrs?: Attrs) => void;
948
+ warn: (msg: string, attrs?: Attrs) => void;
949
+ error: (msg: string, attrs?: Attrs) => void;
950
+ };
951
+ declare function enableDebugMode(): void;
952
+ declare function isDebugMode(): boolean;
953
+
954
+ /**
955
+ * The public Virtualdisplay client: the shared base composition (transport, facades, the
956
+ * standard interface) with no studio capability. This is the buyer-facing SDK published as
957
+ * `@virtualdisplay.io/client`. The internal studio client adds the editing facades over the
958
+ * same base in `@virtualdisplay.io/client-studio`.
959
+ */
960
+ declare class VirtualdisplayClient extends BaseClient {
961
+ }
962
+
963
+ /**
964
+ * Viewer settings from model metadata (server → client via MAPPING).
965
+ * Customer-configured in the portal.
966
+ */
967
+ interface ViewerSettings {
968
+ readonly ar?: boolean;
969
+ readonly fullscreen?: boolean;
970
+ readonly screenshot?: boolean;
971
+ readonly cameraControls?: boolean;
972
+ readonly annotations?: boolean;
973
+ readonly language?: string;
974
+ /** Where the attribute controls sit: in-frame bottom-left (default) or a docked right panel. */
975
+ readonly configuratorLayout?: 'inframe-bottom-left' | 'sidebar-right' | 'hidden';
976
+ }
977
+ /**
978
+ * What the configurator does with a matched configuration. Mirrors the backend
979
+ * `availability` enum (pricing rule provenance lives on `PricingRule.source`).
980
+ */
981
+ declare const AVAILABILITY: {
982
+ /** Price and submit button both shown (default). */
983
+ readonly AVAILABLE: "available";
984
+ /** Price shown, submit hidden (sold out / coming soon). */
985
+ readonly VIEW_ONLY: "view_only";
986
+ /** Behaves exactly like an unpriced combination: no price, no button. */
987
+ readonly HIDDEN: "hidden";
988
+ };
989
+ type Availability = typeof AVAILABILITY[keyof typeof AVAILABILITY];
990
+ /**
991
+ * One pricing rule: a subset of attribute values mapped to a reference + price.
992
+ * Matches the backend pricing read contract (`GET /api/public/models/.../pricing`).
993
+ */
994
+ interface PricingRule {
995
+ /**
996
+ * The attribute name→value pairs this rule names. `{}` is a catch-all;
997
+ * omitted names are wildcards. A rule matches when every pair equals the
998
+ * current selection; the rule naming the most attributes wins.
999
+ */
1000
+ readonly attributes: Record<string, string>;
1001
+ /** The customer's own identifier (SKU, EAN, artikelnummer, name code). */
1002
+ readonly reference: string;
1003
+ /** Price in minor units (cents), exactly as the source displays it. */
1004
+ readonly priceCents: number;
1005
+ readonly availability: Availability;
1006
+ /** Provenance (manual | imported); audit-only, unused by the resolver. */
1007
+ readonly source: 'manual' | 'imported';
1008
+ }
1009
+ /**
1010
+ * A model's pricing document. The viewer's fetcher merges it into the model
1011
+ * metadata and the server forwards it to the client via {@link PricingMessage}.
1012
+ */
1013
+ interface PricingDocument {
1014
+ /** Optimistic-concurrency version from the editor; unused by the viewer. */
1015
+ readonly version: number;
1016
+ /** Customer-authored CTA label; null when the adapter's defaultLabel fills in. */
1017
+ readonly buttonLabel: string | null;
1018
+ /** ISO 4217 currency code for price formatting. */
1019
+ readonly currency: string;
1020
+ /** VAT label for bare embeds, e.g. "incl. btw". */
1021
+ readonly vatDisplay: string;
1022
+ readonly rules: PricingRule[];
1023
+ }
1024
+
1115
1025
  export { Annotations, Attribute, AttributeValue, Camera, Configuration, ERROR_CODES, Focus, Photo, Snapshot, VD_EVENTS, VirtualdisplayClient, VirtualdisplayError, enableDebugMode, isDebugMode, logger };
1116
- export type { AnnotationPosition, Availability, ClientOptions, ErrorCode, FocusState, MutationFailedEvent, PhotoData, PricingDocument, PricingRule, ResolvedConfiguration, SubmitOptions, SubmittedConfiguration, VdEvent, ViewerSettings, VirtualdisplaySelectEvent, VirtualdisplaySelectedEvent, VirtualdisplayUiEventMap, VirtualdisplayUiEventType };
1026
+ export type { Availability, ClientOptions, ErrorCode, FocusState, MutationFailedEvent, PhotoData, PricingDocument, PricingRule, ResolvedConfiguration, SubmitOptions, SubmittedConfiguration, VdEvent, ViewerSettings, VirtualdisplaySelectEvent, VirtualdisplaySelectedEvent, VirtualdisplayUiEventMap, VirtualdisplayUiEventType };