@mapvx/web-js 3.2.0 → 3.3.0-dev.2

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.
@@ -1,11 +1,50 @@
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
+ * Declarative GLB scenes can legitimately include very large assets — a client's ground-mesh
11
+ * "Suelo" model measured at ~400 MB took ~26s to download alone on a solid connection, before any
12
+ * Draco decode time, and totem hardware/networks in the field are often slower than that. The
13
+ * timeout only needs to be long enough that no realistically-sized valid asset ever hits it; a
14
+ * truly hung load (the actual failure this guards against) would still hang forever whether this
15
+ * is 30 seconds or 5 minutes, so there is no tradeoff in keeping it generous.
16
+ *
17
+ * @group Configuration
18
+ */
19
+ export declare const DEFAULT_GLB_LOAD_TIMEOUT_MS = 300000;
20
+ /** Options for {@link loadGlbRoot}. */
21
+ export interface LoadGlbRootOptions {
22
+ /**
23
+ * Rejects with a timeout error if the load has not settled after this many milliseconds, and
24
+ * aborts the underlying fetch so it doesn't keep running unattended. Pass a non-finite or
25
+ * non-positive value to disable the timeout. Defaults to {@link DEFAULT_GLB_LOAD_TIMEOUT_MS}.
26
+ * Note that if the bytes had already fully arrived right as the timeout fired, the Draco/KTX2
27
+ * decode already under way for them is not itself cancelled — only the fetch is.
28
+ */
29
+ timeoutMs?: number;
30
+ /**
31
+ * Invoked as the GLB downloads, with the same shape `GLTFLoader.load`'s own `onProgress` would
32
+ * receive (`event.loaded` / `event.total`, `event.lengthComputable` false when the server didn't
33
+ * send `Content-Length`).
34
+ */
35
+ onProgress?: (event: ProgressEvent) => void;
36
+ }
2
37
  /**
3
38
  * Loads a GLB/GLTF from URL and returns the root scene group.
4
39
  * Central entry point so the loader implementation can be swapped later (e.g. Draco, KTX2, custom fetch).
5
40
  *
41
+ * Fetches the raw bytes itself (rather than deferring to `GLTFLoader.load`) so a truncated download
42
+ * can be caught and rejected clearly — see {@link assertGlbBufferComplete} — before it ever reaches
43
+ * the Draco/KTX2 decoders.
44
+ *
6
45
  * @param url - GLB URL
7
46
  * @returns Root `THREE.Group` from the loaded glTF scene
8
47
  * @group Utils
9
48
  */
10
- export declare function loadGlbRoot(url: string): Promise<THREE.Group>;
49
+ export declare function loadGlbRoot(url: string, options?: LoadGlbRootOptions): Promise<THREE.Group>;
11
50
  //# 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;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAcnE"}
