@vctrl/embed 0.22.0 → 0.24.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.24.1](https://github.com/Vectreal/vectreal-platform/compare/embed-v0.24.0...embed-v0.24.1) (2026-08-02)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **ci:** resolve workspace protocol at publish time and publish via OIDC ([07e4b62](https://github.com/Vectreal/vectreal-platform/commit/07e4b628be16b61f3ddea9c91cc36c052d60bc8f))
9
+ * **ci:** resolve workspace protocol at publish time and publish via OIDC ([03b9305](https://github.com/Vectreal/vectreal-platform/commit/03b9305719b1cf67548f62730493ca6e3a84c823))
10
+
11
+ ## [0.24.0](https://github.com/Vectreal/vectreal-platform/compare/embed-v0.22.0...embed-v0.24.0) (2026-08-01)
12
+
13
+
14
+ ### Features
15
+
16
+ * **platform:** split /embed from /preview ([#662](https://github.com/Vectreal/vectreal-platform/issues/662)) ([19d9ec5](https://github.com/Vectreal/vectreal-platform/commit/19d9ec5cfa63a83fd04efbebb16ccac588fc325b))
17
+
3
18
  ## [0.22.0](https://github.com/Vectreal/vectreal-platform/compare/embed-v0.21.0...embed-v0.22.0) (2026-07-01)
4
19
 
5
20
 
package/README.md CHANGED
@@ -23,7 +23,7 @@ npm install @vctrl/embed
23
23
  <div style="width: 100%; height: 400px;">
24
24
  <iframe
25
25
  id="vectreal-scene"
26
- src="https://vectreal.com/preview/fullscreen/<projectId>/<sceneId>?token=YOUR_PREVIEW_API_KEY"
26
+ src="https://vectreal.com/embed/<projectId>/<sceneId>?token=YOUR_PREVIEW_API_KEY"
27
27
  style="width: 100%; height: 100%; border: 0;"
28
28
  allow="autoplay; xr-spatial-tracking"
29
29
  allowfullscreen
package/embed.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { EmbedCameraDescriptor } from './protocol';
2
+ export interface EmbedOptions {
3
+ /** Override the detected iframe origin for postMessage targeting. */
4
+ iframeOrigin?: string;
5
+ /** Milliseconds before ready() rejects. Default: 15 000. */
6
+ readyTimeout?: number;
7
+ }
8
+ export interface EmbedReadyInfo {
9
+ sceneId: string | undefined;
10
+ cameras: EmbedCameraDescriptor[];
11
+ }
12
+ export interface SetTransitionOptions {
13
+ type: 'none' | 'linear' | 'object_avoidance';
14
+ duration?: number;
15
+ easing?: 'linear' | 'ease_in' | 'ease_out' | 'ease_in_out';
16
+ }
17
+ export type EmbedEventMap = {
18
+ viewer_ready: void;
19
+ model_loaded: void;
20
+ camera_changed: {
21
+ cameraId: string;
22
+ };
23
+ auto_rotate_changed: {
24
+ enabled: boolean;
25
+ };
26
+ interaction_event: {
27
+ interactionId?: string;
28
+ eventName: string;
29
+ payload?: Record<string, unknown>;
30
+ };
31
+ };
32
+ export type EmbedEventType = keyof EmbedEventMap;
33
+ export type EmbedEventHandler<K extends EmbedEventType> = (data: EmbedEventMap[K]) => void;
34
+ export declare class VectrealEmbed {
35
+ private readonly iframe;
36
+ private readonly targetOrigin;
37
+ private readonly readyTimeout;
38
+ private handlers;
39
+ private pendingCommands;
40
+ private isReady;
41
+ private boundListener;
42
+ private pingIntervalId;
43
+ constructor(iframe: HTMLIFrameElement, options?: EmbedOptions);
44
+ /**
45
+ * Resolves when the embedded viewer emits viewer_ready.
46
+ * Rejects if no response within readyTimeout.
47
+ */
48
+ ready(): Promise<EmbedReadyInfo>;
49
+ /** Remove all listeners and stop responding to messages. */
50
+ destroy(): void;
51
+ /** Switch to a named camera. */
52
+ activateCamera(cameraId: string): void;
53
+ /** Override the transition behaviour for subsequent camera switches. */
54
+ setTransition(options: SetTransitionOptions): void;
55
+ /** Enable or disable orbit controls. */
56
+ setControlsEnabled(enabled: boolean): void;
57
+ /** Toggle auto-rotate. */
58
+ setAutoRotate(enabled: boolean, speed?: number): void;
59
+ /** Toggle scroll-zoom. */
60
+ setZoomEnabled(enabled: boolean): void;
61
+ /** Toggle right-click pan. */
62
+ setPanEnabled(enabled: boolean): void;
63
+ /**
64
+ * Drive scroll-triggered interactions defined in the Publisher.
65
+ * @param progress 0 (page top) – 1 (page bottom)
66
+ */
67
+ sendScrollProgress(progress: number): void;
68
+ /**
69
+ * Trigger a named host_message interaction defined in the Publisher.
70
+ */
71
+ sendMessage(message: string, payload?: Record<string, unknown>): void;
72
+ /** Subscribe to a viewer event. Returns an unsubscribe function. */
73
+ on<K extends EmbedEventType>(type: K, handler: EmbedEventHandler<K>): () => void;
74
+ /** Remove a specific handler. */
75
+ off<K extends EmbedEventType>(type: K, handler: EmbedEventHandler<K>): void;
76
+ private sceneId;
77
+ private cameras;
78
+ private sendCommand;
79
+ private postToIframe;
80
+ private startPingPolling;
81
+ private stopPingPolling;
82
+ private flushPendingCommands;
83
+ private emit;
84
+ private handleMessage;
85
+ private handleViewerEvent;
86
+ }
package/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=`vectreal-host`,t=`vectreal-preview`;function n(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function r(e){if(!n(e)||typeof e.type!=`string`)return!1;switch(e.type){case`activate_camera`:return typeof e.cameraId==`string`&&e.cameraId.trim().length>0;case`set_controls_enabled`:return typeof e.enabled==`boolean`;case`set_auto_rotate`:return typeof e.enabled==`boolean`&&(e.speed===void 0||typeof e.speed==`number`);case`set_controls_options`:return(e.zoom===void 0||typeof e.zoom==`boolean`)&&(e.pan===void 0||typeof e.pan==`boolean`);case`set_transition`:return typeof e.transitionType==`string`&&[`none`,`linear`,`object_avoidance`].includes(e.transitionType);default:return!1}}function i(e){if(!n(e)||e.source!==`vectreal-host`||typeof e.type!=`string`)return!1;switch(e.type){case`ping`:return!0;case`viewer_command`:return r(e.command);case`host_scroll_progress`:return typeof e.progress==`number`&&Number.isFinite(e.progress);case`host_message`:return typeof e.message==`string`&&e.message.trim().length>0&&(e.payload===void 0||n(e.payload));default:return!1}}var a=15e3,o=class{constructor(e,t={}){this.handlers=new Map,this.pendingCommands=[],this.isReady=!1,this.pingIntervalId=null,this.sceneId=void 0,this.cameras=[],this.iframe=e,this.readyTimeout=t.readyTimeout??a;let n=e.src||e.getAttribute(`src`)||``,r;if(n)try{r=new URL(n,window.location.href).origin}catch{}if(!t.iframeOrigin&&!r)throw Error(`VectrealEmbed: cannot determine iframe origin from src. Pass options.iframeOrigin explicitly.`);this.targetOrigin=t.iframeOrigin??r,this.boundListener=this.handleMessage.bind(this),window.addEventListener(`message`,this.boundListener),this.startPingPolling()}ready(){return new Promise((e,t)=>{let n=window.setTimeout(()=>{this.off(`viewer_ready`,r),t(Error(`VectrealEmbed: viewer did not become ready within ${this.readyTimeout}ms`))},this.readyTimeout),r=()=>{window.clearTimeout(n),e({sceneId:this.sceneId,cameras:this.cameras})};if(this.isReady){window.clearTimeout(n),e({sceneId:this.sceneId,cameras:this.cameras});return}this.on(`viewer_ready`,r)})}destroy(){this.stopPingPolling(),window.removeEventListener(`message`,this.boundListener),this.handlers.clear(),this.pendingCommands=[]}activateCamera(e){this.sendCommand({type:`activate_camera`,cameraId:e})}setTransition(e){this.sendCommand({type:`set_transition`,transitionType:e.type,duration:e.duration,easing:e.easing})}setControlsEnabled(e){this.sendCommand({type:`set_controls_enabled`,enabled:e})}setAutoRotate(e,t){this.sendCommand({type:`set_auto_rotate`,enabled:e,speed:t})}setZoomEnabled(e){this.sendCommand({type:`set_controls_options`,zoom:e})}setPanEnabled(e){this.sendCommand({type:`set_controls_options`,pan:e})}sendScrollProgress(t){this.postToIframe({source:e,type:`host_scroll_progress`,progress:Math.min(1,Math.max(0,t))})}sendMessage(t,n){this.postToIframe({source:e,type:`host_message`,message:t,payload:n})}on(e,t){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(t),()=>this.off(e,t)}off(e,t){this.handlers.get(e)?.delete(t)}sendCommand(t){let n={source:e,type:`viewer_command`,command:t};if(!this.isReady){this.pendingCommands.push(n);return}this.postToIframe(n)}postToIframe(e){try{this.iframe.contentWindow?.postMessage(e,this.targetOrigin)}catch{}}startPingPolling(){this.postToIframe({source:e,type:`ping`}),this.pingIntervalId=window.setInterval(()=>{if(this.isReady){this.stopPingPolling();return}this.postToIframe({source:e,type:`ping`})},500)}stopPingPolling(){this.pingIntervalId!==null&&(window.clearInterval(this.pingIntervalId),this.pingIntervalId=null)}flushPendingCommands(){for(let e of this.pendingCommands)this.postToIframe(e);this.pendingCommands=[]}emit(e,t){this.handlers.get(e)?.forEach(e=>{e(t)})}handleMessage(e){let t=e.data;if(!(!t||typeof t!=`object`||t.source!==`vectreal-preview`)&&!(this.targetOrigin!==`*`&&e.origin!==this.targetOrigin))switch(t.type){case`pong`:this.stopPingPolling(),this.sceneId=t.sceneId,this.cameras=t.cameras,this.isReady=!0,this.flushPendingCommands();break;case`viewer_event`:this.handleViewerEvent(t.event);break;case`interaction_event`:this.emit(`interaction_event`,{interactionId:t.interactionId,eventName:t.eventName,payload:t.payload});break}}handleViewerEvent(e){switch(e.type){case`viewer_ready`:this.isReady=!0,this.flushPendingCommands(),this.emit(`viewer_ready`,void 0);break;case`model_loaded`:this.emit(`model_loaded`,void 0);break;case`camera_changed`:this.emit(`camera_changed`,{cameraId:e.cameraId});break;case`auto_rotate_changed`:this.emit(`auto_rotate_changed`,{enabled:e.enabled});break;case`initial_framing_completed`:this.emit(`model_loaded`,void 0);break}}};exports.HOSTED_PREVIEW_HOST_SOURCE=e,exports.HOSTED_PREVIEW_VIEWER_SOURCE=t,exports.VectrealEmbed=o,exports.isHostedPreviewIncomingMessage=i,exports.isViewerCommand=r;
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { VectrealEmbed } from './embed';
2
+ export type { EmbedEventHandler, EmbedEventMap, EmbedEventType, EmbedOptions, EmbedReadyInfo, SetTransitionOptions } from './embed';
3
+ export { HOSTED_PREVIEW_HOST_SOURCE, HOSTED_PREVIEW_VIEWER_SOURCE, isHostedPreviewIncomingMessage, isViewerCommand } from './protocol';
4
+ export type { EmbedCameraDescriptor, HostedPreviewCustomEventMessage, HostedPreviewHostMessage, HostedPreviewIncomingMessage, HostedPreviewOutgoingMessage, HostedPreviewPingMessage, HostedPreviewPongMessage, HostedPreviewScrollProgressMessage, HostedPreviewViewerCommandMessage, HostedPreviewViewerEventMessage } from './protocol';
package/index.js ADDED
@@ -0,0 +1,209 @@
1
+ //#region packages/embed/src/protocol.ts
2
+ var e = "vectreal-host", t = "vectreal-preview";
3
+ function n(e) {
4
+ return typeof e == "object" && !!e && !Array.isArray(e);
5
+ }
6
+ function r(e) {
7
+ if (!n(e) || typeof e.type != "string") return !1;
8
+ switch (e.type) {
9
+ case "activate_camera": return typeof e.cameraId == "string" && e.cameraId.trim().length > 0;
10
+ case "set_controls_enabled": return typeof e.enabled == "boolean";
11
+ case "set_auto_rotate": return typeof e.enabled == "boolean" && (e.speed === void 0 || typeof e.speed == "number");
12
+ case "set_controls_options": return (e.zoom === void 0 || typeof e.zoom == "boolean") && (e.pan === void 0 || typeof e.pan == "boolean");
13
+ case "set_transition": return typeof e.transitionType == "string" && [
14
+ "none",
15
+ "linear",
16
+ "object_avoidance"
17
+ ].includes(e.transitionType);
18
+ default: return !1;
19
+ }
20
+ }
21
+ function i(e) {
22
+ if (!n(e) || e.source !== "vectreal-host" || typeof e.type != "string") return !1;
23
+ switch (e.type) {
24
+ case "ping": return !0;
25
+ case "viewer_command": return r(e.command);
26
+ case "host_scroll_progress": return typeof e.progress == "number" && Number.isFinite(e.progress);
27
+ case "host_message": return typeof e.message == "string" && e.message.trim().length > 0 && (e.payload === void 0 || n(e.payload));
28
+ default: return !1;
29
+ }
30
+ }
31
+ //#endregion
32
+ //#region packages/embed/src/embed.ts
33
+ var a = 15e3, o = class {
34
+ constructor(e, t = {}) {
35
+ this.handlers = /* @__PURE__ */ new Map(), this.pendingCommands = [], this.isReady = !1, this.pingIntervalId = null, this.sceneId = void 0, this.cameras = [], this.iframe = e, this.readyTimeout = t.readyTimeout ?? a;
36
+ let n = e.src || e.getAttribute("src") || "", r;
37
+ if (n) try {
38
+ r = new URL(n, window.location.href).origin;
39
+ } catch {}
40
+ if (!t.iframeOrigin && !r) throw Error("VectrealEmbed: cannot determine iframe origin from src. Pass options.iframeOrigin explicitly.");
41
+ this.targetOrigin = t.iframeOrigin ?? r, this.boundListener = this.handleMessage.bind(this), window.addEventListener("message", this.boundListener), this.startPingPolling();
42
+ }
43
+ ready() {
44
+ return new Promise((e, t) => {
45
+ let n = window.setTimeout(() => {
46
+ this.off("viewer_ready", r), t(/* @__PURE__ */ Error(`VectrealEmbed: viewer did not become ready within ${this.readyTimeout}ms`));
47
+ }, this.readyTimeout), r = () => {
48
+ window.clearTimeout(n), e({
49
+ sceneId: this.sceneId,
50
+ cameras: this.cameras
51
+ });
52
+ };
53
+ if (this.isReady) {
54
+ window.clearTimeout(n), e({
55
+ sceneId: this.sceneId,
56
+ cameras: this.cameras
57
+ });
58
+ return;
59
+ }
60
+ this.on("viewer_ready", r);
61
+ });
62
+ }
63
+ destroy() {
64
+ this.stopPingPolling(), window.removeEventListener("message", this.boundListener), this.handlers.clear(), this.pendingCommands = [];
65
+ }
66
+ activateCamera(e) {
67
+ this.sendCommand({
68
+ type: "activate_camera",
69
+ cameraId: e
70
+ });
71
+ }
72
+ setTransition(e) {
73
+ this.sendCommand({
74
+ type: "set_transition",
75
+ transitionType: e.type,
76
+ duration: e.duration,
77
+ easing: e.easing
78
+ });
79
+ }
80
+ setControlsEnabled(e) {
81
+ this.sendCommand({
82
+ type: "set_controls_enabled",
83
+ enabled: e
84
+ });
85
+ }
86
+ setAutoRotate(e, t) {
87
+ this.sendCommand({
88
+ type: "set_auto_rotate",
89
+ enabled: e,
90
+ speed: t
91
+ });
92
+ }
93
+ setZoomEnabled(e) {
94
+ this.sendCommand({
95
+ type: "set_controls_options",
96
+ zoom: e
97
+ });
98
+ }
99
+ setPanEnabled(e) {
100
+ this.sendCommand({
101
+ type: "set_controls_options",
102
+ pan: e
103
+ });
104
+ }
105
+ sendScrollProgress(t) {
106
+ this.postToIframe({
107
+ source: e,
108
+ type: "host_scroll_progress",
109
+ progress: Math.min(1, Math.max(0, t))
110
+ });
111
+ }
112
+ sendMessage(t, n) {
113
+ this.postToIframe({
114
+ source: e,
115
+ type: "host_message",
116
+ message: t,
117
+ payload: n
118
+ });
119
+ }
120
+ on(e, t) {
121
+ return this.handlers.has(e) || this.handlers.set(e, /* @__PURE__ */ new Set()), this.handlers.get(e).add(t), () => this.off(e, t);
122
+ }
123
+ off(e, t) {
124
+ this.handlers.get(e)?.delete(t);
125
+ }
126
+ sendCommand(t) {
127
+ let n = {
128
+ source: e,
129
+ type: "viewer_command",
130
+ command: t
131
+ };
132
+ if (!this.isReady) {
133
+ this.pendingCommands.push(n);
134
+ return;
135
+ }
136
+ this.postToIframe(n);
137
+ }
138
+ postToIframe(e) {
139
+ try {
140
+ this.iframe.contentWindow?.postMessage(e, this.targetOrigin);
141
+ } catch {}
142
+ }
143
+ startPingPolling() {
144
+ this.postToIframe({
145
+ source: e,
146
+ type: "ping"
147
+ }), this.pingIntervalId = window.setInterval(() => {
148
+ if (this.isReady) {
149
+ this.stopPingPolling();
150
+ return;
151
+ }
152
+ this.postToIframe({
153
+ source: e,
154
+ type: "ping"
155
+ });
156
+ }, 500);
157
+ }
158
+ stopPingPolling() {
159
+ this.pingIntervalId !== null && (window.clearInterval(this.pingIntervalId), this.pingIntervalId = null);
160
+ }
161
+ flushPendingCommands() {
162
+ for (let e of this.pendingCommands) this.postToIframe(e);
163
+ this.pendingCommands = [];
164
+ }
165
+ emit(e, t) {
166
+ this.handlers.get(e)?.forEach((e) => {
167
+ e(t);
168
+ });
169
+ }
170
+ handleMessage(e) {
171
+ let t = e.data;
172
+ if (!(!t || typeof t != "object" || t.source !== "vectreal-preview") && !(this.targetOrigin !== "*" && e.origin !== this.targetOrigin)) switch (t.type) {
173
+ case "pong":
174
+ this.stopPingPolling(), this.sceneId = t.sceneId, this.cameras = t.cameras, this.isReady = !0, this.flushPendingCommands();
175
+ break;
176
+ case "viewer_event":
177
+ this.handleViewerEvent(t.event);
178
+ break;
179
+ case "interaction_event":
180
+ this.emit("interaction_event", {
181
+ interactionId: t.interactionId,
182
+ eventName: t.eventName,
183
+ payload: t.payload
184
+ });
185
+ break;
186
+ }
187
+ }
188
+ handleViewerEvent(e) {
189
+ switch (e.type) {
190
+ case "viewer_ready":
191
+ this.isReady = !0, this.flushPendingCommands(), this.emit("viewer_ready", void 0);
192
+ break;
193
+ case "model_loaded":
194
+ this.emit("model_loaded", void 0);
195
+ break;
196
+ case "camera_changed":
197
+ this.emit("camera_changed", { cameraId: e.cameraId });
198
+ break;
199
+ case "auto_rotate_changed":
200
+ this.emit("auto_rotate_changed", { enabled: e.enabled });
201
+ break;
202
+ case "initial_framing_completed":
203
+ this.emit("model_loaded", void 0);
204
+ break;
205
+ }
206
+ }
207
+ };
208
+ //#endregion
209
+ export { e as HOSTED_PREVIEW_HOST_SOURCE, t as HOSTED_PREVIEW_VIEWER_SOURCE, o as VectrealEmbed, i as isHostedPreviewIncomingMessage, r as isViewerCommand };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "0.22.0",
2
+ "version": "0.24.1",
3
3
  "name": "@vctrl/embed",
4
4
  "description": "Framework-agnostic JavaScript SDK for controlling Vectreal embedded 3D scene previews via postMessage.",
5
5
  "bugs": {
6
- "url": "https://github.com/vectreal/vectreal-platform/issues"
6
+ "url": "https://github.com/Vectreal/vectreal-platform/issues"
7
7
  },
8
8
  "homepage": "https://vectreal.com",
9
9
  "keywords": [
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/vectreal/vectreal-platform.git"
22
+ "url": "git+https://github.com/Vectreal/vectreal-platform.git"
23
23
  },
24
24
  "license": "AGPL-3.0-only",
25
25
  "main": "./index.cjs",
package/protocol.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { ViewerCommand, ViewerInteractionEvent } from '../../viewer/src/index.ts';
2
+ export declare const HOSTED_PREVIEW_HOST_SOURCE = "vectreal-host";
3
+ export declare const HOSTED_PREVIEW_VIEWER_SOURCE = "vectreal-preview";
4
+ export interface HostedPreviewPingMessage {
5
+ source: typeof HOSTED_PREVIEW_HOST_SOURCE;
6
+ type: 'ping';
7
+ }
8
+ export interface HostedPreviewViewerCommandMessage {
9
+ source: typeof HOSTED_PREVIEW_HOST_SOURCE;
10
+ type: 'viewer_command';
11
+ command: ViewerCommand;
12
+ }
13
+ export interface HostedPreviewScrollProgressMessage {
14
+ source: typeof HOSTED_PREVIEW_HOST_SOURCE;
15
+ type: 'host_scroll_progress';
16
+ progress: number;
17
+ }
18
+ export interface HostedPreviewHostMessage {
19
+ source: typeof HOSTED_PREVIEW_HOST_SOURCE;
20
+ type: 'host_message';
21
+ message: string;
22
+ payload?: Record<string, unknown>;
23
+ }
24
+ export type HostedPreviewIncomingMessage = HostedPreviewHostMessage | HostedPreviewPingMessage | HostedPreviewScrollProgressMessage | HostedPreviewViewerCommandMessage;
25
+ export interface EmbedCameraDescriptor {
26
+ id: string;
27
+ name: string;
28
+ fov?: number;
29
+ }
30
+ export interface HostedPreviewPongMessage {
31
+ source: typeof HOSTED_PREVIEW_VIEWER_SOURCE;
32
+ type: 'pong';
33
+ sceneId?: string;
34
+ cameras: EmbedCameraDescriptor[];
35
+ }
36
+ export interface HostedPreviewViewerEventMessage {
37
+ source: typeof HOSTED_PREVIEW_VIEWER_SOURCE;
38
+ type: 'viewer_event';
39
+ sceneId?: string;
40
+ event: ViewerInteractionEvent;
41
+ }
42
+ export interface HostedPreviewCustomEventMessage {
43
+ source: typeof HOSTED_PREVIEW_VIEWER_SOURCE;
44
+ type: 'interaction_event';
45
+ sceneId?: string;
46
+ interactionId?: string;
47
+ eventName: string;
48
+ payload?: Record<string, unknown>;
49
+ }
50
+ export type HostedPreviewOutgoingMessage = HostedPreviewCustomEventMessage | HostedPreviewPongMessage | HostedPreviewViewerEventMessage;
51
+ export declare function isViewerCommand(value: unknown): value is ViewerCommand;
52
+ export declare function isHostedPreviewIncomingMessage(value: unknown): value is HostedPreviewIncomingMessage;
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.VectrealEmbed={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`vectreal-host`,n=`vectreal-preview`;function r(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function i(e){if(!r(e)||typeof e.type!=`string`)return!1;switch(e.type){case`activate_camera`:return typeof e.cameraId==`string`&&e.cameraId.trim().length>0;case`set_controls_enabled`:return typeof e.enabled==`boolean`;case`set_auto_rotate`:return typeof e.enabled==`boolean`&&(e.speed===void 0||typeof e.speed==`number`);case`set_controls_options`:return(e.zoom===void 0||typeof e.zoom==`boolean`)&&(e.pan===void 0||typeof e.pan==`boolean`);case`set_transition`:return typeof e.transitionType==`string`&&[`none`,`linear`,`object_avoidance`].includes(e.transitionType);default:return!1}}function a(e){if(!r(e)||e.source!==`vectreal-host`||typeof e.type!=`string`)return!1;switch(e.type){case`ping`:return!0;case`viewer_command`:return i(e.command);case`host_scroll_progress`:return typeof e.progress==`number`&&Number.isFinite(e.progress);case`host_message`:return typeof e.message==`string`&&e.message.trim().length>0&&(e.payload===void 0||r(e.payload));default:return!1}}var o=15e3,s=class{constructor(e,t={}){this.handlers=new Map,this.pendingCommands=[],this.isReady=!1,this.pingIntervalId=null,this.sceneId=void 0,this.cameras=[],this.iframe=e,this.readyTimeout=t.readyTimeout??o;let n=e.src||e.getAttribute(`src`)||``,r;if(n)try{r=new URL(n,window.location.href).origin}catch{}if(!t.iframeOrigin&&!r)throw Error(`VectrealEmbed: cannot determine iframe origin from src. Pass options.iframeOrigin explicitly.`);this.targetOrigin=t.iframeOrigin??r,this.boundListener=this.handleMessage.bind(this),window.addEventListener(`message`,this.boundListener),this.startPingPolling()}ready(){return new Promise((e,t)=>{let n=window.setTimeout(()=>{this.off(`viewer_ready`,r),t(Error(`VectrealEmbed: viewer did not become ready within ${this.readyTimeout}ms`))},this.readyTimeout),r=()=>{window.clearTimeout(n),e({sceneId:this.sceneId,cameras:this.cameras})};if(this.isReady){window.clearTimeout(n),e({sceneId:this.sceneId,cameras:this.cameras});return}this.on(`viewer_ready`,r)})}destroy(){this.stopPingPolling(),window.removeEventListener(`message`,this.boundListener),this.handlers.clear(),this.pendingCommands=[]}activateCamera(e){this.sendCommand({type:`activate_camera`,cameraId:e})}setTransition(e){this.sendCommand({type:`set_transition`,transitionType:e.type,duration:e.duration,easing:e.easing})}setControlsEnabled(e){this.sendCommand({type:`set_controls_enabled`,enabled:e})}setAutoRotate(e,t){this.sendCommand({type:`set_auto_rotate`,enabled:e,speed:t})}setZoomEnabled(e){this.sendCommand({type:`set_controls_options`,zoom:e})}setPanEnabled(e){this.sendCommand({type:`set_controls_options`,pan:e})}sendScrollProgress(e){this.postToIframe({source:t,type:`host_scroll_progress`,progress:Math.min(1,Math.max(0,e))})}sendMessage(e,n){this.postToIframe({source:t,type:`host_message`,message:e,payload:n})}on(e,t){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(t),()=>this.off(e,t)}off(e,t){this.handlers.get(e)?.delete(t)}sendCommand(e){let n={source:t,type:`viewer_command`,command:e};if(!this.isReady){this.pendingCommands.push(n);return}this.postToIframe(n)}postToIframe(e){try{this.iframe.contentWindow?.postMessage(e,this.targetOrigin)}catch{}}startPingPolling(){this.postToIframe({source:t,type:`ping`}),this.pingIntervalId=window.setInterval(()=>{if(this.isReady){this.stopPingPolling();return}this.postToIframe({source:t,type:`ping`})},500)}stopPingPolling(){this.pingIntervalId!==null&&(window.clearInterval(this.pingIntervalId),this.pingIntervalId=null)}flushPendingCommands(){for(let e of this.pendingCommands)this.postToIframe(e);this.pendingCommands=[]}emit(e,t){this.handlers.get(e)?.forEach(e=>{e(t)})}handleMessage(e){let t=e.data;if(!(!t||typeof t!=`object`||t.source!==`vectreal-preview`)&&!(this.targetOrigin!==`*`&&e.origin!==this.targetOrigin))switch(t.type){case`pong`:this.stopPingPolling(),this.sceneId=t.sceneId,this.cameras=t.cameras,this.isReady=!0,this.flushPendingCommands();break;case`viewer_event`:this.handleViewerEvent(t.event);break;case`interaction_event`:this.emit(`interaction_event`,{interactionId:t.interactionId,eventName:t.eventName,payload:t.payload});break}}handleViewerEvent(e){switch(e.type){case`viewer_ready`:this.isReady=!0,this.flushPendingCommands(),this.emit(`viewer_ready`,void 0);break;case`model_loaded`:this.emit(`model_loaded`,void 0);break;case`camera_changed`:this.emit(`camera_changed`,{cameraId:e.cameraId});break;case`auto_rotate_changed`:this.emit(`auto_rotate_changed`,{enabled:e.enabled});break;case`initial_framing_completed`:this.emit(`model_loaded`,void 0);break}}};e.HOSTED_PREVIEW_HOST_SOURCE=t,e.HOSTED_PREVIEW_VIEWER_SOURCE=n,e.VectrealEmbed=s,e.isHostedPreviewIncomingMessage=a,e.isViewerCommand=i});
package/project.json DELETED
@@ -1,63 +0,0 @@
1
- {
2
- "name": "vctrl/embed",
3
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "packages/embed/src",
5
- "projectType": "library",
6
- "tags": [],
7
- "targets": {
8
- "lint": {
9
- "executor": "@nx/eslint:lint",
10
- "outputs": ["{options.outputFile}"],
11
- "options": {
12
- "fix": true,
13
- "lintFilePatterns": [
14
- "packages/embed/src/**/*.{ts,js}",
15
- "packages/embed/vite.config.ts",
16
- "packages/embed/package.json"
17
- ]
18
- }
19
- },
20
- "typecheck": {
21
- "executor": "nx:run-commands",
22
- "outputs": [],
23
- "options": {
24
- "command": "tsc --noEmit -p packages/embed/tsconfig.json"
25
- }
26
- },
27
- "build": {
28
- "executor": "@nx/vite:build",
29
- "outputs": ["{options.outputPath}"],
30
- "options": {
31
- "outputPath": "build/packages/vctrl/embed",
32
- "configFile": "packages/embed/vite.config.ts"
33
- }
34
- },
35
- "build-ci": {
36
- "executor": "@nx/vite:build",
37
- "outputs": ["{options.outputPath}"],
38
- "options": {
39
- "outputPath": "build/packages/vctrl/embed",
40
- "configFile": "packages/embed/vite.config.ts"
41
- }
42
- },
43
- "copy-md": {
44
- "dependsOn": ["build"],
45
- "executor": "nx:run-commands",
46
- "outputs": [],
47
- "options": {
48
- "commands": [
49
- "rsync -rat packages/embed/*.md build/packages/vctrl/embed/"
50
- ]
51
- }
52
- },
53
- "publish": {
54
- "dependsOn": ["copy-md"],
55
- "executor": "nx:run-commands",
56
- "outputs": [],
57
- "options": {
58
- "command": "pnpm publish --no-git-checks --access public",
59
- "cwd": "build/packages/vctrl/embed"
60
- }
61
- }
62
- }
63
- }
package/src/embed.ts DELETED
@@ -1,362 +0,0 @@
1
- import {
2
- HOSTED_PREVIEW_HOST_SOURCE,
3
- HOSTED_PREVIEW_VIEWER_SOURCE,
4
- type EmbedCameraDescriptor,
5
- type HostedPreviewOutgoingMessage
6
- } from './protocol'
7
-
8
- import type { ViewerCommand, ViewerInteractionEvent } from '@vctrl/viewer'
9
-
10
- // ---------------------------------------------------------------------------
11
- // Public types
12
- // ---------------------------------------------------------------------------
13
-
14
- export interface EmbedOptions {
15
- /** Override the detected iframe origin for postMessage targeting. */
16
- iframeOrigin?: string
17
- /** Milliseconds before ready() rejects. Default: 15 000. */
18
- readyTimeout?: number
19
- }
20
-
21
- export interface EmbedReadyInfo {
22
- sceneId: string | undefined
23
- cameras: EmbedCameraDescriptor[]
24
- }
25
-
26
- export interface SetTransitionOptions {
27
- type: 'none' | 'linear' | 'object_avoidance'
28
- duration?: number
29
- easing?: 'linear' | 'ease_in' | 'ease_out' | 'ease_in_out'
30
- }
31
-
32
- export type EmbedEventMap = {
33
- viewer_ready: void
34
- model_loaded: void
35
- camera_changed: { cameraId: string }
36
- auto_rotate_changed: { enabled: boolean }
37
- interaction_event: {
38
- interactionId?: string
39
- eventName: string
40
- payload?: Record<string, unknown>
41
- }
42
- }
43
-
44
- export type EmbedEventType = keyof EmbedEventMap
45
- export type EmbedEventHandler<K extends EmbedEventType> = (
46
- data: EmbedEventMap[K]
47
- ) => void
48
-
49
- // ---------------------------------------------------------------------------
50
- // VectrealEmbed
51
- // ---------------------------------------------------------------------------
52
-
53
- const DEFAULT_READY_TIMEOUT_MS = 15_000
54
-
55
- export class VectrealEmbed {
56
- private readonly iframe: HTMLIFrameElement
57
- private readonly targetOrigin: string
58
- private readonly readyTimeout: number
59
-
60
- private handlers = new Map<
61
- EmbedEventType,
62
- Set<EmbedEventHandler<EmbedEventType>>
63
- >()
64
- private pendingCommands: Array<{
65
- source: string
66
- type: string
67
- [key: string]: unknown
68
- }> = []
69
- private isReady = false
70
- private boundListener: (event: MessageEvent<unknown>) => void
71
- private pingIntervalId: number | null = null
72
-
73
- constructor(iframe: HTMLIFrameElement, options: EmbedOptions = {}) {
74
- this.iframe = iframe
75
- this.readyTimeout = options.readyTimeout ?? DEFAULT_READY_TIMEOUT_MS
76
-
77
- const src = iframe.src || iframe.getAttribute('src') || ''
78
- let detectedOrigin: string | undefined
79
- if (src) {
80
- try {
81
- detectedOrigin = new URL(src, window.location.href).origin
82
- } catch {
83
- // unparseable src - fall through to require explicit iframeOrigin
84
- }
85
- }
86
- if (!options.iframeOrigin && !detectedOrigin) {
87
- throw new Error(
88
- 'VectrealEmbed: cannot determine iframe origin from src. Pass options.iframeOrigin explicitly.'
89
- )
90
- }
91
- this.targetOrigin = options.iframeOrigin ?? detectedOrigin!
92
-
93
- this.boundListener = this.handleMessage.bind(this)
94
- window.addEventListener('message', this.boundListener)
95
-
96
- // Retry ping every 500 ms until we receive a pong. The iframe's React app
97
- // may not have hydrated and registered its message listener yet when the
98
- // constructor runs, so a single fire-and-forget ping is unreliable.
99
- this.startPingPolling()
100
- }
101
-
102
- // ---------------------------------------------------------------------------
103
- // Lifecycle
104
- // ---------------------------------------------------------------------------
105
-
106
- /**
107
- * Resolves when the embedded viewer emits viewer_ready.
108
- * Rejects if no response within readyTimeout.
109
- */
110
- ready(): Promise<EmbedReadyInfo> {
111
- return new Promise((resolve, reject) => {
112
- const timer = window.setTimeout(() => {
113
- this.off('viewer_ready', onReady as EmbedEventHandler<'viewer_ready'>)
114
- reject(
115
- new Error(
116
- `VectrealEmbed: viewer did not become ready within ${this.readyTimeout}ms`
117
- )
118
- )
119
- }, this.readyTimeout)
120
-
121
- const onReady = () => {
122
- window.clearTimeout(timer)
123
- resolve({
124
- sceneId: this.sceneId,
125
- cameras: this.cameras
126
- })
127
- }
128
-
129
- if (this.isReady) {
130
- window.clearTimeout(timer)
131
- resolve({ sceneId: this.sceneId, cameras: this.cameras })
132
- return
133
- }
134
-
135
- this.on('viewer_ready', onReady as EmbedEventHandler<'viewer_ready'>)
136
- })
137
- }
138
-
139
- /** Remove all listeners and stop responding to messages. */
140
- destroy(): void {
141
- this.stopPingPolling()
142
- window.removeEventListener('message', this.boundListener)
143
- this.handlers.clear()
144
- this.pendingCommands = []
145
- }
146
-
147
- // ---------------------------------------------------------------------------
148
- // Commands
149
- // ---------------------------------------------------------------------------
150
-
151
- /** Switch to a named camera. */
152
- activateCamera(cameraId: string): void {
153
- this.sendCommand({ type: 'activate_camera', cameraId })
154
- }
155
-
156
- /** Override the transition behaviour for subsequent camera switches. */
157
- setTransition(options: SetTransitionOptions): void {
158
- this.sendCommand({
159
- type: 'set_transition',
160
- transitionType: options.type,
161
- duration: options.duration,
162
- easing: options.easing
163
- })
164
- }
165
-
166
- /** Enable or disable orbit controls. */
167
- setControlsEnabled(enabled: boolean): void {
168
- this.sendCommand({ type: 'set_controls_enabled', enabled })
169
- }
170
-
171
- /** Toggle auto-rotate. */
172
- setAutoRotate(enabled: boolean, speed?: number): void {
173
- this.sendCommand({ type: 'set_auto_rotate', enabled, speed })
174
- }
175
-
176
- /** Toggle scroll-zoom. */
177
- setZoomEnabled(enabled: boolean): void {
178
- this.sendCommand({ type: 'set_controls_options', zoom: enabled })
179
- }
180
-
181
- /** Toggle right-click pan. */
182
- setPanEnabled(enabled: boolean): void {
183
- this.sendCommand({ type: 'set_controls_options', pan: enabled })
184
- }
185
-
186
- /**
187
- * Drive scroll-triggered interactions defined in the Publisher.
188
- * @param progress 0 (page top) – 1 (page bottom)
189
- */
190
- sendScrollProgress(progress: number): void {
191
- this.postToIframe({
192
- source: HOSTED_PREVIEW_HOST_SOURCE,
193
- type: 'host_scroll_progress',
194
- progress: Math.min(1, Math.max(0, progress))
195
- })
196
- }
197
-
198
- /**
199
- * Trigger a named host_message interaction defined in the Publisher.
200
- */
201
- sendMessage(message: string, payload?: Record<string, unknown>): void {
202
- this.postToIframe({
203
- source: HOSTED_PREVIEW_HOST_SOURCE,
204
- type: 'host_message',
205
- message,
206
- payload
207
- })
208
- }
209
-
210
- // ---------------------------------------------------------------------------
211
- // Events
212
- // ---------------------------------------------------------------------------
213
-
214
- /** Subscribe to a viewer event. Returns an unsubscribe function. */
215
- on<K extends EmbedEventType>(
216
- type: K,
217
- handler: EmbedEventHandler<K>
218
- ): () => void {
219
- if (!this.handlers.has(type)) {
220
- this.handlers.set(type, new Set())
221
- }
222
- this.handlers.get(type)!.add(handler as EmbedEventHandler<EmbedEventType>)
223
- return () => this.off(type, handler)
224
- }
225
-
226
- /** Remove a specific handler. */
227
- off<K extends EmbedEventType>(type: K, handler: EmbedEventHandler<K>): void {
228
- this.handlers
229
- .get(type)
230
- ?.delete(handler as EmbedEventHandler<EmbedEventType>)
231
- }
232
-
233
- // ---------------------------------------------------------------------------
234
- // Internal state (populated via pong)
235
- // ---------------------------------------------------------------------------
236
-
237
- private sceneId: string | undefined = undefined
238
- private cameras: EmbedCameraDescriptor[] = []
239
-
240
- // ---------------------------------------------------------------------------
241
- // Private helpers
242
- // ---------------------------------------------------------------------------
243
-
244
- private sendCommand(command: ViewerCommand): void {
245
- const message = {
246
- source: HOSTED_PREVIEW_HOST_SOURCE,
247
- type: 'viewer_command' as const,
248
- command
249
- }
250
-
251
- if (!this.isReady) {
252
- this.pendingCommands.push(message)
253
- return
254
- }
255
-
256
- this.postToIframe(message)
257
- }
258
-
259
- private postToIframe(message: Record<string, unknown>): void {
260
- try {
261
- this.iframe.contentWindow?.postMessage(message, this.targetOrigin)
262
- } catch {
263
- // iframe may not be loaded yet; silently discard
264
- }
265
- }
266
-
267
- private startPingPolling(): void {
268
- this.postToIframe({ source: HOSTED_PREVIEW_HOST_SOURCE, type: 'ping' })
269
- this.pingIntervalId = window.setInterval(() => {
270
- if (this.isReady) {
271
- this.stopPingPolling()
272
- return
273
- }
274
- this.postToIframe({ source: HOSTED_PREVIEW_HOST_SOURCE, type: 'ping' })
275
- }, 500)
276
- }
277
-
278
- private stopPingPolling(): void {
279
- if (this.pingIntervalId !== null) {
280
- window.clearInterval(this.pingIntervalId)
281
- this.pingIntervalId = null
282
- }
283
- }
284
-
285
- private flushPendingCommands(): void {
286
- for (const cmd of this.pendingCommands) {
287
- this.postToIframe(cmd)
288
- }
289
- this.pendingCommands = []
290
- }
291
-
292
- private emit<K extends EmbedEventType>(
293
- type: K,
294
- data: EmbedEventMap[K]
295
- ): void {
296
- this.handlers.get(type)?.forEach((handler) => {
297
- ;(handler as EmbedEventHandler<K>)(data)
298
- })
299
- }
300
-
301
- private handleMessage(event: MessageEvent<unknown>): void {
302
- const data = event.data as HostedPreviewOutgoingMessage
303
-
304
- if (
305
- !data ||
306
- typeof data !== 'object' ||
307
- data.source !== HOSTED_PREVIEW_VIEWER_SOURCE
308
- ) {
309
- return
310
- }
311
-
312
- // Validate the message origin matches our iframe
313
- if (this.targetOrigin !== '*' && event.origin !== this.targetOrigin) {
314
- return
315
- }
316
-
317
- switch (data.type) {
318
- case 'pong':
319
- this.stopPingPolling()
320
- this.sceneId = data.sceneId
321
- this.cameras = data.cameras
322
- this.isReady = true
323
- this.flushPendingCommands()
324
- break
325
-
326
- case 'viewer_event':
327
- this.handleViewerEvent(data.event)
328
- break
329
-
330
- case 'interaction_event':
331
- this.emit('interaction_event', {
332
- interactionId: data.interactionId,
333
- eventName: data.eventName,
334
- payload: data.payload
335
- })
336
- break
337
- }
338
- }
339
-
340
- private handleViewerEvent(event: ViewerInteractionEvent): void {
341
- switch (event.type) {
342
- case 'viewer_ready':
343
- this.isReady = true
344
- this.flushPendingCommands()
345
- this.emit('viewer_ready', undefined as void)
346
- break
347
- case 'model_loaded':
348
- this.emit('model_loaded', undefined as void)
349
- break
350
- case 'camera_changed':
351
- this.emit('camera_changed', { cameraId: event.cameraId })
352
- break
353
- case 'auto_rotate_changed':
354
- this.emit('auto_rotate_changed', { enabled: event.enabled })
355
- break
356
- case 'initial_framing_completed':
357
- // Translate to model_loaded for embed consumers - signals scene is visible
358
- this.emit('model_loaded', undefined as void)
359
- break
360
- }
361
- }
362
- }
package/src/index.ts DELETED
@@ -1,28 +0,0 @@
1
- export { VectrealEmbed } from './embed'
2
- export type {
3
- EmbedEventHandler,
4
- EmbedEventMap,
5
- EmbedEventType,
6
- EmbedOptions,
7
- EmbedReadyInfo,
8
- SetTransitionOptions
9
- } from './embed'
10
-
11
- export {
12
- HOSTED_PREVIEW_HOST_SOURCE,
13
- HOSTED_PREVIEW_VIEWER_SOURCE,
14
- isHostedPreviewIncomingMessage,
15
- isViewerCommand
16
- } from './protocol'
17
- export type {
18
- EmbedCameraDescriptor,
19
- HostedPreviewCustomEventMessage,
20
- HostedPreviewHostMessage,
21
- HostedPreviewIncomingMessage,
22
- HostedPreviewOutgoingMessage,
23
- HostedPreviewPingMessage,
24
- HostedPreviewPongMessage,
25
- HostedPreviewScrollProgressMessage,
26
- HostedPreviewViewerCommandMessage,
27
- HostedPreviewViewerEventMessage
28
- } from './protocol'
package/src/protocol.ts DELETED
@@ -1,149 +0,0 @@
1
- import type { ViewerCommand, ViewerInteractionEvent } from '@vctrl/viewer'
2
-
3
- export const HOSTED_PREVIEW_HOST_SOURCE = 'vectreal-host'
4
- export const HOSTED_PREVIEW_VIEWER_SOURCE = 'vectreal-preview'
5
-
6
- // ---------------------------------------------------------------------------
7
- // Incoming messages (parent page → iframe)
8
- // ---------------------------------------------------------------------------
9
-
10
- export interface HostedPreviewPingMessage {
11
- source: typeof HOSTED_PREVIEW_HOST_SOURCE
12
- type: 'ping'
13
- }
14
-
15
- export interface HostedPreviewViewerCommandMessage {
16
- source: typeof HOSTED_PREVIEW_HOST_SOURCE
17
- type: 'viewer_command'
18
- command: ViewerCommand
19
- }
20
-
21
- export interface HostedPreviewScrollProgressMessage {
22
- source: typeof HOSTED_PREVIEW_HOST_SOURCE
23
- type: 'host_scroll_progress'
24
- progress: number
25
- }
26
-
27
- export interface HostedPreviewHostMessage {
28
- source: typeof HOSTED_PREVIEW_HOST_SOURCE
29
- type: 'host_message'
30
- message: string
31
- payload?: Record<string, unknown>
32
- }
33
-
34
- export type HostedPreviewIncomingMessage =
35
- | HostedPreviewHostMessage
36
- | HostedPreviewPingMessage
37
- | HostedPreviewScrollProgressMessage
38
- | HostedPreviewViewerCommandMessage
39
-
40
- // ---------------------------------------------------------------------------
41
- // Outgoing messages (iframe → parent page)
42
- // ---------------------------------------------------------------------------
43
-
44
- export interface EmbedCameraDescriptor {
45
- id: string
46
- name: string
47
- fov?: number
48
- }
49
-
50
- export interface HostedPreviewPongMessage {
51
- source: typeof HOSTED_PREVIEW_VIEWER_SOURCE
52
- type: 'pong'
53
- sceneId?: string
54
- cameras: EmbedCameraDescriptor[]
55
- }
56
-
57
- export interface HostedPreviewViewerEventMessage {
58
- source: typeof HOSTED_PREVIEW_VIEWER_SOURCE
59
- type: 'viewer_event'
60
- sceneId?: string
61
- event: ViewerInteractionEvent
62
- }
63
-
64
- export interface HostedPreviewCustomEventMessage {
65
- source: typeof HOSTED_PREVIEW_VIEWER_SOURCE
66
- type: 'interaction_event'
67
- sceneId?: string
68
- interactionId?: string
69
- eventName: string
70
- payload?: Record<string, unknown>
71
- }
72
-
73
- export type HostedPreviewOutgoingMessage =
74
- | HostedPreviewCustomEventMessage
75
- | HostedPreviewPongMessage
76
- | HostedPreviewViewerEventMessage
77
-
78
- // ---------------------------------------------------------------------------
79
- // Type guards
80
- // ---------------------------------------------------------------------------
81
-
82
- function isRecord(value: unknown): value is Record<string, unknown> {
83
- return typeof value === 'object' && value !== null && !Array.isArray(value)
84
- }
85
-
86
- export function isViewerCommand(value: unknown): value is ViewerCommand {
87
- if (!isRecord(value) || typeof value.type !== 'string') {
88
- return false
89
- }
90
-
91
- switch (value.type) {
92
- case 'activate_camera':
93
- return (
94
- typeof value.cameraId === 'string' && value.cameraId.trim().length > 0
95
- )
96
- case 'set_controls_enabled':
97
- return typeof value.enabled === 'boolean'
98
- case 'set_auto_rotate':
99
- return (
100
- typeof value.enabled === 'boolean' &&
101
- (value.speed === undefined || typeof value.speed === 'number')
102
- )
103
- case 'set_controls_options':
104
- return (
105
- (value.zoom === undefined || typeof value.zoom === 'boolean') &&
106
- (value.pan === undefined || typeof value.pan === 'boolean')
107
- )
108
- case 'set_transition':
109
- return (
110
- typeof value.transitionType === 'string' &&
111
- ['none', 'linear', 'object_avoidance'].includes(
112
- value.transitionType as string
113
- )
114
- )
115
- default:
116
- return false
117
- }
118
- }
119
-
120
- export function isHostedPreviewIncomingMessage(
121
- value: unknown
122
- ): value is HostedPreviewIncomingMessage {
123
- if (
124
- !isRecord(value) ||
125
- value.source !== HOSTED_PREVIEW_HOST_SOURCE ||
126
- typeof value.type !== 'string'
127
- ) {
128
- return false
129
- }
130
-
131
- switch (value.type) {
132
- case 'ping':
133
- return true
134
- case 'viewer_command':
135
- return isViewerCommand(value.command)
136
- case 'host_scroll_progress':
137
- return (
138
- typeof value.progress === 'number' && Number.isFinite(value.progress)
139
- )
140
- case 'host_message':
141
- return (
142
- typeof value.message === 'string' &&
143
- value.message.trim().length > 0 &&
144
- (value.payload === undefined || isRecord(value.payload))
145
- )
146
- default:
147
- return false
148
- }
149
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "jsx": "react-jsx",
4
- "allowJs": false,
5
- "esModuleInterop": false,
6
- "allowSyntheticDefaultImports": true,
7
- "strict": true,
8
- "lib": ["ESNext", "DOM", "DOM.Iterable"],
9
- "types": ["vite/client"]
10
- },
11
- "files": [],
12
- "include": [],
13
- "references": [
14
- {
15
- "path": "./tsconfig.lib.json"
16
- }
17
- ],
18
- "extends": "../../tsconfig.base.json"
19
- }
package/tsconfig.lib.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "declaration": true,
6
- "types": ["node", "vite/client"]
7
- },
8
- "exclude": [
9
- "**/*.spec.ts",
10
- "**/*.test.ts",
11
- "**/*.spec.js",
12
- "**/*.test.js"
13
- ],
14
- "include": ["src/**/*.ts"]
15
- }
package/vite.config.ts DELETED
@@ -1,49 +0,0 @@
1
- import * as path from 'path'
2
-
3
- import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'
4
- import { defineConfig } from 'vite'
5
- import dts from 'vite-plugin-dts'
6
-
7
- export default defineConfig({
8
- root: __dirname,
9
- cacheDir: '../../node_modules/.vite/packages/@vctrl/embed',
10
- plugins: [
11
- nxViteTsPaths(),
12
- dts({
13
- entryRoot: 'src',
14
- tsconfigPath: path.join(__dirname, 'tsconfig.lib.json')
15
- })
16
- ],
17
-
18
- build: {
19
- emptyOutDir: true,
20
- reportCompressedSize: true,
21
- commonjsOptions: {
22
- transformMixedEsModules: true
23
- },
24
- lib: {
25
- entry: 'src/index.ts',
26
- name: '@vctrl/embed',
27
- fileName: 'index',
28
- formats: ['es', 'cjs']
29
- },
30
- rollupOptions: {
31
- output: [
32
- {
33
- format: 'es',
34
- entryFileNames: 'index.js'
35
- },
36
- {
37
- format: 'cjs',
38
- entryFileNames: 'index.cjs'
39
- },
40
- {
41
- format: 'umd',
42
- name: 'VectrealEmbed',
43
- entryFileNames: 'vectreal-embed.umd.js',
44
- exports: 'named'
45
- }
46
- ]
47
- }
48
- }
49
- })