@mapvx/web-js 3.2.0 → 3.3.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/dist/cjs/index.js.map +1 -1
- package/dist/cjs/logger/sentry.js +1 -1
- package/dist/cjs/map/map.js +1 -1
- package/dist/cjs/map/map.js.map +1 -1
- package/dist/cjs/map/scene3DController.js +82 -10
- package/dist/cjs/map/scene3DController.js.map +1 -1
- package/dist/cjs/utils/loadGlb.js +105 -7
- package/dist/cjs/utils/loadGlb.js.map +1 -1
- package/dist/es/domain/models/mapConfig.d.ts +44 -0
- package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
- package/dist/es/index.d.ts +1 -1
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js.map +1 -1
- package/dist/es/logger/sentry.js +1 -1
- package/dist/es/map/map.d.ts.map +1 -1
- package/dist/es/map/map.js +1 -1
- package/dist/es/map/map.js.map +1 -1
- package/dist/es/map/scene3DController.d.ts +10 -1
- package/dist/es/map/scene3DController.d.ts.map +1 -1
- package/dist/es/map/scene3DController.js +82 -10
- package/dist/es/map/scene3DController.js.map +1 -1
- package/dist/es/utils/loadGlb.d.ts +33 -1
- package/dist/es/utils/loadGlb.d.ts.map +1 -1
- package/dist/es/utils/loadGlb.js +104 -7
- package/dist/es/utils/loadGlb.js.map +1 -1
- package/dist/umd/index.js +348 -106
- package/dist/umd/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import type * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* Default per-asset timeout for {@link loadGlbRoot}.
|
|
4
|
+
*
|
|
5
|
+
* Guards against loaders that hang instead of throwing — a GLB whose binary chunk is truncated
|
|
6
|
+
* (see {@link loadGlbRoot}'s completeness check) or otherwise malformed can put Three.js's
|
|
7
|
+
* `DRACOLoader` into an undefined state: sometimes a decode error, sometimes no callback at all.
|
|
8
|
+
* Without a timeout, one such asset never settles and blocks every other load queued behind it.
|
|
9
|
+
*
|
|
10
|
+
* @group Configuration
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_GLB_LOAD_TIMEOUT_MS = 30000;
|
|
13
|
+
/** Options for {@link loadGlbRoot}. */
|
|
14
|
+
export interface LoadGlbRootOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Rejects with a timeout error if the load has not settled after this many milliseconds, and
|
|
17
|
+
* aborts the underlying fetch so it doesn't keep running unattended. Pass a non-finite or
|
|
18
|
+
* non-positive value to disable the timeout. Defaults to {@link DEFAULT_GLB_LOAD_TIMEOUT_MS}.
|
|
19
|
+
* Note that if the bytes had already fully arrived right as the timeout fired, the Draco/KTX2
|
|
20
|
+
* decode already under way for them is not itself cancelled — only the fetch is.
|
|
21
|
+
*/
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Invoked as the GLB downloads, with the same shape `GLTFLoader.load`'s own `onProgress` would
|
|
25
|
+
* receive (`event.loaded` / `event.total`, `event.lengthComputable` false when the server didn't
|
|
26
|
+
* send `Content-Length`).
|
|
27
|
+
*/
|
|
28
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
29
|
+
}
|
|
2
30
|
/**
|
|
3
31
|
* Loads a GLB/GLTF from URL and returns the root scene group.
|
|
4
32
|
* Central entry point so the loader implementation can be swapped later (e.g. Draco, KTX2, custom fetch).
|
|
5
33
|
*
|
|
34
|
+
* Fetches the raw bytes itself (rather than deferring to `GLTFLoader.load`) so a truncated download
|
|
35
|
+
* can be caught and rejected clearly — see {@link assertGlbBufferComplete} — before it ever reaches
|
|
36
|
+
* the Draco/KTX2 decoders.
|
|
37
|
+
*
|
|
6
38
|
* @param url - GLB URL
|
|
7
39
|
* @returns Root `THREE.Group` from the loaded glTF scene
|
|
8
40
|
* @group Utils
|
|
9
41
|
*/
|
|
10
|
-
export declare function loadGlbRoot(url: string): Promise<THREE.Group>;
|
|
42
|
+
export declare function loadGlbRoot(url: string, options?: LoadGlbRootOptions): Promise<THREE.Group>;
|
|
11
43
|
//# sourceMappingURL=loadGlb.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadGlb.d.ts","sourceRoot":"","sources":["../../../src/utils/loadGlb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAiCnC
|
|
1
|
+
{"version":3,"file":"loadGlb.d.ts","sourceRoot":"","sources":["../../../src/utils/loadGlb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAiCnC;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,QAAS,CAAA;AAkCjD,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;CAC5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAyEtB"}
|
package/dist/es/utils/loadGlb.js
CHANGED
|
@@ -21,22 +21,119 @@ function getGltfLoader() {
|
|
|
21
21
|
}));
|
|
22
22
|
return gltfLoaderPromise;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Default per-asset timeout for {@link loadGlbRoot}.
|
|
26
|
+
*
|
|
27
|
+
* Guards against loaders that hang instead of throwing — a GLB whose binary chunk is truncated
|
|
28
|
+
* (see {@link loadGlbRoot}'s completeness check) or otherwise malformed can put Three.js's
|
|
29
|
+
* `DRACOLoader` into an undefined state: sometimes a decode error, sometimes no callback at all.
|
|
30
|
+
* Without a timeout, one such asset never settles and blocks every other load queued behind it.
|
|
31
|
+
*
|
|
32
|
+
* @group Configuration
|
|
33
|
+
*/
|
|
34
|
+
export const DEFAULT_GLB_LOAD_TIMEOUT_MS = 30000;
|
|
35
|
+
/**
|
|
36
|
+
* Magic number ("glTF" read little-endian) at byte offset 0 of every binary glTF (`.glb`)
|
|
37
|
+
* container. Used only to tell a binary GLB apart from a plain JSON `.gltf` payload — the latter
|
|
38
|
+
* has no such header and skips the completeness check in {@link assertGlbBufferComplete}.
|
|
39
|
+
*/
|
|
40
|
+
const GLB_MAGIC = 0x46546c67;
|
|
41
|
+
/**
|
|
42
|
+
* Rejects a GLB download that was cut short before it reaches Three.js's `GLTFLoader`/`DRACOLoader`,
|
|
43
|
+
* whose behavior on truncated input ranges from a cryptic decode error (e.g. "Unexpected geometry
|
|
44
|
+
* type") to an outright hang, rather than a clear message pointing at the actual problem.
|
|
45
|
+
*
|
|
46
|
+
* The binary glTF container format requires the 12-byte header's declared total length to equal
|
|
47
|
+
* the file's real byte length (see the {@link https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#binary-header | spec}).
|
|
48
|
+
* A mismatch means the response body was truncated — a dropped connection, or the object stored at
|
|
49
|
+
* `url` is itself incomplete — so every buffer-view offset past that point reads garbage. This is
|
|
50
|
+
* exactly what was found in the field for one "Suelo" asset that both threw a decode error and (in
|
|
51
|
+
* a larger sibling file) hung: the file's own header declared a ~383 MB binary chunk while only
|
|
52
|
+
* ~8.7 MB had actually been received.
|
|
53
|
+
*/
|
|
54
|
+
function assertGlbBufferComplete(buffer, url) {
|
|
55
|
+
if (buffer.byteLength < 12)
|
|
56
|
+
return; // too short to have a GLB header; let parse() report it
|
|
57
|
+
const header = new DataView(buffer, 0, 12);
|
|
58
|
+
if (header.getUint32(0, true) !== GLB_MAGIC)
|
|
59
|
+
return; // plain JSON .gltf, not a binary .glb
|
|
60
|
+
const declaredLength = header.getUint32(8, true);
|
|
61
|
+
if (declaredLength !== buffer.byteLength) {
|
|
62
|
+
throw new Error(`GLB download is truncated or corrupt: header declares ${declaredLength} bytes, received ${buffer.byteLength}: ${url}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
24
65
|
/**
|
|
25
66
|
* Loads a GLB/GLTF from URL and returns the root scene group.
|
|
26
67
|
* Central entry point so the loader implementation can be swapped later (e.g. Draco, KTX2, custom fetch).
|
|
27
68
|
*
|
|
69
|
+
* Fetches the raw bytes itself (rather than deferring to `GLTFLoader.load`) so a truncated download
|
|
70
|
+
* can be caught and rejected clearly — see {@link assertGlbBufferComplete} — before it ever reaches
|
|
71
|
+
* the Draco/KTX2 decoders.
|
|
72
|
+
*
|
|
28
73
|
* @param url - GLB URL
|
|
29
74
|
* @returns Root `THREE.Group` from the loaded glTF scene
|
|
30
75
|
* @group Utils
|
|
31
76
|
*/
|
|
32
|
-
export async function loadGlbRoot(url) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
77
|
+
export async function loadGlbRoot(url, options = {}) {
|
|
78
|
+
const { timeoutMs = DEFAULT_GLB_LOAD_TIMEOUT_MS, onProgress } = options;
|
|
79
|
+
const [gltfLoader, { THREE: three }] = await Promise.all([getGltfLoader(), loadThree()]);
|
|
80
|
+
// Set once the caller has stopped waiting (success, failure, or timeout) so any callback three
|
|
81
|
+
// fires afterwards — e.g. a timed-out fetch/decode that keeps running in the background — is a
|
|
82
|
+
// no-op instead of resolving/rejecting an already-settled promise or emitting a stray progress
|
|
83
|
+
// event after the batch this asset belongs to already reported `done: true`.
|
|
84
|
+
let settled = false;
|
|
85
|
+
const fileLoader = new three.FileLoader();
|
|
86
|
+
fileLoader.setResponseType("arraybuffer");
|
|
87
|
+
const load = new Promise((resolve, reject) => {
|
|
88
|
+
const onError = (error) => {
|
|
89
|
+
if (settled)
|
|
90
|
+
return;
|
|
38
91
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
39
|
-
}
|
|
92
|
+
};
|
|
93
|
+
fileLoader.load(url, (data) => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
const buffer = data;
|
|
97
|
+
try {
|
|
98
|
+
assertGlbBufferComplete(buffer, url);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
onError(err);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
gltfLoader.parse(buffer, three.LoaderUtils.extractUrlBase(url), (gltf) => {
|
|
105
|
+
if (settled)
|
|
106
|
+
return;
|
|
107
|
+
resolve(gltf.scene);
|
|
108
|
+
}, onError);
|
|
109
|
+
}, (event) => {
|
|
110
|
+
if (!settled)
|
|
111
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress(event);
|
|
112
|
+
}, onError);
|
|
113
|
+
});
|
|
114
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
115
|
+
try {
|
|
116
|
+
return await load;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
settled = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let timer;
|
|
123
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
124
|
+
timer = setTimeout(() => reject(new Error(`Timed out loading GLB after ${timeoutMs}ms: ${url}`)), timeoutMs);
|
|
40
125
|
});
|
|
126
|
+
try {
|
|
127
|
+
return await Promise.race([load, timeout]);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
settled = true;
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
// Cancels the underlying fetch (and, by never delivering its buffer, the Draco/KTX2 decode)
|
|
133
|
+
// so a timed-out asset actually frees its network/CPU usage instead of running to completion
|
|
134
|
+
// unattended — otherwise GLB_LOAD_CONCURRENCY only bounds how many loads the caller awaits,
|
|
135
|
+
// not how many are truly in flight. A no-op if `load` already won the race.
|
|
136
|
+
fileLoader.abort();
|
|
137
|
+
}
|
|
41
138
|
}
|
|
42
139
|
//# sourceMappingURL=loadGlb.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadGlb.js","sourceRoot":"","sources":["../../../src/utils/loadGlb.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAA;AAC1F,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC;;;;;GAKG;AACH,IAAI,iBAES,CAAA;AAEb,SAAS,aAAa;IACpB,iBAAiB,aAAjB,iBAAiB,cAAjB,iBAAiB,IAAjB,iBAAiB,GAAK,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,CAAA;QACzC,WAAW,CAAC,cAAc,CAAC,0BAA0B,CAAC,CAAA;QAEtD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,UAAU,EAAE,CAAA;QACvC,UAAU,CAAC,cAAc,CAAC,WAAW,CAAC,CAAA;QAEtC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,UAAU,EAAE,CAAA;QACvC,UAAU,CAAC,iBAAiB,CAAC,4BAA4B,CAAC,CAAA;QAC1D,UAAU,CAAC,aAAa,CAAC,UAAU,CAAC,CAAA;QACpC,UAAU,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAE1E,OAAO,UAAU,CAAA;IACnB,CAAC,CAAC,EAAA;IACF,OAAO,iBAAiB,CAAA;AAC1B,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"loadGlb.js","sourceRoot":"","sources":["../../../src/utils/loadGlb.ts"],"names":[],"mappings":"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"}
|