@3dn-builder/builder 0.637.0 → 0.638.0

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/README.md CHANGED
@@ -36,16 +36,18 @@ instance.destroy();
36
36
 
37
37
  `createBuilder()` touches nothing on the page, so it is safe during server-side rendering; the builder loads on `load()` or `mount()`. The target needs a real height.
38
38
 
39
- | Method | Behavior |
40
- | ------------------ | --------------------------------------------------------------------------------------------------------- |
41
- | `load()` | Optional preload, once per page. |
42
- | `mount(options)` | `{ target, hostProductId, mode?: 'designer' \| 'viewer', designId?, externalExport? }` → instance. |
43
- | `open()` | Shows the builder UI after a hidden mount. |
44
- | `save()` | Resolves `{ productId, version }` after the save, any clone record and `onSaved`. 45 s timeout. |
45
- | `refreshSession()` | Calls `session.get()` again and applies the result. |
46
- | `refreshLicense()` | One shared license refresh for every mounted builder. |
47
- | `logout()` | Removes the shopper tokens, keeps the license token. Late login results are dropped. |
48
- | `destroy()` | Removes the builder and its listeners; pending calls reject with `DESTROYED`. |
39
+ | Method | Behavior |
40
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
41
+ | `load()` | Optional preload, once per page. |
42
+ | `mount(options)` | `{ target, hostProductId, mode?: 'designer' \| 'viewer', designId?, externalExport?, observabilityContext? }` → instance. |
43
+ | `open()` | Shows the builder UI after a hidden mount. |
44
+ | `save()` | Resolves `{ productId, version }` after the save, any clone record and `onSaved`. 45 s timeout. |
45
+ | `refreshSession()` | Calls `session.get()` again and applies the result. |
46
+ | `refreshLicense()` | One shared license refresh for every mounted builder. |
47
+ | `logout()` | Removes the shopper tokens, keeps the license token. Late login results are dropped. |
48
+ | `destroy()` | Removes the builder and its listeners; pending calls reject with `DESTROYED`. |
49
+
50
+ `observabilityContext` is the builder's `observability-context`: a JSON object string naming your app, tenant, site, pseudonymous user and request, plus your page's `sentry-trace` and `baggage`, so the builder's own error reports say where they happened. It is set before the element connects; `instance.setObservabilityContext(json)` replaces it later, for example once your page's Sentry has a trace. The keys and their rules are in the integration guide, section 6.3.
49
51
 
50
52
  The controller's methods act on the most recently mounted builder. With several builders on a page, call the instance `mount()` returned.
51
53
 
