@virtualdisplay.io/client 4.0.0-alpha.1 → 4.0.0-alpha.3

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/src/attributes/attribute.d.ts +38 -0
  3. package/dist/src/attributes/attribute.d.ts.map +1 -0
  4. package/dist/src/attributes/attributes.d.ts +43 -0
  5. package/dist/src/attributes/attributes.d.ts.map +1 -0
  6. package/dist/src/attributes/selections.d.ts +16 -0
  7. package/dist/src/attributes/selections.d.ts.map +1 -0
  8. package/dist/src/attributes/value.d.ts +13 -0
  9. package/dist/src/attributes/value.d.ts.map +1 -0
  10. package/dist/src/camera/camera.d.ts +40 -0
  11. package/dist/src/camera/camera.d.ts.map +1 -0
  12. package/dist/src/camera/index.d.ts +4 -0
  13. package/dist/src/camera/index.d.ts.map +1 -0
  14. package/dist/src/camera/position.d.ts +43 -0
  15. package/dist/src/camera/position.d.ts.map +1 -0
  16. package/dist/src/client/client.d.ts +83 -0
  17. package/dist/src/client/client.d.ts.map +1 -0
  18. package/dist/src/client/error.d.ts +26 -0
  19. package/dist/src/client/error.d.ts.map +1 -0
  20. package/dist/src/client/iframe-builder.d.ts +25 -0
  21. package/dist/src/client/iframe-builder.d.ts.map +1 -0
  22. package/dist/src/client/options.d.ts +17 -0
  23. package/dist/src/client/options.d.ts.map +1 -0
  24. package/dist/src/client/ready-state-tracker.d.ts +26 -0
  25. package/dist/src/client/ready-state-tracker.d.ts.map +1 -0
  26. package/dist/src/client/types.d.ts +5 -0
  27. package/dist/src/client/types.d.ts.map +1 -0
  28. package/dist/src/index.d.ts +11 -0
  29. package/dist/src/index.d.ts.map +1 -0
  30. package/dist/src/snapshot/index.d.ts +3 -0
  31. package/dist/src/snapshot/index.d.ts.map +1 -0
  32. package/dist/src/snapshot/photo.d.ts +47 -0
  33. package/dist/src/snapshot/photo.d.ts.map +1 -0
  34. package/dist/src/snapshot/snapshot.d.ts +30 -0
  35. package/dist/src/snapshot/snapshot.d.ts.map +1 -0
  36. package/dist/src/ui/viewer-config.d.ts +63 -0
  37. package/dist/src/ui/viewer-config.d.ts.map +1 -0
  38. package/dist/src/utils/logger.d.ts +11 -0
  39. package/dist/src/utils/logger.d.ts.map +1 -0
  40. package/dist/virtualdisplay.client.cjs.js +1 -2
  41. package/dist/virtualdisplay.client.es.js +798 -2
  42. package/dist/virtualdisplay.client.umd.js +1 -2
  43. package/package.json +3 -3
  44. package/dist/index.d.ts +0 -415
  45. package/dist/virtualdisplay.client.cjs.js.map +0 -1
  46. package/dist/virtualdisplay.client.es.js.map +0 -1
  47. package/dist/virtualdisplay.client.umd.js.map +0 -1
