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

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.0-alpha.22
4
+
5
+ ### Patch Changes
6
+
7
+ Fix the 3D model rendering behind the docked sidebar panel instead of next to it. On wide embeds the viewer now narrows to leave room for the panel, instead of the panel floating over part of the model.
8
+
9
+ ## 4.0.0-alpha.21
10
+
11
+ ### Minor Changes
12
+
13
+ Add `activateAr()` method to `VirtualdisplayClient`. Triggers the AR flow directly, useful when the built-in AR button is hidden with `viewerSettings.ar = false` and a custom control should start AR instead.
14
+
15
+ Add `client.onSyncChange(callback)`: subscribe to whether the viewer is currently live-syncing its configuration with another device in a hand-off (e.g. a desktop and the phone it was scanned to). The callback receives a boolean and fires again each time the connection state changes.
16
+
17
+ ### Patch Changes
18
+
19
+ Change the desktop AR button's QR code to open the model in AR on the phone with the shopper's current configuration, instead of a fresh default viewer. The QR dialog now opens immediately and shows the code as soon as it is ready.
20
+
3
21
  ## 4.0.0-alpha.20
4
22
 
5
23
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ declare const MESSAGE_TYPES: {
19
19
  readonly ANNOTATION_TOGGLE: "annotation_toggle";
20
20
  readonly FOCUS: "focus";
21
21
  readonly PRICING: "pricing";
22
+ readonly QR_SHOWN: "qr_shown";
23
+ readonly SYNC: "sync";
22
24
  };
