@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.
- package/build/es2015/plattar-ar-adapter.js +1307 -1155
- package/build/es2015/plattar-ar-adapter.min.js +1 -1
- package/build/es2019/plattar-ar-adapter.js +303 -150
- package/build/es2019/plattar-ar-adapter.min.js +1 -1
- package/dist/ar/configurator-ar.js +2 -0
- package/dist/ar/model-ar.js +2 -0
- package/dist/ar/product-ar.js +2 -2
- package/dist/ar/raw-ar.js +2 -0
- package/dist/ar/scene-ar.js +2 -1
- package/dist/ar/scene-graph-ar.d.ts +1 -0
- package/dist/ar/scene-graph-ar.js +31 -25
- package/dist/embed/controllers/configurator-controller.d.ts +5 -0
- package/dist/embed/controllers/configurator-controller.js +50 -18
- package/dist/embed/controllers/gallery-controller.js +3 -9
- package/dist/embed/controllers/launcher-controller.js +9 -4
- package/dist/embed/controllers/plattar-controller.d.ts +9 -2
- package/dist/embed/controllers/plattar-controller.js +30 -12
- package/dist/embed/controllers/vto-controller.js +4 -10
- package/dist/embed/controllers/webxr-controller.js +4 -10
- package/dist/embed/plattar-embed.d.ts +6 -0
- package/dist/embed/plattar-embed.js +12 -0
- package/dist/util/configurator-state.d.ts +13 -1
- package/dist/util/configurator-state.js +74 -35
- package/dist/util/util.d.ts +1 -0
- package/dist/util/util.js +74 -22
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -4
|
@@ -12,6 +12,16 @@ class ConfiguratorState {
|
|
|
12
12
|
_mappedVariationIDValues;
|
|
13
13
|
// This maps Variation SKU against a Variation ID - populated by decodeScene() function
|
|
14
14
|
_mappedVariationSKUValues;
|
|
15
|
+
// Cache for encodeSceneGraphID() - keyed by serialised scene graph JSON so state
|
|
16
|
+
// changes automatically bust the cache, and concurrent in-flight calls share one fetch
|
|
17
|
+
_cachedSceneGraphID = null;
|
|
18
|
+
_cachedSceneGraphJSON = null;
|
|
19
|
+
// Memoised result of the sceneGraph getter - cleared whenever state is mutated
|
|
20
|
+
_cachedSceneGraph = null;
|
|
21
|
+
// Static page-lifetime cache for Scene API responses, keyed by sceneID.
|
|
22
|
+
// Shared across all controller instances so that multiple <plattar-embed>
|
|
23
|
+
// elements with the same scene-id (or a recreated controller) reuse one fetch.
|
|
24
|
+
static _sceneAPICache = new Map();
|
|
15
25
|
constructor(state = null) {
|
|
16
26
|
this._mappedVariationIDValues = new Map();
|
|
17
27
|
this._mappedVariationSKUValues = new Map();
|
|
@@ -108,6 +118,7 @@ class ConfiguratorState {
|
|
|
108
118
|
newData[meta.scene_product_index] = SceneModelID;
|
|
109
119
|
newData[meta.product_variation_index] = null;
|
|
110
120
|
newData[meta.meta_index] = metaData;
|
|
121
|
+
this._cachedSceneGraph = null;
|
|
111
122
|
}
|
|
112
123
|
}
|
|
113
124
|
/**
|
|
@@ -135,6 +146,7 @@ class ConfiguratorState {
|
|
|
135
146
|
newData[meta.product_index] = productID;
|
|
136
147
|
newData[meta.product_variation_index] = productVariationID;
|
|
137
148
|
newData[meta.meta_index] = metaData;
|
|
149
|
+
this._cachedSceneGraph = null;
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
152
|
/**
|
|
@@ -163,6 +175,7 @@ class ConfiguratorState {
|
|
|
163
175
|
newData[meta.scene_product_index] = sceneProductID;
|
|
164
176
|
newData[meta.product_variation_index] = productVariationID;
|
|
165
177
|
newData[meta.meta_index] = metaData;
|
|
178
|
+
this._cachedSceneGraph = null;
|
|
166
179
|
}
|
|
167
180
|
}
|
|
168
181
|
/**
|
|
@@ -347,24 +360,43 @@ class ConfiguratorState {
|
|
|
347
360
|
static decode(state) {
|
|
348
361
|
return new ConfiguratorState(state);
|
|
349
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Returns a cached Promise<Scene> for the given sceneID, firing at most one
|
|
365
|
+
* API request per sceneID per page lifetime. Both decodeState() and
|
|
366
|
+
* decodeScene() share this cache so they never race each other.
|
|
367
|
+
*/
|
|
368
|
+
static _fetchScene(sceneID) {
|
|
369
|
+
const cached = ConfiguratorState._sceneAPICache.get(sceneID);
|
|
370
|
+
if (cached) {
|
|
371
|
+
return cached;
|
|
372
|
+
}
|
|
373
|
+
const fscene = new plattar_api_1.Scene(sceneID);
|
|
374
|
+
fscene.include(plattar_api_1.Project);
|
|
375
|
+
fscene.include(plattar_api_1.SceneModel);
|
|
376
|
+
fscene.include(plattar_api_1.Product);
|
|
377
|
+
fscene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product.include(plattar_api_1.ProductVariation)));
|
|
378
|
+
const promise = fscene.get();
|
|
379
|
+
ConfiguratorState._sceneAPICache.set(sceneID, promise);
|
|
380
|
+
// Bust the cache on failure so the next call retries
|
|
381
|
+
promise.catch(() => {
|
|
382
|
+
ConfiguratorState._sceneAPICache.delete(sceneID);
|
|
383
|
+
});
|
|
384
|
+
return promise;
|
|
385
|
+
}
|
|
350
386
|
/**
|
|
351
387
|
* Decodes a previously generated state
|
|
352
388
|
* @param sceneID
|
|
353
389
|
* @param state
|
|
390
|
+
* @param existingState - (optional) reuse this already-decoded instance instead of
|
|
391
|
+
* parsing a fresh one, so mutations applied to it before this call are preserved
|
|
354
392
|
* @returns
|
|
355
393
|
*/
|
|
356
|
-
static async decodeState(sceneID = null, state = null) {
|
|
394
|
+
static async decodeState(sceneID = null, state = null, existingState = null) {
|
|
357
395
|
if (!sceneID || !state) {
|
|
358
396
|
throw new Error("ConfiguratorState.decodeState(sceneID, state) - sceneID and state must be defined");
|
|
359
397
|
}
|
|
360
|
-
const configState = new ConfiguratorState(state);
|
|
361
|
-
const
|
|
362
|
-
fscene.include(plattar_api_1.Project);
|
|
363
|
-
fscene.include(plattar_api_1.Product);
|
|
364
|
-
fscene.include(plattar_api_1.SceneProduct);
|
|
365
|
-
fscene.include(plattar_api_1.SceneModel);
|
|
366
|
-
fscene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product.include(plattar_api_1.ProductVariation)));
|
|
367
|
-
const scene = await fscene.get();
|
|
398
|
+
const configState = existingState ?? new ConfiguratorState(state);
|
|
399
|
+
const scene = await ConfiguratorState._fetchScene(sceneID);
|
|
368
400
|
return {
|
|
369
401
|
scene: scene,
|
|
370
402
|
state: configState
|
|
@@ -381,13 +413,7 @@ class ConfiguratorState {
|
|
|
381
413
|
throw new Error("ConfiguratorState.decodeScene(sceneID) - sceneID must be defined");
|
|
382
414
|
}
|
|
383
415
|
const configState = new ConfiguratorState();
|
|
384
|
-
const
|
|
385
|
-
fscene.include(plattar_api_1.Project);
|
|
386
|
-
fscene.include(plattar_api_1.SceneProduct);
|
|
387
|
-
fscene.include(plattar_api_1.SceneModel);
|
|
388
|
-
fscene.include(plattar_api_1.Product);
|
|
389
|
-
fscene.include(plattar_api_1.SceneProduct.include(plattar_api_1.Product.include(plattar_api_1.ProductVariation)));
|
|
390
|
-
const scene = await fscene.get();
|
|
416
|
+
const scene = await ConfiguratorState._fetchScene(sceneID);
|
|
391
417
|
const sceneProducts = scene.relationships.filter(plattar_api_1.SceneProduct);
|
|
392
418
|
const sceneModels = scene.relationships.filter(plattar_api_1.SceneModel);
|
|
393
419
|
const products = scene.relationships.filter(plattar_api_1.Product);
|
|
@@ -445,42 +471,54 @@ class ConfiguratorState {
|
|
|
445
471
|
encode() {
|
|
446
472
|
return btoa(JSON.stringify(this._state));
|
|
447
473
|
}
|
|
448
|
-
|
|
474
|
+
encodeSceneGraphID() {
|
|
449
475
|
const graph = this.sceneGraph;
|
|
476
|
+
const graphJSON = JSON.stringify(graph);
|
|
477
|
+
// Return cached promise if the scene graph state hasn't changed.
|
|
478
|
+
// Caching the Promise (not the resolved value) means concurrent in-flight
|
|
479
|
+
// calls also share a single fetch rather than firing multiple requests.
|
|
480
|
+
if (this._cachedSceneGraphID !== null && this._cachedSceneGraphJSON === graphJSON) {
|
|
481
|
+
return this._cachedSceneGraphID;
|
|
482
|
+
}
|
|
450
483
|
// some scene-graphs are very large in size, we store it remotely
|
|
451
484
|
// this storage will expire in 10 minutes so this is a non-permanent version
|
|
452
485
|
// and is designed for quick ar
|
|
453
486
|
const url = plattar_api_1.Server.location().type === 'staging' ? 'https://c.plattar.space/v3/redir/store' : 'https://c.plattar.com/v3/redir/store';
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
data: graph
|
|
465
|
-
}
|
|
487
|
+
this._cachedSceneGraphJSON = graphJSON;
|
|
488
|
+
this._cachedSceneGraphID = fetch(url, {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: {
|
|
491
|
+
"Content-Type": "application/json"
|
|
492
|
+
},
|
|
493
|
+
body: JSON.stringify({
|
|
494
|
+
data: {
|
|
495
|
+
attributes: {
|
|
496
|
+
data: graph
|
|
466
497
|
}
|
|
467
|
-
}
|
|
468
|
-
})
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
}).then(async (response) => {
|
|
469
501
|
if (!response.ok) {
|
|
470
502
|
throw new Error(`ConfiguratorState.encodeSceneGraphID() - network response was not ok ${response.status}`);
|
|
471
503
|
}
|
|
472
504
|
const data = await response.json();
|
|
473
505
|
return data.data.id;
|
|
474
|
-
}
|
|
475
|
-
|
|
506
|
+
}).catch((error) => {
|
|
507
|
+
// Bust the cache on failure so the next call retries
|
|
508
|
+
this._cachedSceneGraphID = null;
|
|
509
|
+
this._cachedSceneGraphJSON = null;
|
|
476
510
|
throw new Error(`ConfiguratorState.encodeSceneGraphID() - there was a request error to ${url}, error was ${error.message}`);
|
|
477
|
-
}
|
|
511
|
+
});
|
|
512
|
+
return this._cachedSceneGraphID;
|
|
478
513
|
}
|
|
479
514
|
/**
|
|
480
515
|
* Compiles and returns the Dynamic Scene Graph (Updated for 2025 for DynamicAR)
|
|
481
516
|
* NOTE: Eventually this structure should replace ConfiguratorState
|
|
482
517
|
*/
|
|
483
518
|
get sceneGraph() {
|
|
519
|
+
if (this._cachedSceneGraph !== null) {
|
|
520
|
+
return this._cachedSceneGraph;
|
|
521
|
+
}
|
|
484
522
|
const objects = this.array();
|
|
485
523
|
// in here we need to generate the schema input to be sent to the backend service
|
|
486
524
|
const schema = {
|
|
@@ -507,7 +545,8 @@ class ConfiguratorState {
|
|
|
507
545
|
schema.inputs.push(data);
|
|
508
546
|
}
|
|
509
547
|
});
|
|
510
|
-
|
|
548
|
+
this._cachedSceneGraph = schema;
|
|
549
|
+
return this._cachedSceneGraph;
|
|
511
550
|
}
|
|
512
551
|
}
|
|
513
552
|
exports.ConfiguratorState = ConfiguratorState;
|
package/dist/util/util.d.ts
CHANGED
package/dist/util/util.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.Util = void 0;
|
|
|
5
5
|
* Static Utility Functions
|
|
6
6
|
*/
|
|
7
7
|
class Util {
|
|
8
|
+
static _cache = new Map();
|
|
8
9
|
static isValidServerLocation(server) {
|
|
9
10
|
if (!server) {
|
|
10
11
|
return false;
|
|
@@ -30,67 +31,118 @@ class Util {
|
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
33
|
static canAugment() {
|
|
33
|
-
|
|
34
|
+
if (Util._cache.has('canAugment'))
|
|
35
|
+
return Util._cache.get('canAugment');
|
|
36
|
+
const result = Util.canQuicklook() || Util.canSceneViewer();
|
|
37
|
+
Util._cache.set('canAugment', result);
|
|
38
|
+
return result;
|
|
34
39
|
}
|
|
35
40
|
static canQuicklook() {
|
|
41
|
+
if (Util._cache.has('canQuicklook'))
|
|
42
|
+
return Util._cache.get('canQuicklook');
|
|
43
|
+
let result = false;
|
|
36
44
|
if (Util.isIOS()) {
|
|
37
45
|
const isWKWebView = Boolean((window && window).webkit && window.webkit.messageHandlers);
|
|
38
46
|
if (isWKWebView) {
|
|
39
|
-
|
|
47
|
+
result = Boolean(/CriOS\/|EdgiOS\/|FxiOS\/|GSA\/|DuckDuckGo\//.test(navigator.userAgent));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const tempAnchor = document.createElement("a");
|
|
51
|
+
result = Boolean(tempAnchor.relList && tempAnchor.relList.supports && tempAnchor.relList.supports("ar"));
|
|
40
52
|
}
|
|
41
|
-
const tempAnchor = document.createElement("a");
|
|
42
|
-
return tempAnchor.relList && tempAnchor.relList.supports && tempAnchor.relList.supports("ar");
|
|
43
53
|
}
|
|
44
|
-
|
|
54
|
+
Util._cache.set('canQuicklook', result);
|
|
55
|
+
return result;
|
|
45
56
|
}
|
|
46
57
|
static canSceneViewer() {
|
|
47
|
-
|
|
58
|
+
if (Util._cache.has('canSceneViewer'))
|
|
59
|
+
return Util._cache.get('canSceneViewer');
|
|
60
|
+
const result = Util.isAndroid() && !Util.isFirefox() && !Util.isOculus();
|
|
61
|
+
Util._cache.set('canSceneViewer', result);
|
|
62
|
+
return result;
|
|
48
63
|
}
|
|
49
64
|
static canRealityViewer() {
|
|
50
|
-
|
|
65
|
+
if (Util._cache.has('canRealityViewer'))
|
|
66
|
+
return Util._cache.get('canRealityViewer');
|
|
67
|
+
const result = Util.isIOS() && Util.getIOSVersion()[0] >= 13;
|
|
68
|
+
Util._cache.set('canRealityViewer', result);
|
|
69
|
+
return result;
|
|
51
70
|
}
|
|
52
71
|
static isSafariOnIOS() {
|
|
53
|
-
|
|
72
|
+
if (Util._cache.has('isSafariOnIOS'))
|
|
73
|
+
return Util._cache.get('isSafariOnIOS');
|
|
74
|
+
const result = Util.isIOS() && Util.isSafari();
|
|
75
|
+
Util._cache.set('isSafariOnIOS', result);
|
|
76
|
+
return result;
|
|
54
77
|
}
|
|
55
78
|
static isChromeOnIOS() {
|
|
56
|
-
|
|
79
|
+
if (Util._cache.has('isChromeOnIOS'))
|
|
80
|
+
return Util._cache.get('isChromeOnIOS');
|
|
81
|
+
const result = Util.isIOS() && /CriOS\//.test(navigator.userAgent);
|
|
82
|
+
Util._cache.set('isChromeOnIOS', result);
|
|
83
|
+
return result;
|
|
57
84
|
}
|
|
58
85
|
static isIOS() {
|
|
59
|
-
|
|
86
|
+
if (Util._cache.has('isIOS'))
|
|
87
|
+
return Util._cache.get('isIOS');
|
|
88
|
+
const result = (/iPad|iPhone|iPod/.test(navigator.userAgent) && !self.MSStream) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
89
|
+
Util._cache.set('isIOS', result);
|
|
90
|
+
return result;
|
|
60
91
|
}
|
|
61
92
|
static isAndroid() {
|
|
62
|
-
|
|
93
|
+
if (Util._cache.has('isAndroid'))
|
|
94
|
+
return Util._cache.get('isAndroid');
|
|
95
|
+
const result = /android/i.test(navigator.userAgent);
|
|
96
|
+
Util._cache.set('isAndroid', result);
|
|
97
|
+
return result;
|
|
63
98
|
}
|
|
64
99
|
static isFirefox() {
|
|
65
|
-
|
|
100
|
+
if (Util._cache.has('isFirefox'))
|
|
101
|
+
return Util._cache.get('isFirefox');
|
|
102
|
+
const result = /firefox/i.test(navigator.userAgent);
|
|
103
|
+
Util._cache.set('isFirefox', result);
|
|
104
|
+
return result;
|
|
66
105
|
}
|
|
67
106
|
static isOculus() {
|
|
68
|
-
|
|
107
|
+
if (Util._cache.has('isOculus'))
|
|
108
|
+
return Util._cache.get('isOculus');
|
|
109
|
+
const result = /OculusBrowser/.test(navigator.userAgent);
|
|
110
|
+
Util._cache.set('isOculus', result);
|
|
111
|
+
return result;
|
|
69
112
|
}
|
|
70
113
|
static isSafari() {
|
|
71
|
-
|
|
114
|
+
if (Util._cache.has('isSafari'))
|
|
115
|
+
return Util._cache.get('isSafari');
|
|
116
|
+
const result = Util.isIOS() && /Safari\//.test(navigator.userAgent);
|
|
117
|
+
Util._cache.set('isSafari', result);
|
|
118
|
+
return result;
|
|
72
119
|
}
|
|
73
120
|
static getIOSVersion() {
|
|
121
|
+
if (Util._cache.has('getIOSVersion'))
|
|
122
|
+
return Util._cache.get('getIOSVersion');
|
|
123
|
+
let result = [-1, -1, -1];
|
|
74
124
|
if (/iP(hone|od|ad)/.test(navigator.platform)) {
|
|
75
125
|
const v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
|
|
76
126
|
if (v !== null) {
|
|
77
|
-
|
|
127
|
+
result = [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3], 10)];
|
|
78
128
|
}
|
|
79
129
|
}
|
|
80
|
-
if (/Mac/.test(navigator.platform)) {
|
|
130
|
+
else if (/Mac/.test(navigator.platform)) {
|
|
81
131
|
const v = (navigator.appVersion).match(/Version\/(\d+)\.(\d+)\.?(\d+)?/);
|
|
82
132
|
if (v !== null) {
|
|
83
|
-
|
|
133
|
+
result = [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3], 10)];
|
|
84
134
|
}
|
|
85
135
|
}
|
|
86
|
-
|
|
136
|
+
Util._cache.set('getIOSVersion', result);
|
|
137
|
+
return result;
|
|
87
138
|
}
|
|
88
139
|
static getChromeVersion() {
|
|
140
|
+
if (Util._cache.has('getChromeVersion'))
|
|
141
|
+
return Util._cache.get('getChromeVersion');
|
|
89
142
|
const raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return 1;
|
|
143
|
+
const result = raw !== null ? parseInt(raw[2], 10) : 1;
|
|
144
|
+
Util._cache.set('getChromeVersion', result);
|
|
145
|
+
return result;
|
|
94
146
|
}
|
|
95
147
|
}
|
|
96
148
|
exports.Util = Util;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.7.
|
|
1
|
+
declare const _default: "2.7.3";
|
|
2
2
|
export default _default;
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plattar/plattar-ar-adapter",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.3",
|
|
4
4
|
"description": "Plattar AR Adapter for interfacing with Google & Apple WebAR",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"clean": "
|
|
9
|
+
"clean": "node -e \"['build','dist','node_modules'].forEach(p=>require('fs').rmSync(p,{recursive:true,force:true}))\"",
|
|
10
10
|
"build": "npm run clean && npm install && npm run build-ts && npm run build-es2019 && npm run build-es2015",
|
|
11
11
|
"build-ts": "tsc --noEmitOnError",
|
|
12
|
-
"build-es2019": "
|
|
13
|
-
"build-es2015": "
|
|
12
|
+
"build-es2019": "rimraf build/es2019 && mkdirp build/es2019 && browserify --standalone PlattarARAdapter dist/index.js -o build/es2019/plattar-ar-adapter.js && uglifyjs build/es2019/plattar-ar-adapter.js --output build/es2019/plattar-ar-adapter.min.js",
|
|
13
|
+
"build-es2015": "rimraf build/es2015 && mkdirp build/es2015 && babel build/es2019/plattar-ar-adapter.js --presets=@babel/env > build/es2015/plattar-ar-adapter.js && uglifyjs build/es2015/plattar-ar-adapter.js --output build/es2015/plattar-ar-adapter.min.js",
|
|
14
|
+
"serve": "http-server build/es2019 -p 3000 --cors -c-1",
|
|
15
|
+
"watch:rebuild": "npm run build-ts && npm run build-es2019",
|
|
16
|
+
"watch:files": "chokidar src/**/*.ts -c \"npm run watch:rebuild\"",
|
|
17
|
+
"prewatch": "npm run watch:rebuild",
|
|
18
|
+
"watch": "concurrently \"npm run serve\" \"npm run watch:files\"",
|
|
14
19
|
"clean:build": "npm run clean && npm run build"
|
|
15
20
|
},
|
|
16
21
|
"repository": {
|
|
@@ -47,6 +52,11 @@
|
|
|
47
52
|
"@babel/core": "^7.29.0",
|
|
48
53
|
"@babel/preset-env": "^7.29.5",
|
|
49
54
|
"browserify": "^17.0.1",
|
|
55
|
+
"chokidar-cli": "^3.0.0",
|
|
56
|
+
"concurrently": "^9.2.1",
|
|
57
|
+
"http-server": "^14.1.1",
|
|
58
|
+
"mkdirp": "^3.0.1",
|
|
59
|
+
"rimraf": "^6.1.3",
|
|
50
60
|
"typescript": "^6.0.3",
|
|
51
61
|
"uglify-js": "^3.19.3"
|
|
52
62
|
},
|