@@ -1,2 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VirtualdisplayClient={})}(this,function(e){"use strict";const t={NO_MAPPING:"NO_MAPPING",ATTRIBUTE_NOT_FOUND:"ATTRIBUTE_NOT_FOUND",VALUE_NOT_FOUND:"VALUE_NOT_FOUND",PARENT_NOT_FOUND:"PARENT_NOT_FOUND",INVALID_PARAMETER:"INVALID_PARAMETER",SNAPSHOT_FAILED:"SNAPSHOT_FAILED"};class i extends Error{code;constructor(e,t){super(e),this.name="VirtualdisplayError",this.code=t,void 0!==Error.captureStackTrace&&Error.captureStackTrace(this,i)}static noMapping(){return new i("No attributes available. Waiting for server metadata.",t.NO_MAPPING)}static attributeNotFound(e){return new i(`Attribute "${e}" not found in mapping`,t.ATTRIBUTE_NOT_FOUND)}static valueNotFound(e,n){return new i(`Value "${n}" not found for attribute "${e}"`,t.VALUE_NOT_FOUND)}static parentNotFound(e){return new i(`Parent element not found: ${e}`,t.PARENT_NOT_FOUND)}static invalidParameter(e){return new i(e,t.INVALID_PARAMETER)}static snapshotFailed(e,n){return new i(`Snapshot failed for "${e}": ${n}`,t.SNAPSHOT_FAILED)}}const n={};class s{defaultServer="https://server.virtualdisplay.io";options;constructor(e){this.options=e}create(){const e=this.createIframe();return this.attachToParent(e),e}createIframe(){const e=document.createElement("iframe");return e.src=this.buildUrl(),Object.assign(e.style,{width:"100%",height:"100%",border:"none"}),e.setAttribute("allow","xr-spatial-tracking; fullscreen"),e}attachToParent(e){this.getParentElement().appendChild(e)}buildUrl(){const e=this.getServerUrl(),t=new URL(e);return t.searchParams.set("license",this.options.license),t.searchParams.set("model",this.options.model),!0===this.options.debug&&t.searchParams.set("debug","true"),void 0!==this.options.language&&t.searchParams.set("language",this.options.language),t.toString()}getServerUrl(){if(void 0===n?.VITE_VIRTUALDISPLAY_SERVER_HOST)return this.defaultServer}getParentElement(){if("string"!=typeof this.options.parent)return this.options.parent;const e=document.querySelector(this.options.parent);if(null===e)throw i.parentNotFound(this.options.parent);return e}}function a(e,t){const i=t?function(e){const t=new Date(e.timestamp).toLocaleTimeString("en-GB",{hour12:!1}),i=e.level.toUpperCase().padEnd(5),n=e.trace_id?.slice(0,8)??"--------",s=e.msg,a=["timestamp","level","msg","service","trace_id","span_id"],r=Object.entries(e).filter(([e])=>!a.includes(e)).map(([e,t])=>`${e}=${JSON.stringify(t)}`).join(" ");return`${t} ${i} [${n}] ${s}${r?" "+r:""}`}(e):function(e){return JSON.stringify(e)}(e);"error"===e.level?console.error(i):"warn"===e.level?console.warn(i):console.log(i)}class r{constructor(e){this.config=e}log(e,t,i={}){const n=this.config.traceProvider?.()??{};var s;a({timestamp:(s=new Date,s.toISOString()),level:e,msg:t,service:this.config.service,...n,...i},this.config.isDev)}debug(e,t){this.config.isDev&&this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}with(e){const t=new r(this.config);return t.log=((t,i,n={})=>{this.log(t,i,{...e,...n})}).bind(this),t}}function o(e,t){const i=void 0!==t?.isDev?t.isDev:"undefined"==typeof process||"production"!==process.env.NODE_ENV;return new r({service:e,isDev:i,traceProvider:t?.traceProvider})}function l(e){if(void 0!==e)return"object"!=typeof e||null===e||Array.isArray(e)||e instanceof Error?e instanceof Error?{error:e.message}:{value:e}:e}function d(e){return{debug:(t,i)=>e.debug(t,l(i)),info:(t,i)=>e.info(t,l(i)),warn:(t,i)=>e.warn(t,l(i)),error:(t,i)=>e.error(t,l(i))}}let u=!1;function h(){u=!0,e.logger=d(o("client",{isDev:!0}))}e.logger=d(o("client",{isDev:!1}));class c{ready=!1;onReady;constructor(e){this.onReady=e}handleMappingReceived(){this.markReady("Mapping received from server")}handleMutationResult(e){e.isInitial&&this.markReady("Initial mutations processed")}get isReady(){return this.ready}clear(){this.ready=!1}markReady(t){this.ready||(this.ready=!0,e.logger.info(`${t} - client is now ready`),this.onReady?.())}}class g{constructor(e,t){this.value=e,this.attribute=t}isSelectedValue=!1;get isSelected(){return this.isSelectedValue}setSelected(e){this.isSelectedValue=e}}class f{name;defaultValue;values;onSelect;onChangeCallback;constructor(e){this.name=e.name,this.defaultValue=e.defaultValue,this.onSelect=e.onSelect,this.values=this.createValues(e.values)}select(e){if(!this.values.has(e))throw i.valueNotFound(this.name,e);this.onSelect(this.name,e)}getValues(){return Array.from(this.values.values())}getValue(e){return this.values.get(e)}onChange(e){this.onChangeCallback=e}handleResult(e){let t;for(const i of this.values.values()){const n=i.value===e;i.setSelected(n),n&&(t=i)}void 0!==t&&void 0!==this.onChangeCallback&&this.onChangeCallback(t)}createValues(e){const t=new Map;for(const i of e){const e=new g(i,this.name);i===this.defaultValue&&e.setSelected(!0),t.set(i,e)}return t}}class p{attributes=new Map;constructor(e,t,i){for(const n of e){const e=this.buildOptions(n,t);this.attributes.set(n.name,new f(e))}this.onMutationFailed=i}onMutationFailed;get(e){return this.attributes.get(e)}defaults(){const e=[];for(const t of this.attributes.values())void 0!==t.defaultValue&&e.push({name:t.name,value:t.defaultValue});return e}handleResults(e){for(const t of e)t.success?this.get(t.name)?.handleResult(t.value):this.onMutationFailed?.(t.name,t.value)}buildOptions(e,t){const i={name:e.name,values:e.values,onSelect:(e,i)=>{t([{name:e,value:i}],!1)}};return void 0!==e.defaultValue?{...i,defaultValue:e.defaultValue}:i}}const m={READY:"ready",MUTATION:"mutation",STATE:"state",CONFIG:"config",CAMERA:"camera",SNAPSHOT:"snapshot",MAPPING:"mapping"},b=new Set(Object.values(m));function v(e){return"object"!=typeof e||null===e?null:e}class y{constructor(e){this.processor=e}pending=[];isReady=!1;enqueue(e){this.isReady?this.processor(e):this.pending.push(e)}markReady(){if(this.isReady)return;if(this.isReady=!0,0===this.pending.length)return;const e=[...this.pending];this.pending.length=0;for(const t of e)this.processor(t)}get ready(){return this.isReady}get pendingCount(){return this.pending.length}hasPending(e){return this.pending.some(t=>t.type===e)}clear(){this.pending.length=0}}class A{constructor(e,t){this.getTarget=e,this.sourceId=`vd-${Math.random().toString(36).slice(2,8)}`,this.publisher=new y(e=>this.post(e)),this.consumer=new y(e=>this.routeToHandler(e)),this.shouldBuffer=t?.shouldBuffer??null,!0!==t?.bufferIncoming&&this.consumer.markReady()}handlers=new Map;publisher;consumer;shouldBuffer;listener=null;sourceId;on(e,t){this.handlers.set(e,t)}off(e){this.handlers.delete(e)}send(e){this.publisher.enqueue(e)}markReady(){this.publisher.markReady()}markConsumerReady(){this.consumer.markReady()}consumerHasPending(e){return this.consumer.hasPending(e)}start(){null===this.listener&&(this.listener=e=>{const{data:t}=e;if(!function(e){if("object"!=typeof e||null===e)return!1;const t=e;return"string"!=typeof t.target&&"string"==typeof t.type&&b.has(t.type)}(t))return;if(t._source===this.sourceId)return;const i=t;null===this.shouldBuffer||this.shouldBuffer(i)?this.consumer.enqueue(i):this.routeToHandler(i)},window.addEventListener("message",this.listener))}destroy(){null!==this.listener&&(window.removeEventListener("message",this.listener),this.listener=null),this.publisher.clear(),this.consumer.clear(),this.handlers.clear()}get ready(){return this.publisher.ready}get pendingCount(){return this.publisher.pendingCount}post(e){const t=this.getTarget();null!==t&&t.postMessage({...e,_source:this.sourceId},"*")}routeToHandler(e){const t=this.handlers.get(e.type);if(void 0===t)return;const i=t(e);i instanceof Promise&&i.catch(t=>{const i=t instanceof Error?t.message:String(t);console.error(`Virtualdisplay: error handling ${e.type}: ${i}`)})}}class E{constructor(e,t){this.send=e,this.onMutationFailed=t}registry;pendingCallbacks=[];handleMappingReceived(t){e.logger.debug("Initializing from server mapping",{attributeCount:t.attributes.length}),this.registry=new p(t.attributes,(e,t)=>this.sendMutation(e,t),this.onMutationFailed);const i=this.registry.defaults();i.length>0&&this.sendMutation(i,!0),this.replayPendingCallbacks()}handleMutationResult(e){this.registry?.handleResults(e.results)}select(e,t){this.sendMutation([{name:e,value:t}],!1)}getValues(e){return this.registry?.get(e)?.getValues()??[]}getValue(e,t){return this.registry?.get(e)?.getValue(t)}onChange(t,i){if(void 0===this.registry)return void this.pendingCallbacks.push({name:t,callback:i});const n=this.registry.get(t);void 0!==n?n.onChange(i):e.logger.warn(`onChange: attribute "${t}" not found — callback will not fire`)}getAttribute(e){return this.registry?.get(e)}get hasMapping(){return void 0!==this.registry}sendMutation(e,t){const i={type:m.MUTATION,mutations:e,isInitial:t};this.send(i)}replayPendingCallbacks(){for(const{name:t,callback:i}of this.pendingCallbacks){const n=this.registry?.get(t);void 0===n&&e.logger.warn(`onChange: attribute "${t}" not found — buffered callback will not fire`),n?.onChange(i)}this.pendingCallbacks.length=0}}class C{constructor(e){this.send=e}commands=[];rotate(e){return this.validateFiniteNumber(e,"rotate"),this.commands.push({action:"rotate",value:e}),this}tilt(e){return this.validateFiniteNumber(e,"tilt"),this.commands.push({action:"tilt",value:e}),this}zoom(e){return this.validateFiniteNumber(e,"zoom"),this.commands.push({action:"zoom",value:e}),this}set(){if(0===this.commands.length)throw i.invalidParameter("No camera commands to execute. Call rotate(), tilt(), or zoom() before set().");const e={type:m.CAMERA,commands:this.commands};this.send(e),this.commands=[]}validateFiniteNumber(e,t){if(!Number.isFinite(e))throw i.invalidParameter(`${t}() requires a finite number, got ${String(e)}`)}reset(){const e={type:m.CAMERA,commands:[{action:"reset"}]};this.send(e)}}class I{state;changeCallbacks=[];constructor(){this.state={rotation:0,tilt:0,zoom:0}}get rotation(){return this.state.rotation}get tilt(){return this.state.tilt}get zoom(){return this.state.zoom}onChange(e){this.changeCallbacks.push(e)}handlePositionUpdate(e){this.update(e.transforms)}update(e){let t=!1;for(const i of e)switch(i.type){case"rotation":this.state.rotation=i.value,t=!0;break;case"tilt":this.state.tilt=i.value,t=!0;break;case"zoom":this.state.zoom=i.value,t=!0}if(t){const e={rotation:this.rotation,tilt:this.tilt,zoom:this.zoom};for(const t of this.changeCallbacks)t(e)}}}class w{constructor(e){this.filename=e}developedCallbacks=new Set;errorCallbacks=new Set;developed=!1;failed=!1;photoData=null;onDeveloped(e){return this.developed&&null!==this.photoData?(e(this.photoData),this):(this.developedCallbacks.add(e),this)}onError(e){return this.errorCallbacks.add(e),this}getFilename(){return this.filename}develop(e){this.developed||this.failed||(this.photoData=e,this.developed=!0,this.developedCallbacks.forEach(e=>{e(this.photoData)}),this.developedCallbacks.clear(),this.errorCallbacks.clear())}fail(e){this.developed||this.failed||(this.failed=!0,this.errorCallbacks.forEach(t=>{t(e)}),this.developedCallbacks.clear(),this.errorCallbacks.clear())}}class S{constructor(e){this.send=e}validExtensions=[".png"];pendingPhotos=new Map;take(t){this.validateFilename(t),e.logger.debug("Taking snapshot",{filename:t});const i=new w(t);return this.pendingPhotos.set(i.getFilename(),i),this.send({type:m.SNAPSHOT,filename:t}),i}handleSnapshotResult(n){const s=this.pendingPhotos.get(n.filename);void 0!==s?(this.pendingPhotos.delete(n.filename),void 0===n.error?void 0!==n.data&&s.develop({filename:n.filename,data:n.data}):s.fail(new i(n.error,t.SNAPSHOT_FAILED))):e.logger.warn("No pending photo found for filename",{filename:n.filename,pendingFilenames:Array.from(this.pendingPhotos.keys())})}validateFilename(e){if(""===e||"string"!=typeof e)throw new i("Filename must be a non-empty string",t.INVALID_PARAMETER);if(!this.validExtensions.some(t=>e.toLowerCase().endsWith(t)))throw new i(`Invalid filename: "${e}". Filename must end with one of: ${this.validExtensions.join(", ")}`,t.INVALID_PARAMETER);if(e.includes("..")||e.includes("/")||e.includes("\\"))throw new i('Filename cannot contain path separators or ".."',t.INVALID_PARAMETER)}}class R{send;constructor(e){this.send=e}sendInitialConfig(e){const t=this.buildUIConfig(e),i=this.buildViewerConfig(t,e.camera);void 0!==i&&this.sendConfig(i)}buildUIConfig(e){const t=this.extractUIValues(e);if(Object.values(t).some(e=>void 0!==e))return{...void 0!==t.arEnabled&&{arEnabled:t.arEnabled},...void 0!==t.fullscreenEnabled&&{fullscreenEnabled:t.fullscreenEnabled},...void 0!==t.loadingIndicatorEnabled&&{loadingIndicatorEnabled:t.loadingIndicatorEnabled}}}extractUIValues(e){return{arEnabled:e.arEnabled??e.ui?.arEnabled,fullscreenEnabled:e.fullscreenEnabled??e.ui?.fullscreenEnabled,loadingIndicatorEnabled:e.loadingIndicatorEnabled??e.ui?.loadingIndicatorEnabled}}buildViewerConfig(e,t){if(void 0!==e||void 0!==t)return{...void 0!==e&&{ui:e},...void 0!==t&&{camera:t}}}setArEnabled(t){e.logger.debug(`Setting AR button enabled: ${t}`),this.updateUIConfig({arEnabled:t})}setFullscreenEnabled(t){e.logger.debug(`Setting fullscreen button enabled: ${t}`),this.updateUIConfig({fullscreenEnabled:t})}setLoadingIndicatorEnabled(t){e.logger.debug(`Setting loading indicator enabled: ${t}`),this.updateUIConfig({loadingIndicatorEnabled:t})}updateUIConfig(t){e.logger.debug("Updating UI configuration",t),Object.keys(t).length>0&&this.sendConfig({ui:t})}hideAllUI(){e.logger.debug("Hiding all UI elements"),this.sendUIConfig({arEnabled:!1,fullscreenEnabled:!1,loadingIndicatorEnabled:!1})}showAllUI(){e.logger.debug("Showing all UI elements"),this.sendUIConfig({arEnabled:!0,fullscreenEnabled:!0,loadingIndicatorEnabled:!0})}sendUIConfig(e){this.sendConfig({ui:e})}sendConfig(t){const i={type:m.CONFIG,config:t};e.logger.debug("Sending config",i),this.send(i)}}e.Attribute=f,e.AttributeValue=g,e.Camera=C,e.ERROR_CODES=t,e.Photo=w,e.Snapshot=S,e.VirtualdisplayClient=class{attributes;readyStateTracker;messenger;viewerService;cameraApi;cameraPositionApi;snapshotApi;options;iframe;readyCallbacks=[];mutationFailedCallbacks=[];constructor(e){this.options=e,this.initializeLogging(),this.messenger=new A(()=>this.iframe?.contentWindow??null);const t=e=>{this.messenger.send(e)};this.attributes=new E(t,(e,t)=>{for(const i of this.mutationFailedCallbacks)i({attribute:e,value:t})}),this.readyStateTracker=new c(()=>{const e=this.readyCallbacks;this.readyCallbacks=[];for(const t of e)t(this)}),this.viewerService=new R(t),this.cameraApi=new C(t),this.cameraPositionApi=new I,this.snapshotApi=new S(t),this.registerHandlers(),this.messenger.start(),this.createAndConnectIframe()}registerHandlers(){this.messenger.on(m.READY,t=>{(function(e){const t=v(e);return null!==t&&t.type===m.READY})(t)&&(e.logger.info("Server ready — flushing publisher queue"),this.messenger.markReady(),this.viewerService.sendInitialConfig(this.options))}),this.messenger.on(m.MUTATION,e=>{(function(e){const t=v(e);return null!==t&&t.type===m.MUTATION&&Array.isArray(t.results)})(e)&&(this.attributes.handleMutationResult(e),this.readyStateTracker.handleMutationResult(e))}),this.messenger.on(m.SNAPSHOT,e=>{(function(e){const t=v(e);return null!==t&&t.type===m.SNAPSHOT&&("data"in t||"error"in t)})(e)&&this.snapshotApi.handleSnapshotResult(e)}),this.messenger.on(m.CAMERA,e=>{(function(e){const t=v(e);return null!==t&&t.type===m.CAMERA&&Array.isArray(t.transforms)})(e)&&this.cameraPositionApi.handlePositionUpdate(e)}),this.messenger.on(m.MAPPING,e=>{(function(e){const t=v(e);return null!==t&&t.type===m.MAPPING&&Array.isArray(t.attributes)})(e)&&(this.attributes.handleMappingReceived(e),this.readyStateTracker.handleMappingReceived())})}initializeLogging(){!0===this.options.debug&&h(),e.logger.info("Initializing Virtualdisplay client",{model:this.options.model,debug:this.options.debug,language:this.options.language})}createAndConnectIframe(){e.logger.debug("Creating iframe for Virtualdisplay viewer");const t=new s(this.options);this.iframe=t.create(),e.logger.debug("Iframe created, waiting for server READY")}select(e,t){this.attributes.select(e,t)}getValues(e){return this.attributes.getValues(e)}getValue(e,t){return this.attributes.getValue(e,t)}onChange(e,t){this.attributes.onChange(e,t)}getAttribute(e){return this.attributes.getAttribute(e)}get isReady(){return this.readyStateTracker.isReady}onReady(e){this.readyStateTracker.isReady?e(this):this.readyCallbacks.push(e)}onMutationFailed(e){this.mutationFailedCallbacks.push(e)}get viewer(){return this.viewerService}get camera(){return this.cameraApi}get cameraPosition(){return this.cameraPositionApi}get snapshot(){return this.snapshotApi}destroy(){e.logger.info("Destroying Virtualdisplay client"),this.readyStateTracker.clear(),this.readyCallbacks=[],this.messenger.destroy(),null!==this.iframe?.parentElement&&void 0!==this.iframe?.parentElement&&this.iframe.parentElement.removeChild(this.iframe),this.iframe=void 0,e.logger.debug("Virtualdisplay client destroyed")}},e.VirtualdisplayError=i,e.VirtualdisplayViewerService=R,e.enableDebugMode=h,e.isDebugMode=function(){return u},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
2
- //# sourceMappingURL=virtualdisplay.client.umd.js.map
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.VirtualdisplayClient={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t={NO_MAPPING:`NO_MAPPING`,ATTRIBUTE_NOT_FOUND:`ATTRIBUTE_NOT_FOUND`,VALUE_NOT_FOUND:`VALUE_NOT_FOUND`,PARENT_NOT_FOUND:`PARENT_NOT_FOUND`,INVALID_PARAMETER:`INVALID_PARAMETER`,SNAPSHOT_FAILED:`SNAPSHOT_FAILED`},n=class e extends Error{code;constructor(t,n){super(t),this.name=`VirtualdisplayError`,this.code=n,Error.captureStackTrace?.(this,e)}static noMapping(){return new e(`No attributes available. Waiting for server metadata.`,t.NO_MAPPING)}static attributeNotFound(n){return new e(`Attribute "${n}" not found in mapping`,t.ATTRIBUTE_NOT_FOUND)}static valueNotFound(n,r){return new e(`Value "${r}" not found for attribute "${n}"`,t.VALUE_NOT_FOUND)}static parentNotFound(n){return new e(`Parent element not found: ${n}`,t.PARENT_NOT_FOUND)}static invalidParameter(n){return new e(n,t.INVALID_PARAMETER)}static snapshotFailed(n,r){return new e(`Snapshot failed for "${n}": ${r}`,t.SNAPSHOT_FAILED)}},r=class{defaultServer=`https://server.virtualdisplay.io`;options;constructor(e){this.options=e}create(){let e=this.createIframe();return this.attachToParent(e),e}createIframe(){let e=document.createElement(`iframe`);return e.src=this.buildUrl(),Object.assign(e.style,{width:`100%`,height:`100%`,border:`none`}),e.setAttribute(`allow`,`xr-spatial-tracking; fullscreen`),e}attachToParent(e){this.getParentElement().appendChild(e)}buildUrl(){let e=this.getServerUrl(),t=new URL(e);return t.searchParams.set(`license`,this.options.license),t.searchParams.set(`model`,this.options.model),this.options.debug===!0&&t.searchParams.set(`debug`,`true`),this.options.language!==void 0&&t.searchParams.set(`language`,this.options.language),t.toString()}getServerUrl(){return this.defaultServer}getParentElement(){if(typeof this.options.parent!=`string`)return this.options.parent;let e=document.querySelector(this.options.parent);if(e===null)throw n.parentNotFound(this.options.parent);return e}};function i(e){let t=new Date(e.timestamp).toLocaleTimeString(`en-GB`,{hour12:!1}),n=e.level.toUpperCase().padEnd(5),r=e.trace_id?.slice(0,8)??`--------`,i=e.msg,a=[`timestamp`,`level`,`msg`,`service`,`trace_id`,`span_id`],o=Object.entries(e).filter(([e])=>!a.includes(e)).map(([e,t])=>`${e}=${JSON.stringify(t)}`).join(` `);return`${t} ${n} [${r}] ${i}${o?` `+o:``}`}function a(e){return JSON.stringify(e)}function o(e,t){let n=t?i(e):a(e);e.level===`error`?console.error(n):e.level===`warn`?console.warn(n):console.log(n)}function s(){return new Date}function c(e){return e.toISOString()}var l=class e{constructor(e){this.config=e}log(e,t,n={}){let r=this.config.traceProvider?.()??{};o({timestamp:c(s()),level:e,msg:t,service:this.config.service,...r,...n},this.config.isDev)}debug(e,t){this.config.isDev&&this.log(`debug`,e,t)}info(e,t){this.log(`info`,e,t)}warn(e,t){this.log(`warn`,e,t)}error(e,t){this.log(`error`,e,t)}with(t){let n=new e(this.config);return n.log=((e,n,r={})=>{this.log(e,n,{...t,...r})}).bind(this),n}};function u(e,t){return new l({service:e,isDev:t?.isDev===void 0?typeof process<`u`?process.env.NODE_ENV!==`production`:!0:t.isDev,traceProvider:t?.traceProvider})}function d(e){if(e!==void 0)return typeof e==`object`&&e&&!Array.isArray(e)&&!(e instanceof Error)?e:e instanceof Error?{error:e.message}:{value:e}}function f(e){return{debug:(t,n)=>e.debug(t,d(n)),info:(t,n)=>e.info(t,d(n)),warn:(t,n)=>e.warn(t,d(n)),error:(t,n)=>e.error(t,d(n))}}var p=!1,m=f(u(`client`,{isDev:!1}));function h(){p=!0,m=f(u(`client`,{isDev:!0}))}function g(){return p}var _=class{ready=!1;onReady;constructor(e){this.onReady=e}handleMappingReceived(){this.markReady(`Mapping received from server`)}handleMutationResult(e){e.isInitial&&this.markReady(`Initial mutations processed`)}get isReady(){return this.ready}clear(){this.ready=!1}markReady(e){this.ready||(this.ready=!0,m.info(`${e} - client is now ready`),this.onReady?.())}},v=class{isSelectedValue=!1;constructor(e,t){this.value=e,this.attribute=t}get isSelected(){return this.isSelectedValue}setSelected(e){this.isSelectedValue=e}},y=class{name;defaultValue;values;onSelect;onChangeCallback;constructor(e){this.name=e.name,this.defaultValue=e.defaultValue,this.onSelect=e.onSelect,this.values=this.createValues(e.values)}select(e){if(!this.values.has(e))throw n.valueNotFound(this.name,e);this.onSelect(this.name,e)}getValues(){return Array.from(this.values.values())}getValue(e){return this.values.get(e)}onChange(e){this.onChangeCallback=e}handleResult(e){let t;for(let n of this.values.values()){let r=n.value===e;n.setSelected(r),r&&(t=n)}t!==void 0&&this.onChangeCallback!==void 0&&this.onChangeCallback(t)}createValues(e){let t=new Map;for(let n of e){let e=new v(n,this.name);n===this.defaultValue&&e.setSelected(!0),t.set(n,e)}return t}},b=class{attributes=new Map;constructor(e,t,n){for(let n of e){let e=this.buildOptions(n,t);this.attributes.set(n.name,new y(e))}this.onMutationFailed=n}onMutationFailed;get(e){return this.attributes.get(e)}defaults(){let e=[];for(let t of this.attributes.values())t.defaultValue!==void 0&&e.push({name:t.name,value:t.defaultValue});return e}handleResults(e){for(let t of e){if(!t.success){this.onMutationFailed?.(t.name,t.value);continue}this.get(t.name)?.handleResult(t.value)}}buildOptions(e,t){let n={name:e.name,values:e.values,onSelect:(e,n)=>{t([{name:e,value:n}],!1)}};return e.defaultValue===void 0?n:{...n,defaultValue:e.defaultValue}}},x={READY:`ready`,MUTATION:`mutation`,STATE:`state`,CONFIG:`config`,CAMERA:`camera`,SNAPSHOT:`snapshot`,MAPPING:`mapping`},S=new Set(Object.values(x));function C(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.target==`string`?!1:typeof t.type==`string`&&S.has(t.type)}function w(e){let t=k(e);return t!==null&&t.type===x.READY}function T(e){let t=k(e);return t!==null&&t.type===x.MUTATION&&Array.isArray(t.results)}function E(e){let t=k(e);return t!==null&&t.type===x.SNAPSHOT&&(`data`in t||`error`in t)}function D(e){let t=k(e);return t!==null&&t.type===x.CAMERA&&Array.isArray(t.transforms)}function O(e){let t=k(e);return t!==null&&t.type===x.MAPPING&&Array.isArray(t.attributes)}function k(e){return typeof e!=`object`||!e?null:e}var A=class{pending=[];isReady=!1;constructor(e){this.processor=e}enqueue(e){if(this.isReady){this.processor(e);return}this.pending.push(e)}markReady(){if(this.isReady||(this.isReady=!0,this.pending.length===0))return;let e=[...this.pending];this.pending.length=0;for(let t of e)this.processor(t)}get ready(){return this.isReady}get pendingCount(){return this.pending.length}hasPending(e){return this.pending.some(t=>t.type===e)}clear(){this.pending.length=0}},j=class{handlers=new Map;publisher;consumer;shouldBuffer;listener=null;sourceId;constructor(e,t){this.getTarget=e,this.sourceId=`vd-${Math.random().toString(36).slice(2,8)}`,this.publisher=new A(e=>this.post(e)),this.consumer=new A(e=>this.routeToHandler(e)),this.shouldBuffer=t?.shouldBuffer??null,t?.bufferIncoming!==!0&&this.consumer.markReady()}on(e,t){this.handlers.set(e,t)}off(e){this.handlers.delete(e)}send(e){this.publisher.enqueue(e)}markReady(){this.publisher.markReady()}markConsumerReady(){this.consumer.markReady()}consumerHasPending(e){return this.consumer.hasPending(e)}start(){this.listener===null&&(this.listener=e=>{let{data:t}=e;if(!C(t)||t._source===this.sourceId)return;let n=t;if(this.shouldBuffer!==null&&!this.shouldBuffer(n)){this.routeToHandler(n);return}this.consumer.enqueue(n)},window.addEventListener(`message`,this.listener))}destroy(){this.listener!==null&&(window.removeEventListener(`message`,this.listener),this.listener=null),this.publisher.clear(),this.consumer.clear(),this.handlers.clear()}get ready(){return this.publisher.ready}get pendingCount(){return this.publisher.pendingCount}post(e){let t=this.getTarget();t!==null&&t.postMessage({...e,_source:this.sourceId},`*`)}routeToHandler(e){let t=this.handlers.get(e.type);if(t===void 0)return;let n=t(e);n instanceof Promise&&n.catch(t=>{let n=t instanceof Error?t.message:String(t);console.error(`Virtualdisplay: error handling ${e.type}: ${n}`)})}};new Set([x.CAMERA]);var M=class{registry;pendingCallbacks=[];constructor(e,t){this.send=e,this.onMutationFailed=t}handleMappingReceived(e){m.debug(`Initializing from server mapping`,{attributeCount:e.attributes.length}),this.registry=new b(e.attributes,(e,t)=>this.sendMutation(e,t),this.onMutationFailed);let t=this.registry.defaults();t.length>0&&this.sendMutation(t,!0),this.replayPendingCallbacks()}handleMutationResult(e){this.registry?.handleResults(e.results)}select(e,t){this.sendMutation([{name:e,value:t}],!1)}getValues(e){return this.registry?.get(e)?.getValues()??[]}getValue(e,t){return this.registry?.get(e)?.getValue(t)}onChange(e,t){if(this.registry===void 0){this.pendingCallbacks.push({name:e,callback:t});return}let n=this.registry.get(e);if(n===void 0){m.warn(`onChange: attribute "${e}" not found — callback will not fire`);return}n.onChange(t)}getAttribute(e){return this.registry?.get(e)}get hasMapping(){return this.registry!==void 0}sendMutation(e,t){let n={type:x.MUTATION,mutations:e,isInitial:t};this.send(n)}replayPendingCallbacks(){for(let{name:e,callback:t}of this.pendingCallbacks){let n=this.registry?.get(e);n===void 0&&m.warn(`onChange: attribute "${e}" not found — buffered callback will not fire`),n?.onChange(t)}this.pendingCallbacks.length=0}},N=class{commands=[];constructor(e){this.send=e}rotate(e){return this.validateFiniteNumber(e,`rotate`),this.commands.push({action:`rotate`,value:e}),this}tilt(e){return this.validateFiniteNumber(e,`tilt`),this.commands.push({action:`tilt`,value:e}),this}zoom(e){return this.validateFiniteNumber(e,`zoom`),this.commands.push({action:`zoom`,value:e}),this}set(){if(this.commands.length===0)throw n.invalidParameter(`No camera commands to execute. Call rotate(), tilt(), or zoom() before set().`);let e={type:x.CAMERA,commands:this.commands};this.send(e),this.commands=[]}validateFiniteNumber(e,t){if(!Number.isFinite(e))throw n.invalidParameter(`${t}() requires a finite number, got ${String(e)}`)}reset(){let e={type:x.CAMERA,commands:[{action:`reset`}]};this.send(e)}},P=class{state;changeCallbacks=[];constructor(){this.state={rotation:0,tilt:0,zoom:0}}get rotation(){return this.state.rotation}get tilt(){return this.state.tilt}get zoom(){return this.state.zoom}onChange(e){this.changeCallbacks.push(e)}handlePositionUpdate(e){this.update(e.transforms)}update(e){let t=!1;for(let n of e)switch(n.type){case`rotation`:this.state.rotation=n.value,t=!0;break;case`tilt`:this.state.tilt=n.value,t=!0;break;case`zoom`:this.state.zoom=n.value,t=!0;break;default:break}if(t){let e={rotation:this.rotation,tilt:this.tilt,zoom:this.zoom};for(let t of this.changeCallbacks)t(e)}}},F=class{developedCallbacks=new Set;errorCallbacks=new Set;developed=!1;failed=!1;photoData=null;constructor(e){this.filename=e}onDeveloped(e){return this.developed&&this.photoData!==null?(e(this.photoData),this):(this.developedCallbacks.add(e),this)}onError(e){return this.errorCallbacks.add(e),this}getFilename(){return this.filename}develop(e){this.developed||this.failed||(this.photoData=e,this.developed=!0,this.developedCallbacks.forEach(e=>{e(this.photoData)}),this.developedCallbacks.clear(),this.errorCallbacks.clear())}fail(e){this.developed||this.failed||(this.failed=!0,this.errorCallbacks.forEach(t=>{t(e)}),this.developedCallbacks.clear(),this.errorCallbacks.clear())}},I=class{validExtensions=[`.png`];pendingPhotos=new Map;constructor(e){this.send=e}take(e){this.validateFilename(e),m.debug(`Taking snapshot`,{filename:e});let t=new F(e);return this.pendingPhotos.set(t.getFilename(),t),this.send({type:x.SNAPSHOT,filename:e}),t}handleSnapshotResult(e){let r=this.pendingPhotos.get(e.filename);if(r===void 0){m.warn(`No pending photo found for filename`,{filename:e.filename,pendingFilenames:Array.from(this.pendingPhotos.keys())});return}if(this.pendingPhotos.delete(e.filename),e.error!==void 0){r.fail(new n(e.error,t.SNAPSHOT_FAILED));return}e.data!==void 0&&r.develop({filename:e.filename,data:e.data})}validateFilename(e){if(e===``||typeof e!=`string`)throw new n(`Filename must be a non-empty string`,t.INVALID_PARAMETER);if(!this.validExtensions.some(t=>e.toLowerCase().endsWith(t)))throw new n(`Invalid filename: "${e}". Filename must end with one of: ${this.validExtensions.join(`, `)}`,t.INVALID_PARAMETER);if(e.includes(`..`)||e.includes(`/`)||e.includes(`\\`))throw new n(`Filename cannot contain path separators or ".."`,t.INVALID_PARAMETER)}},L=class{send;constructor(e){this.send=e}sendInitialConfig(e){let t=this.buildUIConfig(e),n=this.buildViewerConfig(t,e.camera);n!==void 0&&this.sendConfig(n)}buildUIConfig(e){let t=this.extractUIValues(e);if(Object.values(t).some(e=>e!==void 0))return{...t.arEnabled!==void 0&&{arEnabled:t.arEnabled},...t.fullscreenEnabled!==void 0&&{fullscreenEnabled:t.fullscreenEnabled},...t.loadingIndicatorEnabled!==void 0&&{loadingIndicatorEnabled:t.loadingIndicatorEnabled}}}extractUIValues(e){return{arEnabled:e.arEnabled??e.ui?.arEnabled,fullscreenEnabled:e.fullscreenEnabled??e.ui?.fullscreenEnabled,loadingIndicatorEnabled:e.loadingIndicatorEnabled??e.ui?.loadingIndicatorEnabled}}buildViewerConfig(e,t){if(!(e===void 0&&t===void 0))return{...e!==void 0&&{ui:e},...t!==void 0&&{camera:t}}}setArEnabled(e){m.debug(`Setting AR button enabled: ${e}`),this.updateUIConfig({arEnabled:e})}setFullscreenEnabled(e){m.debug(`Setting fullscreen button enabled: ${e}`),this.updateUIConfig({fullscreenEnabled:e})}setLoadingIndicatorEnabled(e){m.debug(`Setting loading indicator enabled: ${e}`),this.updateUIConfig({loadingIndicatorEnabled:e})}updateUIConfig(e){m.debug(`Updating UI configuration`,e),Object.keys(e).length>0&&this.sendConfig({ui:e})}hideAllUI(){m.debug(`Hiding all UI elements`),this.sendUIConfig({arEnabled:!1,fullscreenEnabled:!1,loadingIndicatorEnabled:!1})}showAllUI(){m.debug(`Showing all UI elements`),this.sendUIConfig({arEnabled:!0,fullscreenEnabled:!0,loadingIndicatorEnabled:!0})}sendUIConfig(e){this.sendConfig({ui:e})}sendConfig(e){let t={type:x.CONFIG,config:e};m.debug(`Sending config`,t),this.send(t)}},R=class{attributes;readyStateTracker;messenger;viewerService;cameraApi;cameraPositionApi;snapshotApi;options;iframe;readyCallbacks=[];mutationFailedCallbacks=[];constructor(e){this.options=e,this.initializeLogging(),this.messenger=new j(()=>this.iframe?.contentWindow??null);let t=e=>{this.messenger.send(e)};this.attributes=new M(t,(e,t)=>{for(let n of this.mutationFailedCallbacks)n({attribute:e,value:t})}),this.readyStateTracker=new _(()=>{let e=this.readyCallbacks;this.readyCallbacks=[];for(let t of e)t(this)}),this.viewerService=new L(t),this.cameraApi=new N(t),this.cameraPositionApi=new P,this.snapshotApi=new I(t),this.registerHandlers(),this.messenger.start(),this.createAndConnectIframe()}registerHandlers(){this.messenger.on(x.READY,e=>{w(e)&&(m.info(`Server ready — flushing publisher queue`),this.messenger.markReady(),this.viewerService.sendInitialConfig(this.options))}),this.messenger.on(x.MUTATION,e=>{T(e)&&(this.attributes.handleMutationResult(e),this.readyStateTracker.handleMutationResult(e))}),this.messenger.on(x.SNAPSHOT,e=>{E(e)&&this.snapshotApi.handleSnapshotResult(e)}),this.messenger.on(x.CAMERA,e=>{D(e)&&this.cameraPositionApi.handlePositionUpdate(e)}),this.messenger.on(x.MAPPING,e=>{O(e)&&(this.attributes.handleMappingReceived(e),this.readyStateTracker.handleMappingReceived())})}initializeLogging(){this.options.debug===!0&&h(),m.info(`Initializing Virtualdisplay client`,{model:this.options.model,debug:this.options.debug,language:this.options.language})}createAndConnectIframe(){m.debug(`Creating iframe for Virtualdisplay viewer`),this.iframe=new r(this.options).create(),m.debug(`Iframe created, waiting for server READY`)}select(e,t){this.attributes.select(e,t)}getValues(e){return this.attributes.getValues(e)}getValue(e,t){return this.attributes.getValue(e,t)}onChange(e,t){this.attributes.onChange(e,t)}getAttribute(e){return this.attributes.getAttribute(e)}get isReady(){return this.readyStateTracker.isReady}onReady(e){if(this.readyStateTracker.isReady){e(this);return}this.readyCallbacks.push(e)}onMutationFailed(e){this.mutationFailedCallbacks.push(e)}get viewer(){return this.viewerService}get camera(){return this.cameraApi}get cameraPosition(){return this.cameraPositionApi}get snapshot(){return this.snapshotApi}destroy(){m.info(`Destroying Virtualdisplay client`),this.readyStateTracker.clear(),this.readyCallbacks=[],this.messenger.destroy(),this.iframe?.parentElement!==null&&this.iframe?.parentElement!==void 0&&this.iframe.parentElement.removeChild(this.iframe),this.iframe=void 0,m.debug(`Virtualdisplay client destroyed`)}};e.Attribute=y,e.AttributeValue=v,e.Camera=N,e.ERROR_CODES=t,e.Photo=F,e.Snapshot=I,e.VirtualdisplayClient=R,e.VirtualdisplayError=n,e.VirtualdisplayViewerService=L,e.enableDebugMode=h,e.isDebugMode=g,Object.defineProperty(e,`logger`,{enumerable:!0,get:function(){return m}})});
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@virtualdisplay.io/client",
3
3
  "description": "TypeScript-first 3D model integration library with iframe-based architecture",
4
- "version": "4.0.0-alpha.1",
4
+ "version": "4.0.0-alpha.3",
5
5
  "author": "Virtualdisplay <support@virtualdisplay.io>",
6
6
  "homepage": "https://virtualdisplay.io",
7
7
  "license": "MIT",
8
8
  "type": "module",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./dist/index.d.ts",
11
+ "types": "./dist/src/index.d.ts",
12
12
  "import": "./dist/virtualdisplay.client.es.js",
13
13
  "require": "./dist/virtualdisplay.client.cjs.js"
14
14
  }
