@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.
@@ -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;;;;;;;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;;;;;;;;;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"}
@@ -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 gltfLoader = await getGltfLoader();
34
- return new Promise((resolve, reject) => {
35
- gltfLoader.load(url, (gltf) => {
36
- resolve(gltf.scene);
37
- }, undefined, (error) => {
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;;;;;;;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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"}