1
+ {"version":3,"file":"loadGlb.d.ts","sourceRoot":"","sources":["../../../src/utils/loadGlb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAiCnC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,2BAA2B,SAAU,CAAA;AAkClD,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"}
@@ -21,22 +21,126 @@ 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
+ * Declarative GLB scenes can legitimately include very large assets — a client's ground-mesh
33
+ * "Suelo" model measured at ~400 MB took ~26s to download alone on a solid connection, before any
34
+ * Draco decode time, and totem hardware/networks in the field are often slower than that. The
35
+ * timeout only needs to be long enough that no realistically-sized valid asset ever hits it; a
36
+ * truly hung load (the actual failure this guards against) would still hang forever whether this
37
+ * is 30 seconds or 5 minutes, so there is no tradeoff in keeping it generous.
38
+ *
39
+ * @group Configuration
40
+ */
41
+ export const DEFAULT_GLB_LOAD_TIMEOUT_MS = 300000;
42
+ /**
43
+ * Magic number ("glTF" read little-endian) at byte offset 0 of every binary glTF (`.glb`)
44
+ * container. Used only to tell a binary GLB apart from a plain JSON `.gltf` payload — the latter
45
+ * has no such header and skips the completeness check in {@link assertGlbBufferComplete}.
46
+ */
47
+ const GLB_MAGIC = 0x46546c67;
48
+ /**
49
+ * Rejects a GLB download that was cut short before it reaches Three.js's `GLTFLoader`/`DRACOLoader`,
50
+ * whose behavior on truncated input ranges from a cryptic decode error (e.g. "Unexpected geometry
51
+ * type") to an outright hang, rather than a clear message pointing at the actual problem.
52
+ *
53
+ * The binary glTF container format requires the 12-byte header's declared total length to equal
54
+ * the file's real byte length (see the {@link https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#binary-header | spec}).
55
+ * A mismatch means the response body was truncated — a dropped connection, or the object stored at
56
+ * `url` is itself incomplete — so every buffer-view offset past that point reads garbage. This is
57
+ * exactly what was found in the field for one "Suelo" asset that both threw a decode error and (in
58
+ * a larger sibling file) hung: the file's own header declared a ~383 MB binary chunk while only
59
+ * ~8.7 MB had actually been received.
60
+ */
61
+ function assertGlbBufferComplete(buffer, url) {
62
+ if (buffer.byteLength < 12)
63
+ return; // too short to have a GLB header; let parse() report it
64
+ const header = new DataView(buffer, 0, 12);
65
+ if (header.getUint32(0, true) !== GLB_MAGIC)
66
+ return; // plain JSON .gltf, not a binary .glb
67
+ const declaredLength = header.getUint32(8, true);
68
+ if (declaredLength !== buffer.byteLength) {
69
+ throw new Error(`GLB download is truncated or corrupt: header declares ${declaredLength} bytes, received ${buffer.byteLength}: ${url}`);
70
+ }
71
+ }
24
72
  /**
25
73
  * Loads a GLB/GLTF from URL and returns the root scene group.
26
74
  * Central entry point so the loader implementation can be swapped later (e.g. Draco, KTX2, custom fetch).
27
75
  *
76
+ * Fetches the raw bytes itself (rather than deferring to `GLTFLoader.load`) so a truncated download
77
+ * can be caught and rejected clearly — see {@link assertGlbBufferComplete} — before it ever reaches
78
+ * the Draco/KTX2 decoders.
79
+ *
28
80
  * @param url - GLB URL
29
81
  * @returns Root `THREE.Group` from the loaded glTF scene
30
82
  * @group Utils
31
83
  */
32
- export async function loadGlbRoot(url) {
33
- const gltfLoader = await getGltfLoader();
34
- return new Promise((resolve, reject) => {
35
- gltfLoader.load(url, (gltf) => {
36
- resolve(gltf.scene);
37
- }, undefined, (error) => {
84
+ export async function loadGlbRoot(url, options = {}) {
85
+ const { timeoutMs = DEFAULT_GLB_LOAD_TIMEOUT_MS, onProgress } = options;
86
+ const [gltfLoader, { THREE: three }] = await Promise.all([getGltfLoader(), loadThree()]);
87
+ // Set once the caller has stopped waiting (success, failure, or timeout) so any callback three
88
+ // fires afterwards — e.g. a timed-out fetch/decode that keeps running in the background — is a
89
+ // no-op instead of resolving/rejecting an already-settled promise or emitting a stray progress
90
+ // event after the batch this asset belongs to already reported `done: true`.
91
+ let settled = false;
92
+ const fileLoader = new three.FileLoader();
93
+ fileLoader.setResponseType("arraybuffer");
94
+ const load = new Promise((resolve, reject) => {
95
+ const onError = (error) => {
96
+ if (settled)
97
+ return;
38
98
  reject(error instanceof Error ? error : new Error(String(error)));
39
- });
99
+ };
100
+ fileLoader.load(url, (data) => {
101
+ if (settled)
102
+ return;
103
+ const buffer = data;
104
+ try {
105
+ assertGlbBufferComplete(buffer, url);
106
+ }
107
+ catch (err) {
108
+ onError(err);
109
+ return;
110
+ }
111
+ gltfLoader.parse(buffer, three.LoaderUtils.extractUrlBase(url), (gltf) => {
112
+ if (settled)
113
+ return;
114
+ resolve(gltf.scene);
115
+ }, onError);
116
+ }, (event) => {
117
+ if (!settled)
118
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress(event);
119
+ }, onError);
120
+ });
121
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
122
+ try {
123
+ return await load;
124
+ }
125
+ finally {
126
+ settled = true;
127
+ }
128
+ }
129
+ let timer;
130
+ const timeout = new Promise((_resolve, reject) => {
131
+ timer = setTimeout(() => reject(new Error(`Timed out loading GLB after ${timeoutMs}ms: ${url}`)), timeoutMs);
40
132
  });
133
+ try {
134
+ return await Promise.race([load, timeout]);
135
+ }
136
+ finally {
137
+ settled = true;
138
+ clearTimeout(timer);
139
+ // Cancels the underlying fetch (and, by never delivering its buffer, the Draco/KTX2 decode)
140
+ // so a timed-out asset actually frees its network/CPU usage instead of running to completion
141
+ // unattended — otherwise GLB_LOAD_CONCURRENCY only bounds how many loads the caller awaits,
142
+ // not how many are truly in flight. A no-op if `load` already won the race.
143
+ fileLoader.abort();
144
+ }
41
145
  }
42
146
  //# 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;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,MAAM,UAAU,GAAG,MAAM,aAAa,EAAE,CAAA;IACxC,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAClD,UAAU,CAAC,IAAI,CACb,GAAG,EACH,CAAC,IAAU,EAAE,EAAE;YACb,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC,EACD,SAAS,EACT,CAAC,KAAc,EAAE,EAAE;YACjB,MAAM,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACnE,CAAC,CACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"loadGlb.js","sourceRoot":"","sources":["../../../src/utils/loadGlb.ts"],"names":[],"mappings":"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"}