23
25
  interface MutationDto {
24
26
  readonly name: string;
@@ -30,10 +32,50 @@ interface MutationResult {
30
32
  readonly success: boolean;
31
33
  readonly error?: string;
32
34
  }
35
+ /**
36
+ * The folded family state riding along on a live sync channel broadcast
37
+ * (ar-session-handoff, sync-channel.md): the same shape the public
38
+ * session-state read returns. Authoritative self-heal input; `mutations`
39
+ * on the same message is only the delta that triggered the broadcast.
40
+ */
41
+ interface SyncStateSnapshot {
42
+ readonly license: string;
43
+ readonly model: string;
44
+ readonly attributes: Record<string, string>;
45
+ }
33
46
  interface MutationMessage {
34
47
  readonly type: typeof MESSAGE_TYPES.MUTATION;
35
48
  readonly mutations: MutationDto[];
36
49
  readonly isInitial: boolean;
50
+ /**
51
+ * Live sync channel extras (ar-session-handoff, sync-channel.md), present
52
+ * only on a mutation the viewer engine's sync module received over the SSE
53
+ * live-state channel; a MutationMessage posted locally by the client never
54
+ * carries these. `origin` is the sender's page session id: its presence
55
+ * marks the mutation as remote, so the tracker must never record it again
56
+ * (the no-re-track loop guard, decision 15), and the sync module drops it
57
+ * when it equals the receiving engine's own session id (the echo filter).
58
+ * `seq` is the sender's per-session sequence number, used by the sync
59
+ * module to track how far the channel has progressed. `state` is the
60
+ * authoritative folded family state: the sync module diffs it against the
61
+ * channel's last-seen state and applies the difference, so a catch-up
62
+ * (empty `mutations`) or a broadcast the hub dropped still converges the
63
+ * subscriber. None of these fields are read by `MutationRouter`.
64
+ */
65
+ readonly origin?: string;
66
+ readonly seq?: number;
67
+ readonly state?: SyncStateSnapshot;
68
+ }
69
+ /**
70
+ * Sync channel status (server → client, ar-session-handoff, sync-channel.md):
71
+ * whether the viewer engine's live-state channel is currently connected to its
72
+ * hand-off family. One-way, latest-wins: no history, no buffering. The public
73
+ * client exposes it as `onSyncChange`; the studio composes nothing, so its
74
+ * engine never sends this (it never opens the channel).
75
+ */
76
+ interface SyncMessage {
77
+ readonly type: typeof MESSAGE_TYPES.SYNC;
78
+ readonly connected: boolean;
37
79
  }
38
80
  interface MutationResultMessage {
39
81
  readonly type: typeof MESSAGE_TYPES.MUTATION;
@@ -127,6 +169,16 @@ interface ArActivateMessage {
127
169
  interface FullscreenMessage {
128
170
  readonly type: typeof MESSAGE_TYPES.FULLSCREEN;
129
171
  }
172
+ /**
173
+ * QR shown (client → server): the desktop QR hand-off modal (ar-session-handoff)
174
+ * was displayed to the shopper. The QR itself is generated client-side; this
175
+ * only lets the server record the engagement (`qr_shown`, the funnel's
176
+ * denominator) against the tracking session it owns. One-way, fire-and-forget,
177
+ * nothing to act on, same shape as FULLSCREEN.
178
+ */
179
+ interface QrShownMessage {
180
+ readonly type: typeof MESSAGE_TYPES.QR_SHOWN;
181
+ }
130
182
  /**
131
183
  * Config (server → client): viewer settings and camera limits from model metadata.
132
184
  * Sent early — before MAPPING — so the client can show/hide controls and disable
@@ -247,7 +299,7 @@ interface PricingMessage {
247
299
  readonly type: typeof MESSAGE_TYPES.PRICING;
248
300
  readonly pricing: PricingDocument$1;
249
301
  }
250
- type Message = HandshakeMessage | SessionMessage | MutationMessage | MutationResultMessage | CameraCommandMessage | CameraPositionMessage | SnapshotRequestMessage | SnapshotResultMessage | MappingMessage | LoadingIndicatorMessage | ArActivateMessage | FullscreenMessage | ConfigMessage | AnnotationToggleMessage | FocusMessage | FocusStateMessage | PricingMessage;
302
+ type Message = HandshakeMessage | SessionMessage | MutationMessage | MutationResultMessage | CameraCommandMessage | CameraPositionMessage | SnapshotRequestMessage | SnapshotResultMessage | MappingMessage | LoadingIndicatorMessage | ArActivateMessage | FullscreenMessage | ConfigMessage | AnnotationToggleMessage | FocusMessage | FocusStateMessage | PricingMessage | QrShownMessage | SyncMessage;
251
303
  type MessageCallback = (message: Message) => void | Promise<void>;
252
304
 
253
305
  /**
@@ -348,6 +400,16 @@ declare class IframeMessenger {
348
400
  * Queued until markReady() is called
349
401
  */
350
402
  send(message: Message): void;
403
+ /**
404
+ * Feed a message through the same routing path as one that just arrived via
405
+ * postMessage, without an actual postMessage round-trip: still validated
406
+ * against `isValid`, and still respects the consumer queue's buffering and
407
+ * the `shouldBuffer` bypass. For a transport that delivers messages some
408
+ * other way than the iframe channel this messenger already listens on (the
409
+ * viewer engine's SSE-sourced live sync channel, ar-session-handoff) and
410
+ * needs them to land exactly where a postMessage would.
411
+ */
412
+ receive(message: Message): void;
351
413
  /**
352
414
  * Enable sending and flush queued outgoing messages
353
415
  */
@@ -808,6 +870,13 @@ interface ClientOptions {
808
870
  readonly language?: string;
809
871
  /** W3C traceparent string to propagate to the viewer iframe for end-to-end tracing. */
810
872
  readonly traceparent?: string;
873
+ /**
874
+ * A source viewer session id to restore into this mount (ar-session-handoff): the
875
+ * iframe's metadata fetch merges that session's latest configuration into the
876
+ * attribute defaults, so the viewer boots straight into it. Internal option, used
877
+ * by the AR landing, not a customer-facing setting.
878
+ */
879
+ readonly session?: string;
811
880
  /**
812
881
  * Override viewer settings from metadata. Omitted fields use the metadata value.
813
882
  * A warning is logged when an override differs from the metadata value.
@@ -857,6 +926,31 @@ type VirtualdisplayUiEventMap = {
857
926
  type VirtualdisplayUiEventType = keyof VirtualdisplayUiEventMap;
858
927
 
859
928
  type SendFn = (message: Message) => void;
929
+ /**
930
+ * The seam for the AR flow behind the standard interface's AR button. The base
931
+ * only routes to it: the implementation (mobile AR_ACTIVATE, desktop hand-off
932
+ * QR) lives in the public client package (`@virtualdisplay.io/client`), which
933
+ * composes it via {@link BaseClientExtensions.createArActivation}.
934
+ */
935
+ interface ArFlow {
936
+ /** The AR button handler. */
937
+ activate(): void;
938
+ /** The engine-announced tracking session id (SESSION), which the flow may wait on. */
939
+ onSession(sessionId: string): void;
940
+ destroy(): void;
941
+ }
942
+ /**
943
+ * The seam for the live sync channel's connection status (ar-session-handoff,
944
+ * sync-channel.md). The base only routes SYNC messages to it; the concrete
945
+ * tracker (callback list + `onSyncChange`) lives in the public client package,
946
+ * composed via {@link BaseClientExtensions.createSyncTracker}. The studio
947
+ * composes nothing, so `onSyncChange` does not exist on the studio client
948
+ * (its engine never opens the channel, so it never sends SYNC either).
949
+ */
950
+ interface SyncTracker {
951
+ /** Latest-wins, no buffering: the channel's current connection state. */
952
+ handleSync(connected: boolean): void;
953
+ }
860
954
  /**
861
955
  * Extensions a subclass (the studio client) layers onto the base composition without
862
956
  * re-implementing it: a custom incoming-message validator and extra message handlers.
@@ -872,6 +966,19 @@ interface BaseClientExtensions {
872
966
  * so the host cannot be a single build-time env shared by both.
873
967
  */
874
968
  readonly serverHost?: string;
969
+ /**
970
+ * The AR flow behind the standard interface's AR button. The public client
971
+ * composes its `ArActivation` here; the studio composes nothing (the editor
972
+ * has no AR use case), so the base wires no AR button at all.
973
+ */
974
+ readonly createArActivation?: (send: SendFn) => ArFlow;
975
+ /**
976
+ * The live sync channel's connection tracker (ar-session-handoff). The
977
+ * public client composes a tracker and exposes `onSyncChange`; the studio
978
+ * composes nothing, so SYNC messages (which its own engine never sends
979
+ * anyway) are simply dropped.
980
+ */
981
+ readonly createSyncTracker?: () => SyncTracker;
875
982
  }
876
983
  /**
877
984
  * The shared client composition root. Wires the engine (transport, facades, UI) and exposes
@@ -890,9 +997,12 @@ declare class BaseClient {
890
997
  private readonly annotationsApi;
891
998
  private readonly eventBus;
892
999
  private readonly ui;
893
- private readonly qrModal;
894
1000
  private readonly options;
895
1001
  private readonly serverHost;
1002
+ /** The composed AR flow, or undefined in builds without one (the studio). */
1003
+ private readonly arActivation;
1004
+ /** The composed sync tracker, or undefined in builds without one (the studio). */
1005
+ private readonly syncTracker;
896
1006
  private iframe;
897
1007
  private container;
898
1008
  private readyCallbacks;
@@ -902,6 +1012,12 @@ declare class BaseClient {
902
1012
  constructor(options: ClientOptions, extensions?: BaseClientExtensions);
903
1013
  /** Wire the inbound viewer protocol to the facades. */
904
1014
  private wireIncomingMessages;
1015
+ /** Store the tracking session id and hand it to the AR flow, which waits on it for the QR. */
1016
+ private handleSession;
1017
+ /** Forward a failed mutation to every registered `onMutationFailed` callback. */
1018
+ private notifyMutationFailed;
1019
+ /** Flush the queued `onReady` callbacks once, then let the standard interface know. */
1020
+ private notifyReady;
905
1021
  /**
906
1022
  * Dispatch a bubbling `vd:focus-change` event on the container so a page can
907
1023
  * react to focus without registering an `onChange` callback.
@@ -915,7 +1031,6 @@ declare class BaseClient {
915
1031
  /** Dispatch a bubbling `vd:configuration-submit` event when the CTA is used. */
916
1032
  private dispatchConfigurationSubmit;
917
1033
  private handleConfig;
918
- private activateAr;
919
1034
  /** Tell the viewer the user entered fullscreen, so it records the engagement. */
920
1035
  private trackFullscreen;
921
1036
  private initializeLogging;
@@ -928,6 +1043,14 @@ declare class BaseClient {
928
1043
  get isReady(): boolean;
929
1044
  onReady(callback: (client: this) => void): void;
930
1045
  onMutationFailed(callback: (event: MutationFailedEvent) => void): void;
1046
+ /**
1047
+ * Trigger the composed AR flow directly (ar-session-handoff task 035): a host
1048
+ * page that hides the built-in corner button (`viewerSettings.ar = false`,
1049
+ * e.g. the AR landing, which owns a single custom AR affordance) still needs
1050
+ * an entry point to the same flow that button would have called. A no-op
1051
+ * where no AR flow is composed (the studio client has none).
1052
+ */
1053
+ activateAr(): void;
931
1054
  on<T extends VirtualdisplayUiEventType>(type: T, callback: (event: VirtualdisplayUiEventMap[T]) => void): void;
932
1055
  get annotations(): Annotations;
933
1056
  get camera(): Camera;
@@ -956,8 +1079,17 @@ declare function isDebugMode(): boolean;
956
1079
  * standard interface) with no studio capability. This is the buyer-facing SDK published as
957
1080
  * `@virtualdisplay.io/client`. The internal studio client adds the editing facades over the
958
1081
  * same base in `@virtualdisplay.io/client-studio`.
1082
+ *
1083
+ * The AR flow (mobile AR_ACTIVATE, desktop hand-off QR) is composed here: it is a shopper
1084
+ * feature, so the studio client does not compose it and renders no AR button. The live sync
1085
+ * channel's connection status (`onSyncChange`) is composed the same way: the studio client
1086
+ * has no such method, its engine never opens the channel.
959
1087
  */
960
1088
  declare class VirtualdisplayClient extends BaseClient {
1089
+ private readonly syncStatusTracker;
1090
+ constructor(options: ClientOptions);
1091
+ /** Subscribe to the live sync channel's connection state (ar-session-handoff). Latest-wins, no buffering. */
1092
+ onSyncChange(callback: (connected: boolean) => void): void;
961
1093
  }
962
1094
 
963
1095
  /**