@@ -96,19 +98,19 @@ customization?: {
96
98
 
97
99
  Every failure is a `BuilderError`: `code`, a `message` safe to show, `retryable`, `fatal`, and the builder's `reason`.
98
100
 
99
- | Code | When |
100
- | ------------------ | ------------------------------------------------------------ |
101
- | `CONFIG_INVALID` | Bad option, or a secret or script URL passed in. |
102
- | `NOT_IN_BROWSER` | `load()` or `mount()` during SSR. |
103
- | `LOAD_FAILED` | The builder did not load; the next call retries. |
104
- | `VERSION_CONFLICT` | Another builder version already runs on the page. |
105
- | `SESSION_FAILED` | `session.get()` failed or returned an invalid session. |
106
- | `LICENSE_ERROR` | The license was refused. Fatal for the mount; `onLicenseError` is called. |
107
- | `LOGIN_CANCELLED` | The shopper did not log in. |
108
- | `SAVE_FAILED` | The save failed, or `customization.record()` failed (`reason: 'customization'`). |
109
- | `SAVE_TIMEOUT` | No outcome within `saveTimeoutMs`. |
110
- | `EXPORT_FAILED`, `EXPORT_TIMEOUT` | `externalExport.run()` failed or timed out. |
111
- | `DESTROYED` | `destroy()` ran while the call was pending. |
101
+ | Code | When |
102
+ | --------------------------------- | -------------------------------------------------------------------------------- |
103
+ | `CONFIG_INVALID` | Bad option, or a secret or script URL passed in. |
104
+ | `NOT_IN_BROWSER` | `load()` or `mount()` during SSR. |
105
+ | `LOAD_FAILED` | The builder did not load; the next call retries. |
106
+ | `VERSION_CONFLICT` | Another builder version already runs on the page. |
107
+ | `SESSION_FAILED` | `session.get()` failed or returned an invalid session. |
108
+ | `LICENSE_ERROR` | The license was refused. Fatal for the mount; `onLicenseError` is called. |
109
+ | `LOGIN_CANCELLED` | The shopper did not log in. |
110
+ | `SAVE_FAILED` | The save failed, or `customization.record()` failed (`reason: 'customization'`). |
111
+ | `SAVE_TIMEOUT` | No outcome within `saveTimeoutMs`. |
112
+ | `EXPORT_FAILED`, `EXPORT_TIMEOUT` | `externalExport.run()` failed or timed out. |
113
+ | `DESTROYED` | `destroy()` ran while the call was pending. |
112
114
 
113
115
  Other callbacks: `onCloseRequested({ productId?, hasUnsavedChanges })`, `onStatusChange({ status, step?, message?, error? })` (load progress, never a save).
114
116
 
package/index.d.ts CHANGED
@@ -159,6 +159,14 @@ interface MountOptions {
159
159
  designId?: string;
160
160
  /** Opt-in, GLB products only. Off unless passed. */
161
161
  externalExport?: ExternalExportOptions;
162
+ /**
163
+ * The host page's observability context, a JSON object string the builder
164
+ * reads as `observability-context`: which host app, tenant, site and user
165
+ * its own Sentry events belong to, and the page's trace to join. Set before
166
+ * the element connects; update it with `setObservabilityContext()`. The
167
+ * builder drops keys it does not allow and ignores an invalid value.
168
+ */
169
+ observabilityContext?: string;
162
170
  }
163
171
  interface ResolvedConfig extends BuilderHostConfig {
164
172
  environment: BuilderEnvironment;
@@ -218,6 +226,11 @@ declare class BuilderInstance {
218
226
  /** Removes the shopper layer. The site layer stays and the builder revokes the session. */
219
227
  logout(): void;
220
228
  destroy(): void;
229
+ /**
230
+ * Replaces the host page's observability context, for example once the
231
+ * page's own Sentry has started and a trace exists to join.
232
+ */
233
+ setObservabilityContext(context: string): void;
221
234
  /** Used by the controller to hand every instance the shared fresh license token. */
222
235
  applyLicense(licenseToken: string): void;
223
236
  get isDestroyed(): boolean;
package/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{b as R,c as k,d as x,e as P,f as _,g as B,h as O,j as m,k as C,l as A,m as N,n as M,p as F,q as U}from"./chunks/chunk-F4FUPJ4Y.js";var s=class t extends Error{constructor(e,n,r={}){var o,i;super(n),this.name="BuilderError",this.code=e,this.retryable=(o=r.retryable)!=null?o:!1,this.fatal=(i=r.fatal)!=null?i:!1,this.reason=r.reason,this.cause=r.cause,Object.setPrototypeOf(this,t.prototype)}},V={conflict:"Someone else saved this design first.",forbidden:"This shopper may not save this design.",unauthorized:"The shopper could not be signed in to save.","login-cancelled":"The design was not saved because the shopper did not log in.","installation-inactive":"This site's 3DN installation is not active.",license:"This page is not licensed to use the 3DN builder.",error:"The design could not be saved. Check the connection and try again."};function H(t){var n;let e=(n=V[t.reason])!=null?n:V.error;return t.reason==="login-cancelled"?new s("LOGIN_CANCELLED",e,{retryable:!0,reason:t.reason,cause:t}):new s("SAVE_FAILED",e,{retryable:t.retryable,reason:t.reason,cause:t})}function q(t){let e=t==="ORIGIN_NOT_ALLOWED"?"This website is not on the 3DN license. Add its hostname to the license.":"The 3DN license token was rejected. The license may be suspended, revoked or expired.";return new s("LICENSE_ERROR",e,{fatal:!0,reason:t,cause:{code:t}})}function g(){return new s("DESTROYED","The builder was destroyed.")}function a(t){return new s("CONFIG_INVALID",t,{fatal:!0})}var re=45e3,$=12e4,oe=new Set(["environment","version","saveTimeoutMs","session","login","customization","onSaved","onLicenseError","onCloseRequested","onStatusChange","debug"]),ie=new Set(["target","hostProductId","mode","designId","externalExport"]),se=/^\d+\.\d+\.\d+$/,ae=/^tdn_inst_/;function w(t){return se.test(t)}function c(t){return typeof t=="object"&&t!==null}function f(t){return typeof t=="function"}function j(t){return typeof t=="number"&&Number.isFinite(t)&&t>0}function z(t,e=0){return typeof t=="string"?ae.test(t):!c(t)||e>3?!1:Object.values(t).some(n=>z(n,e+1))}function G(t,e,n){for(let r of Object.keys(t))if(!e.has(r))throw/url|src|host|origin/i.test(r)?a(`${n}: "${r}" is not allowed. The SDK decides where the builder loads from; choose an environment and a version instead.`):a(`${n}: unknown option "${r}".`)}function X(t){var h,u,l;if(!c(t))throw a("createBuilder() needs a configuration object.");if(z(t))throw a("createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.");G(t,oe,"createBuilder()");let e=(h=t.environment)!=null?h:"production";if(e!=="production"&&e!=="staging")throw a(`createBuilder(): environment must be "production" or "staging", got ${JSON.stringify(e)}.`);let n=(u=t.version)!=null?u:"latest";if(typeof n!="string"||n!=="latest"&&!w(n))throw a(`createBuilder(): version must be "latest" or an exact release such as "1.2.3", got ${JSON.stringify(n)}.`);let r=(l=t.saveTimeoutMs)!=null?l:re;if(!j(r))throw a("createBuilder(): saveTimeoutMs must be a positive number of milliseconds.");let o=t.session;if(!c(o)||!f(o.get)||!f(o.refreshLicense))throw a("createBuilder(): session.get() and session.refreshLicense() are required. They call your session endpoint.");let i=t.login;if(i!==void 0){if(!c(i)||!f(i.getTokens)||!f(i.onRequired))throw a("createBuilder(): login needs getTokens() (your login-token endpoint) and onRequired() (your login UI).");if(i.onMintFailed!==void 0&&!f(i.onMintFailed))throw a("createBuilder(): login.onMintFailed must be a function.")}let d=t.customization;if(d!==void 0&&(!c(d)||!f(d.record)))throw a("createBuilder(): customization.record() is required when customization is set. It calls your customization endpoint.");for(let y of["onSaved","onLicenseError","onCloseRequested","onStatusChange"])if(t[y]!==void 0&&!f(t[y]))throw a(`createBuilder(): ${y} must be a function.`);return{...t,environment:e,version:n,saveTimeoutMs:r}}function K(t){var d;if(!c(t))throw a("mount() needs an options object.");G(t,ie,"mount()");let e=t.target;if(typeof HTMLElement=="undefined"||!(e instanceof HTMLElement))throw a("mount(): target must be an HTMLElement on the page.");let n=t.hostProductId;if(typeof n!="string"||n.trim()==="")throw a("mount(): hostProductId must be your own, non-empty product reference.");let r=(d=t.mode)!=null?d:"designer";if(r!=="designer"&&r!=="viewer")throw a(`mount(): mode must be "designer" or "viewer", got ${JSON.stringify(r)}.`);let o=t.designId;if(o!==void 0&&(typeof o!="string"||o===""))throw a("mount(): designId must be a non-empty string.");let i=t.externalExport;if(i!==void 0){if(!c(i)||!f(i.run))throw a("mount(): externalExport.run() is required when externalExport is set.");if(i.timeoutMs!==void 0&&!j(i.timeoutMs))throw a("mount(): externalExport.timeoutMs must be a positive number of milliseconds.")}return t}function S(t){let e=o=>new s("SESSION_FAILED",`session.get() returned an invalid session: ${o}.`,{retryable:!1});if(!c(t))throw e("expected an object");if(typeof t.licenseToken!="string"||t.licenseToken==="")throw e("licenseToken is missing");if(typeof t.productId!="string"||t.productId==="")throw e("productId is missing");if(t.viewToken!=null&&typeof t.viewToken!="string")throw e("viewToken must be a string");let n=t.token!=null,r=t.refreshToken!=null;if(n!==r)throw e("token and refreshToken must be returned together");if(n&&(typeof t.token!="string"||typeof t.refreshToken!="string"))throw e("token and refreshToken must be strings");return t}function W(t){if(t===null)return null;if(!c(t)||typeof t.token!="string"||t.token===""||typeof t.refreshToken!="string"||t.refreshToken==="")throw new s("SESSION_FAILED","login.getTokens() must resolve { token, refreshToken } or null.");return t}var de=/token|secret|key|authorization/i;function b(t,e=0){if(t===null||typeof t!="object"||e>4)return t;if(Array.isArray(t))return t.map(r=>b(r,e+1));let n={};for(let[r,o]of Object.entries(t))n[r]=de.test(r)&&o!=null?"[redacted]":b(o,e+1);return n}function Y(t){return t?(e,n)=>{n===void 0?console.debug(`[3DN] ${e}`):console.debug(`[3DN] ${e}`,b(n))}:()=>{}}var E="threed-builder",Z={production:"https://3d-builder.3dn.app",staging:void 0},ue=3e4,J=Symbol.for("@3dn-builder/builder/loader");function le(){let t=globalThis,e=t[J];return e||(e={},t[J]=e),e}function ce(){return typeof window!="undefined"&&typeof document!="undefined"&&typeof customElements!="undefined"}function ee(t){if(!Z[t])throw a(`createBuilder(): the "${t}" environment is not available in this SDK release.`)}function p(t,e){return new s("LOAD_FAILED",t,{retryable:!0,cause:e})}async function he(t,e){if(e!=="latest")return e;let n;try{n=await fetch(`${t}/version-info.json`,{cache:"no-store",credentials:"omit"})}catch(i){throw p("The 3DN builder could not be reached.",i)}if(!n.ok)throw p(`The 3DN builder release could not be resolved (HTTP ${n.status}).`);let r=await n.json().catch(()=>null),o=r==null?void 0:r.version;if(typeof o!="string"||!w(o))throw p("The 3DN builder release information was not valid.");return o}function fe(t){return new Promise((e,n)=>{let r=document.createElement("script");r.type="module",r.src=t,r.crossOrigin="anonymous",r.onload=()=>e(r),r.onerror=()=>{r.remove(),n(p("The 3DN builder failed to load."))},document.head.appendChild(r)})}function pe(){return new Promise((t,e)=>{let n=setTimeout(()=>e(p("The 3DN builder loaded but did not start.")),ue);customElements.whenDefined(E).then(()=>{clearTimeout(n),t()},e)})}function Q(t,e,n){if(t.environment&&t.environment!==e)throw new s("VERSION_CONFLICT",`The ${t.environment} builder already runs on this page; ${e} cannot load beside it.`,{fatal:!0});if(n!=="latest"&&t.version&&t.version!==n)throw new s("VERSION_CONFLICT",`Builder ${t.version} already runs on this page; ${n} cannot load beside it.`,{fatal:!0})}async function te(t,e){if(!ce())throw new s("NOT_IN_BROWSER","The 3DN builder can only load in a browser.");let n=le();if(n.loading){await n.loading,Q(n,t,e);return}if(customElements.get(E))return;let r=Z[t];if(!r)throw a(`The "${t}" environment is not available in this SDK release.`);n.environment=t,n.loading=(async()=>{let o=await he(r,e);n.version=o;let i=await fe(`${r}/builder-${o}.js`);try{await pe()}catch(d){throw i.remove(),d}})();try{await n.loading}catch(o){throw n.loading=void 0,n.environment=void 0,n.version=void 0,o instanceof s?o:p("The 3DN builder failed to load.",o)}Q(n,t,e)}function ne(){let t,e,n=new Promise((r,o)=>{t=r,e=o});return n.catch(()=>{}),{promise:n,resolve:t,reject:e}}var I=class{constructor(e,n){this.onExpire=n;this.startedAt=0;this.remaining=e}start(){this.handle===void 0&&(this.startedAt=Date.now(),this.handle=setTimeout(this.onExpire,this.remaining))}pause(){this.handle!==void 0&&(clearTimeout(this.handle),this.handle=void 0,this.remaining=Math.max(0,this.remaining-(Date.now()-this.startedAt)))}stop(){this.handle!==void 0&&clearTimeout(this.handle),this.handle=void 0}},v=class t{constructor(e,n,r){this.ctx=e;this.options=n;this.epoch=0;this.destroyed=!1;this.loginPending=!1;this.exports=new Set;this.listeners=[];this.currentProductId=r.productId,this.element=this.createElement(r)}static async mount(e,n){let r;try{r=await e.config.session.get({hostProductId:n.hostProductId})}catch(d){throw new s("SESSION_FAILED","The builder session could not be loaded.",{retryable:!0,cause:d})}let o=S(r),i=new t(e,n,o);return n.target.appendChild(i.element),e.log("mounted",{hostProductId:n.hostProductId,productId:o.productId}),i}get productId(){return this.currentProductId}get hostProductId(){return this.options.hostProductId}createElement(e){let n=document.createElement(E);return this.attachListeners(n),n.setAttribute("product",e.productId),n.setAttribute("license-token",e.licenseToken),e.viewToken&&n.setAttribute("view-token",e.viewToken),e.token&&e.refreshToken&&D(n,{token:e.token,refreshToken:e.refreshToken}),this.options.designId&&n.setAttribute("design-id",this.options.designId),this.options.mode==="viewer"&&n.setAttribute("display-only","true"),this.options.externalExport&&n.setAttribute("external-export","true"),n.style.display="block",n.style.width="100%",n.style.height="100%",n}attachListeners(e){let n=(r,o)=>{let i=d=>{d.target!==e||this.destroyed||(this.ctx.log(r,d.detail),o(d.detail))};e.addEventListener(r,i),this.listeners.push([r,i])};n(O,r=>{var o;this.answerLogin((o=r==null?void 0:r.action)!=null?o:"save")}),n(M,()=>{this.ctx.refreshLicense()}),n(C,r=>this.onLicenseError(r)),n(A,r=>{this.onDesignSaved(r)}),n(N,r=>this.finishSave(H(r))),n(B,r=>{this.onProductCloned(r)}),n(U,r=>{var o,i;return(i=(o=this.ctx.config).onCloseRequested)==null?void 0:i.call(o,r)}),n(k,r=>{var o,i;return(i=(o=this.ctx.config).onStatusChange)==null?void 0:i.call(o,r)}),this.options.externalExport&&n(x,r=>{this.answerExport(r)})}open(){this.assertUsable(),R(this.element)}save(){if(this.destroyed)return Promise.reject(g());if(this.fatal)return Promise.reject(this.fatal);if(this.saveRun)return this.saveRun.result.promise;let e={result:ne(),timer:new I(this.ctx.config.saveTimeoutMs,()=>this.finishSave(new s("SAVE_TIMEOUT","Saving the design took too long. Try again.",{retryable:!0})))};return this.saveRun=e,this.loginPending||e.timer.start(),F(this.element),e.result.promise}async refreshSession(){this.assertUsable();let e=this.epoch,n;try{n=await this.ctx.config.session.get({hostProductId:this.options.hostProductId})}catch(o){throw new s("SESSION_FAILED","The builder session could not be refreshed.",{retryable:!0,cause:o})}let r=S(n);e!==this.epoch||this.destroyed||(this.applyLicense(r.licenseToken),r.viewToken&&this.element.setAttribute("view-token",r.viewToken),r.token&&r.refreshToken&&D(this.element,{token:r.token,refreshToken:r.refreshToken}),this.fatal=void 0)}logout(){this.destroyed||(this.epoch++,this.element.removeAttribute("bearer-token"),this.element.removeAttribute("refresh-token"),this.loginPending&&(this.loginPending=!1,this.finishSave(new s("LOGIN_CANCELLED","The shopper logged out before the design was saved.",{retryable:!0}))))}destroy(){var e;if(!this.destroyed){this.epoch++,this.finishSave(g()),(e=this.cloneWait)==null||e.reject(g()),this.cloneWait=void 0;for(let n of this.exports)n.abort();this.destroyed=!0;for(let[n,r]of this.listeners)this.element.removeEventListener(n,r);this.listeners.length=0,this.element.remove(),this.ctx.onDestroyed(this),this.ctx.log("destroyed")}}applyLicense(e){this.destroyed||this.element.setAttribute("license-token",e)}get isDestroyed(){return this.destroyed}async answerLogin(e){if(this.loginPending)return;let n=this.ctx.config.login;if(!n){m(this.element,{action:e});return}let r=this.epoch,o=()=>r!==this.epoch||this.destroyed;this.setLoginPending(!0);let i=!1;try{for(;;){let d;try{d=W(await n.getTokens())}catch(u){if(o())return;this.ctx.log("login.getTokens() failed",{cause:String(u)});let l=n.onMintFailed?await n.onMintFailed():"cancel";if(o())return;if(l==="retry")continue;m(this.element,{action:e});return}if(o())return;if(d){D(this.element,d);return}let h=await n.onRequired(e);if(o())return;if(h!=="logged-in"){if(h==="redirected"){i=!0;return}m(this.element,{action:e});return}}}finally{!i&&r===this.epoch&&this.setLoginPending(!1)}}setLoginPending(e){this.loginPending=e,this.saveRun&&(e?this.saveRun.timer.pause():this.saveRun.timer.start())}onLicenseError(e){var r,o;let n=q(e.code);this.fatal=n,this.finishSave(n),(o=(r=this.ctx.config).onLicenseError)==null||o.call(r,n)}async onDesignSaved(e){e.productId!==this.currentProductId&&!this.cloneWait&&(this.cloneWait=ne());try{this.cloneWait&&await this.cloneWait.promise,this.unrecordedLink&&await this.recordLink(this.unrecordedLink)}catch(r){this.finishSave(r instanceof s?r:new s("SAVE_FAILED","The design was saved but could not be linked to this shopper.",{retryable:!0,reason:"customization",cause:r}));return}if(this.destroyed)return;let n={productId:e.productId,version:e.version};this.notifySaved(n),this.finishSave(void 0,n)}notifySaved(e){var n,r;try{(r=(n=this.ctx.config).onSaved)==null||r.call(n,{hostProductId:this.options.hostProductId,...e})}catch(o){this.ctx.log("onSaved threw",{cause:String(o)})}}async onProductCloned(e){let n={hostProductId:this.options.hostProductId,sourceProductId:e.originalProductId,savedProductId:e.newProductId};this.currentProductId=e.newProductId;let r=this.cloneWait;this.cloneWait=void 0;try{await this.recordLink(n),r==null||r.resolve()}catch(o){r==null||r.reject(o)}}async recordLink(e){let n=this.ctx.config.customization;if(n)try{await n.record(e),this.unrecordedLink===e&&(this.unrecordedLink=void 0)}catch(r){throw this.unrecordedLink=e,new s("SAVE_FAILED","The design was saved but could not be linked to this shopper. Try saving again.",{retryable:!0,reason:"customization",cause:r})}}finishSave(e,n){let r=this.saveRun;r&&(this.saveRun=void 0,r.timer.stop(),e?r.result.reject(e):n&&r.result.resolve(n))}async answerExport(e){var h;let n=this.options.externalExport;if(!n)return;let r=new AbortController;this.exports.add(r);let o=!1,i=setTimeout(()=>{o=!0,r.abort()},(h=n.timeoutMs)!=null?h:$),d=new Promise((u,l)=>r.signal.addEventListener("abort",()=>l(new Error("aborted"))));try{let u=await Promise.race([n.run(e,r.signal),d]);_(this.element,{files:u})}catch(u){let l=o?"EXPORT_TIMEOUT":"EXPORT_FAILED";this.ctx.log("export failed",{code:l,cause:String(u)}),this.replyExportFailure(l)}finally{clearTimeout(i),this.exports.delete(r)}}replyExportFailure(e){let n={files:[],error:{code:e}};this.element.dispatchEvent(new CustomEvent(P,{bubbles:!1,composed:!1,detail:n}))}assertUsable(){if(this.destroyed)throw g()}};function D(t,e){t.setAttribute("refresh-token",e.refreshToken),t.setAttribute("bearer-token",e.token)}var L=class{constructor(e){this.config=e;this.instances=[];this.log=Y(e.debug),this.context={config:e,log:this.log,refreshLicense:()=>this.refreshLicense().catch(()=>{}),onDestroyed:n=>{let r=this.instances.indexOf(n);r>=0&&this.instances.splice(r,1)}}}get current(){return this.instances[this.instances.length-1]}load(){return te(this.config.environment,this.config.version)}async mount(e){let n=K(e);await this.load();let r=await v.mount(this.context,n);return this.instances.push(r),r}open(){this.requireCurrent().open()}save(){let e=this.current;return e?e.save():Promise.reject(this.notMounted())}refreshSession(){let e=this.current;return e?e.refreshSession():Promise.reject(this.notMounted())}refreshLicense(){return this.licenseRefresh||(this.licenseRefresh=(async()=>{try{let e=await this.config.session.refreshLicense(),n=e==null?void 0:e.licenseToken;if(typeof n!="string"||n==="")throw new s("SESSION_FAILED","session.refreshLicense() must resolve { licenseToken }.");for(let r of this.instances)r.applyLicense(n);this.log("license refreshed")}catch(e){throw this.log("license refresh failed",{cause:String(e)}),e instanceof s?e:new s("SESSION_FAILED","A fresh license token could not be fetched.",{retryable:!0,cause:e})}finally{this.licenseRefresh=void 0}})()),this.licenseRefresh}logout(){for(let e of this.instances)e.logout();this.log("logged out")}destroy(){for(let e of[...this.instances])e.destroy()}requireCurrent(){let e=this.current;if(!e)throw this.notMounted();return e}notMounted(){return a("No builder is mounted. Call mount() first.")}};function ge(t){let e=X(t);return ee(e.environment),new L(e)}var T="tdn_resume",me="tdn-draft-index";function Ee(){let t=new URL(window.location.href);return t.searchParams.set(T,"1"),t.pathname+t.search+t.hash}function ve(t){return/^\/(?![/\\])/.test(t)}function Te(){if(typeof window=="undefined")return!1;let t=new URL(window.location.href);return t.searchParams.get(T)!=="1"?!1:(t.searchParams.delete(T),window.history.replaceState(window.history.state,"",t.pathname+t.search+t.hash),!0)}function ye(t){var e;if(typeof localStorage=="undefined")return!1;try{let n=JSON.parse((e=localStorage.getItem(me))!=null?e:"{}");return Object.values(n).some(r=>(r==null?void 0:r.productId)===t)}catch{return!1}}export{s as BuilderError,T as RESUME_PARAM,Te as consumeResumeMarker,ge as createBuilder,ye as hasDraft,ve as isSafeReturnPath,Ee as loginReturnPath};
1
+ import{b as R,c as k,d as P,e as _,f as B,g as O,h as C,j as m,k as A,l as N,m as M,n as F,p as U,q as V}from"./chunks/chunk-F4FUPJ4Y.js";var s=class t extends Error{constructor(e,n,r={}){var o,i;super(n),this.name="BuilderError",this.code=e,this.retryable=(o=r.retryable)!=null?o:!1,this.fatal=(i=r.fatal)!=null?i:!1,this.reason=r.reason,this.cause=r.cause,Object.setPrototypeOf(this,t.prototype)}},H={conflict:"Someone else saved this design first.",forbidden:"This shopper may not save this design.",unauthorized:"The shopper could not be signed in to save.","login-cancelled":"The design was not saved because the shopper did not log in.","installation-inactive":"This site's 3DN installation is not active.",license:"This page is not licensed to use the 3DN builder.",error:"The design could not be saved. Check the connection and try again."};function q(t){var n;let e=(n=H[t.reason])!=null?n:H.error;return t.reason==="login-cancelled"?new s("LOGIN_CANCELLED",e,{retryable:!0,reason:t.reason,cause:t}):new s("SAVE_FAILED",e,{retryable:t.retryable,reason:t.reason,cause:t})}function $(t){let e=t==="ORIGIN_NOT_ALLOWED"?"This website is not on the 3DN license. Add its hostname to the license.":"The 3DN license token was rejected. The license may be suspended, revoked or expired.";return new s("LICENSE_ERROR",e,{fatal:!0,reason:t,cause:{code:t}})}function g(){return new s("DESTROYED","The builder was destroyed.")}function a(t){return new s("CONFIG_INVALID",t,{fatal:!0})}var oe=45e3,j=12e4,ie=new Set(["environment","version","saveTimeoutMs","session","login","customization","onSaved","onLicenseError","onCloseRequested","onStatusChange","debug"]),se=new Set(["target","hostProductId","mode","designId","externalExport","observabilityContext"]),ae=/^\d+\.\d+\.\d+$/,de=/^tdn_inst_/;function b(t){return ae.test(t)}function u(t){return typeof t=="object"&&t!==null}function f(t){return typeof t=="function"}function z(t){return typeof t=="number"&&Number.isFinite(t)&&t>0}function G(t,e=0){return typeof t=="string"?de.test(t):!u(t)||e>3?!1:Object.values(t).some(n=>G(n,e+1))}function X(t,e,n){for(let r of Object.keys(t))if(!e.has(r))throw/url|src|host|origin/i.test(r)?a(`${n}: "${r}" is not allowed. The SDK decides where the builder loads from; choose an environment and a version instead.`):a(`${n}: unknown option "${r}".`)}function K(t){var h,l,c;if(!u(t))throw a("createBuilder() needs a configuration object.");if(G(t))throw a("createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.");X(t,ie,"createBuilder()");let e=(h=t.environment)!=null?h:"production";if(e!=="production"&&e!=="staging")throw a(`createBuilder(): environment must be "production" or "staging", got ${JSON.stringify(e)}.`);let n=(l=t.version)!=null?l:"latest";if(typeof n!="string"||n!=="latest"&&!b(n))throw a(`createBuilder(): version must be "latest" or an exact release such as "1.2.3", got ${JSON.stringify(n)}.`);let r=(c=t.saveTimeoutMs)!=null?c:oe;if(!z(r))throw a("createBuilder(): saveTimeoutMs must be a positive number of milliseconds.");let o=t.session;if(!u(o)||!f(o.get)||!f(o.refreshLicense))throw a("createBuilder(): session.get() and session.refreshLicense() are required. They call your session endpoint.");let i=t.login;if(i!==void 0){if(!u(i)||!f(i.getTokens)||!f(i.onRequired))throw a("createBuilder(): login needs getTokens() (your login-token endpoint) and onRequired() (your login UI).");if(i.onMintFailed!==void 0&&!f(i.onMintFailed))throw a("createBuilder(): login.onMintFailed must be a function.")}let d=t.customization;if(d!==void 0&&(!u(d)||!f(d.record)))throw a("createBuilder(): customization.record() is required when customization is set. It calls your customization endpoint.");for(let T of["onSaved","onLicenseError","onCloseRequested","onStatusChange"])if(t[T]!==void 0&&!f(t[T]))throw a(`createBuilder(): ${T} must be a function.`);return{...t,environment:e,version:n,saveTimeoutMs:r}}function W(t){var d;if(!u(t))throw a("mount() needs an options object.");X(t,se,"mount()");let e=t.target;if(typeof HTMLElement=="undefined"||!(e instanceof HTMLElement))throw a("mount(): target must be an HTMLElement on the page.");let n=t.hostProductId;if(typeof n!="string"||n.trim()==="")throw a("mount(): hostProductId must be your own, non-empty product reference.");let r=(d=t.mode)!=null?d:"designer";if(r!=="designer"&&r!=="viewer")throw a(`mount(): mode must be "designer" or "viewer", got ${JSON.stringify(r)}.`);let o=t.designId;if(o!==void 0&&(typeof o!="string"||o===""))throw a("mount(): designId must be a non-empty string.");t.observabilityContext!==void 0&&w(t.observabilityContext,"mount()");let i=t.externalExport;if(i!==void 0){if(!u(i)||!f(i.run))throw a("mount(): externalExport.run() is required when externalExport is set.");if(i.timeoutMs!==void 0&&!z(i.timeoutMs))throw a("mount(): externalExport.timeoutMs must be a positive number of milliseconds.")}return t}function w(t,e){let n;try{n=typeof t=="string"?JSON.parse(t):void 0}catch{n=void 0}if(!u(n)||Array.isArray(n))throw a(`${e}: observabilityContext must be a JSON object string.`);return t}function S(t){let e=o=>new s("SESSION_FAILED",`session.get() returned an invalid session: ${o}.`,{retryable:!1});if(!u(t))throw e("expected an object");if(typeof t.licenseToken!="string"||t.licenseToken==="")throw e("licenseToken is missing");if(typeof t.productId!="string"||t.productId==="")throw e("productId is missing");if(t.viewToken!=null&&typeof t.viewToken!="string")throw e("viewToken must be a string");let n=t.token!=null,r=t.refreshToken!=null;if(n!==r)throw e("token and refreshToken must be returned together");if(n&&(typeof t.token!="string"||typeof t.refreshToken!="string"))throw e("token and refreshToken must be strings");return t}function Y(t){if(t===null)return null;if(!u(t)||typeof t.token!="string"||t.token===""||typeof t.refreshToken!="string"||t.refreshToken==="")throw new s("SESSION_FAILED","login.getTokens() must resolve { token, refreshToken } or null.");return t}var ue=/token|secret|key|authorization/i;function D(t,e=0){if(t===null||typeof t!="object"||e>4)return t;if(Array.isArray(t))return t.map(r=>D(r,e+1));let n={};for(let[r,o]of Object.entries(t))n[r]=ue.test(r)&&o!=null?"[redacted]":D(o,e+1);return n}function J(t){return t?(e,n)=>{n===void 0?console.debug(`[3DN] ${e}`):console.debug(`[3DN] ${e}`,D(n))}:()=>{}}var v="threed-builder",ee={production:"https://3d-builder.3dn.app",staging:void 0},le=3e4,Q=Symbol.for("@3dn-builder/builder/loader");function ce(){let t=globalThis,e=t[Q];return e||(e={},t[Q]=e),e}function he(){return typeof window!="undefined"&&typeof document!="undefined"&&typeof customElements!="undefined"}function te(t){if(!ee[t])throw a(`createBuilder(): the "${t}" environment is not available in this SDK release.`)}function p(t,e){return new s("LOAD_FAILED",t,{retryable:!0,cause:e})}async function fe(t,e){if(e!=="latest")return e;let n;try{n=await fetch(`${t}/version-info.json`,{cache:"no-store",credentials:"omit"})}catch(i){throw p("The 3DN builder could not be reached.",i)}if(!n.ok)throw p(`The 3DN builder release could not be resolved (HTTP ${n.status}).`);let r=await n.json().catch(()=>null),o=r==null?void 0:r.version;if(typeof o!="string"||!b(o))throw p("The 3DN builder release information was not valid.");return o}function pe(t){return new Promise((e,n)=>{let r=document.createElement("script");r.type="module",r.src=t,r.crossOrigin="anonymous",r.onload=()=>e(r),r.onerror=()=>{r.remove(),n(p("The 3DN builder failed to load."))},document.head.appendChild(r)})}function ge(){return new Promise((t,e)=>{let n=setTimeout(()=>e(p("The 3DN builder loaded but did not start.")),le);customElements.whenDefined(v).then(()=>{clearTimeout(n),t()},e)})}function Z(t,e,n){if(t.environment&&t.environment!==e)throw new s("VERSION_CONFLICT",`The ${t.environment} builder already runs on this page; ${e} cannot load beside it.`,{fatal:!0});if(n!=="latest"&&t.version&&t.version!==n)throw new s("VERSION_CONFLICT",`Builder ${t.version} already runs on this page; ${n} cannot load beside it.`,{fatal:!0})}async function ne(t,e){if(!he())throw new s("NOT_IN_BROWSER","The 3DN builder can only load in a browser.");let n=ce();if(n.loading){await n.loading,Z(n,t,e);return}if(customElements.get(v))return;let r=ee[t];if(!r)throw a(`The "${t}" environment is not available in this SDK release.`);n.environment=t,n.loading=(async()=>{let o=await fe(r,e);n.version=o;let i=await pe(`${r}/builder-${o}.js`);try{await ge()}catch(d){throw i.remove(),d}})();try{await n.loading}catch(o){throw n.loading=void 0,n.environment=void 0,n.version=void 0,o instanceof s?o:p("The 3DN builder failed to load.",o)}Z(n,t,e)}function re(){let t,e,n=new Promise((r,o)=>{t=r,e=o});return n.catch(()=>{}),{promise:n,resolve:t,reject:e}}var L=class{constructor(e,n){this.onExpire=n;this.startedAt=0;this.remaining=e}start(){this.handle===void 0&&(this.startedAt=Date.now(),this.handle=setTimeout(this.onExpire,this.remaining))}pause(){this.handle!==void 0&&(clearTimeout(this.handle),this.handle=void 0,this.remaining=Math.max(0,this.remaining-(Date.now()-this.startedAt)))}stop(){this.handle!==void 0&&clearTimeout(this.handle),this.handle=void 0}},E=class t{constructor(e,n,r){this.ctx=e;this.options=n;this.epoch=0;this.destroyed=!1;this.loginPending=!1;this.exports=new Set;this.listeners=[];this.currentProductId=r.productId,this.element=this.createElement(r)}static async mount(e,n){let r;try{r=await e.config.session.get({hostProductId:n.hostProductId})}catch(d){throw new s("SESSION_FAILED","The builder session could not be loaded.",{retryable:!0,cause:d})}let o=S(r),i=new t(e,n,o);return n.target.appendChild(i.element),e.log("mounted",{hostProductId:n.hostProductId,productId:o.productId}),i}get productId(){return this.currentProductId}get hostProductId(){return this.options.hostProductId}createElement(e){let n=document.createElement(v);return this.attachListeners(n),n.setAttribute("product",e.productId),n.setAttribute("license-token",e.licenseToken),e.viewToken&&n.setAttribute("view-token",e.viewToken),e.token&&e.refreshToken&&I(n,{token:e.token,refreshToken:e.refreshToken}),this.options.designId&&n.setAttribute("design-id",this.options.designId),this.options.mode==="viewer"&&n.setAttribute("display-only","true"),this.options.externalExport&&n.setAttribute("external-export","true"),this.options.observabilityContext&&n.setAttribute("observability-context",this.options.observabilityContext),n.style.display="block",n.style.width="100%",n.style.height="100%",n}attachListeners(e){let n=(r,o)=>{let i=d=>{d.target!==e||this.destroyed||(this.ctx.log(r,d.detail),o(d.detail))};e.addEventListener(r,i),this.listeners.push([r,i])};n(C,r=>{var o;this.answerLogin((o=r==null?void 0:r.action)!=null?o:"save")}),n(F,()=>{this.ctx.refreshLicense()}),n(A,r=>this.onLicenseError(r)),n(N,r=>{this.onDesignSaved(r)}),n(M,r=>this.finishSave(q(r))),n(O,r=>{this.onProductCloned(r)}),n(V,r=>{var o,i;return(i=(o=this.ctx.config).onCloseRequested)==null?void 0:i.call(o,r)}),n(k,r=>{var o,i;return(i=(o=this.ctx.config).onStatusChange)==null?void 0:i.call(o,r)}),this.options.externalExport&&n(P,r=>{this.answerExport(r)})}open(){this.assertUsable(),R(this.element)}save(){if(this.destroyed)return Promise.reject(g());if(this.fatal)return Promise.reject(this.fatal);if(this.saveRun)return this.saveRun.result.promise;let e={result:re(),timer:new L(this.ctx.config.saveTimeoutMs,()=>this.finishSave(new s("SAVE_TIMEOUT","Saving the design took too long. Try again.",{retryable:!0})))};return this.saveRun=e,this.loginPending||e.timer.start(),U(this.element),e.result.promise}async refreshSession(){this.assertUsable();let e=this.epoch,n;try{n=await this.ctx.config.session.get({hostProductId:this.options.hostProductId})}catch(o){throw new s("SESSION_FAILED","The builder session could not be refreshed.",{retryable:!0,cause:o})}let r=S(n);e!==this.epoch||this.destroyed||(this.applyLicense(r.licenseToken),r.viewToken&&this.element.setAttribute("view-token",r.viewToken),r.token&&r.refreshToken&&I(this.element,{token:r.token,refreshToken:r.refreshToken}),this.fatal=void 0)}logout(){this.destroyed||(this.epoch++,this.element.removeAttribute("bearer-token"),this.element.removeAttribute("refresh-token"),this.loginPending&&(this.loginPending=!1,this.finishSave(new s("LOGIN_CANCELLED","The shopper logged out before the design was saved.",{retryable:!0}))))}destroy(){var e;if(!this.destroyed){this.epoch++,this.finishSave(g()),(e=this.cloneWait)==null||e.reject(g()),this.cloneWait=void 0;for(let n of this.exports)n.abort();this.destroyed=!0;for(let[n,r]of this.listeners)this.element.removeEventListener(n,r);this.listeners.length=0,this.element.remove(),this.ctx.onDestroyed(this),this.ctx.log("destroyed")}}setObservabilityContext(e){this.assertUsable(),this.element.setAttribute("observability-context",w(e,"setObservabilityContext()"))}applyLicense(e){this.destroyed||this.element.setAttribute("license-token",e)}get isDestroyed(){return this.destroyed}async answerLogin(e){if(this.loginPending)return;let n=this.ctx.config.login;if(!n){m(this.element,{action:e});return}let r=this.epoch,o=()=>r!==this.epoch||this.destroyed;this.setLoginPending(!0);let i=!1;try{for(;;){let d;try{d=Y(await n.getTokens())}catch(l){if(o())return;this.ctx.log("login.getTokens() failed",{cause:String(l)});let c=n.onMintFailed?await n.onMintFailed():"cancel";if(o())return;if(c==="retry")continue;m(this.element,{action:e});return}if(o())return;if(d){I(this.element,d);return}let h=await n.onRequired(e);if(o())return;if(h!=="logged-in"){if(h==="redirected"){i=!0;return}m(this.element,{action:e});return}}}finally{!i&&r===this.epoch&&this.setLoginPending(!1)}}setLoginPending(e){this.loginPending=e,this.saveRun&&(e?this.saveRun.timer.pause():this.saveRun.timer.start())}onLicenseError(e){var r,o;let n=$(e.code);this.fatal=n,this.finishSave(n),(o=(r=this.ctx.config).onLicenseError)==null||o.call(r,n)}async onDesignSaved(e){e.productId!==this.currentProductId&&!this.cloneWait&&(this.cloneWait=re());try{this.cloneWait&&await this.cloneWait.promise,this.unrecordedLink&&await this.recordLink(this.unrecordedLink)}catch(r){this.finishSave(r instanceof s?r:new s("SAVE_FAILED","The design was saved but could not be linked to this shopper.",{retryable:!0,reason:"customization",cause:r}));return}if(this.destroyed)return;let n={productId:e.productId,version:e.version};this.notifySaved(n),this.finishSave(void 0,n)}notifySaved(e){var n,r;try{(r=(n=this.ctx.config).onSaved)==null||r.call(n,{hostProductId:this.options.hostProductId,...e})}catch(o){this.ctx.log("onSaved threw",{cause:String(o)})}}async onProductCloned(e){let n={hostProductId:this.options.hostProductId,sourceProductId:e.originalProductId,savedProductId:e.newProductId};this.currentProductId=e.newProductId;let r=this.cloneWait;this.cloneWait=void 0;try{await this.recordLink(n),r==null||r.resolve()}catch(o){r==null||r.reject(o)}}async recordLink(e){let n=this.ctx.config.customization;if(n)try{await n.record(e),this.unrecordedLink===e&&(this.unrecordedLink=void 0)}catch(r){throw this.unrecordedLink=e,new s("SAVE_FAILED","The design was saved but could not be linked to this shopper. Try saving again.",{retryable:!0,reason:"customization",cause:r})}}finishSave(e,n){let r=this.saveRun;r&&(this.saveRun=void 0,r.timer.stop(),e?r.result.reject(e):n&&r.result.resolve(n))}async answerExport(e){var h;let n=this.options.externalExport;if(!n)return;let r=new AbortController;this.exports.add(r);let o=!1,i=setTimeout(()=>{o=!0,r.abort()},(h=n.timeoutMs)!=null?h:j),d=new Promise((l,c)=>r.signal.addEventListener("abort",()=>c(new Error("aborted"))));try{let l=await Promise.race([n.run(e,r.signal),d]);B(this.element,{files:l})}catch(l){let c=o?"EXPORT_TIMEOUT":"EXPORT_FAILED";this.ctx.log("export failed",{code:c,cause:String(l)}),this.replyExportFailure(c)}finally{clearTimeout(i),this.exports.delete(r)}}replyExportFailure(e){let n={files:[],error:{code:e}};this.element.dispatchEvent(new CustomEvent(_,{bubbles:!1,composed:!1,detail:n}))}assertUsable(){if(this.destroyed)throw g()}};function I(t,e){t.setAttribute("refresh-token",e.refreshToken),t.setAttribute("bearer-token",e.token)}var x=class{constructor(e){this.config=e;this.instances=[];this.log=J(e.debug),this.context={config:e,log:this.log,refreshLicense:()=>this.refreshLicense().catch(()=>{}),onDestroyed:n=>{let r=this.instances.indexOf(n);r>=0&&this.instances.splice(r,1)}}}get current(){return this.instances[this.instances.length-1]}load(){return ne(this.config.environment,this.config.version)}async mount(e){let n=W(e);await this.load();let r=await E.mount(this.context,n);return this.instances.push(r),r}open(){this.requireCurrent().open()}save(){let e=this.current;return e?e.save():Promise.reject(this.notMounted())}refreshSession(){let e=this.current;return e?e.refreshSession():Promise.reject(this.notMounted())}refreshLicense(){return this.licenseRefresh||(this.licenseRefresh=(async()=>{try{let e=await this.config.session.refreshLicense(),n=e==null?void 0:e.licenseToken;if(typeof n!="string"||n==="")throw new s("SESSION_FAILED","session.refreshLicense() must resolve { licenseToken }.");for(let r of this.instances)r.applyLicense(n);this.log("license refreshed")}catch(e){throw this.log("license refresh failed",{cause:String(e)}),e instanceof s?e:new s("SESSION_FAILED","A fresh license token could not be fetched.",{retryable:!0,cause:e})}finally{this.licenseRefresh=void 0}})()),this.licenseRefresh}logout(){for(let e of this.instances)e.logout();this.log("logged out")}destroy(){for(let e of[...this.instances])e.destroy()}requireCurrent(){let e=this.current;if(!e)throw this.notMounted();return e}notMounted(){return a("No builder is mounted. Call mount() first.")}};function me(t){let e=K(t);return te(e.environment),new x(e)}var y="tdn_resume",ve="tdn-draft-index";function Ee(){let t=new URL(window.location.href);return t.searchParams.set(y,"1"),t.pathname+t.search+t.hash}function ye(t){return/^\/(?![/\\])/.test(t)}function Te(){if(typeof window=="undefined")return!1;let t=new URL(window.location.href);return t.searchParams.get(y)!=="1"?!1:(t.searchParams.delete(y),window.history.replaceState(window.history.state,"",t.pathname+t.search+t.hash),!0)}function be(t){var e;if(typeof localStorage=="undefined")return!1;try{let n=JSON.parse((e=localStorage.getItem(ve))!=null?e:"{}");return Object.values(n).some(r=>(r==null?void 0:r.productId)===t)}catch{return!1}}export{s as BuilderError,y as RESUME_PARAM,Te as consumeResumeMarker,me as createBuilder,be as hasDraft,ye as isSafeReturnPath,Ee as loginReturnPath};
2
2
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../libs/builder-host/src/lib/errors.ts", "../../../libs/builder-host/src/lib/config.ts", "../../../libs/builder-host/src/lib/debug.ts", "../../../libs/builder-host/src/lib/loader.ts", "../../../libs/builder-host/src/lib/instance.ts", "../../../libs/builder-host/src/lib/builder.ts", "../../../libs/builder-host/src/lib/resume.ts"],
4
- "sourcesContent": ["import type {\n DesignSaveFailedDetail,\n DesignSaveFailureReason,\n LicenseErrorCode,\n} from '@three-dn/constants';\n\nexport type BuilderErrorCode =\n | 'CONFIG_INVALID'\n | 'NOT_IN_BROWSER'\n | 'LOAD_FAILED'\n | 'VERSION_CONFLICT'\n | 'SESSION_FAILED'\n | 'LICENSE_ERROR'\n | 'LOGIN_CANCELLED'\n | 'SAVE_FAILED'\n | 'SAVE_TIMEOUT'\n | 'EXPORT_FAILED'\n | 'EXPORT_TIMEOUT'\n | 'DESTROYED';\n\n/** The builder's own reason, plus `customization` when only the record failed. */\nexport type BuilderErrorReason =\n | DesignSaveFailureReason\n | LicenseErrorCode\n | 'customization';\n\nexport interface BuilderErrorOptions {\n retryable?: boolean;\n fatal?: boolean;\n reason?: BuilderErrorReason;\n cause?: unknown;\n}\n\n/**\n * Every failure the SDK reports. Hosts switch on `code`; `message` is safe to\n * show or log and never contains a token.\n */\nexport class BuilderError extends Error {\n readonly code: BuilderErrorCode;\n readonly retryable: boolean;\n /** The mount cannot continue until the host refreshes or recreates it. */\n readonly fatal: boolean;\n readonly reason?: BuilderErrorReason;\n readonly cause?: unknown;\n\n constructor(\n code: BuilderErrorCode,\n message: string,\n options: BuilderErrorOptions = {}\n ) {\n super(message);\n this.name = 'BuilderError';\n this.code = code;\n this.retryable = options.retryable ?? false;\n this.fatal = options.fatal ?? false;\n this.reason = options.reason;\n this.cause = options.cause;\n Object.setPrototypeOf(this, BuilderError.prototype);\n }\n}\n\nconst SAVE_FAILURE_MESSAGES: Record<DesignSaveFailureReason, string> = {\n conflict: 'Someone else saved this design first.',\n forbidden: 'This shopper may not save this design.',\n unauthorized: 'The shopper could not be signed in to save.',\n 'login-cancelled':\n 'The design was not saved because the shopper did not log in.',\n 'installation-inactive': \"This site's 3DN installation is not active.\",\n license: 'This page is not licensed to use the 3DN builder.',\n error: 'The design could not be saved. Check the connection and try again.',\n};\n\nexport function saveFailedError(detail: DesignSaveFailedDetail): BuilderError {\n const message =\n SAVE_FAILURE_MESSAGES[detail.reason] ?? SAVE_FAILURE_MESSAGES.error;\n if (detail.reason === 'login-cancelled') {\n return new BuilderError('LOGIN_CANCELLED', message, {\n retryable: true,\n reason: detail.reason,\n cause: detail,\n });\n }\n return new BuilderError('SAVE_FAILED', message, {\n retryable: detail.retryable,\n reason: detail.reason,\n cause: detail,\n });\n}\n\nexport function licenseError(code: LicenseErrorCode): BuilderError {\n const message =\n code === 'ORIGIN_NOT_ALLOWED'\n ? 'This website is not on the 3DN license. Add its hostname to the license.'\n : 'The 3DN license token was rejected. The license may be suspended, revoked or expired.';\n return new BuilderError('LICENSE_ERROR', message, {\n fatal: true,\n reason: code,\n cause: { code },\n });\n}\n\nexport function destroyedError(): BuilderError {\n return new BuilderError('DESTROYED', 'The builder was destroyed.');\n}\n\nexport function configError(message: string): BuilderError {\n return new BuilderError('CONFIG_INVALID', message, { fatal: true });\n}\n", "import type {\n BuilderStatusDetail,\n CloseRequestedDetail,\n ExportFile,\n ExportRequestedDetail,\n LoginRequiredAction,\n} from '@three-dn/constants';\nimport { BuilderError, configError } from './errors';\n\nexport type BuilderEnvironment = 'production' | 'staging';\nexport type BuilderVersion = 'latest' | `${number}.${number}.${number}`;\nexport type BuilderMode = 'designer' | 'viewer';\n\n/** What the host's session endpoint returns: site access plus the product to open. */\nexport interface BuilderSession {\n licenseToken: string;\n /** The 3DN product to open: the shopper's clone, else the global product. */\n productId: string;\n /** Required when the product to open is private. */\n viewToken?: string;\n /** Optional fast path: shopper tokens for a shopper the host backend verified. */\n token?: string;\n refreshToken?: string;\n}\n\n/** Shopper identity, from the host's login-token endpoint. */\nexport interface ShopperTokens {\n token: string;\n refreshToken: string;\n}\n\nexport type LoginOutcome = 'logged-in' | 'redirected' | 'cancelled';\n\n/** What the host stores when a save created the shopper's clone. */\nexport interface CustomizationLink {\n hostProductId: string;\n sourceProductId: string;\n savedProductId: string;\n}\n\n/** One successful save, however it started: `save()` or the builder's own Save button. */\nexport interface SavedDesignEvent {\n hostProductId: string;\n /** The saved 3DN product: the shopper's clone after a first save. */\n productId: string;\n version: number;\n}\n\nexport interface BuilderHostConfig {\n /** Default `'production'`. */\n environment?: BuilderEnvironment;\n /** `'latest'` or an exact builder release. Default `'latest'`. */\n version?: BuilderVersion;\n /** Default 45 000 ms. */\n saveTimeoutMs?: number;\n session: {\n get(request: { hostProductId: string }): Promise<BuilderSession>;\n refreshLicense(): Promise<Pick<BuilderSession, 'licenseToken'>>;\n };\n login?: {\n /** Answers `tdn-login-required`. Resolve `null` when the endpoint answers 401. */\n getTokens(): Promise<ShopperTokens | null>;\n /** The host's login UX for the action the builder gated. */\n onRequired(action: LoginRequiredAction): Promise<LoginOutcome>;\n /** `getTokens()` failed (5xx, network). Default: `'cancel'`. */\n onMintFailed?(): Promise<'retry' | 'cancel'>;\n };\n customization?: {\n record(link: CustomizationLink): Promise<void>;\n };\n /**\n * Every successful save, including ones the shopper starts from the builder's\n * own Save button. Fires after any clone mapping has been recorded; never\n * when recording failed.\n */\n onSaved?(saved: SavedDesignEvent): void;\n onLicenseError?(error: BuilderError): void;\n onCloseRequested?(detail: CloseRequestedDetail): void;\n onStatusChange?(detail: BuilderStatusDetail): void;\n /** Log lifecycle steps and events to the console. Tokens are always redacted. */\n debug?: boolean;\n}\n\nexport interface ExternalExportOptions {\n run(\n request: ExportRequestedDetail,\n signal: AbortSignal\n ): Promise<ExportFile[]>;\n /** Default 120 000 ms. */\n timeoutMs?: number;\n}\n\nexport interface MountOptions {\n target: HTMLElement;\n /** The host's own product reference, passed to `session.get()`. Never a 3DN id. */\n hostProductId: string;\n /** Default `'designer'`. `'viewer'` is the display-only mode. */\n mode?: BuilderMode;\n designId?: string;\n /** Opt-in, GLB products only. Off unless passed. */\n externalExport?: ExternalExportOptions;\n}\n\nexport interface ResolvedConfig extends BuilderHostConfig {\n environment: BuilderEnvironment;\n version: BuilderVersion;\n saveTimeoutMs: number;\n}\n\nexport const DEFAULT_SAVE_TIMEOUT_MS = 45_000;\nexport const DEFAULT_EXPORT_TIMEOUT_MS = 120_000;\n\nconst CONFIG_KEYS = new Set([\n 'environment',\n 'version',\n 'saveTimeoutMs',\n 'session',\n 'login',\n 'customization',\n 'onSaved',\n 'onLicenseError',\n 'onCloseRequested',\n 'onStatusChange',\n 'debug',\n]);\n\nconst MOUNT_KEYS = new Set([\n 'target',\n 'hostProductId',\n 'mode',\n 'designId',\n 'externalExport',\n]);\n\nconst VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+$/;\nconst INSTALLATION_KEY_PATTERN = /^tdn_inst_/;\n\nexport function isExactVersion(value: string): boolean {\n return VERSION_PATTERN.test(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isFunction(value: unknown): value is (...args: never[]) => unknown {\n return typeof value === 'function';\n}\n\nfunction isPositiveNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n/** Walks the config looking for anything that looks like an installation key. */\nfunction containsSecret(value: unknown, depth = 0): boolean {\n if (typeof value === 'string') return INSTALLATION_KEY_PATTERN.test(value);\n if (!isRecord(value) || depth > 3) return false;\n return Object.values(value).some((v) => containsSecret(v, depth + 1));\n}\n\nfunction rejectUnknownKeys(\n value: Record<string, unknown>,\n allowed: Set<string>,\n where: string\n): void {\n for (const key of Object.keys(value)) {\n if (allowed.has(key)) continue;\n if (/url|src|host|origin/i.test(key)) {\n throw configError(\n `${where}: \"${key}\" is not allowed. The SDK decides where the builder loads from; choose an environment and a version instead.`\n );\n }\n throw configError(`${where}: unknown option \"${key}\".`);\n }\n}\n\nexport function resolveConfig(config: unknown): ResolvedConfig {\n if (!isRecord(config)) {\n throw configError('createBuilder() needs a configuration object.');\n }\n if (containsSecret(config)) {\n throw configError(\n 'createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.'\n );\n }\n rejectUnknownKeys(config, CONFIG_KEYS, 'createBuilder()');\n\n const environment = config['environment'] ?? 'production';\n if (environment !== 'production' && environment !== 'staging') {\n throw configError(\n `createBuilder(): environment must be \"production\" or \"staging\", got ${JSON.stringify(\n environment\n )}.`\n );\n }\n\n const version = config['version'] ?? 'latest';\n if (\n typeof version !== 'string' ||\n (version !== 'latest' && !isExactVersion(version))\n ) {\n throw configError(\n `createBuilder(): version must be \"latest\" or an exact release such as \"1.2.3\", got ${JSON.stringify(\n version\n )}.`\n );\n }\n\n const saveTimeoutMs = config['saveTimeoutMs'] ?? DEFAULT_SAVE_TIMEOUT_MS;\n if (!isPositiveNumber(saveTimeoutMs)) {\n throw configError(\n 'createBuilder(): saveTimeoutMs must be a positive number of milliseconds.'\n );\n }\n\n const session = config['session'];\n if (\n !isRecord(session) ||\n !isFunction(session['get']) ||\n !isFunction(session['refreshLicense'])\n ) {\n throw configError(\n 'createBuilder(): session.get() and session.refreshLicense() are required. They call your session endpoint.'\n );\n }\n\n const login = config['login'];\n if (login !== undefined) {\n if (\n !isRecord(login) ||\n !isFunction(login['getTokens']) ||\n !isFunction(login['onRequired'])\n ) {\n throw configError(\n 'createBuilder(): login needs getTokens() (your login-token endpoint) and onRequired() (your login UI).'\n );\n }\n if (\n login['onMintFailed'] !== undefined &&\n !isFunction(login['onMintFailed'])\n ) {\n throw configError(\n 'createBuilder(): login.onMintFailed must be a function.'\n );\n }\n }\n\n const customization = config['customization'];\n if (\n customization !== undefined &&\n (!isRecord(customization) || !isFunction(customization['record']))\n ) {\n throw configError(\n 'createBuilder(): customization.record() is required when customization is set. It calls your customization endpoint.'\n );\n }\n\n for (const key of [\n 'onSaved',\n 'onLicenseError',\n 'onCloseRequested',\n 'onStatusChange',\n ] as const) {\n if (config[key] !== undefined && !isFunction(config[key])) {\n throw configError(`createBuilder(): ${key} must be a function.`);\n }\n }\n\n return {\n ...(config as unknown as BuilderHostConfig),\n environment,\n version: version as BuilderVersion,\n saveTimeoutMs,\n };\n}\n\nexport function validateMountOptions(options: unknown): MountOptions {\n if (!isRecord(options)) {\n throw configError('mount() needs an options object.');\n }\n rejectUnknownKeys(options, MOUNT_KEYS, 'mount()');\n\n const target = options['target'];\n if (typeof HTMLElement === 'undefined' || !(target instanceof HTMLElement)) {\n throw configError('mount(): target must be an HTMLElement on the page.');\n }\n const hostProductId = options['hostProductId'];\n if (typeof hostProductId !== 'string' || hostProductId.trim() === '') {\n throw configError(\n 'mount(): hostProductId must be your own, non-empty product reference.'\n );\n }\n const mode = options['mode'] ?? 'designer';\n if (mode !== 'designer' && mode !== 'viewer') {\n throw configError(\n `mount(): mode must be \"designer\" or \"viewer\", got ${JSON.stringify(\n mode\n )}.`\n );\n }\n const designId = options['designId'];\n if (\n designId !== undefined &&\n (typeof designId !== 'string' || designId === '')\n ) {\n throw configError('mount(): designId must be a non-empty string.');\n }\n const externalExport = options['externalExport'];\n if (externalExport !== undefined) {\n if (!isRecord(externalExport) || !isFunction(externalExport['run'])) {\n throw configError(\n 'mount(): externalExport.run() is required when externalExport is set.'\n );\n }\n if (\n externalExport['timeoutMs'] !== undefined &&\n !isPositiveNumber(externalExport['timeoutMs'])\n ) {\n throw configError(\n 'mount(): externalExport.timeoutMs must be a positive number of milliseconds.'\n );\n }\n }\n return options as unknown as MountOptions;\n}\n\n/** Checks the shape of a session response before anything reaches the element. */\nexport function validateSession(session: unknown): BuilderSession {\n const fail = (what: string) =>\n new BuilderError(\n 'SESSION_FAILED',\n `session.get() returned an invalid session: ${what}.`,\n {\n retryable: false,\n }\n );\n if (!isRecord(session)) throw fail('expected an object');\n if (\n typeof session['licenseToken'] !== 'string' ||\n session['licenseToken'] === ''\n ) {\n throw fail('licenseToken is missing');\n }\n if (typeof session['productId'] !== 'string' || session['productId'] === '') {\n throw fail('productId is missing');\n }\n if (\n session['viewToken'] != null &&\n typeof session['viewToken'] !== 'string'\n ) {\n throw fail('viewToken must be a string');\n }\n const hasToken = session['token'] != null;\n const hasRefresh = session['refreshToken'] != null;\n if (hasToken !== hasRefresh) {\n throw fail('token and refreshToken must be returned together');\n }\n if (\n hasToken &&\n (typeof session['token'] !== 'string' ||\n typeof session['refreshToken'] !== 'string')\n ) {\n throw fail('token and refreshToken must be strings');\n }\n return session as unknown as BuilderSession;\n}\n\nexport function validateShopperTokens(tokens: unknown): ShopperTokens | null {\n if (tokens === null) return null;\n if (\n !isRecord(tokens) ||\n typeof tokens['token'] !== 'string' ||\n tokens['token'] === '' ||\n typeof tokens['refreshToken'] !== 'string' ||\n tokens['refreshToken'] === ''\n ) {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'login.getTokens() must resolve { token, refreshToken } or null.'\n );\n }\n return tokens as unknown as ShopperTokens;\n}\n", "const TOKEN_KEY = /token|secret|key|authorization/i;\n\n/** Copies a value for logging with every token-like field replaced. */\nexport function redact(value: unknown, depth = 0): unknown {\n if (value === null || typeof value !== 'object' || depth > 4) return value;\n if (Array.isArray(value)) return value.map((v) => redact(v, depth + 1));\n const out: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(value)) {\n out[key] =\n TOKEN_KEY.test(key) && v != null ? '[redacted]' : redact(v, depth + 1);\n }\n return out;\n}\n\nexport type DebugLog = (message: string, detail?: unknown) => void;\n\nexport function createDebugLog(enabled: boolean | undefined): DebugLog {\n if (!enabled) return () => undefined;\n return (message, detail) => {\n if (detail === undefined) console.debug(`[3DN] ${message}`);\n else console.debug(`[3DN] ${message}`, redact(detail));\n };\n}\n", "import type { BuilderEnvironment, BuilderVersion } from './config';\nimport { isExactVersion } from './config';\nimport { BuilderError, configError } from './errors';\n\nexport const BUILDER_TAG = 'threed-builder';\n\n/**\n * Where each environment's builder is served from. SDK-owned: hosts choose an\n * environment, never a location. Staging stays unset until its location is\n * confirmed; createBuilder() refuses `environment: 'staging'` until then.\n */\nconst BUNDLE_HOSTS: Record<BuilderEnvironment, string | undefined> = {\n production: 'https://3d-builder.3dn.app',\n staging: undefined,\n};\n\nconst DEFINE_TIMEOUT_MS = 30_000;\n\n/** Shared by every copy of the SDK on the page: one custom element registry. */\ninterface PageLoaderState {\n environment?: BuilderEnvironment;\n /** The exact version loaded, once known. */\n version?: string;\n loading?: Promise<void>;\n}\n\nconst STATE_KEY = Symbol.for('@3dn-builder/builder/loader');\n\nfunction pageState(): PageLoaderState {\n const holder = globalThis as unknown as Record<\n symbol,\n PageLoaderState | undefined\n >;\n let state = holder[STATE_KEY];\n if (!state) {\n state = {};\n holder[STATE_KEY] = state;\n }\n return state;\n}\n\nexport function isBrowser(): boolean {\n return (\n typeof window !== 'undefined' &&\n typeof document !== 'undefined' &&\n typeof customElements !== 'undefined'\n );\n}\n\nexport function assertEnvironmentAvailable(\n environment: BuilderEnvironment\n): void {\n if (!BUNDLE_HOSTS[environment]) {\n throw configError(\n `createBuilder(): the \"${environment}\" environment is not available in this SDK release.`\n );\n }\n}\n\nfunction loadFailed(message: string, cause?: unknown): BuilderError {\n return new BuilderError('LOAD_FAILED', message, { retryable: true, cause });\n}\n\nasync function resolveVersion(\n host: string,\n requested: BuilderVersion\n): Promise<string> {\n if (requested !== 'latest') return requested;\n let response: Response;\n try {\n response = await fetch(`${host}/version-info.json`, {\n cache: 'no-store',\n credentials: 'omit',\n });\n } catch (cause) {\n throw loadFailed('The 3DN builder could not be reached.', cause);\n }\n if (!response.ok) {\n throw loadFailed(\n `The 3DN builder release could not be resolved (HTTP ${response.status}).`\n );\n }\n const info = (await response.json().catch(() => null)) as {\n version?: unknown;\n } | null;\n const version = info?.version;\n if (typeof version !== 'string' || !isExactVersion(version)) {\n throw loadFailed('The 3DN builder release information was not valid.');\n }\n return version;\n}\n\nfunction injectScript(src: string): Promise<HTMLScriptElement> {\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.type = 'module';\n script.src = src;\n script.crossOrigin = 'anonymous';\n script.onload = () => resolve(script);\n script.onerror = () => {\n script.remove();\n reject(loadFailed('The 3DN builder failed to load.'));\n };\n document.head.appendChild(script);\n });\n}\n\nfunction whenDefined(): Promise<void> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(\n () => reject(loadFailed('The 3DN builder loaded but did not start.')),\n DEFINE_TIMEOUT_MS\n );\n customElements.whenDefined(BUILDER_TAG).then(() => {\n clearTimeout(timer);\n resolve();\n }, reject);\n });\n}\n\nfunction assertCompatible(\n state: PageLoaderState,\n environment: BuilderEnvironment,\n requested: BuilderVersion\n): void {\n if (state.environment && state.environment !== environment) {\n throw new BuilderError(\n 'VERSION_CONFLICT',\n `The ${state.environment} builder already runs on this page; ${environment} cannot load beside it.`,\n { fatal: true }\n );\n }\n if (requested !== 'latest' && state.version && state.version !== requested) {\n throw new BuilderError(\n 'VERSION_CONFLICT',\n `Builder ${state.version} already runs on this page; ${requested} cannot load beside it.`,\n { fatal: true }\n );\n }\n}\n\n/**\n * Loads the builder once per page. A failed load leaves nothing behind, so the\n * next call tries again.\n */\nexport async function loadBuilder(\n environment: BuilderEnvironment,\n requested: BuilderVersion\n): Promise<void> {\n if (!isBrowser()) {\n throw new BuilderError(\n 'NOT_IN_BROWSER',\n 'The 3DN builder can only load in a browser.'\n );\n }\n const state = pageState();\n\n if (state.loading) {\n await state.loading;\n assertCompatible(state, environment, requested);\n return;\n }\n // Defined by something other than this SDK (an older embed, or the testing\n // fake): its version is unknown, so use it as it is.\n if (customElements.get(BUILDER_TAG)) return;\n\n const host = BUNDLE_HOSTS[environment];\n if (!host) {\n throw configError(\n `The \"${environment}\" environment is not available in this SDK release.`\n );\n }\n\n state.environment = environment;\n state.loading = (async () => {\n const version = await resolveVersion(host, requested);\n state.version = version;\n const script = await injectScript(`${host}/builder-${version}.js`);\n try {\n await whenDefined();\n } catch (error) {\n script.remove();\n throw error;\n }\n })();\n\n try {\n await state.loading;\n } catch (error) {\n state.loading = undefined;\n state.environment = undefined;\n state.version = undefined;\n throw error instanceof BuilderError\n ? error\n : loadFailed('The 3DN builder failed to load.', error);\n }\n assertCompatible(state, environment, requested);\n}\n", "import {\n THREED_BUILDER_CLOSE_REQUESTED_EVENT,\n THREED_BUILDER_DESIGN_SAVED_EVENT,\n THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT,\n THREED_BUILDER_EXPORT_COMPLETE_EVENT,\n THREED_BUILDER_EXPORT_REQUESTED_EVENT,\n THREED_BUILDER_LICENSE_ERROR_EVENT,\n THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT,\n THREED_BUILDER_LOGIN_REQUIRED_EVENT,\n THREED_BUILDER_PRODUCT_CLONED_EVENT,\n THREED_BUILDER_STATUS_EVENT,\n dispatchExportComplete,\n dispatchLoginCancelled,\n dispatchOpenThreedBuilder,\n dispatchSaveRequest,\n type BuilderStatusDetail,\n type CloseRequestedDetail,\n type DesignSaveFailedDetail,\n type DesignSavedDetail,\n type ExportRequestedDetail,\n type LicenseErrorDetail,\n type LoginRequiredAction,\n type LoginRequiredDetail,\n type ProductClonedDetail,\n} from '@three-dn/constants';\nimport {\n DEFAULT_EXPORT_TIMEOUT_MS,\n validateSession,\n validateShopperTokens,\n type BuilderSession,\n type CustomizationLink,\n type MountOptions,\n type ResolvedConfig,\n type ShopperTokens,\n} from './config';\nimport type { DebugLog } from './debug';\nimport {\n BuilderError,\n destroyedError,\n licenseError,\n saveFailedError,\n} from './errors';\nimport { BUILDER_TAG } from './loader';\n\n/** What `save()` resolves with. `productId` is always the one to put in the cart. */\nexport interface SavedDesign {\n productId: string;\n version: number;\n}\n\nexport interface InstanceContext {\n config: ResolvedConfig;\n log: DebugLog;\n /** Single-flight license refresh shared by every instance of a controller. */\n refreshLicense(): Promise<void>;\n onDestroyed(instance: BuilderInstance): void;\n}\n\ninterface Deferred<T> {\n promise: Promise<T>;\n resolve(value: T): void;\n reject(error: unknown): void;\n}\n\nfunction deferred<T>(): Deferred<T> {\n let resolve!: (value: T) => void;\n let reject!: (error: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n // A rejection nobody awaits yet must not surface as unhandled.\n promise.catch(() => undefined);\n return { promise, resolve, reject };\n}\n\n/** A timer that stops counting while a login round-trip is pending. */\nclass PausableTimer {\n private handle?: ReturnType<typeof setTimeout>;\n private startedAt = 0;\n private remaining: number;\n\n constructor(ms: number, private readonly onExpire: () => void) {\n this.remaining = ms;\n }\n\n start(): void {\n if (this.handle !== undefined) return;\n this.startedAt = Date.now();\n this.handle = setTimeout(this.onExpire, this.remaining);\n }\n\n pause(): void {\n if (this.handle === undefined) return;\n clearTimeout(this.handle);\n this.handle = undefined;\n this.remaining = Math.max(\n 0,\n this.remaining - (Date.now() - this.startedAt)\n );\n }\n\n stop(): void {\n if (this.handle !== undefined) clearTimeout(this.handle);\n this.handle = undefined;\n }\n}\n\ninterface SaveRun {\n result: Deferred<SavedDesign>;\n timer: PausableTimer;\n}\n\ninterface ExportReply {\n files: { url: string; format: string; surface: string }[];\n error?: { code: 'EXPORT_FAILED' | 'EXPORT_TIMEOUT' };\n}\n\n/** One mounted `<threed-builder>`. */\nexport class BuilderInstance {\n readonly element: HTMLElement;\n private currentProductId: string;\n /** Bumped by logout() and destroy(): async results from an older epoch are dropped. */\n private epoch = 0;\n private destroyed = false;\n private fatal?: BuilderError;\n private loginPending = false;\n private saveRun?: SaveRun;\n /** A clone mapping whose record() failed; retried before the next save resolves. */\n private unrecordedLink?: CustomizationLink;\n /** Set between a save that changed the product id and its tdn-product-cloned. */\n private cloneWait?: Deferred<void>;\n private readonly exports = new Set<AbortController>();\n private readonly listeners: [string, EventListener][] = [];\n\n private constructor(\n private readonly ctx: InstanceContext,\n private readonly options: MountOptions,\n session: BuilderSession\n ) {\n this.currentProductId = session.productId;\n this.element = this.createElement(session);\n }\n\n /** Builds the element in the documented order and inserts it last. */\n static async mount(\n ctx: InstanceContext,\n options: MountOptions\n ): Promise<BuilderInstance> {\n let raw: unknown;\n try {\n raw = await ctx.config.session.get({\n hostProductId: options.hostProductId,\n });\n } catch (cause) {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'The builder session could not be loaded.',\n {\n retryable: true,\n cause,\n }\n );\n }\n const session = validateSession(raw);\n const instance = new BuilderInstance(ctx, options, session);\n options.target.appendChild(instance.element);\n ctx.log('mounted', {\n hostProductId: options.hostProductId,\n productId: session.productId,\n });\n return instance;\n }\n\n /** The 3DN product the builder is editing now: the clone, after a first save. */\n get productId(): string {\n return this.currentProductId;\n }\n\n get hostProductId(): string {\n return this.options.hostProductId;\n }\n\n private createElement(session: BuilderSession): HTMLElement {\n const el = document.createElement(BUILDER_TAG);\n this.attachListeners(el);\n el.setAttribute('product', session.productId);\n el.setAttribute('license-token', session.licenseToken);\n if (session.viewToken) el.setAttribute('view-token', session.viewToken);\n if (session.token && session.refreshToken) {\n setShopperTokens(el, {\n token: session.token,\n refreshToken: session.refreshToken,\n });\n }\n if (this.options.designId)\n el.setAttribute('design-id', this.options.designId);\n if (this.options.mode === 'viewer') el.setAttribute('display-only', 'true');\n if (this.options.externalExport) el.setAttribute('external-export', 'true');\n el.style.display = 'block';\n el.style.width = '100%';\n el.style.height = '100%';\n return el;\n }\n\n private attachListeners(el: HTMLElement): void {\n const on = <T>(name: string, handler: (detail: T) => void) => {\n const listener: EventListener = (event) => {\n // Only this builder's own events: never a neighbour's, never a bubbled copy.\n if (event.target !== el || this.destroyed) return;\n this.ctx.log(name, (event as CustomEvent<T>).detail);\n handler((event as CustomEvent<T>).detail);\n };\n el.addEventListener(name, listener);\n this.listeners.push([name, listener]);\n };\n\n on<LoginRequiredDetail>(\n THREED_BUILDER_LOGIN_REQUIRED_EVENT,\n (d) => void this.answerLogin(d?.action ?? 'save')\n );\n on(\n THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT,\n () => void this.ctx.refreshLicense()\n );\n on<LicenseErrorDetail>(THREED_BUILDER_LICENSE_ERROR_EVENT, (d) =>\n this.onLicenseError(d)\n );\n on<DesignSavedDetail>(\n THREED_BUILDER_DESIGN_SAVED_EVENT,\n (d) => void this.onDesignSaved(d)\n );\n on<DesignSaveFailedDetail>(THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT, (d) =>\n this.finishSave(saveFailedError(d))\n );\n on<ProductClonedDetail>(\n THREED_BUILDER_PRODUCT_CLONED_EVENT,\n (d) => void this.onProductCloned(d)\n );\n on<CloseRequestedDetail>(THREED_BUILDER_CLOSE_REQUESTED_EVENT, (d) =>\n this.ctx.config.onCloseRequested?.(d)\n );\n on<BuilderStatusDetail>(THREED_BUILDER_STATUS_EVENT, (d) =>\n this.ctx.config.onStatusChange?.(d)\n );\n if (this.options.externalExport) {\n on<ExportRequestedDetail>(\n THREED_BUILDER_EXPORT_REQUESTED_EVENT,\n (d) => void this.answerExport(d)\n );\n }\n }\n\n // \u2500\u2500 Host-facing methods \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Shows the builder UI, for a host that mounted it before showing it. */\n open(): void {\n this.assertUsable();\n dispatchOpenThreedBuilder(this.element);\n }\n\n /**\n * Saves the design. Resolves once it is saved and any clone mapping it\n * required has been recorded; concurrent calls share one save.\n */\n save(): Promise<SavedDesign> {\n if (this.destroyed) return Promise.reject(destroyedError());\n if (this.fatal) return Promise.reject(this.fatal);\n if (this.saveRun) return this.saveRun.result.promise;\n\n const run: SaveRun = {\n result: deferred<SavedDesign>(),\n timer: new PausableTimer(this.ctx.config.saveTimeoutMs, () =>\n this.finishSave(\n new BuilderError(\n 'SAVE_TIMEOUT',\n 'Saving the design took too long. Try again.',\n {\n retryable: true,\n }\n )\n )\n ),\n };\n this.saveRun = run;\n if (!this.loginPending) run.timer.start();\n dispatchSaveRequest(this.element);\n return run.result.promise;\n }\n\n /** Calls session.get() again and applies what it returns. */\n async refreshSession(): Promise<void> {\n this.assertUsable();\n const epoch = this.epoch;\n let raw: unknown;\n try {\n raw = await this.ctx.config.session.get({\n hostProductId: this.options.hostProductId,\n });\n } catch (cause) {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'The builder session could not be refreshed.',\n {\n retryable: true,\n cause,\n }\n );\n }\n const session = validateSession(raw);\n if (epoch !== this.epoch || this.destroyed) return;\n this.applyLicense(session.licenseToken);\n if (session.viewToken)\n this.element.setAttribute('view-token', session.viewToken);\n if (session.token && session.refreshToken) {\n setShopperTokens(this.element, {\n token: session.token,\n refreshToken: session.refreshToken,\n });\n }\n this.fatal = undefined;\n }\n\n /** Removes the shopper layer. The site layer stays and the builder revokes the session. */\n logout(): void {\n if (this.destroyed) return;\n this.epoch++;\n this.element.removeAttribute('bearer-token');\n this.element.removeAttribute('refresh-token');\n if (this.loginPending) {\n this.loginPending = false;\n this.finishSave(\n new BuilderError(\n 'LOGIN_CANCELLED',\n 'The shopper logged out before the design was saved.',\n {\n retryable: true,\n }\n )\n );\n }\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.epoch++;\n this.finishSave(destroyedError());\n this.cloneWait?.reject(destroyedError());\n this.cloneWait = undefined;\n for (const controller of this.exports) controller.abort();\n this.destroyed = true;\n for (const [name, listener] of this.listeners) {\n this.element.removeEventListener(name, listener);\n }\n this.listeners.length = 0;\n this.element.remove();\n this.ctx.onDestroyed(this);\n this.ctx.log('destroyed');\n }\n\n /** Used by the controller to hand every instance the shared fresh license token. */\n applyLicense(licenseToken: string): void {\n if (this.destroyed) return;\n this.element.setAttribute('license-token', licenseToken);\n }\n\n get isDestroyed(): boolean {\n return this.destroyed;\n }\n\n // \u2500\u2500 Login round-trip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async answerLogin(action: LoginRequiredAction): Promise<void> {\n if (this.loginPending) return;\n const login = this.ctx.config.login;\n if (!login) {\n dispatchLoginCancelled(this.element, { action });\n return;\n }\n const epoch = this.epoch;\n const stale = () => epoch !== this.epoch || this.destroyed;\n this.setLoginPending(true);\n let redirected = false;\n try {\n for (;;) {\n let tokens: ShopperTokens | null;\n try {\n tokens = validateShopperTokens(await login.getTokens());\n } catch (cause) {\n if (stale()) return;\n this.ctx.log('login.getTokens() failed', { cause: String(cause) });\n const choice = login.onMintFailed\n ? await login.onMintFailed()\n : 'cancel';\n if (stale()) return;\n if (choice === 'retry') continue;\n dispatchLoginCancelled(this.element, { action });\n return;\n }\n if (stale()) return;\n if (tokens) {\n setShopperTokens(this.element, tokens); // resumes the pending action\n return;\n }\n const outcome = await login.onRequired(action);\n if (stale()) return;\n if (outcome === 'logged-in') continue;\n if (outcome === 'redirected') {\n // The page is navigating to the host's login. Never cancel: the\n // builder resumes the save after the shopper comes back.\n redirected = true;\n return;\n }\n dispatchLoginCancelled(this.element, { action });\n return;\n }\n } finally {\n if (!redirected && epoch === this.epoch) this.setLoginPending(false);\n }\n }\n\n private setLoginPending(pending: boolean): void {\n this.loginPending = pending;\n if (!this.saveRun) return;\n if (pending) this.saveRun.timer.pause();\n else this.saveRun.timer.start();\n }\n\n // \u2500\u2500 License \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private onLicenseError(detail: LicenseErrorDetail): void {\n const error = licenseError(detail.code);\n this.fatal = error;\n this.finishSave(error);\n this.ctx.config.onLicenseError?.(error);\n }\n\n // \u2500\u2500 Save and clone recording \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async onDesignSaved(detail: DesignSavedDetail): Promise<void> {\n if (detail.productId !== this.currentProductId && !this.cloneWait) {\n // The save created a clone; tdn-product-cloned follows the thumbnail upload.\n this.cloneWait = deferred<void>();\n }\n // Saves from the builder's own Save button come through here too, with no\n // save() pending: they wait for the clone and its record all the same, so\n // onSaved only ever reports a save the host can act on.\n try {\n if (this.cloneWait) await this.cloneWait.promise;\n if (this.unrecordedLink) await this.recordLink(this.unrecordedLink);\n } catch (error) {\n this.finishSave(\n error instanceof BuilderError\n ? error\n : new BuilderError(\n 'SAVE_FAILED',\n 'The design was saved but could not be linked to this shopper.',\n {\n retryable: true,\n reason: 'customization',\n cause: error,\n }\n )\n );\n return;\n }\n if (this.destroyed) return;\n const saved: SavedDesign = {\n productId: detail.productId,\n version: detail.version,\n };\n this.notifySaved(saved);\n this.finishSave(undefined, saved);\n }\n\n private notifySaved(saved: SavedDesign): void {\n try {\n this.ctx.config.onSaved?.({\n hostProductId: this.options.hostProductId,\n ...saved,\n });\n } catch (cause) {\n this.ctx.log('onSaved threw', { cause: String(cause) });\n }\n }\n\n private async onProductCloned(detail: ProductClonedDetail): Promise<void> {\n const link: CustomizationLink = {\n hostProductId: this.options.hostProductId,\n sourceProductId: detail.originalProductId,\n savedProductId: detail.newProductId,\n };\n this.currentProductId = detail.newProductId; // later saves update the clone\n const wait = this.cloneWait;\n this.cloneWait = undefined;\n try {\n await this.recordLink(link);\n wait?.resolve();\n } catch (error) {\n wait?.reject(error);\n }\n }\n\n private async recordLink(link: CustomizationLink): Promise<void> {\n const customization = this.ctx.config.customization;\n if (!customization) return;\n try {\n await customization.record(link);\n if (this.unrecordedLink === link) this.unrecordedLink = undefined;\n } catch (cause) {\n this.unrecordedLink = link;\n throw new BuilderError(\n 'SAVE_FAILED',\n 'The design was saved but could not be linked to this shopper. Try saving again.',\n { retryable: true, reason: 'customization', cause }\n );\n }\n }\n\n private finishSave(error?: BuilderError, saved?: SavedDesign): void {\n const run = this.saveRun;\n if (!run) return;\n this.saveRun = undefined;\n run.timer.stop();\n if (error) run.result.reject(error);\n else if (saved) run.result.resolve(saved);\n }\n\n // \u2500\u2500 External export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async answerExport(request: ExportRequestedDetail): Promise<void> {\n const external = this.options.externalExport;\n if (!external) return;\n const controller = new AbortController();\n this.exports.add(controller);\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, external.timeoutMs ?? DEFAULT_EXPORT_TIMEOUT_MS);\n\n const abort = new Promise<never>((_, reject) =>\n controller.signal.addEventListener('abort', () =>\n reject(new Error('aborted'))\n )\n );\n try {\n const files = await Promise.race([\n external.run(request, controller.signal),\n abort,\n ]);\n dispatchExportComplete(this.element, { files });\n } catch (cause) {\n const code = timedOut ? 'EXPORT_TIMEOUT' : 'EXPORT_FAILED';\n this.ctx.log('export failed', { code, cause: String(cause) });\n this.replyExportFailure(code);\n } finally {\n clearTimeout(timer);\n this.exports.delete(controller);\n }\n }\n\n /**\n * The builder has no error field on tdn-export-complete yet: it ignores\n * `error`, and the empty file list ends its wait.\n */\n private replyExportFailure(code: 'EXPORT_FAILED' | 'EXPORT_TIMEOUT'): void {\n const detail: ExportReply = { files: [], error: { code } };\n this.element.dispatchEvent(\n new CustomEvent<ExportReply>(THREED_BUILDER_EXPORT_COMPLETE_EVENT, {\n bubbles: false,\n composed: false,\n detail,\n })\n );\n }\n\n private assertUsable(): void {\n if (this.destroyed) throw destroyedError();\n }\n}\n\n/** Refresh first, then bearer: setting the bearer token resumes a pending action. */\nfunction setShopperTokens(el: HTMLElement, tokens: ShopperTokens): void {\n el.setAttribute('refresh-token', tokens.refreshToken);\n el.setAttribute('bearer-token', tokens.token);\n}\n", "import {\n resolveConfig,\n validateMountOptions,\n type ResolvedConfig,\n} from './config';\nimport { createDebugLog, type DebugLog } from './debug';\nimport { BuilderError, configError } from './errors';\nimport {\n BuilderInstance,\n type InstanceContext,\n type SavedDesign,\n} from './instance';\nimport { assertEnvironmentAvailable, loadBuilder } from './loader';\nimport type { BuilderHostConfig, MountOptions } from './config';\n\nexport interface Builder {\n /** Optional preload: loads the builder code once per page. */\n load(): Promise<void>;\n /** Creates and inserts a builder. Later calls add more instances. */\n mount(options: MountOptions): Promise<BuilderInstance>;\n /** The most recently mounted builder that is still alive. */\n readonly current: BuilderInstance | undefined;\n /** Shows the current builder's UI. */\n open(): void;\n /** Saves the current builder's design; see BuilderInstance.save(). */\n save(): Promise<SavedDesign>;\n /** Calls session.get() again for the current builder. */\n refreshSession(): Promise<void>;\n /** Fetches a fresh license token once and applies it to every builder. */\n refreshLicense(): Promise<void>;\n /** Removes the shopper layer from every builder. */\n logout(): void;\n /** Destroys every builder this controller mounted. */\n destroy(): void;\n}\n\nclass BuilderController implements Builder {\n private readonly instances: BuilderInstance[] = [];\n private licenseRefresh?: Promise<void>;\n private readonly log: DebugLog;\n private readonly context: InstanceContext;\n\n constructor(private readonly config: ResolvedConfig) {\n this.log = createDebugLog(config.debug);\n this.context = {\n config,\n log: this.log,\n refreshLicense: () => this.refreshLicense().catch(() => undefined),\n onDestroyed: (instance) => {\n const index = this.instances.indexOf(instance);\n if (index >= 0) this.instances.splice(index, 1);\n },\n };\n }\n\n get current(): BuilderInstance | undefined {\n return this.instances[this.instances.length - 1];\n }\n\n load(): Promise<void> {\n return loadBuilder(this.config.environment, this.config.version);\n }\n\n async mount(options: MountOptions): Promise<BuilderInstance> {\n const valid = validateMountOptions(options);\n await this.load();\n const instance = await BuilderInstance.mount(this.context, valid);\n this.instances.push(instance);\n return instance;\n }\n\n open(): void {\n this.requireCurrent().open();\n }\n\n save(): Promise<SavedDesign> {\n const current = this.current;\n return current ? current.save() : Promise.reject(this.notMounted());\n }\n\n refreshSession(): Promise<void> {\n const current = this.current;\n return current\n ? current.refreshSession()\n : Promise.reject(this.notMounted());\n }\n\n refreshLicense(): Promise<void> {\n if (!this.licenseRefresh) {\n this.licenseRefresh = (async () => {\n try {\n const result = await this.config.session.refreshLicense();\n const token = result?.licenseToken;\n if (typeof token !== 'string' || token === '') {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'session.refreshLicense() must resolve { licenseToken }.'\n );\n }\n for (const instance of this.instances) instance.applyLicense(token);\n this.log('license refreshed');\n } catch (cause) {\n this.log('license refresh failed', { cause: String(cause) });\n throw cause instanceof BuilderError\n ? cause\n : new BuilderError(\n 'SESSION_FAILED',\n 'A fresh license token could not be fetched.',\n {\n retryable: true,\n cause,\n }\n );\n } finally {\n this.licenseRefresh = undefined;\n }\n })();\n }\n return this.licenseRefresh;\n }\n\n logout(): void {\n for (const instance of this.instances) instance.logout();\n this.log('logged out');\n }\n\n destroy(): void {\n for (const instance of [...this.instances]) instance.destroy();\n }\n\n private requireCurrent(): BuilderInstance {\n const current = this.current;\n if (!current) throw this.notMounted();\n return current;\n }\n\n private notMounted(): BuilderError {\n return configError('No builder is mounted. Call mount() first.');\n }\n}\n\n/**\n * Records the host's configuration and callbacks. Touches nothing on the page\n * until load() or mount(), so it is safe to call during server-side rendering.\n */\nexport function createBuilder(config: BuilderHostConfig): Builder {\n const resolved = resolveConfig(config);\n assertEnvironmentAvailable(resolved.environment);\n return new BuilderController(resolved);\n}\n", "/** Query parameter that marks \"back from the login page, resume the save\". */\nexport const RESUME_PARAM = 'tdn_resume';\n\n/** localStorage key where the builder indexes the drafts it kept on this device. */\nconst DRAFT_INDEX_KEY = 'tdn-draft-index';\n\n/**\n * A relative return path for the host's login redirect: the current page with\n * the resume marker. Only same-site paths, so the redirect can never become an\n * open redirect.\n */\nexport function loginReturnPath(): string {\n const url = new URL(window.location.href);\n url.searchParams.set(RESUME_PARAM, '1');\n return url.pathname + url.search + url.hash;\n}\n\n/** True for a path that stays on this site: starts with \"/\" but not \"//\" or \"/\\\". */\nexport function isSafeReturnPath(path: string): boolean {\n return /^\\/(?![/\\\\])/.test(path);\n}\n\n/**\n * True once, when the page was opened by a login redirect carrying the resume\n * marker. Strips the marker from the address bar.\n */\nexport function consumeResumeMarker(): boolean {\n if (typeof window === 'undefined') return false;\n const url = new URL(window.location.href);\n if (url.searchParams.get(RESUME_PARAM) !== '1') return false;\n url.searchParams.delete(RESUME_PARAM);\n window.history.replaceState(\n window.history.state,\n '',\n url.pathname + url.search + url.hash\n );\n return true;\n}\n\n/** True when the builder kept an unsaved draft of this 3DN product on this device. */\nexport function hasDraft(productId: string): boolean {\n if (typeof localStorage === 'undefined') return false;\n try {\n const index = JSON.parse(\n localStorage.getItem(DRAFT_INDEX_KEY) ?? '{}'\n ) as Record<string, { productId?: string } | undefined>;\n return Object.values(index).some((draft) => draft?.productId === productId);\n } catch {\n return false;\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["BuilderError", "_BuilderError", "code", "message", "options", "_a", "_b", "SAVE_FAILURE_MESSAGES", "saveFailedError", "detail", "licenseError", "destroyedError", "configError", "DEFAULT_SAVE_TIMEOUT_MS", "DEFAULT_EXPORT_TIMEOUT_MS", "CONFIG_KEYS", "MOUNT_KEYS", "VERSION_PATTERN", "INSTALLATION_KEY_PATTERN", "isExactVersion", "value", "isRecord", "isFunction", "isPositiveNumber", "containsSecret", "depth", "v", "rejectUnknownKeys", "allowed", "where", "key", "configError", "resolveConfig", "config", "_a", "_b", "_c", "environment", "version", "saveTimeoutMs", "session", "login", "customization", "validateMountOptions", "options", "target", "hostProductId", "mode", "designId", "externalExport", "validateSession", "fail", "what", "BuilderError", "hasToken", "hasRefresh", "validateShopperTokens", "tokens", "TOKEN_KEY", "redact", "value", "depth", "v", "out", "key", "createDebugLog", "enabled", "message", "detail", "BUILDER_TAG", "BUNDLE_HOSTS", "DEFINE_TIMEOUT_MS", "STATE_KEY", "pageState", "holder", "state", "isBrowser", "assertEnvironmentAvailable", "environment", "configError", "loadFailed", "message", "cause", "BuilderError", "resolveVersion", "host", "requested", "response", "info", "version", "isExactVersion", "injectScript", "src", "resolve", "reject", "script", "whenDefined", "timer", "assertCompatible", "loadBuilder", "error", "deferred", "resolve", "reject", "promise", "res", "rej", "PausableTimer", "ms", "onExpire", "BuilderInstance", "_BuilderInstance", "ctx", "options", "session", "raw", "cause", "BuilderError", "validateSession", "instance", "el", "BUILDER_TAG", "setShopperTokens", "on", "name", "handler", "listener", "event", "THREED_BUILDER_LOGIN_REQUIRED_EVENT", "d", "_a", "THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT", "THREED_BUILDER_LICENSE_ERROR_EVENT", "THREED_BUILDER_DESIGN_SAVED_EVENT", "THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT", "saveFailedError", "THREED_BUILDER_PRODUCT_CLONED_EVENT", "THREED_BUILDER_CLOSE_REQUESTED_EVENT", "_b", "THREED_BUILDER_STATUS_EVENT", "THREED_BUILDER_EXPORT_REQUESTED_EVENT", "dispatchOpenThreedBuilder", "destroyedError", "run", "dispatchSaveRequest", "epoch", "controller", "licenseToken", "action", "login", "dispatchLoginCancelled", "stale", "redirected", "tokens", "validateShopperTokens", "choice", "outcome", "pending", "detail", "error", "licenseError", "saved", "link", "wait", "customization", "request", "external", "timedOut", "timer", "DEFAULT_EXPORT_TIMEOUT_MS", "abort", "_", "files", "dispatchExportComplete", "code", "THREED_BUILDER_EXPORT_COMPLETE_EVENT", "BuilderController", "config", "createDebugLog", "instance", "index", "loadBuilder", "options", "valid", "validateMountOptions", "BuilderInstance", "current", "result", "token", "BuilderError", "cause", "configError", "createBuilder", "resolved", "resolveConfig", "assertEnvironmentAvailable", "RESUME_PARAM", "DRAFT_INDEX_KEY", "loginReturnPath", "url", "isSafeReturnPath", "path", "consumeResumeMarker", "hasDraft", "productId", "_a", "index", "draft"]
4
+ "sourcesContent": ["import type {\n DesignSaveFailedDetail,\n DesignSaveFailureReason,\n LicenseErrorCode,\n} from '@three-dn/constants';\n\nexport type BuilderErrorCode =\n | 'CONFIG_INVALID'\n | 'NOT_IN_BROWSER'\n | 'LOAD_FAILED'\n | 'VERSION_CONFLICT'\n | 'SESSION_FAILED'\n | 'LICENSE_ERROR'\n | 'LOGIN_CANCELLED'\n | 'SAVE_FAILED'\n | 'SAVE_TIMEOUT'\n | 'EXPORT_FAILED'\n | 'EXPORT_TIMEOUT'\n | 'DESTROYED';\n\n/** The builder's own reason, plus `customization` when only the record failed. */\nexport type BuilderErrorReason =\n | DesignSaveFailureReason\n | LicenseErrorCode\n | 'customization';\n\nexport interface BuilderErrorOptions {\n retryable?: boolean;\n fatal?: boolean;\n reason?: BuilderErrorReason;\n cause?: unknown;\n}\n\n/**\n * Every failure the SDK reports. Hosts switch on `code`; `message` is safe to\n * show or log and never contains a token.\n */\nexport class BuilderError extends Error {\n readonly code: BuilderErrorCode;\n readonly retryable: boolean;\n /** The mount cannot continue until the host refreshes or recreates it. */\n readonly fatal: boolean;\n readonly reason?: BuilderErrorReason;\n readonly cause?: unknown;\n\n constructor(\n code: BuilderErrorCode,\n message: string,\n options: BuilderErrorOptions = {}\n ) {\n super(message);\n this.name = 'BuilderError';\n this.code = code;\n this.retryable = options.retryable ?? false;\n this.fatal = options.fatal ?? false;\n this.reason = options.reason;\n this.cause = options.cause;\n Object.setPrototypeOf(this, BuilderError.prototype);\n }\n}\n\nconst SAVE_FAILURE_MESSAGES: Record<DesignSaveFailureReason, string> = {\n conflict: 'Someone else saved this design first.',\n forbidden: 'This shopper may not save this design.',\n unauthorized: 'The shopper could not be signed in to save.',\n 'login-cancelled':\n 'The design was not saved because the shopper did not log in.',\n 'installation-inactive': \"This site's 3DN installation is not active.\",\n license: 'This page is not licensed to use the 3DN builder.',\n error: 'The design could not be saved. Check the connection and try again.',\n};\n\nexport function saveFailedError(detail: DesignSaveFailedDetail): BuilderError {\n const message =\n SAVE_FAILURE_MESSAGES[detail.reason] ?? SAVE_FAILURE_MESSAGES.error;\n if (detail.reason === 'login-cancelled') {\n return new BuilderError('LOGIN_CANCELLED', message, {\n retryable: true,\n reason: detail.reason,\n cause: detail,\n });\n }\n return new BuilderError('SAVE_FAILED', message, {\n retryable: detail.retryable,\n reason: detail.reason,\n cause: detail,\n });\n}\n\nexport function licenseError(code: LicenseErrorCode): BuilderError {\n const message =\n code === 'ORIGIN_NOT_ALLOWED'\n ? 'This website is not on the 3DN license. Add its hostname to the license.'\n : 'The 3DN license token was rejected. The license may be suspended, revoked or expired.';\n return new BuilderError('LICENSE_ERROR', message, {\n fatal: true,\n reason: code,\n cause: { code },\n });\n}\n\nexport function destroyedError(): BuilderError {\n return new BuilderError('DESTROYED', 'The builder was destroyed.');\n}\n\nexport function configError(message: string): BuilderError {\n return new BuilderError('CONFIG_INVALID', message, { fatal: true });\n}\n", "import type {\n BuilderStatusDetail,\n CloseRequestedDetail,\n ExportFile,\n ExportRequestedDetail,\n LoginRequiredAction,\n} from '@three-dn/constants';\nimport { BuilderError, configError } from './errors';\n\nexport type BuilderEnvironment = 'production' | 'staging';\nexport type BuilderVersion = 'latest' | `${number}.${number}.${number}`;\nexport type BuilderMode = 'designer' | 'viewer';\n\n/** What the host's session endpoint returns: site access plus the product to open. */\nexport interface BuilderSession {\n licenseToken: string;\n /** The 3DN product to open: the shopper's clone, else the global product. */\n productId: string;\n /** Required when the product to open is private. */\n viewToken?: string;\n /** Optional fast path: shopper tokens for a shopper the host backend verified. */\n token?: string;\n refreshToken?: string;\n}\n\n/** Shopper identity, from the host's login-token endpoint. */\nexport interface ShopperTokens {\n token: string;\n refreshToken: string;\n}\n\nexport type LoginOutcome = 'logged-in' | 'redirected' | 'cancelled';\n\n/** What the host stores when a save created the shopper's clone. */\nexport interface CustomizationLink {\n hostProductId: string;\n sourceProductId: string;\n savedProductId: string;\n}\n\n/** One successful save, however it started: `save()` or the builder's own Save button. */\nexport interface SavedDesignEvent {\n hostProductId: string;\n /** The saved 3DN product: the shopper's clone after a first save. */\n productId: string;\n version: number;\n}\n\nexport interface BuilderHostConfig {\n /** Default `'production'`. */\n environment?: BuilderEnvironment;\n /** `'latest'` or an exact builder release. Default `'latest'`. */\n version?: BuilderVersion;\n /** Default 45 000 ms. */\n saveTimeoutMs?: number;\n session: {\n get(request: { hostProductId: string }): Promise<BuilderSession>;\n refreshLicense(): Promise<Pick<BuilderSession, 'licenseToken'>>;\n };\n login?: {\n /** Answers `tdn-login-required`. Resolve `null` when the endpoint answers 401. */\n getTokens(): Promise<ShopperTokens | null>;\n /** The host's login UX for the action the builder gated. */\n onRequired(action: LoginRequiredAction): Promise<LoginOutcome>;\n /** `getTokens()` failed (5xx, network). Default: `'cancel'`. */\n onMintFailed?(): Promise<'retry' | 'cancel'>;\n };\n customization?: {\n record(link: CustomizationLink): Promise<void>;\n };\n /**\n * Every successful save, including ones the shopper starts from the builder's\n * own Save button. Fires after any clone mapping has been recorded; never\n * when recording failed.\n */\n onSaved?(saved: SavedDesignEvent): void;\n onLicenseError?(error: BuilderError): void;\n onCloseRequested?(detail: CloseRequestedDetail): void;\n onStatusChange?(detail: BuilderStatusDetail): void;\n /** Log lifecycle steps and events to the console. Tokens are always redacted. */\n debug?: boolean;\n}\n\nexport interface ExternalExportOptions {\n run(\n request: ExportRequestedDetail,\n signal: AbortSignal\n ): Promise<ExportFile[]>;\n /** Default 120 000 ms. */\n timeoutMs?: number;\n}\n\nexport interface MountOptions {\n target: HTMLElement;\n /** The host's own product reference, passed to `session.get()`. Never a 3DN id. */\n hostProductId: string;\n /** Default `'designer'`. `'viewer'` is the display-only mode. */\n mode?: BuilderMode;\n designId?: string;\n /** Opt-in, GLB products only. Off unless passed. */\n externalExport?: ExternalExportOptions;\n /**\n * The host page's observability context, a JSON object string the builder\n * reads as `observability-context`: which host app, tenant, site and user\n * its own Sentry events belong to, and the page's trace to join. Set before\n * the element connects; update it with `setObservabilityContext()`. The\n * builder drops keys it does not allow and ignores an invalid value.\n */\n observabilityContext?: string;\n}\n\nexport interface ResolvedConfig extends BuilderHostConfig {\n environment: BuilderEnvironment;\n version: BuilderVersion;\n saveTimeoutMs: number;\n}\n\nexport const DEFAULT_SAVE_TIMEOUT_MS = 45_000;\nexport const DEFAULT_EXPORT_TIMEOUT_MS = 120_000;\n\nconst CONFIG_KEYS = new Set([\n 'environment',\n 'version',\n 'saveTimeoutMs',\n 'session',\n 'login',\n 'customization',\n 'onSaved',\n 'onLicenseError',\n 'onCloseRequested',\n 'onStatusChange',\n 'debug',\n]);\n\nconst MOUNT_KEYS = new Set([\n 'target',\n 'hostProductId',\n 'mode',\n 'designId',\n 'externalExport',\n 'observabilityContext',\n]);\n\nconst VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+$/;\nconst INSTALLATION_KEY_PATTERN = /^tdn_inst_/;\n\nexport function isExactVersion(value: string): boolean {\n return VERSION_PATTERN.test(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isFunction(value: unknown): value is (...args: never[]) => unknown {\n return typeof value === 'function';\n}\n\nfunction isPositiveNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n/** Walks the config looking for anything that looks like an installation key. */\nfunction containsSecret(value: unknown, depth = 0): boolean {\n if (typeof value === 'string') return INSTALLATION_KEY_PATTERN.test(value);\n if (!isRecord(value) || depth > 3) return false;\n return Object.values(value).some((v) => containsSecret(v, depth + 1));\n}\n\nfunction rejectUnknownKeys(\n value: Record<string, unknown>,\n allowed: Set<string>,\n where: string\n): void {\n for (const key of Object.keys(value)) {\n if (allowed.has(key)) continue;\n if (/url|src|host|origin/i.test(key)) {\n throw configError(\n `${where}: \"${key}\" is not allowed. The SDK decides where the builder loads from; choose an environment and a version instead.`\n );\n }\n throw configError(`${where}: unknown option \"${key}\".`);\n }\n}\n\nexport function resolveConfig(config: unknown): ResolvedConfig {\n if (!isRecord(config)) {\n throw configError('createBuilder() needs a configuration object.');\n }\n if (containsSecret(config)) {\n throw configError(\n 'createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.'\n );\n }\n rejectUnknownKeys(config, CONFIG_KEYS, 'createBuilder()');\n\n const environment = config['environment'] ?? 'production';\n if (environment !== 'production' && environment !== 'staging') {\n throw configError(\n `createBuilder(): environment must be \"production\" or \"staging\", got ${JSON.stringify(\n environment\n )}.`\n );\n }\n\n const version = config['version'] ?? 'latest';\n if (\n typeof version !== 'string' ||\n (version !== 'latest' && !isExactVersion(version))\n ) {\n throw configError(\n `createBuilder(): version must be \"latest\" or an exact release such as \"1.2.3\", got ${JSON.stringify(\n version\n )}.`\n );\n }\n\n const saveTimeoutMs = config['saveTimeoutMs'] ?? DEFAULT_SAVE_TIMEOUT_MS;\n if (!isPositiveNumber(saveTimeoutMs)) {\n throw configError(\n 'createBuilder(): saveTimeoutMs must be a positive number of milliseconds.'\n );\n }\n\n const session = config['session'];\n if (\n !isRecord(session) ||\n !isFunction(session['get']) ||\n !isFunction(session['refreshLicense'])\n ) {\n throw configError(\n 'createBuilder(): session.get() and session.refreshLicense() are required. They call your session endpoint.'\n );\n }\n\n const login = config['login'];\n if (login !== undefined) {\n if (\n !isRecord(login) ||\n !isFunction(login['getTokens']) ||\n !isFunction(login['onRequired'])\n ) {\n throw configError(\n 'createBuilder(): login needs getTokens() (your login-token endpoint) and onRequired() (your login UI).'\n );\n }\n if (\n login['onMintFailed'] !== undefined &&\n !isFunction(login['onMintFailed'])\n ) {\n throw configError(\n 'createBuilder(): login.onMintFailed must be a function.'\n );\n }\n }\n\n const customization = config['customization'];\n if (\n customization !== undefined &&\n (!isRecord(customization) || !isFunction(customization['record']))\n ) {\n throw configError(\n 'createBuilder(): customization.record() is required when customization is set. It calls your customization endpoint.'\n );\n }\n\n for (const key of [\n 'onSaved',\n 'onLicenseError',\n 'onCloseRequested',\n 'onStatusChange',\n ] as const) {\n if (config[key] !== undefined && !isFunction(config[key])) {\n throw configError(`createBuilder(): ${key} must be a function.`);\n }\n }\n\n return {\n ...(config as unknown as BuilderHostConfig),\n environment,\n version: version as BuilderVersion,\n saveTimeoutMs,\n };\n}\n\nexport function validateMountOptions(options: unknown): MountOptions {\n if (!isRecord(options)) {\n throw configError('mount() needs an options object.');\n }\n rejectUnknownKeys(options, MOUNT_KEYS, 'mount()');\n\n const target = options['target'];\n if (typeof HTMLElement === 'undefined' || !(target instanceof HTMLElement)) {\n throw configError('mount(): target must be an HTMLElement on the page.');\n }\n const hostProductId = options['hostProductId'];\n if (typeof hostProductId !== 'string' || hostProductId.trim() === '') {\n throw configError(\n 'mount(): hostProductId must be your own, non-empty product reference.'\n );\n }\n const mode = options['mode'] ?? 'designer';\n if (mode !== 'designer' && mode !== 'viewer') {\n throw configError(\n `mount(): mode must be \"designer\" or \"viewer\", got ${JSON.stringify(\n mode\n )}.`\n );\n }\n const designId = options['designId'];\n if (\n designId !== undefined &&\n (typeof designId !== 'string' || designId === '')\n ) {\n throw configError('mount(): designId must be a non-empty string.');\n }\n if (options['observabilityContext'] !== undefined) {\n validateObservabilityContext(options['observabilityContext'], 'mount()');\n }\n const externalExport = options['externalExport'];\n if (externalExport !== undefined) {\n if (!isRecord(externalExport) || !isFunction(externalExport['run'])) {\n throw configError(\n 'mount(): externalExport.run() is required when externalExport is set.'\n );\n }\n if (\n externalExport['timeoutMs'] !== undefined &&\n !isPositiveNumber(externalExport['timeoutMs'])\n ) {\n throw configError(\n 'mount(): externalExport.timeoutMs must be a positive number of milliseconds.'\n );\n }\n }\n return options as unknown as MountOptions;\n}\n\n/** A JSON object string: what the builder's `observability-context` accepts. */\nexport function validateObservabilityContext(\n value: unknown,\n where: string\n): string {\n let parsed: unknown;\n try {\n parsed = typeof value === 'string' ? JSON.parse(value) : undefined;\n } catch {\n parsed = undefined;\n }\n if (!isRecord(parsed) || Array.isArray(parsed)) {\n throw configError(\n `${where}: observabilityContext must be a JSON object string.`\n );\n }\n return value as string;\n}\n\n/** Checks the shape of a session response before anything reaches the element. */\nexport function validateSession(session: unknown): BuilderSession {\n const fail = (what: string) =>\n new BuilderError(\n 'SESSION_FAILED',\n `session.get() returned an invalid session: ${what}.`,\n {\n retryable: false,\n }\n );\n if (!isRecord(session)) throw fail('expected an object');\n if (\n typeof session['licenseToken'] !== 'string' ||\n session['licenseToken'] === ''\n ) {\n throw fail('licenseToken is missing');\n }\n if (typeof session['productId'] !== 'string' || session['productId'] === '') {\n throw fail('productId is missing');\n }\n if (\n session['viewToken'] != null &&\n typeof session['viewToken'] !== 'string'\n ) {\n throw fail('viewToken must be a string');\n }\n const hasToken = session['token'] != null;\n const hasRefresh = session['refreshToken'] != null;\n if (hasToken !== hasRefresh) {\n throw fail('token and refreshToken must be returned together');\n }\n if (\n hasToken &&\n (typeof session['token'] !== 'string' ||\n typeof session['refreshToken'] !== 'string')\n ) {\n throw fail('token and refreshToken must be strings');\n }\n return session as unknown as BuilderSession;\n}\n\nexport function validateShopperTokens(tokens: unknown): ShopperTokens | null {\n if (tokens === null) return null;\n if (\n !isRecord(tokens) ||\n typeof tokens['token'] !== 'string' ||\n tokens['token'] === '' ||\n typeof tokens['refreshToken'] !== 'string' ||\n tokens['refreshToken'] === ''\n ) {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'login.getTokens() must resolve { token, refreshToken } or null.'\n );\n }\n return tokens as unknown as ShopperTokens;\n}\n", "const TOKEN_KEY = /token|secret|key|authorization/i;\n\n/** Copies a value for logging with every token-like field replaced. */\nexport function redact(value: unknown, depth = 0): unknown {\n if (value === null || typeof value !== 'object' || depth > 4) return value;\n if (Array.isArray(value)) return value.map((v) => redact(v, depth + 1));\n const out: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(value)) {\n out[key] =\n TOKEN_KEY.test(key) && v != null ? '[redacted]' : redact(v, depth + 1);\n }\n return out;\n}\n\nexport type DebugLog = (message: string, detail?: unknown) => void;\n\nexport function createDebugLog(enabled: boolean | undefined): DebugLog {\n if (!enabled) return () => undefined;\n return (message, detail) => {\n if (detail === undefined) console.debug(`[3DN] ${message}`);\n else console.debug(`[3DN] ${message}`, redact(detail));\n };\n}\n", "import type { BuilderEnvironment, BuilderVersion } from './config';\nimport { isExactVersion } from './config';\nimport { BuilderError, configError } from './errors';\n\nexport const BUILDER_TAG = 'threed-builder';\n\n/**\n * Where each environment's builder is served from. SDK-owned: hosts choose an\n * environment, never a location. Staging stays unset until its location is\n * confirmed; createBuilder() refuses `environment: 'staging'` until then.\n */\nconst BUNDLE_HOSTS: Record<BuilderEnvironment, string | undefined> = {\n production: 'https://3d-builder.3dn.app',\n staging: undefined,\n};\n\nconst DEFINE_TIMEOUT_MS = 30_000;\n\n/** Shared by every copy of the SDK on the page: one custom element registry. */\ninterface PageLoaderState {\n environment?: BuilderEnvironment;\n /** The exact version loaded, once known. */\n version?: string;\n loading?: Promise<void>;\n}\n\nconst STATE_KEY = Symbol.for('@3dn-builder/builder/loader');\n\nfunction pageState(): PageLoaderState {\n const holder = globalThis as unknown as Record<\n symbol,\n PageLoaderState | undefined\n >;\n let state = holder[STATE_KEY];\n if (!state) {\n state = {};\n holder[STATE_KEY] = state;\n }\n return state;\n}\n\nexport function isBrowser(): boolean {\n return (\n typeof window !== 'undefined' &&\n typeof document !== 'undefined' &&\n typeof customElements !== 'undefined'\n );\n}\n\nexport function assertEnvironmentAvailable(\n environment: BuilderEnvironment\n): void {\n if (!BUNDLE_HOSTS[environment]) {\n throw configError(\n `createBuilder(): the \"${environment}\" environment is not available in this SDK release.`\n );\n }\n}\n\nfunction loadFailed(message: string, cause?: unknown): BuilderError {\n return new BuilderError('LOAD_FAILED', message, { retryable: true, cause });\n}\n\nasync function resolveVersion(\n host: string,\n requested: BuilderVersion\n): Promise<string> {\n if (requested !== 'latest') return requested;\n let response: Response;\n try {\n response = await fetch(`${host}/version-info.json`, {\n cache: 'no-store',\n credentials: 'omit',\n });\n } catch (cause) {\n throw loadFailed('The 3DN builder could not be reached.', cause);\n }\n if (!response.ok) {\n throw loadFailed(\n `The 3DN builder release could not be resolved (HTTP ${response.status}).`\n );\n }\n const info = (await response.json().catch(() => null)) as {\n version?: unknown;\n } | null;\n const version = info?.version;\n if (typeof version !== 'string' || !isExactVersion(version)) {\n throw loadFailed('The 3DN builder release information was not valid.');\n }\n return version;\n}\n\nfunction injectScript(src: string): Promise<HTMLScriptElement> {\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.type = 'module';\n script.src = src;\n script.crossOrigin = 'anonymous';\n script.onload = () => resolve(script);\n script.onerror = () => {\n script.remove();\n reject(loadFailed('The 3DN builder failed to load.'));\n };\n document.head.appendChild(script);\n });\n}\n\nfunction whenDefined(): Promise<void> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(\n () => reject(loadFailed('The 3DN builder loaded but did not start.')),\n DEFINE_TIMEOUT_MS\n );\n customElements.whenDefined(BUILDER_TAG).then(() => {\n clearTimeout(timer);\n resolve();\n }, reject);\n });\n}\n\nfunction assertCompatible(\n state: PageLoaderState,\n environment: BuilderEnvironment,\n requested: BuilderVersion\n): void {\n if (state.environment && state.environment !== environment) {\n throw new BuilderError(\n 'VERSION_CONFLICT',\n `The ${state.environment} builder already runs on this page; ${environment} cannot load beside it.`,\n { fatal: true }\n );\n }\n if (requested !== 'latest' && state.version && state.version !== requested) {\n throw new BuilderError(\n 'VERSION_CONFLICT',\n `Builder ${state.version} already runs on this page; ${requested} cannot load beside it.`,\n { fatal: true }\n );\n }\n}\n\n/**\n * Loads the builder once per page. A failed load leaves nothing behind, so the\n * next call tries again.\n */\nexport async function loadBuilder(\n environment: BuilderEnvironment,\n requested: BuilderVersion\n): Promise<void> {\n if (!isBrowser()) {\n throw new BuilderError(\n 'NOT_IN_BROWSER',\n 'The 3DN builder can only load in a browser.'\n );\n }\n const state = pageState();\n\n if (state.loading) {\n await state.loading;\n assertCompatible(state, environment, requested);\n return;\n }\n // Defined by something other than this SDK (an older embed, or the testing\n // fake): its version is unknown, so use it as it is.\n if (customElements.get(BUILDER_TAG)) return;\n\n const host = BUNDLE_HOSTS[environment];\n if (!host) {\n throw configError(\n `The \"${environment}\" environment is not available in this SDK release.`\n );\n }\n\n state.environment = environment;\n state.loading = (async () => {\n const version = await resolveVersion(host, requested);\n state.version = version;\n const script = await injectScript(`${host}/builder-${version}.js`);\n try {\n await whenDefined();\n } catch (error) {\n script.remove();\n throw error;\n }\n })();\n\n try {\n await state.loading;\n } catch (error) {\n state.loading = undefined;\n state.environment = undefined;\n state.version = undefined;\n throw error instanceof BuilderError\n ? error\n : loadFailed('The 3DN builder failed to load.', error);\n }\n assertCompatible(state, environment, requested);\n}\n", "import {\n THREED_BUILDER_CLOSE_REQUESTED_EVENT,\n THREED_BUILDER_DESIGN_SAVED_EVENT,\n THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT,\n THREED_BUILDER_EXPORT_COMPLETE_EVENT,\n THREED_BUILDER_EXPORT_REQUESTED_EVENT,\n THREED_BUILDER_LICENSE_ERROR_EVENT,\n THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT,\n THREED_BUILDER_LOGIN_REQUIRED_EVENT,\n THREED_BUILDER_PRODUCT_CLONED_EVENT,\n THREED_BUILDER_STATUS_EVENT,\n dispatchExportComplete,\n dispatchLoginCancelled,\n dispatchOpenThreedBuilder,\n dispatchSaveRequest,\n type BuilderStatusDetail,\n type CloseRequestedDetail,\n type DesignSaveFailedDetail,\n type DesignSavedDetail,\n type ExportRequestedDetail,\n type LicenseErrorDetail,\n type LoginRequiredAction,\n type LoginRequiredDetail,\n type ProductClonedDetail,\n} from '@three-dn/constants';\nimport {\n DEFAULT_EXPORT_TIMEOUT_MS,\n validateObservabilityContext,\n validateSession,\n validateShopperTokens,\n type BuilderSession,\n type CustomizationLink,\n type MountOptions,\n type ResolvedConfig,\n type ShopperTokens,\n} from './config';\nimport type { DebugLog } from './debug';\nimport {\n BuilderError,\n destroyedError,\n licenseError,\n saveFailedError,\n} from './errors';\nimport { BUILDER_TAG } from './loader';\n\n/** What `save()` resolves with. `productId` is always the one to put in the cart. */\nexport interface SavedDesign {\n productId: string;\n version: number;\n}\n\nexport interface InstanceContext {\n config: ResolvedConfig;\n log: DebugLog;\n /** Single-flight license refresh shared by every instance of a controller. */\n refreshLicense(): Promise<void>;\n onDestroyed(instance: BuilderInstance): void;\n}\n\ninterface Deferred<T> {\n promise: Promise<T>;\n resolve(value: T): void;\n reject(error: unknown): void;\n}\n\nfunction deferred<T>(): Deferred<T> {\n let resolve!: (value: T) => void;\n let reject!: (error: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n // A rejection nobody awaits yet must not surface as unhandled.\n promise.catch(() => undefined);\n return { promise, resolve, reject };\n}\n\n/** A timer that stops counting while a login round-trip is pending. */\nclass PausableTimer {\n private handle?: ReturnType<typeof setTimeout>;\n private startedAt = 0;\n private remaining: number;\n\n constructor(ms: number, private readonly onExpire: () => void) {\n this.remaining = ms;\n }\n\n start(): void {\n if (this.handle !== undefined) return;\n this.startedAt = Date.now();\n this.handle = setTimeout(this.onExpire, this.remaining);\n }\n\n pause(): void {\n if (this.handle === undefined) return;\n clearTimeout(this.handle);\n this.handle = undefined;\n this.remaining = Math.max(\n 0,\n this.remaining - (Date.now() - this.startedAt)\n );\n }\n\n stop(): void {\n if (this.handle !== undefined) clearTimeout(this.handle);\n this.handle = undefined;\n }\n}\n\ninterface SaveRun {\n result: Deferred<SavedDesign>;\n timer: PausableTimer;\n}\n\ninterface ExportReply {\n files: { url: string; format: string; surface: string }[];\n error?: { code: 'EXPORT_FAILED' | 'EXPORT_TIMEOUT' };\n}\n\n/** One mounted `<threed-builder>`. */\nexport class BuilderInstance {\n readonly element: HTMLElement;\n private currentProductId: string;\n /** Bumped by logout() and destroy(): async results from an older epoch are dropped. */\n private epoch = 0;\n private destroyed = false;\n private fatal?: BuilderError;\n private loginPending = false;\n private saveRun?: SaveRun;\n /** A clone mapping whose record() failed; retried before the next save resolves. */\n private unrecordedLink?: CustomizationLink;\n /** Set between a save that changed the product id and its tdn-product-cloned. */\n private cloneWait?: Deferred<void>;\n private readonly exports = new Set<AbortController>();\n private readonly listeners: [string, EventListener][] = [];\n\n private constructor(\n private readonly ctx: InstanceContext,\n private readonly options: MountOptions,\n session: BuilderSession\n ) {\n this.currentProductId = session.productId;\n this.element = this.createElement(session);\n }\n\n /** Builds the element in the documented order and inserts it last. */\n static async mount(\n ctx: InstanceContext,\n options: MountOptions\n ): Promise<BuilderInstance> {\n let raw: unknown;\n try {\n raw = await ctx.config.session.get({\n hostProductId: options.hostProductId,\n });\n } catch (cause) {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'The builder session could not be loaded.',\n {\n retryable: true,\n cause,\n }\n );\n }\n const session = validateSession(raw);\n const instance = new BuilderInstance(ctx, options, session);\n options.target.appendChild(instance.element);\n ctx.log('mounted', {\n hostProductId: options.hostProductId,\n productId: session.productId,\n });\n return instance;\n }\n\n /** The 3DN product the builder is editing now: the clone, after a first save. */\n get productId(): string {\n return this.currentProductId;\n }\n\n get hostProductId(): string {\n return this.options.hostProductId;\n }\n\n private createElement(session: BuilderSession): HTMLElement {\n const el = document.createElement(BUILDER_TAG);\n this.attachListeners(el);\n el.setAttribute('product', session.productId);\n el.setAttribute('license-token', session.licenseToken);\n if (session.viewToken) el.setAttribute('view-token', session.viewToken);\n if (session.token && session.refreshToken) {\n setShopperTokens(el, {\n token: session.token,\n refreshToken: session.refreshToken,\n });\n }\n if (this.options.designId)\n el.setAttribute('design-id', this.options.designId);\n if (this.options.mode === 'viewer') el.setAttribute('display-only', 'true');\n if (this.options.externalExport) el.setAttribute('external-export', 'true');\n if (this.options.observabilityContext) {\n el.setAttribute(\n 'observability-context',\n this.options.observabilityContext\n );\n }\n el.style.display = 'block';\n el.style.width = '100%';\n el.style.height = '100%';\n return el;\n }\n\n private attachListeners(el: HTMLElement): void {\n const on = <T>(name: string, handler: (detail: T) => void) => {\n const listener: EventListener = (event) => {\n // Only this builder's own events: never a neighbour's, never a bubbled copy.\n if (event.target !== el || this.destroyed) return;\n this.ctx.log(name, (event as CustomEvent<T>).detail);\n handler((event as CustomEvent<T>).detail);\n };\n el.addEventListener(name, listener);\n this.listeners.push([name, listener]);\n };\n\n on<LoginRequiredDetail>(\n THREED_BUILDER_LOGIN_REQUIRED_EVENT,\n (d) => void this.answerLogin(d?.action ?? 'save')\n );\n on(\n THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT,\n () => void this.ctx.refreshLicense()\n );\n on<LicenseErrorDetail>(THREED_BUILDER_LICENSE_ERROR_EVENT, (d) =>\n this.onLicenseError(d)\n );\n on<DesignSavedDetail>(\n THREED_BUILDER_DESIGN_SAVED_EVENT,\n (d) => void this.onDesignSaved(d)\n );\n on<DesignSaveFailedDetail>(THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT, (d) =>\n this.finishSave(saveFailedError(d))\n );\n on<ProductClonedDetail>(\n THREED_BUILDER_PRODUCT_CLONED_EVENT,\n (d) => void this.onProductCloned(d)\n );\n on<CloseRequestedDetail>(THREED_BUILDER_CLOSE_REQUESTED_EVENT, (d) =>\n this.ctx.config.onCloseRequested?.(d)\n );\n on<BuilderStatusDetail>(THREED_BUILDER_STATUS_EVENT, (d) =>\n this.ctx.config.onStatusChange?.(d)\n );\n if (this.options.externalExport) {\n on<ExportRequestedDetail>(\n THREED_BUILDER_EXPORT_REQUESTED_EVENT,\n (d) => void this.answerExport(d)\n );\n }\n }\n\n // \u2500\u2500 Host-facing methods \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Shows the builder UI, for a host that mounted it before showing it. */\n open(): void {\n this.assertUsable();\n dispatchOpenThreedBuilder(this.element);\n }\n\n /**\n * Saves the design. Resolves once it is saved and any clone mapping it\n * required has been recorded; concurrent calls share one save.\n */\n save(): Promise<SavedDesign> {\n if (this.destroyed) return Promise.reject(destroyedError());\n if (this.fatal) return Promise.reject(this.fatal);\n if (this.saveRun) return this.saveRun.result.promise;\n\n const run: SaveRun = {\n result: deferred<SavedDesign>(),\n timer: new PausableTimer(this.ctx.config.saveTimeoutMs, () =>\n this.finishSave(\n new BuilderError(\n 'SAVE_TIMEOUT',\n 'Saving the design took too long. Try again.',\n {\n retryable: true,\n }\n )\n )\n ),\n };\n this.saveRun = run;\n if (!this.loginPending) run.timer.start();\n dispatchSaveRequest(this.element);\n return run.result.promise;\n }\n\n /** Calls session.get() again and applies what it returns. */\n async refreshSession(): Promise<void> {\n this.assertUsable();\n const epoch = this.epoch;\n let raw: unknown;\n try {\n raw = await this.ctx.config.session.get({\n hostProductId: this.options.hostProductId,\n });\n } catch (cause) {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'The builder session could not be refreshed.',\n {\n retryable: true,\n cause,\n }\n );\n }\n const session = validateSession(raw);\n if (epoch !== this.epoch || this.destroyed) return;\n this.applyLicense(session.licenseToken);\n if (session.viewToken)\n this.element.setAttribute('view-token', session.viewToken);\n if (session.token && session.refreshToken) {\n setShopperTokens(this.element, {\n token: session.token,\n refreshToken: session.refreshToken,\n });\n }\n this.fatal = undefined;\n }\n\n /** Removes the shopper layer. The site layer stays and the builder revokes the session. */\n logout(): void {\n if (this.destroyed) return;\n this.epoch++;\n this.element.removeAttribute('bearer-token');\n this.element.removeAttribute('refresh-token');\n if (this.loginPending) {\n this.loginPending = false;\n this.finishSave(\n new BuilderError(\n 'LOGIN_CANCELLED',\n 'The shopper logged out before the design was saved.',\n {\n retryable: true,\n }\n )\n );\n }\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.epoch++;\n this.finishSave(destroyedError());\n this.cloneWait?.reject(destroyedError());\n this.cloneWait = undefined;\n for (const controller of this.exports) controller.abort();\n this.destroyed = true;\n for (const [name, listener] of this.listeners) {\n this.element.removeEventListener(name, listener);\n }\n this.listeners.length = 0;\n this.element.remove();\n this.ctx.onDestroyed(this);\n this.ctx.log('destroyed');\n }\n\n /**\n * Replaces the host page's observability context, for example once the\n * page's own Sentry has started and a trace exists to join.\n */\n setObservabilityContext(context: string): void {\n this.assertUsable();\n this.element.setAttribute(\n 'observability-context',\n validateObservabilityContext(context, 'setObservabilityContext()')\n );\n }\n\n /** Used by the controller to hand every instance the shared fresh license token. */\n applyLicense(licenseToken: string): void {\n if (this.destroyed) return;\n this.element.setAttribute('license-token', licenseToken);\n }\n\n get isDestroyed(): boolean {\n return this.destroyed;\n }\n\n // \u2500\u2500 Login round-trip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async answerLogin(action: LoginRequiredAction): Promise<void> {\n if (this.loginPending) return;\n const login = this.ctx.config.login;\n if (!login) {\n dispatchLoginCancelled(this.element, { action });\n return;\n }\n const epoch = this.epoch;\n const stale = () => epoch !== this.epoch || this.destroyed;\n this.setLoginPending(true);\n let redirected = false;\n try {\n for (;;) {\n let tokens: ShopperTokens | null;\n try {\n tokens = validateShopperTokens(await login.getTokens());\n } catch (cause) {\n if (stale()) return;\n this.ctx.log('login.getTokens() failed', { cause: String(cause) });\n const choice = login.onMintFailed\n ? await login.onMintFailed()\n : 'cancel';\n if (stale()) return;\n if (choice === 'retry') continue;\n dispatchLoginCancelled(this.element, { action });\n return;\n }\n if (stale()) return;\n if (tokens) {\n setShopperTokens(this.element, tokens); // resumes the pending action\n return;\n }\n const outcome = await login.onRequired(action);\n if (stale()) return;\n if (outcome === 'logged-in') continue;\n if (outcome === 'redirected') {\n // The page is navigating to the host's login. Never cancel: the\n // builder resumes the save after the shopper comes back.\n redirected = true;\n return;\n }\n dispatchLoginCancelled(this.element, { action });\n return;\n }\n } finally {\n if (!redirected && epoch === this.epoch) this.setLoginPending(false);\n }\n }\n\n private setLoginPending(pending: boolean): void {\n this.loginPending = pending;\n if (!this.saveRun) return;\n if (pending) this.saveRun.timer.pause();\n else this.saveRun.timer.start();\n }\n\n // \u2500\u2500 License \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private onLicenseError(detail: LicenseErrorDetail): void {\n const error = licenseError(detail.code);\n this.fatal = error;\n this.finishSave(error);\n this.ctx.config.onLicenseError?.(error);\n }\n\n // \u2500\u2500 Save and clone recording \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async onDesignSaved(detail: DesignSavedDetail): Promise<void> {\n if (detail.productId !== this.currentProductId && !this.cloneWait) {\n // The save created a clone; tdn-product-cloned follows the thumbnail upload.\n this.cloneWait = deferred<void>();\n }\n // Saves from the builder's own Save button come through here too, with no\n // save() pending: they wait for the clone and its record all the same, so\n // onSaved only ever reports a save the host can act on.\n try {\n if (this.cloneWait) await this.cloneWait.promise;\n if (this.unrecordedLink) await this.recordLink(this.unrecordedLink);\n } catch (error) {\n this.finishSave(\n error instanceof BuilderError\n ? error\n : new BuilderError(\n 'SAVE_FAILED',\n 'The design was saved but could not be linked to this shopper.',\n {\n retryable: true,\n reason: 'customization',\n cause: error,\n }\n )\n );\n return;\n }\n if (this.destroyed) return;\n const saved: SavedDesign = {\n productId: detail.productId,\n version: detail.version,\n };\n this.notifySaved(saved);\n this.finishSave(undefined, saved);\n }\n\n private notifySaved(saved: SavedDesign): void {\n try {\n this.ctx.config.onSaved?.({\n hostProductId: this.options.hostProductId,\n ...saved,\n });\n } catch (cause) {\n this.ctx.log('onSaved threw', { cause: String(cause) });\n }\n }\n\n private async onProductCloned(detail: ProductClonedDetail): Promise<void> {\n const link: CustomizationLink = {\n hostProductId: this.options.hostProductId,\n sourceProductId: detail.originalProductId,\n savedProductId: detail.newProductId,\n };\n this.currentProductId = detail.newProductId; // later saves update the clone\n const wait = this.cloneWait;\n this.cloneWait = undefined;\n try {\n await this.recordLink(link);\n wait?.resolve();\n } catch (error) {\n wait?.reject(error);\n }\n }\n\n private async recordLink(link: CustomizationLink): Promise<void> {\n const customization = this.ctx.config.customization;\n if (!customization) return;\n try {\n await customization.record(link);\n if (this.unrecordedLink === link) this.unrecordedLink = undefined;\n } catch (cause) {\n this.unrecordedLink = link;\n throw new BuilderError(\n 'SAVE_FAILED',\n 'The design was saved but could not be linked to this shopper. Try saving again.',\n { retryable: true, reason: 'customization', cause }\n );\n }\n }\n\n private finishSave(error?: BuilderError, saved?: SavedDesign): void {\n const run = this.saveRun;\n if (!run) return;\n this.saveRun = undefined;\n run.timer.stop();\n if (error) run.result.reject(error);\n else if (saved) run.result.resolve(saved);\n }\n\n // \u2500\u2500 External export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async answerExport(request: ExportRequestedDetail): Promise<void> {\n const external = this.options.externalExport;\n if (!external) return;\n const controller = new AbortController();\n this.exports.add(controller);\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, external.timeoutMs ?? DEFAULT_EXPORT_TIMEOUT_MS);\n\n const abort = new Promise<never>((_, reject) =>\n controller.signal.addEventListener('abort', () =>\n reject(new Error('aborted'))\n )\n );\n try {\n const files = await Promise.race([\n external.run(request, controller.signal),\n abort,\n ]);\n dispatchExportComplete(this.element, { files });\n } catch (cause) {\n const code = timedOut ? 'EXPORT_TIMEOUT' : 'EXPORT_FAILED';\n this.ctx.log('export failed', { code, cause: String(cause) });\n this.replyExportFailure(code);\n } finally {\n clearTimeout(timer);\n this.exports.delete(controller);\n }\n }\n\n /**\n * The builder has no error field on tdn-export-complete yet: it ignores\n * `error`, and the empty file list ends its wait.\n */\n private replyExportFailure(code: 'EXPORT_FAILED' | 'EXPORT_TIMEOUT'): void {\n const detail: ExportReply = { files: [], error: { code } };\n this.element.dispatchEvent(\n new CustomEvent<ExportReply>(THREED_BUILDER_EXPORT_COMPLETE_EVENT, {\n bubbles: false,\n composed: false,\n detail,\n })\n );\n }\n\n private assertUsable(): void {\n if (this.destroyed) throw destroyedError();\n }\n}\n\n/** Refresh first, then bearer: setting the bearer token resumes a pending action. */\nfunction setShopperTokens(el: HTMLElement, tokens: ShopperTokens): void {\n el.setAttribute('refresh-token', tokens.refreshToken);\n el.setAttribute('bearer-token', tokens.token);\n}\n", "import {\n resolveConfig,\n validateMountOptions,\n type ResolvedConfig,\n} from './config';\nimport { createDebugLog, type DebugLog } from './debug';\nimport { BuilderError, configError } from './errors';\nimport {\n BuilderInstance,\n type InstanceContext,\n type SavedDesign,\n} from './instance';\nimport { assertEnvironmentAvailable, loadBuilder } from './loader';\nimport type { BuilderHostConfig, MountOptions } from './config';\n\nexport interface Builder {\n /** Optional preload: loads the builder code once per page. */\n load(): Promise<void>;\n /** Creates and inserts a builder. Later calls add more instances. */\n mount(options: MountOptions): Promise<BuilderInstance>;\n /** The most recently mounted builder that is still alive. */\n readonly current: BuilderInstance | undefined;\n /** Shows the current builder's UI. */\n open(): void;\n /** Saves the current builder's design; see BuilderInstance.save(). */\n save(): Promise<SavedDesign>;\n /** Calls session.get() again for the current builder. */\n refreshSession(): Promise<void>;\n /** Fetches a fresh license token once and applies it to every builder. */\n refreshLicense(): Promise<void>;\n /** Removes the shopper layer from every builder. */\n logout(): void;\n /** Destroys every builder this controller mounted. */\n destroy(): void;\n}\n\nclass BuilderController implements Builder {\n private readonly instances: BuilderInstance[] = [];\n private licenseRefresh?: Promise<void>;\n private readonly log: DebugLog;\n private readonly context: InstanceContext;\n\n constructor(private readonly config: ResolvedConfig) {\n this.log = createDebugLog(config.debug);\n this.context = {\n config,\n log: this.log,\n refreshLicense: () => this.refreshLicense().catch(() => undefined),\n onDestroyed: (instance) => {\n const index = this.instances.indexOf(instance);\n if (index >= 0) this.instances.splice(index, 1);\n },\n };\n }\n\n get current(): BuilderInstance | undefined {\n return this.instances[this.instances.length - 1];\n }\n\n load(): Promise<void> {\n return loadBuilder(this.config.environment, this.config.version);\n }\n\n async mount(options: MountOptions): Promise<BuilderInstance> {\n const valid = validateMountOptions(options);\n await this.load();\n const instance = await BuilderInstance.mount(this.context, valid);\n this.instances.push(instance);\n return instance;\n }\n\n open(): void {\n this.requireCurrent().open();\n }\n\n save(): Promise<SavedDesign> {\n const current = this.current;\n return current ? current.save() : Promise.reject(this.notMounted());\n }\n\n refreshSession(): Promise<void> {\n const current = this.current;\n return current\n ? current.refreshSession()\n : Promise.reject(this.notMounted());\n }\n\n refreshLicense(): Promise<void> {\n if (!this.licenseRefresh) {\n this.licenseRefresh = (async () => {\n try {\n const result = await this.config.session.refreshLicense();\n const token = result?.licenseToken;\n if (typeof token !== 'string' || token === '') {\n throw new BuilderError(\n 'SESSION_FAILED',\n 'session.refreshLicense() must resolve { licenseToken }.'\n );\n }\n for (const instance of this.instances) instance.applyLicense(token);\n this.log('license refreshed');\n } catch (cause) {\n this.log('license refresh failed', { cause: String(cause) });\n throw cause instanceof BuilderError\n ? cause\n : new BuilderError(\n 'SESSION_FAILED',\n 'A fresh license token could not be fetched.',\n {\n retryable: true,\n cause,\n }\n );\n } finally {\n this.licenseRefresh = undefined;\n }\n })();\n }\n return this.licenseRefresh;\n }\n\n logout(): void {\n for (const instance of this.instances) instance.logout();\n this.log('logged out');\n }\n\n destroy(): void {\n for (const instance of [...this.instances]) instance.destroy();\n }\n\n private requireCurrent(): BuilderInstance {\n const current = this.current;\n if (!current) throw this.notMounted();\n return current;\n }\n\n private notMounted(): BuilderError {\n return configError('No builder is mounted. Call mount() first.');\n }\n}\n\n/**\n * Records the host's configuration and callbacks. Touches nothing on the page\n * until load() or mount(), so it is safe to call during server-side rendering.\n */\nexport function createBuilder(config: BuilderHostConfig): Builder {\n const resolved = resolveConfig(config);\n assertEnvironmentAvailable(resolved.environment);\n return new BuilderController(resolved);\n}\n", "/** Query parameter that marks \"back from the login page, resume the save\". */\nexport const RESUME_PARAM = 'tdn_resume';\n\n/** localStorage key where the builder indexes the drafts it kept on this device. */\nconst DRAFT_INDEX_KEY = 'tdn-draft-index';\n\n/**\n * A relative return path for the host's login redirect: the current page with\n * the resume marker. Only same-site paths, so the redirect can never become an\n * open redirect.\n */\nexport function loginReturnPath(): string {\n const url = new URL(window.location.href);\n url.searchParams.set(RESUME_PARAM, '1');\n return url.pathname + url.search + url.hash;\n}\n\n/** True for a path that stays on this site: starts with \"/\" but not \"//\" or \"/\\\". */\nexport function isSafeReturnPath(path: string): boolean {\n return /^\\/(?![/\\\\])/.test(path);\n}\n\n/**\n * True once, when the page was opened by a login redirect carrying the resume\n * marker. Strips the marker from the address bar.\n */\nexport function consumeResumeMarker(): boolean {\n if (typeof window === 'undefined') return false;\n const url = new URL(window.location.href);\n if (url.searchParams.get(RESUME_PARAM) !== '1') return false;\n url.searchParams.delete(RESUME_PARAM);\n window.history.replaceState(\n window.history.state,\n '',\n url.pathname + url.search + url.hash\n );\n return true;\n}\n\n/** True when the builder kept an unsaved draft of this 3DN product on this device. */\nexport function hasDraft(productId: string): boolean {\n if (typeof localStorage === 'undefined') return false;\n try {\n const index = JSON.parse(\n localStorage.getItem(DRAFT_INDEX_KEY) ?? '{}'\n ) as Record<string, { productId?: string } | undefined>;\n return Object.values(index).some((draft) => draft?.productId === productId);\n } catch {\n return false;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["BuilderError", "_BuilderError", "code", "message", "options", "_a", "_b", "SAVE_FAILURE_MESSAGES", "saveFailedError", "detail", "licenseError", "destroyedError", "configError", "DEFAULT_SAVE_TIMEOUT_MS", "DEFAULT_EXPORT_TIMEOUT_MS", "CONFIG_KEYS", "MOUNT_KEYS", "VERSION_PATTERN", "INSTALLATION_KEY_PATTERN", "isExactVersion", "value", "isRecord", "isFunction", "isPositiveNumber", "containsSecret", "depth", "v", "rejectUnknownKeys", "allowed", "where", "key", "configError", "resolveConfig", "config", "_a", "_b", "_c", "environment", "version", "saveTimeoutMs", "session", "login", "customization", "validateMountOptions", "options", "target", "hostProductId", "mode", "designId", "validateObservabilityContext", "externalExport", "parsed", "validateSession", "fail", "what", "BuilderError", "hasToken", "hasRefresh", "validateShopperTokens", "tokens", "TOKEN_KEY", "redact", "value", "depth", "v", "out", "key", "createDebugLog", "enabled", "message", "detail", "BUILDER_TAG", "BUNDLE_HOSTS", "DEFINE_TIMEOUT_MS", "STATE_KEY", "pageState", "holder", "state", "isBrowser", "assertEnvironmentAvailable", "environment", "configError", "loadFailed", "message", "cause", "BuilderError", "resolveVersion", "host", "requested", "response", "info", "version", "isExactVersion", "injectScript", "src", "resolve", "reject", "script", "whenDefined", "timer", "assertCompatible", "loadBuilder", "error", "deferred", "resolve", "reject", "promise", "res", "rej", "PausableTimer", "ms", "onExpire", "BuilderInstance", "_BuilderInstance", "ctx", "options", "session", "raw", "cause", "BuilderError", "validateSession", "instance", "el", "BUILDER_TAG", "setShopperTokens", "on", "name", "handler", "listener", "event", "THREED_BUILDER_LOGIN_REQUIRED_EVENT", "d", "_a", "THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT", "THREED_BUILDER_LICENSE_ERROR_EVENT", "THREED_BUILDER_DESIGN_SAVED_EVENT", "THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT", "saveFailedError", "THREED_BUILDER_PRODUCT_CLONED_EVENT", "THREED_BUILDER_CLOSE_REQUESTED_EVENT", "_b", "THREED_BUILDER_STATUS_EVENT", "THREED_BUILDER_EXPORT_REQUESTED_EVENT", "dispatchOpenThreedBuilder", "destroyedError", "run", "dispatchSaveRequest", "epoch", "controller", "context", "validateObservabilityContext", "licenseToken", "action", "login", "dispatchLoginCancelled", "stale", "redirected", "tokens", "validateShopperTokens", "choice", "outcome", "pending", "detail", "error", "licenseError", "saved", "link", "wait", "customization", "request", "external", "timedOut", "timer", "DEFAULT_EXPORT_TIMEOUT_MS", "abort", "_", "files", "dispatchExportComplete", "code", "THREED_BUILDER_EXPORT_COMPLETE_EVENT", "BuilderController", "config", "createDebugLog", "instance", "index", "loadBuilder", "options", "valid", "validateMountOptions", "BuilderInstance", "current", "result", "token", "BuilderError", "cause", "configError", "createBuilder", "resolved", "resolveConfig", "assertEnvironmentAvailable", "RESUME_PARAM", "DRAFT_INDEX_KEY", "loginReturnPath", "url", "isSafeReturnPath", "path", "consumeResumeMarker", "hasDraft", "productId", "_a", "index", "draft"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3dn-builder/builder",
3
- "version": "0.637.0",
3
+ "version": "0.638.0",
4
4
  "description": "Embed the 3DN product builder in any storefront: loading, login, license refresh, save and customization records.",
5
5
  "license": "MIT",
6
6
  "repository": {