@3dn-builder/builder 0.647.0 → 0.648.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 +13 -0
- package/index.d.ts +10 -1
- package/index.js +1 -1
- package/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,8 +105,21 @@ customization?: {
|
|
|
105
105
|
|
|
106
106
|
`onSaved` comes before the product's images (its thumbnail and camera-angle screenshots) are refreshed. Read the images on `onImagesSaved`: it fires once they show the design at `version`, after that save's `onSaved`, and also when the builder's Close refreshed images that autosave had left behind. It does not fire when the images could not be updated; the product keeps its previous ones. `save()` resolves only after the images are refreshed or have failed, so a host that destroys the builder after `await save()` never cuts the upload short. This needs a builder that sends `tdn-images-saved`, the one released with this SDK or later; with an older pinned `version`, `save()` times out.
|
|
107
107
|
|
|
108
|
+
A shopper's first save names the copy after the source and when it was saved, for example "Mug Grey – 19 Oct, 14:32", so several designs from one product can be told apart.
|
|
109
|
+
|
|
108
110
|
`asCopy` says what the copy means. `true`: the shopper asked for one ("Save as copy", or "keep both" after a conflict), so store it as a new design and keep the original. `false`: Save had to fork a product the shopper doesn't own, so the copy replaces the open design.
|
|
109
111
|
|
|
112
|
+
## Features
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
features?: {
|
|
116
|
+
download?: boolean; // default true: Download print files (and a design area's SVG)
|
|
117
|
+
share?: boolean; // default true: Share a link to the design
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Set `download: false` or `share: false` to hide that action from shoppers, in the desktop and the phone layout, for example when shoppers should not take the artwork away. Both are shown unless you turn them off. Any other key, or a value that is not a boolean, is a `CONFIG_INVALID` error.
|
|
122
|
+
|
|
110
123
|
## Errors
|
|
111
124
|
|
|
112
125
|
Every failure is a `BuilderError`: `code`, a `message` safe to show, `retryable`, `fatal`, and the builder's `reason`.
|
package/index.d.ts
CHANGED
|
@@ -138,6 +138,13 @@ interface SavedDesignEvent {
|
|
|
138
138
|
productId: string;
|
|
139
139
|
version: number;
|
|
140
140
|
}
|
|
141
|
+
/** Shopper actions the host can hide. Each is shown unless set to `false`. */
|
|
142
|
+
interface BuilderFeatures {
|
|
143
|
+
/** Download print files (and a design area's SVG). Default `true`. */
|
|
144
|
+
download?: boolean;
|
|
145
|
+
/** Share a link to the design. Default `true`. */
|
|
146
|
+
share?: boolean;
|
|
147
|
+
}
|
|
141
148
|
interface BuilderHostConfig {
|
|
142
149
|
/** Default `'production'`. */
|
|
143
150
|
environment?: BuilderEnvironment;
|
|
@@ -162,6 +169,8 @@ interface BuilderHostConfig {
|
|
|
162
169
|
customization?: {
|
|
163
170
|
record(link: CustomizationLink): Promise<void>;
|
|
164
171
|
};
|
|
172
|
+
/** Hide Download and/or Share from shoppers. Both shown by default. */
|
|
173
|
+
features?: BuilderFeatures;
|
|
165
174
|
/**
|
|
166
175
|
* Every successful save, including ones the shopper starts from the builder's
|
|
167
176
|
* own Save button. Fires after any clone mapping has been recorded; never
|
|
@@ -341,4 +350,4 @@ declare function consumeResumeMarker(): boolean;
|
|
|
341
350
|
declare function hasDraft(productId: string): boolean;
|
|
342
351
|
|
|
343
352
|
export { BuilderError, BuilderInstance, RESUME_PARAM, consumeResumeMarker, createBuilder, hasDraft, isSafeReturnPath, loginReturnPath };
|
|
344
|
-
export type { Builder, BuilderEnvironment, BuilderErrorCode, BuilderErrorReason, BuilderHostConfig, BuilderMode, BuilderSession, BuilderVersion, CustomizationLink, ExternalExportOptions, LoginOutcome, MountOptions, SavedDesign, SavedDesignEvent, ShopperTokens };
|
|
353
|
+
export type { Builder, BuilderEnvironment, BuilderErrorCode, BuilderErrorReason, BuilderFeatures, BuilderHostConfig, BuilderMode, BuilderSession, BuilderVersion, CustomizationLink, ExternalExportOptions, LoginOutcome, MountOptions, SavedDesign, SavedDesignEvent, ShopperTokens };
|
package/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as k,c as P,d as _,e as B,f as O,g as C,h as A,j as m,k as N,l as M,m as F,n as U,o as V,p as H,r as $,s as q}from"./chunks/chunk-FRDLXZMM.js";var s=class t extends Error{constructor(e,n,r={}){var i,o;super(n),this.name="BuilderError",this.code=e,this.retryable=(i=r.retryable)!=null?i:!1,this.fatal=(o=r.fatal)!=null?o:!1,this.reason=r.reason,this.cause=r.cause,Object.setPrototypeOf(this,t.prototype)}},j={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.",outdated:"The builder on this page is out of date. Reload the page to save the design.",error:"The design could not be saved. Check the connection and try again."};function z(t){var n;let e=(n=j[t.reason])!=null?n:j.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 W(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 se=45e3,G=12e4,ae=new Set(["environment","version","saveTimeoutMs","session","login","customization","onSaved","onImagesSaved","onLicenseError","onCloseRequested","onStatusChange","debug"]),de=new Set(["target","hostProductId","mode","designId","externalExport","observabilityContext"]),ue=/^\d+\.\d+\.\d+$/,le=/^tdn_inst_/;function b(t){return ue.test(t)}function u(t){return typeof t=="object"&&t!==null}function f(t){return typeof t=="function"}function X(t){return typeof t=="number"&&Number.isFinite(t)&&t>0}function K(t,e=0){return typeof t=="string"?le.test(t):!u(t)||e>3?!1:Object.values(t).some(n=>K(n,e+1))}function Y(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 J(t){var h,l,c;if(!u(t))throw a("createBuilder() needs a configuration object.");if(K(t))throw a("createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.");Y(t,ae,"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:se;if(!X(r))throw a("createBuilder(): saveTimeoutMs must be a positive number of milliseconds.");let i=t.session;if(!u(i)||!f(i.get)||!f(i.refreshLicense))throw a("createBuilder(): session.get() and session.refreshLicense() are required. They call your session endpoint.");let o=t.login;if(o!==void 0){if(!u(o)||!f(o.getTokens)||!f(o.onRequired))throw a("createBuilder(): login needs getTokens() (your login-token endpoint) and onRequired() (your login UI).");if(o.onMintFailed!==void 0&&!f(o.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","onImagesSaved","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 Q(t){var d;if(!u(t))throw a("mount() needs an options object.");Y(t,de,"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 i=t.designId;if(i!==void 0&&(typeof i!="string"||i===""))throw a("mount(): designId must be a non-empty string.");t.observabilityContext!==void 0&&S(t.observabilityContext,"mount()");let o=t.externalExport;if(o!==void 0){if(!u(o)||!f(o.run))throw a("mount(): externalExport.run() is required when externalExport is set.");if(o.timeoutMs!==void 0&&!X(o.timeoutMs))throw a("mount(): externalExport.timeoutMs must be a positive number of milliseconds.")}return t}function S(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 w(t){let e=i=>new s("SESSION_FAILED",`session.get() returned an invalid session: ${i}.`,{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 Z(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 ce=/token|secret|key|authorization/i;function I(t,e=0){if(t===null||typeof t!="object"||e>4)return t;if(Array.isArray(t))return t.map(r=>I(r,e+1));let n={};for(let[r,i]of Object.entries(t))n[r]=ce.test(r)&&i!=null?"[redacted]":I(i,e+1);return n}function ee(t){return t?(e,n)=>{n===void 0?console.debug(`[3DN] ${e}`):console.debug(`[3DN] ${e}`,I(n))}:()=>{}}var v="threed-builder",re={production:"https://3d-builder.3dn.app",staging:void 0},he=3e4,te=Symbol.for("@3dn-builder/builder/loader");function fe(){let t=globalThis,e=t[te];return e||(e={},t[te]=e),e}function pe(){return typeof window!="undefined"&&typeof document!="undefined"&&typeof customElements!="undefined"}function ie(t){if(!re[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 ge(t,e){if(e!=="latest")return e;let n;try{n=await fetch(`${t}/version-info.json`,{cache:"no-store",credentials:"omit"})}catch(o){throw p("The 3DN builder could not be reached.",o)}if(!n.ok)throw p(`The 3DN builder release could not be resolved (HTTP ${n.status}).`);let r=await n.json().catch(()=>null),i=r==null?void 0:r.version;if(typeof i!="string"||!b(i))throw p("The 3DN builder release information was not valid.");return i}function me(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 ve(){return new Promise((t,e)=>{let n=setTimeout(()=>e(p("The 3DN builder loaded but did not start.")),he);customElements.whenDefined(v).then(()=>{clearTimeout(n),t()},e)})}function ne(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 oe(t,e){if(!pe())throw new s("NOT_IN_BROWSER","The 3DN builder can only load in a browser.");let n=fe();if(n.loading){await n.loading,ne(n,t,e);return}if(customElements.get(v))return;let r=re[t];if(!r)throw a(`The "${t}" environment is not available in this SDK release.`);n.environment=t,n.loading=(async()=>{let i=await ge(r,e);n.version=i;let o=await me(`${r}/builder-${i}.js`);try{await ve()}catch(d){throw o.remove(),d}})();try{await n.loading}catch(i){throw n.loading=void 0,n.environment=void 0,n.version=void 0,i instanceof s?i:p("The 3DN builder failed to load.",i)}ne(n,t,e)}function D(){let t,e,n=new Promise((r,i)=>{t=r,e=i});return n.catch(()=>{}),{promise:n,resolve:t,reject:e}}var R=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 i=w(r),o=new t(e,n,i);return n.target.appendChild(o.element),e.log("mounted",{hostProductId:n.hostProductId,productId:i.productId}),o}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&&L(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,i)=>{let o=d=>{d.target!==e||this.destroyed||(this.ctx.log(r,d.detail),i(d.detail))};e.addEventListener(r,o),this.listeners.push([r,o])};n(A,r=>{var i;this.answerLogin((i=r==null?void 0:r.action)!=null?i:"save")}),n(H,()=>{this.ctx.refreshLicense()}),n(N,r=>this.onLicenseError(r)),n(M,r=>{this.onDesignSaved(r)}),n(V,r=>this.finishSave(z(r))),n(F,r=>this.onImagesSettled(r,!0)),n(U,r=>this.onImagesSettled(r,!1)),n(C,r=>{this.onProductCloned(r)}),n(q,r=>{var i,o;return(o=(i=this.ctx.config).onCloseRequested)==null?void 0:o.call(i,r)}),n(P,r=>{var i,o;return(o=(i=this.ctx.config).onStatusChange)==null?void 0:o.call(i,r)}),this.options.externalExport&&n(_,r=>{this.answerExport(r)})}open(){this.assertUsable(),k(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:D(),timer:new R(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(),$(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(i){throw new s("SESSION_FAILED","The builder session could not be refreshed.",{retryable:!0,cause:i})}let r=w(n);e!==this.epoch||this.destroyed||(this.applyLicense(r.licenseToken),r.viewToken&&this.element.setAttribute("view-token",r.viewToken),r.token&&r.refreshToken&&L(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,n;if(!this.destroyed){this.epoch++,this.finishSave(g()),(e=this.cloneWait)==null||e.reject(g()),this.cloneWait=void 0,(n=this.imagesWait)==null||n.resolve(!1),this.imagesWait=void 0;for(let r of this.exports)r.abort();this.destroyed=!0;for(let[r,i]of this.listeners)this.element.removeEventListener(r,i);this.listeners.length=0,this.element.remove(),this.ctx.onDestroyed(this),this.ctx.log("destroyed")}}setObservabilityContext(e){this.assertUsable(),this.element.setAttribute("observability-context",S(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,i=()=>r!==this.epoch||this.destroyed;this.setLoginPending(!0);let o=!1;try{for(;;){let d;try{d=Z(await n.getTokens())}catch(l){if(i())return;this.ctx.log("login.getTokens() failed",{cause:String(l)});let c=n.onMintFailed?await n.onMintFailed():"cancel";if(i())return;if(c==="retry")continue;m(this.element,{action:e});return}if(i())return;if(d){L(this.element,d);return}let h=await n.onRequired(e);if(i())return;if(h!=="logged-in"){if(h==="redirected"){o=!0;return}m(this.element,{action:e});return}}}finally{!o&&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,i;let n=W(e.code);this.fatal=n,this.finishSave(n),(i=(r=this.ctx.config).onLicenseError)==null||i.call(r,n)}async onDesignSaved(e){var o;e.productId!==this.currentProductId&&!this.cloneWait&&(this.cloneWait=D()),(o=this.imagesWait)==null||o.resolve(!1);let n=this.imagesWait=D();try{this.cloneWait&&await this.cloneWait.promise,this.unrecordedLink&&await this.recordLink(this.unrecordedLink)}catch(d){this.finishSave(d instanceof s?d:new s("SAVE_FAILED","The design was saved but could not be linked to this shopper.",{retryable:!0,reason:"customization",cause:d}));return}if(this.destroyed)return;let r={productId:e.productId,version:e.version};this.notify("onSaved",r);let i=await n.promise;this.destroyed||(i&&this.notify("onImagesSaved",r),this.finishSave(void 0,r))}onImagesSettled(e,n){let r=this.imagesWait;if(r){this.imagesWait=void 0,r.resolve(n);return}n&&this.notify("onImagesSaved",{productId:e.productId,version:e.version})}notify(e,n){var r,i;try{(i=(r=this.ctx.config)[e])==null||i.call(r,{hostProductId:this.options.hostProductId,...n})}catch(o){this.ctx.log(`${e} threw`,{cause:String(o)})}}async onProductCloned(e){let n={hostProductId:this.options.hostProductId,sourceProductId:e.originalProductId,savedProductId:e.newProductId,asCopy:e.asCopy===!0};this.currentProductId=e.newProductId;let r=this.cloneWait;this.cloneWait=void 0;try{await this.recordLink(n),r==null||r.resolve()}catch(i){r==null||r.reject(i)}}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 i=!1,o=setTimeout(()=>{i=!0,r.abort()},(h=n.timeoutMs)!=null?h:G),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]);O(this.element,{files:l})}catch(l){let c=i?"EXPORT_TIMEOUT":"EXPORT_FAILED";this.ctx.log("export failed",{code:c,cause:String(l)}),this.replyExportFailure(c)}finally{clearTimeout(o),this.exports.delete(r)}}replyExportFailure(e){let n={files:[],error:{code:e}};this.element.dispatchEvent(new CustomEvent(B,{bubbles:!1,composed:!1,detail:n}))}assertUsable(){if(this.destroyed)throw g()}};function L(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=ee(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 oe(this.config.environment,this.config.version)}async mount(e){let n=Q(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 Ee(t){let e=J(t);return ie(e.environment),new x(e)}var y="tdn_resume",ye="tdn-draft-index";function Te(){let t=new URL(window.location.href);return t.searchParams.set(y,"1"),t.pathname+t.search+t.hash}function be(t){return/^\/(?![/\\])/.test(t)}function Se(){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 we(t){var e;if(typeof localStorage=="undefined")return!1;try{let n=JSON.parse((e=localStorage.getItem(ye))!=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,Se as consumeResumeMarker,Ee as createBuilder,we as hasDraft,be as isSafeReturnPath,Te as loginReturnPath};
|
|
1
|
+
import{b as B,c as O,d as C,e as A,f as N,g as M,h as F,j as v,k as U,l as V,m as H,n as $,o as j,p as q,r as z,s as W}from"./chunks/chunk-FRDLXZMM.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)}},G={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.",outdated:"The builder on this page is out of date. Reload the page to save the design.",error:"The design could not be saved. Check the connection and try again."};function K(t){var n;let e=(n=G[t.reason])!=null?n:G.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 X(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 m(){return new s("DESTROYED","The builder was destroyed.")}function a(t){return new s("CONFIG_INVALID",t,{fatal:!0})}var de=45e3,Y=12e4,ue=new Set(["environment","version","saveTimeoutMs","session","login","customization","features","onSaved","onImagesSaved","onLicenseError","onCloseRequested","onStatusChange","debug"]),le=new Set(["download","share"]),ce=new Set(["target","hostProductId","mode","designId","externalExport","observabilityContext"]),he=/^\d+\.\d+\.\d+$/,fe=/^tdn_inst_/;function S(t){return he.test(t)}function l(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 Q(t,e=0){return typeof t=="string"?fe.test(t):!l(t)||e>3?!1:Object.values(t).some(n=>Q(n,e+1))}function b(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 Z(t){var c,h,_;if(!l(t))throw a("createBuilder() needs a configuration object.");if(Q(t))throw a("createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.");b(t,ue,"createBuilder()");let e=(c=t.environment)!=null?c:"production";if(e!=="production"&&e!=="staging")throw a(`createBuilder(): environment must be "production" or "staging", got ${JSON.stringify(e)}.`);let n=(h=t.version)!=null?h:"latest";if(typeof n!="string"||n!=="latest"&&!S(n))throw a(`createBuilder(): version must be "latest" or an exact release such as "1.2.3", got ${JSON.stringify(n)}.`);let r=(_=t.saveTimeoutMs)!=null?_:de;if(!J(r))throw a("createBuilder(): saveTimeoutMs must be a positive number of milliseconds.");let o=t.session;if(!l(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(!l(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&&(!l(d)||!f(d.record)))throw a("createBuilder(): customization.record() is required when customization is set. It calls your customization endpoint.");let u=t.features;if(u!==void 0){if(!l(u)||Array.isArray(u))throw a("createBuilder(): features must be an object.");b(u,le,"createBuilder() features");for(let[g,P]of Object.entries(u))if(P!==void 0&&typeof P!="boolean")throw a(`createBuilder(): features.${g} must be a boolean.`)}for(let g of["onSaved","onImagesSaved","onLicenseError","onCloseRequested","onStatusChange"])if(t[g]!==void 0&&!f(t[g]))throw a(`createBuilder(): ${g} must be a function.`);return{...t,environment:e,version:n,saveTimeoutMs:r}}function ee(t){var d;if(!l(t))throw a("mount() needs an options object.");b(t,ce,"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(!l(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 w(t,e){let n;try{n=typeof t=="string"?JSON.parse(t):void 0}catch{n=void 0}if(!l(n)||Array.isArray(n))throw a(`${e}: observabilityContext must be a JSON object string.`);return t}function I(t){let e=o=>new s("SESSION_FAILED",`session.get() returned an invalid session: ${o}.`,{retryable:!1});if(!l(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 te(t){if(t===null)return null;if(!l(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 pe=/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]=pe.test(r)&&o!=null?"[redacted]":D(o,e+1);return n}function ne(t){return t?(e,n)=>{n===void 0?console.debug(`[3DN] ${e}`):console.debug(`[3DN] ${e}`,D(n))}:()=>{}}var E="threed-builder",ie={production:"https://3d-builder.3dn.app",staging:void 0},ge=3e4,re=Symbol.for("@3dn-builder/builder/loader");function me(){let t=globalThis,e=t[re];return e||(e={},t[re]=e),e}function ve(){return typeof window!="undefined"&&typeof document!="undefined"&&typeof customElements!="undefined"}function se(t){if(!ie[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 Ee(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"||!S(o))throw p("The 3DN builder release information was not valid.");return o}function ye(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 Te(){return new Promise((t,e)=>{let n=setTimeout(()=>e(p("The 3DN builder loaded but did not start.")),ge);customElements.whenDefined(E).then(()=>{clearTimeout(n),t()},e)})}function oe(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 ae(t,e){if(!ve())throw new s("NOT_IN_BROWSER","The 3DN builder can only load in a browser.");let n=me();if(n.loading){await n.loading,oe(n,t,e);return}if(customElements.get(E))return;let r=ie[t];if(!r)throw a(`The "${t}" environment is not available in this SDK release.`);n.environment=t,n.loading=(async()=>{let o=await Ee(r,e);n.version=o;let i=await ye(`${r}/builder-${o}.js`);try{await Te()}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)}oe(n,t,e)}function L(){let t,e,n=new Promise((r,o)=>{t=r,e=o});return n.catch(()=>{}),{promise:n,resolve:t,reject:e}}var x=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}},y=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=I(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);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&&R(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");let r=this.ctx.config.features;return(r==null?void 0:r.download)===!1&&n.setAttribute("hide-download","true"),(r==null?void 0:r.share)===!1&&n.setAttribute("hide-share","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(F,r=>{var o;this.answerLogin((o=r==null?void 0:r.action)!=null?o:"save")}),n(q,()=>{this.ctx.refreshLicense()}),n(U,r=>this.onLicenseError(r)),n(V,r=>{this.onDesignSaved(r)}),n(j,r=>this.finishSave(K(r))),n(H,r=>this.onImagesSettled(r,!0)),n($,r=>this.onImagesSettled(r,!1)),n(M,r=>{this.onProductCloned(r)}),n(W,r=>{var o,i;return(i=(o=this.ctx.config).onCloseRequested)==null?void 0:i.call(o,r)}),n(O,r=>{var o,i;return(i=(o=this.ctx.config).onStatusChange)==null?void 0:i.call(o,r)}),this.options.externalExport&&n(C,r=>{this.answerExport(r)})}open(){this.assertUsable(),B(this.element)}save(){if(this.destroyed)return Promise.reject(m());if(this.fatal)return Promise.reject(this.fatal);if(this.saveRun)return this.saveRun.result.promise;let e={result:L(),timer:new x(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(),z(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=I(n);e!==this.epoch||this.destroyed||(this.applyLicense(r.licenseToken),r.viewToken&&this.element.setAttribute("view-token",r.viewToken),r.token&&r.refreshToken&&R(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,n;if(!this.destroyed){this.epoch++,this.finishSave(m()),(e=this.cloneWait)==null||e.reject(m()),this.cloneWait=void 0,(n=this.imagesWait)==null||n.resolve(!1),this.imagesWait=void 0;for(let r of this.exports)r.abort();this.destroyed=!0;for(let[r,o]of this.listeners)this.element.removeEventListener(r,o);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){v(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=te(await n.getTokens())}catch(c){if(o())return;this.ctx.log("login.getTokens() failed",{cause:String(c)});let h=n.onMintFailed?await n.onMintFailed():"cancel";if(o())return;if(h==="retry")continue;v(this.element,{action:e});return}if(o())return;if(d){R(this.element,d);return}let u=await n.onRequired(e);if(o())return;if(u!=="logged-in"){if(u==="redirected"){i=!0;return}v(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=X(e.code);this.fatal=n,this.finishSave(n),(o=(r=this.ctx.config).onLicenseError)==null||o.call(r,n)}async onDesignSaved(e){var i;e.productId!==this.currentProductId&&!this.cloneWait&&(this.cloneWait=L()),(i=this.imagesWait)==null||i.resolve(!1);let n=this.imagesWait=L();try{this.cloneWait&&await this.cloneWait.promise,this.unrecordedLink&&await this.recordLink(this.unrecordedLink)}catch(d){this.finishSave(d instanceof s?d:new s("SAVE_FAILED","The design was saved but could not be linked to this shopper.",{retryable:!0,reason:"customization",cause:d}));return}if(this.destroyed)return;let r={productId:e.productId,version:e.version};this.notify("onSaved",r);let o=await n.promise;this.destroyed||(o&&this.notify("onImagesSaved",r),this.finishSave(void 0,r))}onImagesSettled(e,n){let r=this.imagesWait;if(r){this.imagesWait=void 0,r.resolve(n);return}n&&this.notify("onImagesSaved",{productId:e.productId,version:e.version})}notify(e,n){var r,o;try{(o=(r=this.ctx.config)[e])==null||o.call(r,{hostProductId:this.options.hostProductId,...n})}catch(i){this.ctx.log(`${e} threw`,{cause:String(i)})}}async onProductCloned(e){let n={hostProductId:this.options.hostProductId,sourceProductId:e.originalProductId,savedProductId:e.newProductId,asCopy:e.asCopy===!0};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 u;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()},(u=n.timeoutMs)!=null?u:Y),d=new Promise((c,h)=>r.signal.addEventListener("abort",()=>h(new Error("aborted"))));try{let c=await Promise.race([n.run(e,r.signal),d]);N(this.element,{files:c})}catch(c){let h=o?"EXPORT_TIMEOUT":"EXPORT_FAILED";this.ctx.log("export failed",{code:h,cause:String(c)}),this.replyExportFailure(h)}finally{clearTimeout(i),this.exports.delete(r)}}replyExportFailure(e){let n={files:[],error:{code:e}};this.element.dispatchEvent(new CustomEvent(A,{bubbles:!1,composed:!1,detail:n}))}assertUsable(){if(this.destroyed)throw m()}};function R(t,e){t.setAttribute("refresh-token",e.refreshToken),t.setAttribute("bearer-token",e.token)}var k=class{constructor(e){this.config=e;this.instances=[];this.log=ne(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 ae(this.config.environment,this.config.version)}async mount(e){let n=ee(e);await this.load();let r=await y.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 be(t){let e=Z(t);return se(e.environment),new k(e)}var T="tdn_resume",Se="tdn-draft-index";function we(){let t=new URL(window.location.href);return t.searchParams.set(T,"1"),t.pathname+t.search+t.hash}function Ie(t){return/^\/(?![/\\])/.test(t)}function De(){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 Le(t){var e;if(typeof localStorage=="undefined")return!1;try{let n=JSON.parse((e=localStorage.getItem(Se))!=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,De as consumeResumeMarker,be as createBuilder,Le as hasDraft,Ie as isSafeReturnPath,we 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 outdated:\n 'The builder on this page is out of date. Reload the page to save the design.',\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 * The shopper asked for a copy (\"Save as copy\", \"keep both\") and keeps the\n * original too. `false` when Save had to fork a product they don't own: the\n * copy replaces the original.\n */\n asCopy: boolean;\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. The product's images may still show the previous\n * save: read them on `onImagesSaved`.\n */\n onSaved?(saved: SavedDesignEvent): void;\n /**\n * The saved product's images now show the design at `version`: read them\n * now. Fires after the `onSaved` of the save that took them, and when the\n * builder's Close refreshed images that autosave left behind. Never fires\n * when the images could not be updated (the product keeps its old ones).\n */\n onImagesSaved?(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 'onImagesSaved',\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 'onImagesSaved',\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_IMAGES_SAVED_EVENT,\n THREED_BUILDER_IMAGES_SAVE_FAILED_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 ImagesSavedDetail,\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 /**\n * Set between a tdn-design-saved and that save's images outcome: true on\n * tdn-images-saved, false on tdn-images-save-failed.\n */\n private imagesWait?: Deferred<boolean>;\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<ImagesSavedDetail>(THREED_BUILDER_IMAGES_SAVED_EVENT, (d) =>\n this.onImagesSettled(d, true)\n );\n on<ImagesSavedDetail>(THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT, (d) =>\n this.onImagesSettled(d, false)\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, any clone mapping it\n * required has been recorded, and the product's images were refreshed (or\n * could not be: the save still resolves); 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 this.imagesWait?.resolve(false);\n this.imagesWait = 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 // Every save reports its images next, before any tdn-product-cloned. A\n // save still waiting on its images when another lands reports none.\n this.imagesWait?.resolve(false);\n const images = (this.imagesWait = deferred<boolean>());\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.notify('onSaved', saved);\n const updated = await images.promise;\n if (this.destroyed) return;\n if (updated) this.notify('onImagesSaved', saved);\n this.finishSave(undefined, saved);\n }\n\n private onImagesSettled(detail: ImagesSavedDetail, updated: boolean): void {\n const wait = this.imagesWait;\n if (wait) {\n // A save's images: onDesignSaved reports them after its onSaved.\n this.imagesWait = undefined;\n wait.resolve(updated);\n return;\n }\n // No save waiting: the builder's Close refreshed images autosave left behind.\n if (updated) {\n this.notify('onImagesSaved', {\n productId: detail.productId,\n version: detail.version,\n });\n }\n }\n\n /** A host callback that throws never breaks the save. */\n private notify(\n callback: 'onSaved' | 'onImagesSaved',\n saved: SavedDesign\n ): void {\n try {\n this.ctx.config[callback]?.({\n hostProductId: this.options.hostProductId,\n ...saved,\n });\n } catch (cause) {\n this.ctx.log(`${callback} 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 asCopy: detail.asCopy === true,\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_IMAGES_SAVED_EVENT", "THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT", "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", "images", "saved", "updated", "wait", "callback", "link", "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 outdated:\n 'The builder on this page is out of date. Reload the page to save the design.',\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 * The shopper asked for a copy (\"Save as copy\", \"keep both\") and keeps the\n * original too. `false` when Save had to fork a product they don't own: the\n * copy replaces the original.\n */\n asCopy: boolean;\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\n/** Shopper actions the host can hide. Each is shown unless set to `false`. */\nexport interface BuilderFeatures {\n /** Download print files (and a design area's SVG). Default `true`. */\n download?: boolean;\n /** Share a link to the design. Default `true`. */\n share?: boolean;\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 /** Hide Download and/or Share from shoppers. Both shown by default. */\n features?: BuilderFeatures;\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. The product's images may still show the previous\n * save: read them on `onImagesSaved`.\n */\n onSaved?(saved: SavedDesignEvent): void;\n /**\n * The saved product's images now show the design at `version`: read them\n * now. Fires after the `onSaved` of the save that took them, and when the\n * builder's Close refreshed images that autosave left behind. Never fires\n * when the images could not be updated (the product keeps its old ones).\n */\n onImagesSaved?(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 'features',\n 'onSaved',\n 'onImagesSaved',\n 'onLicenseError',\n 'onCloseRequested',\n 'onStatusChange',\n 'debug',\n]);\n\nconst FEATURE_KEYS = new Set(['download', 'share']);\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 const features = config['features'];\n if (features !== undefined) {\n if (!isRecord(features) || Array.isArray(features)) {\n throw configError('createBuilder(): features must be an object.');\n }\n rejectUnknownKeys(features, FEATURE_KEYS, 'createBuilder() features');\n for (const [key, value] of Object.entries(features)) {\n if (value !== undefined && typeof value !== 'boolean') {\n throw configError(\n `createBuilder(): features.${key} must be a boolean.`\n );\n }\n }\n }\n\n for (const key of [\n 'onSaved',\n 'onImagesSaved',\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_IMAGES_SAVED_EVENT,\n THREED_BUILDER_IMAGES_SAVE_FAILED_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 ImagesSavedDetail,\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 /**\n * Set between a tdn-design-saved and that save's images outcome: true on\n * tdn-images-saved, false on tdn-images-save-failed.\n */\n private imagesWait?: Deferred<boolean>;\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 const features = this.ctx.config.features;\n if (features?.download === false) el.setAttribute('hide-download', 'true');\n if (features?.share === false) el.setAttribute('hide-share', '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<ImagesSavedDetail>(THREED_BUILDER_IMAGES_SAVED_EVENT, (d) =>\n this.onImagesSettled(d, true)\n );\n on<ImagesSavedDetail>(THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT, (d) =>\n this.onImagesSettled(d, false)\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, any clone mapping it\n * required has been recorded, and the product's images were refreshed (or\n * could not be: the save still resolves); 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 this.imagesWait?.resolve(false);\n this.imagesWait = 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 // Every save reports its images next, before any tdn-product-cloned. A\n // save still waiting on its images when another lands reports none.\n this.imagesWait?.resolve(false);\n const images = (this.imagesWait = deferred<boolean>());\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.notify('onSaved', saved);\n const updated = await images.promise;\n if (this.destroyed) return;\n if (updated) this.notify('onImagesSaved', saved);\n this.finishSave(undefined, saved);\n }\n\n private onImagesSettled(detail: ImagesSavedDetail, updated: boolean): void {\n const wait = this.imagesWait;\n if (wait) {\n // A save's images: onDesignSaved reports them after its onSaved.\n this.imagesWait = undefined;\n wait.resolve(updated);\n return;\n }\n // No save waiting: the builder's Close refreshed images autosave left behind.\n if (updated) {\n this.notify('onImagesSaved', {\n productId: detail.productId,\n version: detail.version,\n });\n }\n }\n\n /** A host callback that throws never breaks the save. */\n private notify(\n callback: 'onSaved' | 'onImagesSaved',\n saved: SavedDesign\n ): void {\n try {\n this.ctx.config[callback]?.({\n hostProductId: this.options.hostProductId,\n ...saved,\n });\n } catch (cause) {\n this.ctx.log(`${callback} 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 asCopy: detail.asCopy === true,\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", "FEATURE_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", "features", "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", "features", "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_IMAGES_SAVED_EVENT", "THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT", "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", "images", "saved", "updated", "wait", "callback", "link", "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