@plattar/plattar-ar-adapter 2.7.2 → 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.
@@ -31,6 +31,8 @@ class ConfiguratorAR extends launcher_ar_1.LauncherAR {
31
31
  this._ar = null;
32
32
  }
33
33
  _SetupAnalytics() {
34
+ if (this._analytics !== null)
35
+ return;
34
36
  const scene = this._options.state.scene;
35
37
  let analytics = null;
36
38
  // setup scene stuff (if any)
@@ -240,6 +242,8 @@ class ModelAR extends launcher_ar_1.LauncherAR {
240
242
  return this._options.modelID;
241
243
  }
242
244
  _SetupAnalytics(model) {
245
+ if (this._analytics !== null)
246
+ return;
243
247
  let analytics = null;
244
248
  const project = model.relationships.find(plattar_api_1.Project);
245
249
  // setup scene stuff (if any)
@@ -387,6 +391,8 @@ class ProductAR extends launcher_ar_1.LauncherAR {
387
391
  return this._options.variationSKU;
388
392
  }
389
393
  _SetupAnalytics(product, variation) {
394
+ if (this._analytics !== null)
395
+ return;
390
396
  let analytics = null;
391
397
  const scene = product.relationships.find(plattar_api_1.Scene);
392
398
  // setup scene stuff (if any)
@@ -436,9 +442,7 @@ class ProductAR extends launcher_ar_1.LauncherAR {
436
442
  return reject(new Error("ProductAR.init() - cannot proceed as AR not available in context"));
437
443
  }
438
444
  const product = new plattar_api_1.Product(this.productID);
439
- product.include(plattar_api_1.ProductVariation);
440
445
  product.include(plattar_api_1.ProductVariation.include(plattar_api_1.FileModel));
441
- product.include(plattar_api_1.Scene);
442
446
  product.include(plattar_api_1.Scene.include(plattar_api_1.Project));
443
447
  product.get().then((product) => {
444
448
  // find the required variation from our product
@@ -594,6 +598,8 @@ class RawAR extends launcher_ar_1.LauncherAR {
594
598
  }
595
599
  _SetupAnalytics() {
596
600
  return new Promise((accept, _reject) => {
601
+ if (this._analytics !== null)
602
+ return accept();
597
603
  const sceneID = this._options.sceneID;
598
604
  if (!sceneID) {
599
605
  return accept();
@@ -746,6 +752,8 @@ class SceneAR extends launcher_ar_1.LauncherAR {
746
752
  return this._options.sceneID;
747
753
  }
748
754
  _SetupAnalytics(scene) {
755
+ if (this._analytics !== null)
756
+ return;
749
757
  let analytics = null;
750
758
  // setup scene stuff (if any)
751
759
  if (scene) {
@@ -871,7 +879,6 @@ class SceneAR extends launcher_ar_1.LauncherAR {
871
879
  }
872
880
  const scene = new plattar_api_1.Scene(this.sceneID);
873
881
  scene.include(plattar_api_1.Project);
874
- scene.include(plattar_api_1.SceneProduct);
875
882
  scene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product));
876
883
  scene.include(plattar_api_1.SceneModel);
877
884
  scene.get().then((scene) => {
@@ -973,6 +980,7 @@ const version_1 = __importDefault(require("../version"));
973
980
  class SceneGraphAR extends launcher_ar_1.LauncherAR {
974
981
  // analytics instance
975
982
  _analytics = null;
983
+ _analyticsSetup = null;
976
984
  _options;
977
985
  // this thing controls the actual AR view
978
986
  // this is setup via .init() function
@@ -982,32 +990,37 @@ class SceneGraphAR extends launcher_ar_1.LauncherAR {
982
990
  this._options = options;
983
991
  this._ar = null;
984
992
  }
985
- async _SetupAnalytics() {
986
- const scene = new plattar_api_1.Scene(this._options.sceneID);
987
- scene.include(plattar_api_1.Project);
988
- const fetchedScene = await scene.get();
989
- let analytics = null;
990
- analytics = new plattar_analytics_1.Analytics(fetchedScene.attributes.application_id);
991
- analytics.origin = plattar_api_1.Server.location().type;
992
- this._analytics = analytics;
993
- analytics.data.push("type", "scene-graph-ar");
994
- analytics.data.push("sdkVersion", version_1.default);
995
- analytics.data.push("sceneId", fetchedScene.id);
996
- analytics.data.push("sceneTitle", fetchedScene.attributes.title);
997
- const application = fetchedScene.relationships.find(plattar_api_1.Project);
998
- // setup application stuff (if any)
999
- if (application) {
1000
- analytics.data.push("applicationId", application.id);
1001
- analytics.data.push("applicationTitle", application.attributes.title);
1002
- if (this._options.useARBanner) {
1003
- this.options.banner = {
1004
- title: application.attributes.title,
1005
- subtitle: fetchedScene.attributes.title,
1006
- button: 'Visit'
1007
- };
993
+ _SetupAnalytics() {
994
+ if (this._analyticsSetup !== null)
995
+ return this._analyticsSetup;
996
+ this._analyticsSetup = (async () => {
997
+ const scene = new plattar_api_1.Scene(this._options.sceneID);
998
+ scene.include(plattar_api_1.Project);
999
+ const fetchedScene = await scene.get();
1000
+ let analytics = null;
1001
+ analytics = new plattar_analytics_1.Analytics(fetchedScene.attributes.application_id);
1002
+ analytics.origin = plattar_api_1.Server.location().type;
1003
+ this._analytics = analytics;
1004
+ analytics.data.push("type", "scene-graph-ar");
1005
+ analytics.data.push("sdkVersion", version_1.default);
1006
+ analytics.data.push("sceneId", fetchedScene.id);
1007
+ analytics.data.push("sceneTitle", fetchedScene.attributes.title);
1008
+ const application = fetchedScene.relationships.find(plattar_api_1.Project);
1009
+ // setup application stuff (if any)
1010
+ if (application) {
1011
+ analytics.data.push("applicationId", application.id);
1012
+ analytics.data.push("applicationTitle", application.attributes.title);
1013
+ if (this._options.useARBanner) {
1014
+ this.options.banner = {
1015
+ title: application.attributes.title,
1016
+ subtitle: fetchedScene.attributes.title,
1017
+ button: 'Visit'
1018
+ };
1019
+ }
1008
1020
  }
1009
- }
1010
- return fetchedScene;
1021
+ return fetchedScene;
1022
+ })();
1023
+ return this._analyticsSetup;
1011
1024
  }
1012
1025
  /**
1013
1026
  * Composes a Scene into an AR Model (remote operation) that can be used to launch
@@ -1176,6 +1189,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
1176
1189
  exports.ConfiguratorController = void 0;
1177
1190
  const plattar_api_1 = require("@plattar/plattar-api");
1178
1191
  const scene_product_ar_1 = require("../../ar/scene-product-ar");
1192
+ const configurator_state_1 = require("../../util/configurator-state");
1179
1193
  const util_1 = require("../../util/util");
1180
1194
  const plattar_controller_1 = require("./plattar-controller");
1181
1195
  const configurator_ar_1 = require("../../ar/configurator-ar");
@@ -1254,6 +1268,11 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
1254
1268
  }
1255
1269
  return Promise.reject(new Error("ConfiguratorController.startARQRCode() - legacy product transition failed"));
1256
1270
  }
1271
+ // Kick off scene-graph encoding immediately while we proceed to the base
1272
+ // implementation — the result is Promise-cached so no duplicate request is fired
1273
+ if (!this.getAttribute("scene-graph-id")) {
1274
+ dState.state.encodeSceneGraphID().catch(() => null);
1275
+ }
1257
1276
  }
1258
1277
  catch (_err) {
1259
1278
  }
@@ -1375,28 +1394,38 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
1375
1394
  // optional attributes
1376
1395
  let configState = null;
1377
1396
  this._state = plattar_controller_1.ControllerState.Renderer;
1378
- try {
1379
- const dState = await this.getConfiguratorState();
1397
+ const suppliedState = this.getAttribute("config-state");
1398
+ if (suppliedState) {
1399
+ // the state was supplied by the host page — decode it locally
1400
+ // rather than paying for the scene fetch getConfiguratorState()
1401
+ // performs before the renderer iframe can even be created. The
1402
+ // renderer only needs the re-encoded state; the flows that do
1403
+ // need the scene (QR/AR) decode on demand and reuse this same
1404
+ // instance via _bootConfigState, keeping user mutations.
1405
+ configState = configurator_state_1.ConfiguratorState.decode(suppliedState);
1406
+ this._bootConfigState = configState;
1380
1407
  // if this is declared, we have a furniture scene that we need to re-create the embed
1381
1408
  // with new attributes
1382
- const product = dState.state.firstOfType("product");
1409
+ const product = configState.firstOfType("product");
1383
1410
  if (product) {
1384
- this.parent.lockObserver();
1385
- this.parent.destroy();
1386
- this.setAttribute("product-id", product.scene_product_id);
1387
- this.removeAttribute("scene-id");
1388
- this.parent.unlockObserver();
1389
- const controller = this.parent.create();
1390
- if (controller) {
1391
- return controller.startRenderer();
1392
- }
1393
- return Promise.reject(new Error("ConfiguratorController.startRenderer() - legacy product transition failed"));
1411
+ return this._transitionLegacyProduct(product);
1394
1412
  }
1395
- configState = dState;
1396
1413
  }
1397
- catch (_err) {
1398
- // config state is not available
1399
- configState = null;
1414
+ else {
1415
+ try {
1416
+ const dState = await this.getConfiguratorState();
1417
+ // if this is declared, we have a furniture scene that we need to re-create the embed
1418
+ // with new attributes
1419
+ const product = dState.state.firstOfType("product");
1420
+ if (product) {
1421
+ return this._transitionLegacyProduct(product);
1422
+ }
1423
+ configState = dState.state;
1424
+ }
1425
+ catch (_err) {
1426
+ // config state is not available
1427
+ configState = null;
1428
+ }
1400
1429
  }
1401
1430
  // required attributes with defaults for plattar-configurator node
1402
1431
  const width = this.getAttribute("width") || "500px";
@@ -1411,7 +1440,7 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
1411
1440
  const showAR = this.getAttribute("show-ar");
1412
1441
  const showUI = this.getAttribute("show-ui");
1413
1442
  if (configState) {
1414
- const encodedState = configState.state.encode();
1443
+ const encodedState = configState.encode();
1415
1444
  if (encodedState.length < 6000) {
1416
1445
  viewer.setAttribute("config-state", encodedState);
1417
1446
  }
@@ -1430,6 +1459,22 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
1430
1459
  return accept(viewer);
1431
1460
  });
1432
1461
  }
1462
+ /**
1463
+ * Legacy furniture scenes carry a "product" state entry — the embed is
1464
+ * re-created with product attributes and rendering restarts there
1465
+ */
1466
+ async _transitionLegacyProduct(product) {
1467
+ this.parent.lockObserver();
1468
+ this.parent.destroy();
1469
+ this.setAttribute("product-id", product.scene_product_id);
1470
+ this.removeAttribute("scene-id");
1471
+ this.parent.unlockObserver();
1472
+ const controller = this.parent.create();
1473
+ if (controller) {
1474
+ return controller.startRenderer();
1475
+ }
1476
+ return Promise.reject(new Error("ConfiguratorController.startRenderer() - legacy product transition failed"));
1477
+ }
1433
1478
  async initAR() {
1434
1479
  if (!util_1.Util.canAugment()) {
1435
1480
  throw new Error("ConfiguratorController.initAR() - cannot proceed as AR not available in context");
@@ -1513,7 +1558,7 @@ class ConfiguratorController extends plattar_controller_1.PlattarController {
1513
1558
  }
1514
1559
  exports.ConfiguratorController = ConfiguratorController;
1515
1560
 
1516
- },{"../../ar/configurator-ar":1,"../../ar/scene-graph-ar":7,"../../ar/scene-product-ar":8,"../../util/util":19,"./plattar-controller":12,"@plattar/plattar-api":48}],10:[function(require,module,exports){
1561
+ },{"../../ar/configurator-ar":1,"../../ar/scene-graph-ar":7,"../../ar/scene-product-ar":8,"../../util/configurator-state":18,"../../util/util":19,"./plattar-controller":12,"@plattar/plattar-api":48}],10:[function(require,module,exports){
1517
1562
  "use strict";
1518
1563
  Object.defineProperty(exports, "__esModule", { value: true });
1519
1564
  exports.GalleryController = void 0;
@@ -1598,16 +1643,10 @@ class GalleryController extends plattar_controller_1.PlattarController {
1598
1643
  this._prevQROpt = opt;
1599
1644
  if (!opt.detached) {
1600
1645
  this._state = plattar_controller_1.ControllerState.QRCode;
1601
- return new Promise((accept, reject) => {
1602
- viewer.onload = () => {
1603
- return accept(viewer);
1604
- };
1605
- this.append(viewer);
1606
- });
1646
+ this.append(viewer);
1647
+ return this._awaitLoad(viewer);
1607
1648
  }
1608
- return new Promise((accept, reject) => {
1609
- return accept(viewer);
1610
- });
1649
+ return Promise.resolve(viewer);
1611
1650
  }
1612
1651
  async startRenderer() {
1613
1652
  // remove the old renderer instance if any
@@ -1704,6 +1743,11 @@ class LauncherController extends plattar_controller_1.PlattarController {
1704
1743
  }
1705
1744
  return Promise.reject(new Error("LauncherController.startARQRCode() - legacy product transition failed"));
1706
1745
  }
1746
+ // Kick off scene-graph encoding immediately while we proceed to the base
1747
+ // implementation — the result is Promise-cached so no duplicate request is fired
1748
+ if (!this.getAttribute("scene-graph-id")) {
1749
+ dState.state.encodeSceneGraphID().catch(() => null);
1750
+ }
1707
1751
  }
1708
1752
  catch (_err) {
1709
1753
  }
@@ -1766,10 +1810,10 @@ class LauncherController extends plattar_controller_1.PlattarController {
1766
1810
  if (sceneGraphID) {
1767
1811
  viewer.setAttribute("scene-graph-id", sceneGraphID);
1768
1812
  }
1769
- else {
1813
+ else if (configState) {
1770
1814
  try {
1771
- const sceneGraphID = await (await this.getConfiguratorState()).state.encodeSceneGraphID();
1772
- viewer.setAttribute("scene-graph-id", sceneGraphID);
1815
+ const encodedID = await configState.state.encodeSceneGraphID();
1816
+ viewer.setAttribute("scene-graph-id", encodedID);
1773
1817
  }
1774
1818
  catch (_err) {
1775
1819
  // scene graph ID not available for some reason
@@ -1780,7 +1824,7 @@ class LauncherController extends plattar_controller_1.PlattarController {
1780
1824
  return new Promise((accept, reject) => {
1781
1825
  this.append(viewer);
1782
1826
  if (configState) {
1783
- this.setupMessengerObservers(viewer, configState);
1827
+ this.setupMessengerObservers(viewer, configState.state);
1784
1828
  }
1785
1829
  return accept(viewer);
1786
1830
  });
@@ -1903,6 +1947,11 @@ class PlattarController {
1903
1947
  _state = ControllerState.None;
1904
1948
  _element = null;
1905
1949
  _prevQROpt = null;
1950
+ // when the renderer was booted from a supplied config-state attribute the
1951
+ // state is decoded locally without the scene fetch — this keeps that live
1952
+ // instance (mutated by the messenger observers as the user configures) so
1953
+ // a later full decode for QR/AR reuses it instead of losing those changes
1954
+ _bootConfigState = null;
1906
1955
  _selectVariationObserver = null;
1907
1956
  _selectVariationIDObserver = null;
1908
1957
  _selectVariationSKUObserver = null;
@@ -1924,7 +1973,7 @@ class PlattarController {
1924
1973
  // get a list of variation SKU's to use for initialising
1925
1974
  const variationSKUs = this.getAttribute("variation-sku");
1926
1975
  // generate the decoded configurator state
1927
- const decodedState = configState ? await configurator_state_1.ConfiguratorState.decodeState(sceneID, configState) : await configurator_state_1.ConfiguratorState.decodeScene(sceneID);
1976
+ const decodedState = configState ? await configurator_state_1.ConfiguratorState.decodeState(sceneID, configState, this._bootConfigState) : await configurator_state_1.ConfiguratorState.decodeScene(sceneID);
1928
1977
  // change the ID's and SKU's (if any) of the default configuration state
1929
1978
  const variationIDList = variationIDs ? variationIDs.split(",") : [];
1930
1979
  const variationSKUList = variationSKUs ? variationSKUs.split(",") : [];
@@ -1947,7 +1996,7 @@ class PlattarController {
1947
1996
  const args = cd.data[0];
1948
1997
  const variations = args ? (Array.isArray(args) ? args : [args]) : [];
1949
1998
  variations.forEach((variationID) => {
1950
- configState.state.setVariationID(variationID);
1999
+ configState.setVariationID(variationID);
1951
2000
  });
1952
2001
  }
1953
2002
  });
@@ -1956,7 +2005,7 @@ class PlattarController {
1956
2005
  const args = cd.data[0];
1957
2006
  const variations = args ? (Array.isArray(args) ? args : [args]) : [];
1958
2007
  variations.forEach((variationID) => {
1959
- configState.state.setVariationID(variationID);
2008
+ configState.setVariationID(variationID);
1960
2009
  });
1961
2010
  }
1962
2011
  });
@@ -1965,7 +2014,7 @@ class PlattarController {
1965
2014
  const args = cd.data[0];
1966
2015
  const variations = args ? (Array.isArray(args) ? args : [args]) : [];
1967
2016
  variations.forEach((variationSKU) => {
1968
- configState.state.setVariationSKU(variationSKU);
2017
+ configState.setVariationSKU(variationSKU);
1969
2018
  });
1970
2019
  }
1971
2020
  });
@@ -2087,8 +2136,9 @@ class PlattarController {
2087
2136
  }
2088
2137
  else {
2089
2138
  try {
2090
- const sceneGraphID = await (await this.getConfiguratorState()).state.encodeSceneGraphID();
2091
- dst += "&scene_graph_id=" + sceneGraphID;
2139
+ const configState = await this.getConfiguratorState();
2140
+ const encodedID = await configState.state.encodeSceneGraphID();
2141
+ dst += "&scene_graph_id=" + encodedID;
2092
2142
  }
2093
2143
  catch (_err) {
2094
2144
  // scene graph ID not available for some reason
@@ -2100,17 +2150,29 @@ class PlattarController {
2100
2150
  this._prevQROpt = opt;
2101
2151
  if (!opt.detached) {
2102
2152
  this._state = ControllerState.QRCode;
2103
- return new Promise((accept, reject) => {
2104
- this.append(viewer);
2105
- viewer.onload = () => {
2106
- return accept(viewer);
2107
- };
2108
- });
2153
+ this.append(viewer);
2154
+ return this._awaitLoad(viewer);
2109
2155
  }
2110
2156
  return new Promise((accept, reject) => {
2111
2157
  return accept(viewer);
2112
2158
  });
2113
2159
  }
2160
+ /**
2161
+ * Appends the viewer to the shadow DOM and waits for its onload event.
2162
+ * Rejects if onload does not fire within timeoutMs (default 30 s) so
2163
+ * callers are never left with a permanently-pending Promise.
2164
+ */
2165
+ _awaitLoad(viewer, timeoutMs = 30000) {
2166
+ return new Promise((accept, reject) => {
2167
+ const timeout = setTimeout(() => {
2168
+ reject(new Error("PlattarController._awaitLoad() - element did not fire onload within " + timeoutMs + "ms"));
2169
+ }, timeoutMs);
2170
+ viewer.onload = () => {
2171
+ clearTimeout(timeout);
2172
+ accept(viewer);
2173
+ };
2174
+ });
2175
+ }
2114
2176
  /**
2115
2177
  * Removes the currently active renderer view from the DOM
2116
2178
  */
@@ -2587,16 +2649,10 @@ class VTOController extends plattar_controller_1.PlattarController {
2587
2649
  this._prevQROpt = opt;
2588
2650
  if (!opt.detached) {
2589
2651
  this._state = plattar_controller_1.ControllerState.QRCode;
2590
- return new Promise((accept, reject) => {
2591
- viewer.onload = () => {
2592
- return accept(viewer);
2593
- };
2594
- this.append(viewer);
2595
- });
2652
+ this.append(viewer);
2653
+ return this._awaitLoad(viewer);
2596
2654
  }
2597
- return new Promise((accept, reject) => {
2598
- return accept(viewer);
2599
- });
2655
+ return Promise.resolve(viewer);
2600
2656
  }
2601
2657
  async startRenderer() {
2602
2658
  // remove the old renderer instance if any
@@ -2647,7 +2703,7 @@ class VTOController extends plattar_controller_1.PlattarController {
2647
2703
  return new Promise((accept, reject) => {
2648
2704
  this.append(viewer);
2649
2705
  if (configState) {
2650
- this.setupMessengerObservers(viewer, configState);
2706
+ this.setupMessengerObservers(viewer, configState.state);
2651
2707
  }
2652
2708
  return accept(viewer);
2653
2709
  });
@@ -2805,12 +2861,8 @@ class WebXRController extends plattar_controller_1.PlattarController {
2805
2861
  viewer.setAttribute("url", opt.url || dst);
2806
2862
  this._state = plattar_controller_1.ControllerState.QRCode;
2807
2863
  this._prevQROpt = opt;
2808
- return new Promise((accept, reject) => {
2809
- viewer.onload = () => {
2810
- return accept(viewer);
2811
- };
2812
- this.append(viewer);
2813
- });
2864
+ this.append(viewer);
2865
+ return this._awaitLoad(viewer);
2814
2866
  }
2815
2867
  async startRenderer() {
2816
2868
  // remove the old renderer instance if any
@@ -2840,10 +2892,8 @@ class WebXRController extends plattar_controller_1.PlattarController {
2840
2892
  if (showUI) {
2841
2893
  viewer.setAttribute("show-ui", showUI);
2842
2894
  }
2843
- return new Promise((accept, reject) => {
2844
- this.append(viewer);
2845
- return accept(viewer);
2846
- });
2895
+ this.append(viewer);
2896
+ return Promise.resolve(viewer);
2847
2897
  }
2848
2898
  async initAR() {
2849
2899
  throw new Error("WebXRController.initAR() - cannot proceed as AR not available in webxr");
@@ -2907,6 +2957,18 @@ class PlattarEmbed extends HTMLElement {
2907
2957
  connectedCallback() {
2908
2958
  this.create();
2909
2959
  }
2960
+ /**
2961
+ * Clean up when the element is removed from the DOM.
2962
+ * Disconnects the MutationObserver and removes all messenger subscriptions
2963
+ * to prevent memory leaks from dangling closures.
2964
+ */
2965
+ disconnectedCallback() {
2966
+ if (this._observer) {
2967
+ this._observer.disconnect();
2968
+ this._observer = null;
2969
+ }
2970
+ this.destroy();
2971
+ }
2910
2972
  /**
2911
2973
  * creates a brand new instance of this embed
2912
2974
  */
@@ -3225,6 +3287,16 @@ class ConfiguratorState {
3225
3287
  _mappedVariationIDValues;
3226
3288
  // This maps Variation SKU against a Variation ID - populated by decodeScene() function
3227
3289
  _mappedVariationSKUValues;
3290
+ // Cache for encodeSceneGraphID() - keyed by serialised scene graph JSON so state
3291
+ // changes automatically bust the cache, and concurrent in-flight calls share one fetch
3292
+ _cachedSceneGraphID = null;
3293
+ _cachedSceneGraphJSON = null;
3294
+ // Memoised result of the sceneGraph getter - cleared whenever state is mutated
3295
+ _cachedSceneGraph = null;
3296
+ // Static page-lifetime cache for Scene API responses, keyed by sceneID.
3297
+ // Shared across all controller instances so that multiple <plattar-embed>
3298
+ // elements with the same scene-id (or a recreated controller) reuse one fetch.
3299
+ static _sceneAPICache = new Map();
3228
3300
  constructor(state = null) {
3229
3301
  this._mappedVariationIDValues = new Map();
3230
3302
  this._mappedVariationSKUValues = new Map();
@@ -3321,6 +3393,7 @@ class ConfiguratorState {
3321
3393
  newData[meta.scene_product_index] = SceneModelID;
3322
3394
  newData[meta.product_variation_index] = null;
3323
3395
  newData[meta.meta_index] = metaData;
3396
+ this._cachedSceneGraph = null;
3324
3397
  }
3325
3398
  }
3326
3399
  /**
@@ -3348,6 +3421,7 @@ class ConfiguratorState {
3348
3421
  newData[meta.product_index] = productID;
3349
3422
  newData[meta.product_variation_index] = productVariationID;
3350
3423
  newData[meta.meta_index] = metaData;
3424
+ this._cachedSceneGraph = null;
3351
3425
  }
3352
3426
  }
3353
3427
  /**
@@ -3376,6 +3450,7 @@ class ConfiguratorState {
3376
3450
  newData[meta.scene_product_index] = sceneProductID;
3377
3451
  newData[meta.product_variation_index] = productVariationID;
3378
3452
  newData[meta.meta_index] = metaData;
3453
+ this._cachedSceneGraph = null;
3379
3454
  }
3380
3455
  }
3381
3456
  /**
@@ -3560,24 +3635,43 @@ class ConfiguratorState {
3560
3635
  static decode(state) {
3561
3636
  return new ConfiguratorState(state);
3562
3637
  }
3638
+ /**
3639
+ * Returns a cached Promise<Scene> for the given sceneID, firing at most one
3640
+ * API request per sceneID per page lifetime. Both decodeState() and
3641
+ * decodeScene() share this cache so they never race each other.
3642
+ */
3643
+ static _fetchScene(sceneID) {
3644
+ const cached = ConfiguratorState._sceneAPICache.get(sceneID);
3645
+ if (cached) {
3646
+ return cached;
3647
+ }
3648
+ const fscene = new plattar_api_1.Scene(sceneID);
3649
+ fscene.include(plattar_api_1.Project);
3650
+ fscene.include(plattar_api_1.SceneModel);
3651
+ fscene.include(plattar_api_1.Product);
3652
+ fscene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product.include(plattar_api_1.ProductVariation)));
3653
+ const promise = fscene.get();
3654
+ ConfiguratorState._sceneAPICache.set(sceneID, promise);
3655
+ // Bust the cache on failure so the next call retries
3656
+ promise.catch(() => {
3657
+ ConfiguratorState._sceneAPICache.delete(sceneID);
3658
+ });
3659
+ return promise;
3660
+ }
3563
3661
  /**
3564
3662
  * Decodes a previously generated state
3565
3663
  * @param sceneID
3566
3664
  * @param state
3665
+ * @param existingState - (optional) reuse this already-decoded instance instead of
3666
+ * parsing a fresh one, so mutations applied to it before this call are preserved
3567
3667
  * @returns
3568
3668
  */
3569
- static async decodeState(sceneID = null, state = null) {
3669
+ static async decodeState(sceneID = null, state = null, existingState = null) {
3570
3670
  if (!sceneID || !state) {
3571
3671
  throw new Error("ConfiguratorState.decodeState(sceneID, state) - sceneID and state must be defined");
3572
3672
  }
3573
- const configState = new ConfiguratorState(state);
3574
- const fscene = new plattar_api_1.Scene(sceneID);
3575
- fscene.include(plattar_api_1.Project);
3576
- fscene.include(plattar_api_1.Product);
3577
- fscene.include(plattar_api_1.SceneProduct);
3578
- fscene.include(plattar_api_1.SceneModel);
3579
- fscene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product.include(plattar_api_1.ProductVariation)));
3580
- const scene = await fscene.get();
3673
+ const configState = existingState ?? new ConfiguratorState(state);
3674
+ const scene = await ConfiguratorState._fetchScene(sceneID);
3581
3675
  return {
3582
3676
  scene: scene,
3583
3677
  state: configState
@@ -3594,13 +3688,7 @@ class ConfiguratorState {
3594
3688
  throw new Error("ConfiguratorState.decodeScene(sceneID) - sceneID must be defined");
3595
3689
  }
3596
3690
  const configState = new ConfiguratorState();
3597
- const fscene = new plattar_api_1.Scene(sceneID);
3598
- fscene.include(plattar_api_1.Project);
3599
- fscene.include(plattar_api_1.SceneProduct);
3600
- fscene.include(plattar_api_1.SceneModel);
3601
- fscene.include(plattar_api_1.Product);
3602
- fscene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product.include(plattar_api_1.ProductVariation)));
3603
- const scene = await fscene.get();
3691
+ const scene = await ConfiguratorState._fetchScene(sceneID);
3604
3692
  const sceneProducts = scene.relationships.filter(plattar_api_1.SceneProduct);
3605
3693
  const sceneModels = scene.relationships.filter(plattar_api_1.SceneModel);
3606
3694
  const products = scene.relationships.filter(plattar_api_1.Product);
@@ -3658,42 +3746,54 @@ class ConfiguratorState {
3658
3746
  encode() {
3659
3747
  return btoa(JSON.stringify(this._state));
3660
3748
  }
3661
- async encodeSceneGraphID() {
3749
+ encodeSceneGraphID() {
3662
3750
  const graph = this.sceneGraph;
3751
+ const graphJSON = JSON.stringify(graph);
3752
+ // Return cached promise if the scene graph state hasn't changed.
3753
+ // Caching the Promise (not the resolved value) means concurrent in-flight
3754
+ // calls also share a single fetch rather than firing multiple requests.
3755
+ if (this._cachedSceneGraphID !== null && this._cachedSceneGraphJSON === graphJSON) {
3756
+ return this._cachedSceneGraphID;
3757
+ }
3663
3758
  // some scene-graphs are very large in size, we store it remotely
3664
3759
  // this storage will expire in 10 minutes so this is a non-permanent version
3665
3760
  // and is designed for quick ar
3666
3761
  const url = plattar_api_1.Server.location().type === 'staging' ? 'https://c.plattar.space/v3/redir/store' : 'https://c.plattar.com/v3/redir/store';
3667
- // finally send our scene-graph to the backend to generate the AR file and return
3668
- try {
3669
- const response = await fetch(url, {
3670
- method: "POST",
3671
- headers: {
3672
- "Content-Type": "application/json"
3673
- },
3674
- body: JSON.stringify({
3675
- data: {
3676
- attributes: {
3677
- data: graph
3678
- }
3762
+ this._cachedSceneGraphJSON = graphJSON;
3763
+ this._cachedSceneGraphID = fetch(url, {
3764
+ method: "POST",
3765
+ headers: {
3766
+ "Content-Type": "application/json"
3767
+ },
3768
+ body: JSON.stringify({
3769
+ data: {
3770
+ attributes: {
3771
+ data: graph
3679
3772
  }
3680
- })
3681
- });
3773
+ }
3774
+ })
3775
+ }).then(async (response) => {
3682
3776
  if (!response.ok) {
3683
3777
  throw new Error(`ConfiguratorState.encodeSceneGraphID() - network response was not ok ${response.status}`);
3684
3778
  }
3685
3779
  const data = await response.json();
3686
3780
  return data.data.id;
3687
- }
3688
- catch (error) {
3781
+ }).catch((error) => {
3782
+ // Bust the cache on failure so the next call retries
3783
+ this._cachedSceneGraphID = null;
3784
+ this._cachedSceneGraphJSON = null;
3689
3785
  throw new Error(`ConfiguratorState.encodeSceneGraphID() - there was a request error to ${url}, error was ${error.message}`);
3690
- }
3786
+ });
3787
+ return this._cachedSceneGraphID;
3691
3788
  }
3692
3789
  /**
3693
3790
  * Compiles and returns the Dynamic Scene Graph (Updated for 2025 for DynamicAR)
3694
3791
  * NOTE: Eventually this structure should replace ConfiguratorState
3695
3792
  */
3696
3793
  get sceneGraph() {
3794
+ if (this._cachedSceneGraph !== null) {
3795
+ return this._cachedSceneGraph;
3796
+ }
3697
3797
  const objects = this.array();
3698
3798
  // in here we need to generate the schema input to be sent to the backend service
3699
3799
  const schema = {
@@ -3720,7 +3820,8 @@ class ConfiguratorState {
3720
3820
  schema.inputs.push(data);
3721
3821
  }
3722
3822
  });
3723
- return schema;
3823
+ this._cachedSceneGraph = schema;
3824
+ return this._cachedSceneGraph;
3724
3825
  }
3725
3826
  }
3726
3827
  exports.ConfiguratorState = ConfiguratorState;
@@ -3733,6 +3834,7 @@ exports.Util = void 0;
3733
3834
  * Static Utility Functions
3734
3835
  */
3735
3836
  class Util {
3837
+ static _cache = new Map();
3736
3838
  static isValidServerLocation(server) {
3737
3839
  if (!server) {
3738
3840
  return false;
@@ -3758,67 +3860,118 @@ class Util {
3758
3860
  return false;
3759
3861
  }
3760
3862
  static canAugment() {
3761
- return Util.canQuicklook() || Util.canSceneViewer();
3863
+ if (Util._cache.has('canAugment'))
3864
+ return Util._cache.get('canAugment');
3865
+ const result = Util.canQuicklook() || Util.canSceneViewer();
3866
+ Util._cache.set('canAugment', result);
3867
+ return result;
3762
3868
  }
3763
3869
  static canQuicklook() {
3870
+ if (Util._cache.has('canQuicklook'))
3871
+ return Util._cache.get('canQuicklook');
3872
+ let result = false;
3764
3873
  if (Util.isIOS()) {
3765
3874
  const isWKWebView = Boolean((window && window).webkit && window.webkit.messageHandlers);
3766
3875
  if (isWKWebView) {
3767
- return Boolean(/CriOS\/|EdgiOS\/|FxiOS\/|GSA\/|DuckDuckGo\//.test(navigator.userAgent));
3876
+ result = Boolean(/CriOS\/|EdgiOS\/|FxiOS\/|GSA\/|DuckDuckGo\//.test(navigator.userAgent));
3877
+ }
3878
+ else {
3879
+ const tempAnchor = document.createElement("a");
3880
+ result = Boolean(tempAnchor.relList && tempAnchor.relList.supports && tempAnchor.relList.supports("ar"));
3768
3881
  }
3769
- const tempAnchor = document.createElement("a");
3770
- return tempAnchor.relList && tempAnchor.relList.supports && tempAnchor.relList.supports("ar");
3771
3882
  }
3772
- return false;
3883
+ Util._cache.set('canQuicklook', result);
3884
+ return result;
3773
3885
  }
3774
3886
  static canSceneViewer() {
3775
- return Util.isAndroid() && !Util.isFirefox() && !Util.isOculus();
3887
+ if (Util._cache.has('canSceneViewer'))
3888
+ return Util._cache.get('canSceneViewer');
3889
+ const result = Util.isAndroid() && !Util.isFirefox() && !Util.isOculus();
3890
+ Util._cache.set('canSceneViewer', result);
3891
+ return result;
3776
3892
  }
3777
3893
  static canRealityViewer() {
3778
- return Util.isIOS() && Util.getIOSVersion()[0] >= 13;
3894
+ if (Util._cache.has('canRealityViewer'))
3895
+ return Util._cache.get('canRealityViewer');
3896
+ const result = Util.isIOS() && Util.getIOSVersion()[0] >= 13;
3897
+ Util._cache.set('canRealityViewer', result);
3898
+ return result;
3779
3899
  }
3780
3900
  static isSafariOnIOS() {
3781
- return Util.isIOS() && Util.isSafari();
3901
+ if (Util._cache.has('isSafariOnIOS'))
3902
+ return Util._cache.get('isSafariOnIOS');
3903
+ const result = Util.isIOS() && Util.isSafari();
3904
+ Util._cache.set('isSafariOnIOS', result);
3905
+ return result;
3782
3906
  }
3783
3907
  static isChromeOnIOS() {
3784
- return Util.isIOS() && /CriOS\//.test(navigator.userAgent);
3908
+ if (Util._cache.has('isChromeOnIOS'))
3909
+ return Util._cache.get('isChromeOnIOS');
3910
+ const result = Util.isIOS() && /CriOS\//.test(navigator.userAgent);
3911
+ Util._cache.set('isChromeOnIOS', result);
3912
+ return result;
3785
3913
  }
3786
3914
  static isIOS() {
3787
- return (/iPad|iPhone|iPod/.test(navigator.userAgent) && !self.MSStream) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
3915
+ if (Util._cache.has('isIOS'))
3916
+ return Util._cache.get('isIOS');
3917
+ const result = (/iPad|iPhone|iPod/.test(navigator.userAgent) && !self.MSStream) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
3918
+ Util._cache.set('isIOS', result);
3919
+ return result;
3788
3920
  }
3789
3921
  static isAndroid() {
3790
- return /android/i.test(navigator.userAgent);
3922
+ if (Util._cache.has('isAndroid'))
3923
+ return Util._cache.get('isAndroid');
3924
+ const result = /android/i.test(navigator.userAgent);
3925
+ Util._cache.set('isAndroid', result);
3926
+ return result;
3791
3927
  }
3792
3928
  static isFirefox() {
3793
- return /firefox/i.test(navigator.userAgent);
3929
+ if (Util._cache.has('isFirefox'))
3930
+ return Util._cache.get('isFirefox');
3931
+ const result = /firefox/i.test(navigator.userAgent);
3932
+ Util._cache.set('isFirefox', result);
3933
+ return result;
3794
3934
  }
3795
3935
  static isOculus() {
3796
- return /OculusBrowser/.test(navigator.userAgent);
3936
+ if (Util._cache.has('isOculus'))
3937
+ return Util._cache.get('isOculus');
3938
+ const result = /OculusBrowser/.test(navigator.userAgent);
3939
+ Util._cache.set('isOculus', result);
3940
+ return result;
3797
3941
  }
3798
3942
  static isSafari() {
3799
- return Util.isIOS() && /Safari\//.test(navigator.userAgent);
3943
+ if (Util._cache.has('isSafari'))
3944
+ return Util._cache.get('isSafari');
3945
+ const result = Util.isIOS() && /Safari\//.test(navigator.userAgent);
3946
+ Util._cache.set('isSafari', result);
3947
+ return result;
3800
3948
  }
3801
3949
  static getIOSVersion() {
3950
+ if (Util._cache.has('getIOSVersion'))
3951
+ return Util._cache.get('getIOSVersion');
3952
+ let result = [-1, -1, -1];
3802
3953
  if (/iP(hone|od|ad)/.test(navigator.platform)) {
3803
3954
  const v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
3804
3955
  if (v !== null) {
3805
- return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3], 10)];
3956
+ result = [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3], 10)];
3806
3957
  }
3807
3958
  }
3808
- if (/Mac/.test(navigator.platform)) {
3959
+ else if (/Mac/.test(navigator.platform)) {
3809
3960
  const v = (navigator.appVersion).match(/Version\/(\d+)\.(\d+)\.?(\d+)?/);
3810
3961
  if (v !== null) {
3811
- return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3], 10)];
3962
+ result = [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3], 10)];
3812
3963
  }
3813
3964
  }
3814
- return [-1, -1, -1];
3965
+ Util._cache.set('getIOSVersion', result);
3966
+ return result;
3815
3967
  }
3816
3968
  static getChromeVersion() {
3969
+ if (Util._cache.has('getChromeVersion'))
3970
+ return Util._cache.get('getChromeVersion');
3817
3971
  const raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
3818
- if (raw !== null) {
3819
- return parseInt(raw[2], 10);
3820
- }
3821
- return 1;
3972
+ const result = raw !== null ? parseInt(raw[2], 10) : 1;
3973
+ Util._cache.set('getChromeVersion', result);
3974
+ return result;
3822
3975
  }
3823
3976
  }
3824
3977
  exports.Util = Util;
@@ -3826,7 +3979,7 @@ exports.Util = Util;
3826
3979
  },{}],20:[function(require,module,exports){
3827
3980
  "use strict";
3828
3981
  Object.defineProperty(exports, "__esModule", { value: true });
3829
- exports.default = "2.7.2";
3982
+ exports.default = "2.7.3";
3830
3983
 
3831
3984
  },{}],21:[function(require,module,exports){
3832
3985
  "use strict";