@plattar/plattar-ar-adapter 2.7.1 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,8 @@ class ConfiguratorAR extends launcher_ar_1.LauncherAR {
30
30
  this._ar = null;
31
31
  }
32
32
  _SetupAnalytics() {
33
+ if (this._analytics !== null)
34
+ return;
33
35
  const scene = this._options.state.scene;
34
36
  let analytics = null;
35
37
  // setup scene stuff (if any)
@@ -35,6 +35,8 @@ class ModelAR extends launcher_ar_1.LauncherAR {
35
35
  return this._options.modelID;
36
36
  }
37
37
  _SetupAnalytics(model) {
38
+ if (this._analytics !== null)
39
+ return;
38
40
  let analytics = null;
39
41
  const project = model.relationships.find(plattar_api_1.Project);
40
42
  // setup scene stuff (if any)
@@ -40,6 +40,8 @@ class ProductAR extends launcher_ar_1.LauncherAR {
40
40
  return this._options.variationSKU;
41
41
  }
42
42
  _SetupAnalytics(product, variation) {
43
+ if (this._analytics !== null)
44
+ return;
43
45
  let analytics = null;
44
46
  const scene = product.relationships.find(plattar_api_1.Scene);
45
47
  // setup scene stuff (if any)
@@ -89,9 +91,7 @@ class ProductAR extends launcher_ar_1.LauncherAR {
89
91
  return reject(new Error("ProductAR.init() - cannot proceed as AR not available in context"));
90
92
  }
91
93
  const product = new plattar_api_1.Product(this.productID);
92
- product.include(plattar_api_1.ProductVariation);
93
94
  product.include(plattar_api_1.ProductVariation.include(plattar_api_1.FileModel));
94
- product.include(plattar_api_1.Scene);
95
95
  product.include(plattar_api_1.Scene.include(plattar_api_1.Project));
96
96
  product.get().then((product) => {
97
97
  // find the required variation from our product
package/dist/ar/raw-ar.js CHANGED
@@ -42,6 +42,8 @@ class RawAR extends launcher_ar_1.LauncherAR {
42
42
  }
43
43
  _SetupAnalytics() {
44
44
  return new Promise((accept, _reject) => {
45
+ if (this._analytics !== null)
46
+ return accept();
45
47
  const sceneID = this._options.sceneID;
46
48
  if (!sceneID) {
47
49
  return accept();
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SceneAR = void 0;
7
7
  const plattar_analytics_1 = require("@plattar/plattar-analytics");
8
8
  const plattar_api_1 = require("@plattar/plattar-api");
9
- const plattar_services_1 = require("@plattar/plattar-services");
10
9
  const util_1 = require("../util/util");
11
10
  const quicklook_viewer_1 = __importDefault(require("../viewers/quicklook-viewer"));
12
11
  const scene_viewer_1 = __importDefault(require("../viewers/scene-viewer"));
@@ -35,6 +34,8 @@ class SceneAR extends launcher_ar_1.LauncherAR {
35
34
  return this._options.sceneID;
36
35
  }
37
36
  _SetupAnalytics(scene) {
37
+ if (this._analytics !== null)
38
+ return;
38
39
  let analytics = null;
39
40
  // setup scene stuff (if any)
40
41
  if (scene) {
@@ -64,58 +65,87 @@ class SceneAR extends launcher_ar_1.LauncherAR {
64
65
  * Composes a Scene into an AR Model (remote operation) that can be used to launch
65
66
  * an AR File
66
67
  */
67
- _ComposeScene(scene, output) {
68
- return new Promise((accept, reject) => {
69
- const sceneProducts = scene.relationships.filter(plattar_api_1.SceneProduct);
70
- const sceneModels = scene.relationships.filter(plattar_api_1.SceneModel);
71
- // nothing to do if no AR components can be found
72
- if ((sceneProducts.length + sceneModels.length) <= 0) {
73
- return reject(new Error("SceneAR.ComposeScene() - cannot proceed as scene does not contain AR components"));
68
+ async _ComposeScene(scene, output) {
69
+ const sceneProducts = scene.relationships.filter(plattar_api_1.SceneProduct);
70
+ const sceneModels = scene.relationships.filter(plattar_api_1.SceneModel);
71
+ // nothing to do if no AR components can be found
72
+ if ((sceneProducts.length + sceneModels.length) <= 0) {
73
+ throw new Error("SceneAR.ComposeScene() - cannot proceed as scene does not contain AR components");
74
+ }
75
+ const url = `${plattar_api_1.Server.location().type === 'staging' ? 'https://converter.plattar.space' : 'https://converter.plattar.com'}/v3/converter/config-to-model`;
76
+ const payload = {
77
+ data: {
78
+ attributes: {
79
+ quality: 100,
80
+ output: output,
81
+ maps: new Array()
82
+ }
74
83
  }
75
- // define our configurator
76
- const configurator = new plattar_services_1.Configurator();
77
- configurator.server = plattar_api_1.Server.location().type;
78
- configurator.output = output;
79
- let totalARObjectCount = 0;
80
- // add our scene products
81
- sceneProducts.forEach((sceneProduct) => {
82
- const product = sceneProduct.relationships.find(plattar_api_1.Product);
83
- const selection = this._options.variationSelection;
84
- // we have a specific product selection
85
- if (sceneProduct.attributes.include_in_augment) {
86
- // check if this product is the one we want (from selection optionally)
87
- if (product && (product.id === selection.productID) && selection.variationID) {
88
- configurator.addSceneProduct(sceneProduct.id, selection.variationID);
84
+ };
85
+ let totalARObjectCount = 0;
86
+ // add our scene products
87
+ sceneProducts.forEach((sceneProduct) => {
88
+ const product = sceneProduct.relationships.find(plattar_api_1.Product);
89
+ const selection = this._options.variationSelection;
90
+ // we have a specific product selection
91
+ if (sceneProduct.attributes.include_in_augment) {
92
+ // check if this product is the one we want (from selection optionally)
93
+ if (product && (product.id === selection.productID) && selection.variationID) {
94
+ payload.data.attributes.maps.push({
95
+ sceneproduct: sceneProduct.id,
96
+ productvariation: selection.variationID
97
+ });
98
+ totalARObjectCount++;
99
+ }
100
+ else if (product) {
101
+ // check if this scene-product is the one we want (from selection)
102
+ if ((sceneProduct.id === selection.sceneProductID) && selection.variationID) {
103
+ payload.data.attributes.maps.push({
104
+ sceneproduct: sceneProduct.id,
105
+ productvariation: selection.variationID
106
+ });
89
107
  totalARObjectCount++;
90
108
  }
91
- else if (product) {
92
- // check if this scene-product is the one we want (from selection)
93
- if ((sceneProduct.id === selection.sceneProductID) && selection.variationID) {
94
- configurator.addSceneProduct(sceneProduct.id, selection.variationID);
95
- totalARObjectCount++;
96
- }
97
- else if (product.attributes.product_variation_id) {
98
- configurator.addSceneProduct(sceneProduct.id, product.attributes.product_variation_id);
99
- totalARObjectCount++;
100
- }
109
+ else if (product.attributes.product_variation_id) {
110
+ payload.data.attributes.maps.push({
111
+ sceneproduct: sceneProduct.id,
112
+ productvariation: product.attributes.product_variation_id
113
+ });
114
+ totalARObjectCount++;
101
115
  }
102
116
  }
103
- });
104
- // add our scene models
105
- sceneModels.forEach((sceneModel) => {
106
- if (sceneModel.attributes.include_in_augment) {
107
- configurator.addModel(sceneModel.id);
108
- totalARObjectCount++;
109
- }
110
- });
111
- // ensure we have actually added AR objects
112
- if (totalARObjectCount <= 0) {
113
- return reject(new Error("SceneAR.ComposeScene() - cannot proceed as scene does not contain any enabled AR components"));
114
117
  }
115
- return configurator.get().then((result) => {
116
- accept(result.filename);
117
- }).catch(reject);
118
118
  });
119
+ // add our scene models
120
+ sceneModels.forEach((sceneModel) => {
121
+ if (sceneModel.attributes.include_in_augment) {
122
+ payload.data.attributes.maps.push({
123
+ scenemodel: sceneModel.id
124
+ });
125
+ totalARObjectCount++;
126
+ }
127
+ });
128
+ // ensure we have actually added AR objects
129
+ if (totalARObjectCount <= 0) {
130
+ throw new Error("SceneAR.ComposeScene() - cannot proceed as scene does not contain any enabled AR components");
131
+ }
132
+ try {
133
+ const response = await fetch(url, {
134
+ method: "POST",
135
+ headers: {
136
+ "Content-Type": "application/json"
137
+ },
138
+ body: JSON.stringify(payload)
139
+ });
140
+ if (!response.ok) {
141
+ throw new Error(`SceneAR.ComposeScene() - network response was not ok ${response.status}`);
142
+ }
143
+ const data = await response.json();
144
+ return data.data.attributes.filename;
145
+ }
146
+ catch (error) {
147
+ throw new Error(`SceneAR.ComposeScene() - there was a request error to ${url}, error was ${error.message}`);
148
+ }
119
149
  }
120
150
  /**
121
151
  * Initialise the SceneAR instance. This returns a Promise that resolves
@@ -131,7 +161,6 @@ class SceneAR extends launcher_ar_1.LauncherAR {
131
161
  }
132
162
  const scene = new plattar_api_1.Scene(this.sceneID);
133
163
  scene.include(plattar_api_1.Project);
134
- scene.include(plattar_api_1.SceneProduct);
135
164
  scene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product));
136
165
  scene.include(plattar_api_1.SceneModel);
137
166
  scene.get().then((scene) => {
@@ -9,6 +9,7 @@ export interface SceneGraphAROptions {
9
9
  */
10
10
  export declare class SceneGraphAR extends LauncherAR {
11
11
  private _analytics;
12
+ private _analyticsSetup;
12
13
  private _options;
13
14
  private _ar;
14
15
  constructor(options: SceneGraphAROptions);
@@ -17,6 +17,7 @@ const version_1 = __importDefault(require("../version"));
17
17
  class SceneGraphAR extends launcher_ar_1.LauncherAR {
18
18
  // analytics instance
19
19
  _analytics = null;
20
+ _analyticsSetup = null;
20
21
  _options;
21
22
  // this thing controls the actual AR view
22
23
  // this is setup via .init() function
@@ -26,32 +27,37 @@ class SceneGraphAR extends launcher_ar_1.LauncherAR {
26
27
  this._options = options;
27
28
  this._ar = null;
28
29
  }
29
- async _SetupAnalytics() {
30
- const scene = new plattar_api_1.Scene(this._options.sceneID);
31
- scene.include(plattar_api_1.Project);
32
- const fetchedScene = await scene.get();
33
- let analytics = null;
34
- analytics = new plattar_analytics_1.Analytics(fetchedScene.attributes.application_id);
35
- analytics.origin = plattar_api_1.Server.location().type;
36
- this._analytics = analytics;
37
- analytics.data.push("type", "scene-graph-ar");
38
- analytics.data.push("sdkVersion", version_1.default);
39
- analytics.data.push("sceneId", fetchedScene.id);
40
- analytics.data.push("sceneTitle", fetchedScene.attributes.title);
41
- const application = fetchedScene.relationships.find(plattar_api_1.Project);
42
- // setup application stuff (if any)
43
- if (application) {
44
- analytics.data.push("applicationId", application.id);
45
- analytics.data.push("applicationTitle", application.attributes.title);
46
- if (this._options.useARBanner) {
47
- this.options.banner = {
48
- title: application.attributes.title,
49
- subtitle: fetchedScene.attributes.title,
50
- button: 'Visit'
51
- };
30
+ _SetupAnalytics() {
31
+ if (this._analyticsSetup !== null)
32
+ return this._analyticsSetup;
33
+ this._analyticsSetup = (async () => {
34
+ const scene = new plattar_api_1.Scene(this._options.sceneID);
35
+ scene.include(plattar_api_1.Project);
36
+ const fetchedScene = await scene.get();
37
+ let analytics = null;
38
+ analytics = new plattar_analytics_1.Analytics(fetchedScene.attributes.application_id);
39
+ analytics.origin = plattar_api_1.Server.location().type;
40
+ this._analytics = analytics;
41
+ analytics.data.push("type", "scene-graph-ar");
42
+ analytics.data.push("sdkVersion", version_1.default);
43
+ analytics.data.push("sceneId", fetchedScene.id);
44
+ analytics.data.push("sceneTitle", fetchedScene.attributes.title);
45
+ const application = fetchedScene.relationships.find(plattar_api_1.Project);
46
+ // setup application stuff (if any)
47
+ if (application) {
48
+ analytics.data.push("applicationId", application.id);
49
+ analytics.data.push("applicationTitle", application.attributes.title);
50
+ if (this._options.useARBanner) {
51
+ this.options.banner = {
52
+ title: application.attributes.title,
53
+ subtitle: fetchedScene.attributes.title,
54
+ button: 'Visit'
55
+ };
56
+ }
52
57
  }
53
- }
54
- return fetchedScene;
58
+ return fetchedScene;
59
+ })();
60
+ return this._analyticsSetup;
55
61
  }
56
62
  /**
57
63
  * Composes a Scene into an AR Model (remote operation) that can be used to launch
@@ -11,6 +11,11 @@ export declare class ConfiguratorController extends PlattarController {
11
11
  startARQRCode(options: any): Promise<HTMLElement>;
12
12
  startViewerQRCode(options: QRCodeOptions): Promise<HTMLElement>;
13
13
  startRenderer(): Promise<HTMLElement>;
14
+ /**
15
+ * Legacy furniture scenes carry a "product" state entry — the embed is
16
+ * re-created with product attributes and rendering restarts there
17
+ */
18
+ private _transitionLegacyProduct;
14
19
  initAR(): Promise<LauncherAR>;
15
20
  /**
16
21
  * Private Function - This launches the Static/Inherited AR Mode
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConfiguratorController = void 0;
4
4
  const plattar_api_1 = require("@plattar/plattar-api");
5
5
  const scene_product_ar_1 = require("../../ar/scene-product-ar");
6
+ const configurator_state_1 = require("../../util/configurator-state");
6
7
  const util_1 = require("../../util/util");
7
8
  const plattar_controller_1 = require("./plattar-controller");
8
9
  const configurator_ar_1 = require("../../ar/configurator-ar");
@@ -81,6 +82,11 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
81
82
  }
82
83
  return Promise.reject(new Error("ConfiguratorController.startARQRCode() - legacy product transition failed"));
83
84
  }
85
+ // Kick off scene-graph encoding immediately while we proceed to the base
86
+ // implementation — the result is Promise-cached so no duplicate request is fired
87
+ if (!this.getAttribute("scene-graph-id")) {
88
+ dState.state.encodeSceneGraphID().catch(() => null);
89
+ }
84
90
  }
85
91
  catch (_err) {
86
92
  }
@@ -202,28 +208,38 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
202
208
  // optional attributes
203
209
  let configState = null;
204
210
  this._state = plattar_controller_1.ControllerState.Renderer;
205
- try {
206
- const dState = await this.getConfiguratorState();
211
+ const suppliedState = this.getAttribute("config-state");
212
+ if (suppliedState) {
213
+ // the state was supplied by the host page — decode it locally
214
+ // rather than paying for the scene fetch getConfiguratorState()
215
+ // performs before the renderer iframe can even be created. The
216
+ // renderer only needs the re-encoded state; the flows that do
217
+ // need the scene (QR/AR) decode on demand and reuse this same
218
+ // instance via _bootConfigState, keeping user mutations.
219
+ configState = configurator_state_1.ConfiguratorState.decode(suppliedState);
220
+ this._bootConfigState = configState;
207
221
  // if this is declared, we have a furniture scene that we need to re-create the embed
208
222
  // with new attributes
209
- const product = dState.state.firstOfType("product");
223
+ const product = configState.firstOfType("product");
210
224
  if (product) {
211
- this.parent.lockObserver();
212
- this.parent.destroy();
213
- this.setAttribute("product-id", product.scene_product_id);
214
- this.removeAttribute("scene-id");
215
- this.parent.unlockObserver();
216
- const controller = this.parent.create();
217
- if (controller) {
218
- return controller.startRenderer();
219
- }
220
- return Promise.reject(new Error("ConfiguratorController.startRenderer() - legacy product transition failed"));
225
+ return this._transitionLegacyProduct(product);
221
226
  }
222
- configState = dState;
223
227
  }
224
- catch (_err) {
225
- // config state is not available
226
- configState = null;
228
+ else {
229
+ try {
230
+ const dState = await this.getConfiguratorState();
231
+ // if this is declared, we have a furniture scene that we need to re-create the embed
232
+ // with new attributes
233
+ const product = dState.state.firstOfType("product");
234
+ if (product) {
235
+ return this._transitionLegacyProduct(product);
236
+ }
237
+ configState = dState.state;
238
+ }
239
+ catch (_err) {
240
+ // config state is not available
241
+ configState = null;
242
+ }
227
243
  }
228
244
  // required attributes with defaults for plattar-configurator node
229
245
  const width = this.getAttribute("width") || "500px";
@@ -238,7 +254,7 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
238
254
  const showAR = this.getAttribute("show-ar");
239
255
  const showUI = this.getAttribute("show-ui");
240
256
  if (configState) {
241
- const encodedState = configState.state.encode();
257
+ const encodedState = configState.encode();
242
258
  if (encodedState.length < 6000) {
243
259
  viewer.setAttribute("config-state", encodedState);
244
260
  }
@@ -257,6 +273,22 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
257
273
  return accept(viewer);
258
274
  });
259
275
  }
276
+ /**
277
+ * Legacy furniture scenes carry a "product" state entry — the embed is
278
+ * re-created with product attributes and rendering restarts there
279
+ */
280
+ async _transitionLegacyProduct(product) {
281
+ this.parent.lockObserver();
282
+ this.parent.destroy();
283
+ this.setAttribute("product-id", product.scene_product_id);
284
+ this.removeAttribute("scene-id");
285
+ this.parent.unlockObserver();
286
+ const controller = this.parent.create();
287
+ if (controller) {
288
+ return controller.startRenderer();
289
+ }
290
+ return Promise.reject(new Error("ConfiguratorController.startRenderer() - legacy product transition failed"));
291
+ }
260
292
  async initAR() {
261
293
  if (!util_1.Util.canAugment()) {
262
294
  throw new Error("ConfiguratorController.initAR() - cannot proceed as AR not available in context");
@@ -82,16 +82,10 @@ class GalleryController extends plattar_controller_1.PlattarController {
82
82
  this._prevQROpt = opt;
83
83
  if (!opt.detached) {
84
84
  this._state = plattar_controller_1.ControllerState.QRCode;
85
- return new Promise((accept, reject) => {
86
- viewer.onload = () => {
87
- return accept(viewer);
88
- };
89
- this.append(viewer);
90
- });
85
+ this.append(viewer);
86
+ return this._awaitLoad(viewer);
91
87
  }
92
- return new Promise((accept, reject) => {
93
- return accept(viewer);
94
- });
88
+ return Promise.resolve(viewer);
95
89
  }
96
90
  async startRenderer() {
97
91
  // remove the old renderer instance if any
@@ -60,6 +60,11 @@ class LauncherController extends plattar_controller_1.PlattarController {
60
60
  }
61
61
  return Promise.reject(new Error("LauncherController.startARQRCode() - legacy product transition failed"));
62
62
  }
63
+ // Kick off scene-graph encoding immediately while we proceed to the base
64
+ // implementation — the result is Promise-cached so no duplicate request is fired
65
+ if (!this.getAttribute("scene-graph-id")) {
66
+ dState.state.encodeSceneGraphID().catch(() => null);
67
+ }
63
68
  }
64
69
  catch (_err) {
65
70
  }
@@ -122,10 +127,10 @@ class LauncherController extends plattar_controller_1.PlattarController {
122
127
  if (sceneGraphID) {
123
128
  viewer.setAttribute("scene-graph-id", sceneGraphID);
124
129
  }
125
- else {
130
+ else if (configState) {
126
131
  try {
127
- const sceneGraphID = await (await this.getConfiguratorState()).state.encodeSceneGraphID();
128
- viewer.setAttribute("scene-graph-id", sceneGraphID);
132
+ const encodedID = await configState.state.encodeSceneGraphID();
133
+ viewer.setAttribute("scene-graph-id", encodedID);
129
134
  }
130
135
  catch (_err) {
131
136
  // scene graph ID not available for some reason
@@ -136,7 +141,7 @@ class LauncherController extends plattar_controller_1.PlattarController {
136
141
  return new Promise((accept, reject) => {
137
142
  this.append(viewer);
138
143
  if (configState) {
139
- this.setupMessengerObservers(viewer, configState);
144
+ this.setupMessengerObservers(viewer, configState.state);
140
145
  }
141
146
  return accept(viewer);
142
147
  });
@@ -1,5 +1,5 @@
1
1
  import { LauncherAR } from "../../ar/launcher-ar";
2
- import { DecodedConfiguratorState } from "../../util/configurator-state";
2
+ import { ConfiguratorState, DecodedConfiguratorState } from "../../util/configurator-state";
3
3
  import type PlattarEmbed from "../plattar-embed";
4
4
  export declare enum ControllerState {
5
5
  None = 0,
@@ -26,6 +26,7 @@ export declare abstract class PlattarController {
26
26
  protected _state: ControllerState;
27
27
  protected _element: HTMLElement | null;
28
28
  protected _prevQROpt: any;
29
+ protected _bootConfigState: ConfiguratorState | null;
29
30
  private _selectVariationObserver;
30
31
  private _selectVariationIDObserver;
31
32
  private _selectVariationSKUObserver;
@@ -51,7 +52,7 @@ export declare abstract class PlattarController {
51
52
  * Setup messenger observers to detect variation changes and apply to the internal
52
53
  * configuration state
53
54
  */
54
- protected setupMessengerObservers(viewer: any, configState: DecodedConfiguratorState): void;
55
+ protected setupMessengerObservers(viewer: any, configState: ConfiguratorState): void;
55
56
  /**
56
57
  * Remove all pre-existing observers
57
58
  */
@@ -81,6 +82,12 @@ export declare abstract class PlattarController {
81
82
  * Initialise and return a launcher that can be used to start AR
82
83
  */
83
84
  abstract initAR(): Promise<LauncherAR>;
85
+ /**
86
+ * Appends the viewer to the shadow DOM and waits for its onload event.
87
+ * Rejects if onload does not fire within timeoutMs (default 30 s) so
88
+ * callers are never left with a permanently-pending Promise.
89
+ */
90
+ protected _awaitLoad(viewer: HTMLElement, timeoutMs?: number): Promise<HTMLElement>;
84
91
  /**
85
92
  * Removes the currently active renderer view from the DOM
86
93
  */
@@ -32,6 +32,11 @@ class PlattarController {
32
32
  _state = ControllerState.None;
33
33
  _element = null;
34
34
  _prevQROpt = null;
35
+ // when the renderer was booted from a supplied config-state attribute the
36
+ // state is decoded locally without the scene fetch — this keeps that live
37
+ // instance (mutated by the messenger observers as the user configures) so
38
+ // a later full decode for QR/AR reuses it instead of losing those changes
39
+ _bootConfigState = null;
35
40
  _selectVariationObserver = null;
36
41
  _selectVariationIDObserver = null;
37
42
  _selectVariationSKUObserver = null;
@@ -53,7 +58,7 @@ class PlattarController {
53
58
  // get a list of variation SKU's to use for initialising
54
59
  const variationSKUs = this.getAttribute("variation-sku");
55
60
  // generate the decoded configurator state
56
- const decodedState = configState ? await configurator_state_1.ConfiguratorState.decodeState(sceneID, configState) : await configurator_state_1.ConfiguratorState.decodeScene(sceneID);
61
+ const decodedState = configState ? await configurator_state_1.ConfiguratorState.decodeState(sceneID, configState, this._bootConfigState) : await configurator_state_1.ConfiguratorState.decodeScene(sceneID);
57
62
  // change the ID's and SKU's (if any) of the default configuration state
58
63
  const variationIDList = variationIDs ? variationIDs.split(",") : [];
59
64
  const variationSKUList = variationSKUs ? variationSKUs.split(",") : [];
@@ -76,7 +81,7 @@ class PlattarController {
76
81
  const args = cd.data[0];
77
82
  const variations = args ? (Array.isArray(args) ? args : [args]) : [];
78
83
  variations.forEach((variationID) => {
79
- configState.state.setVariationID(variationID);
84
+ configState.setVariationID(variationID);
80
85
  });
81
86
  }
82
87
  });
@@ -85,7 +90,7 @@ class PlattarController {
85
90
  const args = cd.data[0];
86
91
  const variations = args ? (Array.isArray(args) ? args : [args]) : [];
87
92
  variations.forEach((variationID) => {
88
- configState.state.setVariationID(variationID);
93
+ configState.setVariationID(variationID);
89
94
  });
90
95
  }
91
96
  });
@@ -94,7 +99,7 @@ class PlattarController {
94
99
  const args = cd.data[0];
95
100
  const variations = args ? (Array.isArray(args) ? args : [args]) : [];
96
101
  variations.forEach((variationSKU) => {
97
- configState.state.setVariationSKU(variationSKU);
102
+ configState.setVariationSKU(variationSKU);
98
103
  });
99
104
  }
100
105
  });
@@ -216,8 +221,9 @@ class PlattarController {
216
221
  }
217
222
  else {
218
223
  try {
219
- const sceneGraphID = await (await this.getConfiguratorState()).state.encodeSceneGraphID();
220
- dst += "&scene_graph_id=" + sceneGraphID;
224
+ const configState = await this.getConfiguratorState();
225
+ const encodedID = await configState.state.encodeSceneGraphID();
226
+ dst += "&scene_graph_id=" + encodedID;
221
227
  }
222
228
  catch (_err) {
223
229
  // scene graph ID not available for some reason
@@ -229,17 +235,29 @@ class PlattarController {
229
235
  this._prevQROpt = opt;
230
236
  if (!opt.detached) {
231
237
  this._state = ControllerState.QRCode;
232
- return new Promise((accept, reject) => {
233
- this.append(viewer);
234
- viewer.onload = () => {
235
- return accept(viewer);
236
- };
237
- });
238
+ this.append(viewer);
239
+ return this._awaitLoad(viewer);
238
240
  }
239
241
  return new Promise((accept, reject) => {
240
242
  return accept(viewer);
241
243
  });
242
244
  }
245
+ /**
246
+ * Appends the viewer to the shadow DOM and waits for its onload event.
247
+ * Rejects if onload does not fire within timeoutMs (default 30 s) so
248
+ * callers are never left with a permanently-pending Promise.
249
+ */
250
+ _awaitLoad(viewer, timeoutMs = 30000) {
251
+ return new Promise((accept, reject) => {
252
+ const timeout = setTimeout(() => {
253
+ reject(new Error("PlattarController._awaitLoad() - element did not fire onload within " + timeoutMs + "ms"));
254
+ }, timeoutMs);
255
+ viewer.onload = () => {
256
+ clearTimeout(timeout);
257
+ accept(viewer);
258
+ };
259
+ });
260
+ }
243
261
  /**
244
262
  * Removes the currently active renderer view from the DOM
245
263
  */
@@ -133,16 +133,10 @@ class VTOController extends plattar_controller_1.PlattarController {
133
133
  this._prevQROpt = opt;
134
134
  if (!opt.detached) {
135
135
  this._state = plattar_controller_1.ControllerState.QRCode;
136
- return new Promise((accept, reject) => {
137
- viewer.onload = () => {
138
- return accept(viewer);
139
- };
140
- this.append(viewer);
141
- });
136
+ this.append(viewer);
137
+ return this._awaitLoad(viewer);
142
138
  }
143
- return new Promise((accept, reject) => {
144
- return accept(viewer);
145
- });
139
+ return Promise.resolve(viewer);
146
140
  }
147
141
  async startRenderer() {
148
142
  // remove the old renderer instance if any
@@ -193,7 +187,7 @@ class VTOController extends plattar_controller_1.PlattarController {
193
187
  return new Promise((accept, reject) => {
194
188
  this.append(viewer);
195
189
  if (configState) {
196
- this.setupMessengerObservers(viewer, configState);
190
+ this.setupMessengerObservers(viewer, configState.state);
197
191
  }
198
192
  return accept(viewer);
199
193
  });