15
15
  },
16
16
  "main": "dist/virtualdisplay.client.cjs.js",
17
17
  "module": "dist/virtualdisplay.client.es.js",
18
- "types": "dist/index.d.ts",
18
+ "types": "dist/src/index.d.ts",
19
19
  "files": [
20
20
  "dist",
21
21
  "CHANGELOG.md"
package/dist/index.d.ts DELETED
@@ -1,415 +0,0 @@
1
- import { CameraConfig } from '@virtualdisplay.io/messaging';
2
- import { CameraParameter } from '@virtualdisplay.io/messaging';
3
- import { CameraPositionMessage } from '@virtualdisplay.io/messaging';
4
- import { MessageCallback } from '@virtualdisplay.io/messaging';
5
- import { SnapshotResultMessage } from '@virtualdisplay.io/messaging';
6
- import { UIConfig } from '@virtualdisplay.io/messaging';
7
-
8
- /**
9
- * Represents a single attribute with its available values
10
- * Flat API - no source/target translation, works with server values directly
11
- */
12
- export declare class Attribute {
13
- readonly name: string;
14
- readonly defaultValue: string | undefined;
15
- private readonly values;
16
- private readonly onSelect;
17
- private onChangeCallback?;
18
- constructor(options: AttributeOptions);
19
- /**
20
- * Select a value for this attribute
21
- * @throws {VirtualdisplayError} When value not found
22
- */
23
- select(value: string): void;
24
- getValues(): AttributeValue[];
25
- getValue(value: string): AttributeValue | undefined;
26
- /**
27
- * Register callback that fires when selection changes
28
- * Receives the newly selected AttributeValue
29
- */
30
- onChange(callback: (selectedValue: AttributeValue) => void): void;
31
- /**
32
- * Update selection state from server mutation result
33
- * Direct value comparison - no source/target translation
34
- */
35
- handleResult(serverValue: string): void;
36
- private createValues;
37
- }
38
-
39
- declare interface AttributeOptions {
40
- readonly name: string;
41
- readonly values: string[];
42
- readonly onSelect: (name: string, value: string) => void;
43
- readonly defaultValue?: string;
44
- }
45
-
46
- /**
47
- * Pure domain entity representing a single attribute value
48
- * No side effects - only state
49
- */
50
- export declare class AttributeValue {
51
- readonly value: string;
52
- readonly attribute: string;
53
- private isSelectedValue;
54
- constructor(value: string, attribute: string);
55
- get isSelected(): boolean;
56
- setSelected(selected: boolean): void;
57
- }
58
-
59
- declare type Attrs = Record<string, unknown> | unknown;
60
-
61
- /**
62
- * Camera control API for positioning and zoom
63
- * Uses chaining pattern for building commands
64
- */
65
- export declare class Camera {
66
- private readonly send;
67
- private commands;
68
- constructor(send: MessageCallback);
69
- /**
70
- * Rotate camera horizontally
71
- * @param degrees - Absolute rotation in degrees (0 = front view)
72
- * @throws VirtualdisplayError if value is not a finite number
73
- */
74
- rotate(degrees: number): this;
75
- /**
76
- * Tilt camera vertically
77
- * @param degrees - Absolute tilt in degrees (0 = top view, 90 = horizontal)
78
- * @throws VirtualdisplayError if value is not a finite number
79
- */
80
- tilt(degrees: number): this;
81
- /**
82
- * Zoom camera
83
- * @param percentage - Zoom percentage (100 = normal, >100 = zoom in, <100 = zoom out)
84
- * @throws VirtualdisplayError if value is not a finite number
85
- */
86
- zoom(percentage: number): this;
87
- /**
88
- * Execute accumulated camera commands
89
- * @throws VirtualdisplayError if no commands have been accumulated
90
- */
91
- set(): void;
92
- private validateFiniteNumber;
93
- /**
94
- * Reset camera to initial position
95
- * This is not chainable and executes immediately
96
- */
97
- reset(): void;
98
- }
99
-
100
- /**
101
- * Camera position state
102
- * Tracks current rotation, tilt, and zoom values from server updates
103
- */
104
- declare class CameraPosition {
105
- private readonly state;
106
- private readonly changeCallbacks;
107
- constructor();
108
- /**
109
- * Get current rotation in degrees
110
- */
111
- get rotation(): number;
112
- /**
113
- * Get current tilt in degrees
114
- */
115
- get tilt(): number;
116
- /**
117
- * Get current zoom percentage
118
- */
119
- get zoom(): number;
120
- /**
121
- * Subscribe to camera position changes
122
- */
123
- onChange(callback: (position: CameraPositionState) => void): void;
124
- /**
125
- * Handle incoming camera position message from server
126
- */
127
- handlePositionUpdate(message: CameraPositionMessage): void;
128
- /**
129
- * Update camera position from server transforms
130
- */
131
- update(transforms: CameraParameter[]): void;
132
- }
133
-
134
- /**
135
- * Camera position state object
136
- */
137
- declare interface CameraPositionState {
138
- rotation: number;
139
- tilt: number;
140
- zoom: number;
141
- }
142
-
143
- /**
144
- * Configuration options for VirtualdisplayClient
145
- */
146
- export declare interface ClientOptions {
147
- readonly parent: string | HTMLElement;
148
- readonly model: string;
149
- readonly license: string;
150
- readonly debug?: boolean;
151
- readonly language?: string;
152
- readonly arEnabled?: boolean;
153
- readonly fullscreenEnabled?: boolean;
154
- readonly loadingIndicatorEnabled?: boolean;
155
- readonly ui?: UIConfig;
156
- readonly camera?: CameraConfig;
157
- }
158
-
159
- export declare function enableDebugMode(): void;
160
-
161
- /**
162
- * Error codes for Virtualdisplay client errors
163
- */
164
- export declare const ERROR_CODES: {
165
- readonly NO_MAPPING: "NO_MAPPING";
166
- readonly ATTRIBUTE_NOT_FOUND: "ATTRIBUTE_NOT_FOUND";
167
- readonly VALUE_NOT_FOUND: "VALUE_NOT_FOUND";
168
- readonly PARENT_NOT_FOUND: "PARENT_NOT_FOUND";
169
- readonly INVALID_PARAMETER: "INVALID_PARAMETER";
170
- readonly SNAPSHOT_FAILED: "SNAPSHOT_FAILED";
171
- };
172
-
173
- export declare type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];
174
-
175
- export declare function isDebugMode(): boolean;
176
-
177
- export declare let logger: {
178
- debug: (msg: string, attrs?: Attrs) => void;
179
- info: (msg: string, attrs?: Attrs) => void;
180
- warn: (msg: string, attrs?: Attrs) => void;
181
- error: (msg: string, attrs?: Attrs) => void;
182
- };
183
-
184
- export declare interface MutationFailedEvent {
185
- attribute: string;
186
- value: string;
187
- }
188
-
189
- /**
190
- * Represents a photo being captured
191
- * Pure domain object that manages callbacks for when the photo is developed
192
- */
193
- export declare class Photo {
194
- private readonly filename;
195
- private readonly developedCallbacks;
196
- private readonly errorCallbacks;
197
- private developed;
198
- private failed;
199
- private photoData;
200
- constructor(filename: string);
201
- /**
202
- * Register a callback for when the photo is developed
203
- * If the photo is already developed, the callback is called immediately
204
- *
205
- * @param callback Function to call with photo data
206
- * @returns This Photo instance for chaining
207
- */
208
- onDeveloped(callback: (photo: PhotoData) => void): this;
209
- /**
210
- * Register a callback for when the photo capture fails
211
- *
212
- * @param callback Function to call with error
213
- * @returns This Photo instance for chaining
214
- */
215
- onError(callback: (error: VirtualdisplayError) => void): this;
216
- /**
217
- * Get the filename for this photo
218
- */
219
- getFilename(): string;
220
- /**
221
- * Mark the photo as developed and notify all callbacks
222
- * This method is called by Snapshot when the photo data arrives from the server
223
- */
224
- develop(photoData: PhotoData): void;
225
- /**
226
- * Mark the photo as failed and notify error callbacks
227
- */
228
- fail(error: VirtualdisplayError): void;
229
- }
230
-
231
- export declare interface PhotoData {
232
- readonly filename: string;
233
- readonly data: string;
234
- }
235
-
236
- /**
237
- * API for taking screenshots of the 3D model
238
- * Following the camera analogy: take() triggers capture, onDeveloped() gets result
239
- */
240
- export declare class Snapshot {
241
- private readonly send;
242
- private readonly validExtensions;
243
- private readonly pendingPhotos;
244
- constructor(send: MessageCallback);
245
- /**
246
- * Take a snapshot of the current 3D model view
247
- *
248
- * @param filename The filename for the snapshot (must include valid extension)
249
- * @returns Photo object for registering callbacks
250
- * @throws VirtualdisplayError if filename is invalid
251
- */
252
- take(filename: string): Photo;
253
- /**
254
- * Handle incoming snapshot result from server
255
- * Matches the message to a pending photo by filename, then develops or fails it
256
- */
257
- handleSnapshotResult(message: SnapshotResultMessage): void;
258
- /**
259
- * Validate filename has a valid image extension
260
- */
261
- private validateFilename;
262
- }
263
-
264
- /**
265
- * Main Virtualdisplay Client
266
- * Provides the public API for 3D product visualization
267
- *
268
- * Acts as the composition root: wires all services together with direct dependency injection
269
- */
270
- export declare class VirtualdisplayClient {
271
- private readonly attributes;
272
- private readonly readyStateTracker;
273
- private readonly messenger;
274
- private readonly viewerService;
275
- private readonly cameraApi;
276
- private readonly cameraPositionApi;
277
- private readonly snapshotApi;
278
- private readonly options;
279
- private iframe;
280
- private readyCallbacks;
281
- private readonly mutationFailedCallbacks;
282
- constructor(options: ClientOptions);
283
- private registerHandlers;
284
- private initializeLogging;
285
- private createAndConnectIframe;
286
- /**
287
- * Select a value for an attribute — sends mutation to server.
288
- * Can be called immediately; the messenger's publisher queue holds
289
- * the message until the iframe is ready.
290
- */
291
- select(name: string, value: string): void;
292
- /**
293
- * Get all available values for an attribute
294
- */
295
- getValues(name: string): AttributeValue[];
296
- /**
297
- * Get a specific value for an attribute
298
- */
299
- getValue(name: string, value: string): AttributeValue | undefined;
300
- /**
301
- * Register callback that fires when an attribute's selection changes
302
- */
303
- onChange(name: string, callback: (selectedValue: AttributeValue) => void): void;
304
- /**
305
- * Get the Attribute instance for advanced use cases
306
- */
307
- getAttribute(name: string): Attribute | undefined;
308
- /**
309
- * Check if the client has received initial mutations and is ready
310
- */
311
- get isReady(): boolean;
312
- /**
313
- * Register a callback to be called when the client is ready
314
- * If already ready, the callback is called immediately
315
- */
316
- onReady(callback: (client: VirtualdisplayClient) => void): void;
317
- /**
318
- * Register a callback for when a mutation fails on the server
319
- */
320
- onMutationFailed(callback: (event: MutationFailedEvent) => void): void;
321
- /**
322
- * Get the viewer service for UI configuration
323
- */
324
- get viewer(): VirtualdisplayViewerService;
325
- /**
326
- * Get the camera control API
327
- */
328
- get camera(): Camera;
329
- /**
330
- * Get the camera position tracking API
331
- */
332
- get cameraPosition(): CameraPosition;
333
- /**
334
- * Get the snapshot API for taking screenshots
335
- */
336
- get snapshot(): Snapshot;
337
- destroy(): void;
338
- }
339
-
340
- /**
341
- * Custom error class for Virtualdisplay client errors
342
- */
343
- export declare class VirtualdisplayError extends Error {
344
- readonly code: ErrorCode;
345
- constructor(message: string, code: ErrorCode);
346
- static noMapping(): VirtualdisplayError;
347
- static attributeNotFound(attributeName: string): VirtualdisplayError;
348
- static valueNotFound(attributeName: string, valueName: string): VirtualdisplayError;
349
- static parentNotFound(selector: string): VirtualdisplayError;
350
- static invalidParameter(reason: string): VirtualdisplayError;
351
- static snapshotFailed(filename: string, reason: string): VirtualdisplayError;
352
- }
353
-
354
- /**
355
- * Service responsible for managing viewer UI configuration
356
- * Sends CONFIG messages to control AR button, fullscreen button, and loading indicator
357
- */
358
- export declare class VirtualdisplayViewerService {
359
- private readonly send;
360
- constructor(send: MessageCallback);
361
- /**
362
- * Send initial configuration based on client options
363
- * Supports both individual properties and ui object
364
- */
365
- sendInitialConfig(options: ClientOptions): void;
366
- /**
367
- * Build UI configuration from client options
368
- *
369
- * Supports both legacy properties (options.arEnabled) and the newer ui object
370
- * for backward compatibility. Legacy properties take precedence when both exist.
371
- */
372
- private buildUIConfig;
373
- /**
374
- * Extract UI values from options, with legacy properties taking precedence
375
- */
376
- private extractUIValues;
377
- /**
378
- * Build viewer configuration from UI config and camera config
379
- */
380
- private buildViewerConfig;
381
- /**
382
- * Update AR button visibility
383
- */
384
- setArEnabled(enabled: boolean): void;
385
- /**
386
- * Update fullscreen button visibility
387
- */
388
- setFullscreenEnabled(enabled: boolean): void;
389
- /**
390
- * Update loading indicator visibility
391
- */
392
- setLoadingIndicatorEnabled(enabled: boolean): void;
393
- /**
394
- * Update multiple UI settings at once
395
- */
396
- updateUIConfig(config: Partial<UIConfig>): void;
397
- /**
398
- * Hide all UI elements
399
- */
400
- hideAllUI(): void;
401
- /**
402
- * Show all UI elements
403
- */
404
- showAllUI(): void;
405
- /**
406
- * Send UI configuration to server
407
- */
408
- private sendUIConfig;
409
- /**
410
- * Send full viewer configuration to server
411
- */
412
- private sendConfig;
413
- }
414
-
415
- export { }