@needle-tools/engine 4.15.0-next.cecd8e7 → 4.15.0-next.f391a30
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/components.needle.json +1 -1
- package/dist/{gltf-progressive-BttGBXw6.umd.cjs → gltf-progressive-CMwJPwEt.umd.cjs} +1 -1
- package/dist/{gltf-progressive-Bm_6aEi4.js → gltf-progressive-CTlvpS3A.js} +1 -1
- package/dist/{gltf-progressive-T5WKTux5.min.js → gltf-progressive-DYL3SLVb.min.js} +1 -1
- package/dist/materialx-4jJLLe9Q.js +4174 -0
- package/dist/materialx-Bt9FHwco.min.js +158 -0
- package/dist/materialx-NDD0y4JY.umd.cjs +158 -0
- package/dist/{needle-engine.bundle-JQGIFVRm.umd.cjs → needle-engine.bundle-C1BFRZDF.umd.cjs} +103 -101
- package/dist/{needle-engine.bundle-VZVrVbc3.js → needle-engine.bundle-DB4kLWO_.js} +2829 -2823
- package/dist/{needle-engine.bundle-CuAiLb-d.min.js → needle-engine.bundle-DsTdfmeb.min.js} +115 -113
- package/dist/needle-engine.d.ts +27 -46
- package/dist/needle-engine.js +287 -288
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-06AXuvdv.min.js → postprocessing-BN-f4viE.min.js} +1 -1
- package/dist/{postprocessing-CPDcA21P.umd.cjs → postprocessing-DYmYOVm4.umd.cjs} +1 -1
- package/dist/{postprocessing-CI2x8Cln.js → postprocessing-De9ZpJrk.js} +1 -1
- package/dist/{three-examples-BMmNgNCN.umd.cjs → three-examples-BHqRVpO_.umd.cjs} +12 -12
- package/dist/{three-examples-CMYCd5nH.js → three-examples-C0ZCCA_K.js} +182 -192
- package/dist/{three-examples-CQl1fFZp.min.js → three-examples-DmTY8tGr.min.js} +14 -14
- package/lib/engine/api.d.ts +0 -2
- package/lib/engine/api.js +0 -2
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/debug/debug.js +1 -1
- package/lib/engine/debug/debug.js.map +1 -1
- package/lib/engine/debug/debug_spatial_console.js +1 -1
- package/lib/engine/debug/debug_spatial_console.js.map +1 -1
- package/lib/engine/engine_context.js +1 -1
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_create_objects.js +1 -1
- package/lib/engine/engine_create_objects.js.map +1 -1
- package/lib/engine/engine_gizmos.js +1 -1
- package/lib/engine/engine_gizmos.js.map +1 -1
- package/lib/engine/engine_license.js +7 -2
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_utils.js +2 -2
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/export/gltf/EXT_mesh_gpu_instancing_exporter.js.map +1 -0
- package/lib/engine/export/gltf/index.js +1 -1
- package/lib/engine/export/gltf/index.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.d.ts +6 -3
- package/lib/engine/webcomponents/logo-element.js +18 -0
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +10 -7
- package/lib/engine/webcomponents/needle menu/needle-menu.js +14 -4
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js +10 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +3 -0
- package/lib/engine/webcomponents/needle-engine.js +10 -0
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/Component.js +0 -1
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +24 -2
- package/lib/engine-components/ReflectionProbe.js +27 -1
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Skybox.js +4 -2
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js.map +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +2 -2
- package/lib/engine-components/export/usdz/USDZExporter.js +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js +2 -2
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js.map +1 -1
- package/package.json +17 -13
- package/plugins/common/buildinfo.js +46 -10
- package/plugins/common/files.js +2 -1
- package/plugins/common/license.js +144 -69
- package/plugins/common/logger.js +172 -11
- package/plugins/common/needle-engine-skill.md +175 -0
- package/plugins/common/worker.js +5 -4
- package/plugins/types/userconfig.d.ts +40 -2
- package/plugins/vite/ai.js +71 -0
- package/plugins/vite/alias.js +6 -5
- package/plugins/vite/asap.js +6 -5
- package/plugins/vite/build-pipeline.js +224 -41
- package/plugins/vite/buildinfo.js +66 -6
- package/plugins/vite/copyfiles.js +41 -12
- package/plugins/vite/custom-element-data.js +26 -16
- package/plugins/vite/defines.js +8 -5
- package/plugins/vite/dependencies.js +16 -10
- package/plugins/vite/dependency-watcher.js +35 -7
- package/plugins/vite/drop-client.js +7 -5
- package/plugins/vite/drop.js +16 -14
- package/plugins/vite/editor-connection.js +18 -16
- package/plugins/vite/imports-logger.js +12 -2
- package/plugins/vite/index.js +8 -3
- package/plugins/vite/local-files-analysis.js +789 -0
- package/plugins/vite/local-files-core.js +992 -0
- package/plugins/vite/local-files-internals.js +28 -0
- package/plugins/vite/local-files-types.d.ts +111 -0
- package/plugins/vite/local-files-utils.js +359 -0
- package/plugins/vite/local-files.js +2 -441
- package/plugins/vite/logger.client.js +45 -35
- package/plugins/vite/logger.js +6 -3
- package/plugins/vite/logging.js +129 -0
- package/plugins/vite/meta.js +18 -4
- package/plugins/vite/needle-app.js +4 -3
- package/plugins/vite/peer.js +2 -1
- package/plugins/vite/pwa.js +33 -17
- package/plugins/vite/reload.js +24 -2
- package/src/engine/api.ts +0 -3
- package/src/engine/debug/debug.ts +1 -1
- package/src/engine/debug/debug_spatial_console.ts +5 -1
- package/src/engine/engine_context.ts +1 -1
- package/src/engine/engine_create_objects.ts +1 -1
- package/src/engine/engine_gizmos.ts +9 -5
- package/src/engine/engine_license.ts +7 -2
- package/src/engine/engine_utils.ts +2 -2
- package/src/engine/export/gltf/index.ts +1 -1
- package/src/engine/webcomponents/logo-element.ts +20 -3
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +6 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +23 -11
- package/src/engine/webcomponents/needle-engine.ar-overlay.ts +13 -2
- package/src/engine/webcomponents/needle-engine.ts +13 -1
- package/src/engine-components/Component.ts +1 -2
- package/src/engine-components/ReflectionProbe.ts +32 -8
- package/src/engine-components/Skybox.ts +4 -2
- package/src/engine-components/export/gltf/GltfExport.ts +1 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
- package/src/engine-components/export/usdz/USDZExporter.ts +1 -1
- package/src/engine-components/export/usdz/extensions/behavior/PhysicsExtension.ts +2 -2
- package/dist/materialx-CJyQZtjt.min.js +0 -90
- package/dist/materialx-DMs1E08Z.js +0 -4636
- package/dist/materialx-DaKKOoVk.umd.cjs +0 -90
- package/lib/engine/engine_test_utils.d.ts +0 -39
- package/lib/engine/engine_test_utils.js +0 -84
- package/lib/engine/engine_test_utils.js.map +0 -1
- package/lib/include/three/EXT_mesh_gpu_instancing_exporter.js.map +0 -1
- package/src/engine/engine_test_utils.ts +0 -109
- package/src/include/draco/draco_decoder.js +0 -34
- package/src/include/draco/draco_decoder.wasm +0 -0
- package/src/include/draco/draco_wasm_wrapper.js +0 -117
- package/src/include/ktx2/basis_transcoder.js +0 -19
- package/src/include/ktx2/basis_transcoder.wasm +0 -0
- package/src/include/needle/arial-msdf.json +0 -1472
- package/src/include/needle/arial.png +0 -0
- package/src/include/needle/poweredbyneedle.webp +0 -0
- /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.d.ts +0 -0
- /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
- /package/src/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {
|
|
2
|
+
getActiveHandlers,
|
|
3
|
+
getFeatureBasePath,
|
|
4
|
+
handleWebXRProfiles,
|
|
5
|
+
makeLocal,
|
|
6
|
+
makeLocalHtml,
|
|
7
|
+
} from './local-files-core.js';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
analyzeProjectGlbs,
|
|
11
|
+
buildAutoPolicy,
|
|
12
|
+
collectAssetGlbs,
|
|
13
|
+
collectIndexHtmlSkyboxUrls,
|
|
14
|
+
collectNeedleComponentExtensionBlobs,
|
|
15
|
+
detectAutoPolicy,
|
|
16
|
+
getWebXRProfilesForMode,
|
|
17
|
+
hasAutoFeatureSelection,
|
|
18
|
+
readGlbJsonChunk,
|
|
19
|
+
resolveSkyboxSelectionUrls,
|
|
20
|
+
resolveSkyboxValueToUrl,
|
|
21
|
+
shouldHandleUrlInAutoMode,
|
|
22
|
+
} from './local-files-analysis.js';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
fixDracoRangeQueryProbe,
|
|
26
|
+
fixRelativeNewURL,
|
|
27
|
+
getRelativeToBasePath,
|
|
28
|
+
} from './local-files-utils.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type declarations for the local-files plugin family.
|
|
3
|
+
* Referenced from local-files-utils.js, local-files-analysis.js, local-files-core.js, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface UrlHandler {
|
|
7
|
+
name: string;
|
|
8
|
+
/** RegExp with the /g flag — match group 1 must capture the URL. */
|
|
9
|
+
pattern: RegExp;
|
|
10
|
+
type: "binary" | "css" | "decoder-dir" | "webxr-profiles";
|
|
11
|
+
feature: string;
|
|
12
|
+
decoderFiles?: string[];
|
|
13
|
+
localDirName?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LocalizationStats {
|
|
17
|
+
fileCount: number;
|
|
18
|
+
totalBytes: number;
|
|
19
|
+
mimeCounts: Map<string, number>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LocalizationOptions {
|
|
23
|
+
features?: string | string[];
|
|
24
|
+
excludeFeatures?: string[];
|
|
25
|
+
/** URL substrings or RegExps to skip. */
|
|
26
|
+
exclude?: (string | RegExp)[];
|
|
27
|
+
/** @deprecated use `exclude` */
|
|
28
|
+
excludeUrls?: string[];
|
|
29
|
+
/** e.g. "facebook-instant", "discord" */
|
|
30
|
+
platform?: string;
|
|
31
|
+
/** Only process files inside these npm packages. */
|
|
32
|
+
packages?: string[];
|
|
33
|
+
/** Skybox selection: URL, magic keyword, array of those, or "all". */
|
|
34
|
+
skybox?: string | string[];
|
|
35
|
+
/** WebXR controller profile mode: "minimal" | "quest" | "pico" | "all" */
|
|
36
|
+
webxr?: string;
|
|
37
|
+
templateExpansions?: Array<{
|
|
38
|
+
cdnPrefix: string;
|
|
39
|
+
variables: Record<string, string[]>;
|
|
40
|
+
localPrefix?: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** Used when makeFilesLocal is an object — set to false to disable. */
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AutoPolicy {
|
|
47
|
+
features: Set<string>;
|
|
48
|
+
hasWebXR: boolean;
|
|
49
|
+
hasVideoPlayer: boolean;
|
|
50
|
+
allowedSkyboxUrls: Set<string> | null;
|
|
51
|
+
selectedWebXRProfiles: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface VitePluginContext {
|
|
55
|
+
emitFile(asset: {
|
|
56
|
+
type: "asset" | "chunk";
|
|
57
|
+
fileName?: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
source?: string | Uint8Array;
|
|
60
|
+
}): string;
|
|
61
|
+
getFileName(referenceId: string): string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface LocalizationContext {
|
|
65
|
+
pluginContext: VitePluginContext | null;
|
|
66
|
+
cache: {
|
|
67
|
+
addToCache(key: string, value: Buffer | string): void;
|
|
68
|
+
getFromCache(key: string): Buffer | string | null;
|
|
69
|
+
readonly map: Map<string, Buffer | string>;
|
|
70
|
+
};
|
|
71
|
+
command: "build" | "serve";
|
|
72
|
+
viteConfig: { build?: { assetsDir?: string } } | null;
|
|
73
|
+
options: LocalizationOptions;
|
|
74
|
+
autoPolicy: AutoPolicy | null;
|
|
75
|
+
failedDownloads: Map<string, string>;
|
|
76
|
+
localizationStats: LocalizationStats;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SceneAnalysisReport {
|
|
80
|
+
hasWebXRComponent: boolean;
|
|
81
|
+
hasVideoPlayerComponent: boolean;
|
|
82
|
+
extensions: Set<string>;
|
|
83
|
+
componentTypes: Set<string>;
|
|
84
|
+
componentCounts: Map<string, number>;
|
|
85
|
+
glbFileCount: number;
|
|
86
|
+
gltfFileCount: number;
|
|
87
|
+
totalSceneFileCount: number;
|
|
88
|
+
totalNodeCount: number;
|
|
89
|
+
totalVertexCount: number;
|
|
90
|
+
totalTextureCount: number;
|
|
91
|
+
totalMeshCount: number;
|
|
92
|
+
totalPrimitiveCount: number;
|
|
93
|
+
dracoPrimitiveCount: number;
|
|
94
|
+
meshoptBufferViewCount: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SceneFile {
|
|
98
|
+
path: string;
|
|
99
|
+
type: "glb" | "gltf";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface NeedleComponentEntry {
|
|
103
|
+
name?: string;
|
|
104
|
+
type?: string;
|
|
105
|
+
guid?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface NeedleComponentExtension {
|
|
109
|
+
components?: NeedleComponentEntry[];
|
|
110
|
+
builtin_components?: NeedleComponentEntry[];
|
|
111
|
+
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import https from 'https';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import { createHash } from 'crypto';
|
|
5
|
+
import { needleLog, setTransientLogLineCleaner } from './logging.js';
|
|
6
|
+
|
|
7
|
+
/** @typedef {import('./local-files-types.js').LocalizationContext} LocalizationContext */
|
|
8
|
+
|
|
9
|
+
const debug = false;
|
|
10
|
+
const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
11
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
12
|
+
let spinnerIndex = 0;
|
|
13
|
+
let spinnerActive = false;
|
|
14
|
+
|
|
15
|
+
setTransientLogLineCleaner(() => clearSpinnerLine());
|
|
16
|
+
|
|
17
|
+
function clearSpinnerLine() {
|
|
18
|
+
if (!process.stdout.isTTY || !spinnerActive) return;
|
|
19
|
+
process.stdout.write("\r\x1b[2K");
|
|
20
|
+
spinnerActive = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @param {string} url */
|
|
24
|
+
function updateMakeLocalProgress(url) {
|
|
25
|
+
if (!process.stdout.isTTY) {
|
|
26
|
+
needleLog("needle:local-files", "Make local: " + url, "log", { dimBody: true, showHeader: false });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const frame = SPINNER_FRAMES[spinnerIndex++ % SPINNER_FRAMES.length];
|
|
30
|
+
const maxLength = Math.max(24, (process.stdout.columns || 120) - 4);
|
|
31
|
+
const message = `Make local: ${url}`;
|
|
32
|
+
const text = message.length > maxLength ? `${message.slice(0, Math.max(0, maxLength - 1))}…` : message;
|
|
33
|
+
process.stdout.write(`\r\x1b[2K${frame} ${text}\x1b[0K`);
|
|
34
|
+
spinnerActive = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function finishMakeLocalProgress() {
|
|
38
|
+
clearSpinnerLine();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} src
|
|
43
|
+
* @param {string} search
|
|
44
|
+
* @param {string} replacement
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
export function replaceAll(src, search, replacement) {
|
|
48
|
+
return src.split(search).join(replacement);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class Cache {
|
|
52
|
+
/** @type {Map<string, Buffer|string>} */
|
|
53
|
+
__cache = new Map();
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} key
|
|
56
|
+
* @param {Buffer|string} value
|
|
57
|
+
*/
|
|
58
|
+
addToCache(key, value) {
|
|
59
|
+
if (debug && this.__cache.has(key)) {
|
|
60
|
+
console.warn("Key " + key + " already exists in cache, overwriting.");
|
|
61
|
+
}
|
|
62
|
+
this.__cache.set(key, value);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* @param {string} key
|
|
66
|
+
* @returns {Buffer|string|null}
|
|
67
|
+
*/
|
|
68
|
+
getFromCache(key) {
|
|
69
|
+
return this.__cache.get(key) ?? null;
|
|
70
|
+
}
|
|
71
|
+
get map() {
|
|
72
|
+
return this.__cache;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {string} path
|
|
78
|
+
* @param {string} basePath
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
export function getRelativeToBasePath(path, basePath) {
|
|
82
|
+
const normalizedPath = normalizeWebPath(path);
|
|
83
|
+
const normalizedBasePath = normalizeWebPath(basePath);
|
|
84
|
+
|
|
85
|
+
if (!normalizedBasePath) return normalizedPath;
|
|
86
|
+
|
|
87
|
+
if (normalizedPath.startsWith(normalizedBasePath)) {
|
|
88
|
+
return "./" + normalizedPath.substring(normalizedBasePath.length);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const baseSegments = normalizedBasePath.replace(/\/+$/g, "").split("/").filter(Boolean);
|
|
92
|
+
const backtrack = baseSegments.map(() => "..").join("/");
|
|
93
|
+
return (backtrack ? backtrack + "/" : "") + normalizedPath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} path
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
export function normalizeWebPath(path) {
|
|
101
|
+
if (!path) return "";
|
|
102
|
+
return path.replace(/\\/g, "/");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {string} path
|
|
107
|
+
* @returns {string}
|
|
108
|
+
*/
|
|
109
|
+
export function ensureTrailingSlash(path) {
|
|
110
|
+
if (!path) return "";
|
|
111
|
+
return path.endsWith("/") ? path : path + "/";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} src
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
export function fixRelativeNewURL(src) {
|
|
119
|
+
src = src.replace(
|
|
120
|
+
/(?<==\s*)(["'])((?:(?:\.{1,2}\/)|\/)?ext\/[^"']*\/)\1/g,
|
|
121
|
+
(/** @type {string} */ match, /** @type {string} */ quote, /** @type {string} */ path) => {
|
|
122
|
+
const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
|
|
123
|
+
return `new URL(${quote}${runtimePath}${quote}, self.location?.href || ${quote}${quote}).href`;
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
src = src.replace(
|
|
128
|
+
/new\s+URL\s*\(\s*(["'`])((?:(?:\.{1,2}\/)|\/)?ext\/[^"'`]+)\1\s*\)/g,
|
|
129
|
+
(/** @type {string} */ match, /** @type {string} */ quote, /** @type {string} */ path) => {
|
|
130
|
+
const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
|
|
131
|
+
return `new URL(${quote}${runtimePath}${quote}, self.location?.href)`;
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return src;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} src
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
export function fixDracoRangeQueryProbe(src) {
|
|
143
|
+
return src;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} path
|
|
148
|
+
* @param {string|Buffer} content
|
|
149
|
+
* @returns {string}
|
|
150
|
+
*/
|
|
151
|
+
export function getValidFilename(path, content) {
|
|
152
|
+
if (path.startsWith("http:") || path.startsWith("https:")) {
|
|
153
|
+
const url = new URL(path);
|
|
154
|
+
const pathParts = url.pathname.split('/');
|
|
155
|
+
const filename = pathParts.pop() || 'file';
|
|
156
|
+
const nameParts = filename.split('.');
|
|
157
|
+
const nameWithoutExt = nameParts.slice(0, -1).join('.');
|
|
158
|
+
path = nameWithoutExt + "-" + createContentMd5(url.host + pathParts.join('/')) + "." + (nameParts.pop() || 'unknown');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let name = path.replace(/[^a-z0-9_\-\.\+]/gi, '-');
|
|
162
|
+
|
|
163
|
+
const maxLength = 200;
|
|
164
|
+
if (path.length > maxLength) {
|
|
165
|
+
const hash = createContentMd5(content);
|
|
166
|
+
let ext = "";
|
|
167
|
+
const extIndex = name.lastIndexOf('.');
|
|
168
|
+
if (extIndex !== -1) {
|
|
169
|
+
ext = name.substring(extIndex + 1);
|
|
170
|
+
name = name.substring(0, extIndex);
|
|
171
|
+
}
|
|
172
|
+
name = name.substring(0, maxLength) + "-" + hash + (ext ? "." + ext : '');
|
|
173
|
+
}
|
|
174
|
+
return name;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {string} str
|
|
179
|
+
* @returns {string}
|
|
180
|
+
*/
|
|
181
|
+
function createContentMd5(str) {
|
|
182
|
+
return createHash('md5')
|
|
183
|
+
.update(str)
|
|
184
|
+
.digest('hex');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {string} url
|
|
189
|
+
* @returns {Promise<string>}
|
|
190
|
+
*/
|
|
191
|
+
export function downloadText(url) {
|
|
192
|
+
return new Promise((res, rej) => {
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
rej(new Error("Download timed out after " + DOWNLOAD_TIMEOUT_MS + "ms: " + url));
|
|
195
|
+
}, DOWNLOAD_TIMEOUT_MS);
|
|
196
|
+
|
|
197
|
+
const req = (url.startsWith("http://") ? http.get(url, (response) => {
|
|
198
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
return downloadText(response.headers.location).then(res).catch(rej);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (response.statusCode !== 200) {
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
clearSpinnerLine();
|
|
206
|
+
rej(new Error("Failed to download (" + response.statusCode + "): " + url));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
updateMakeLocalProgress(url);
|
|
211
|
+
|
|
212
|
+
let data = '';
|
|
213
|
+
response.on('data', (/** @type {Buffer|string} */ chunk) => {
|
|
214
|
+
data += chunk;
|
|
215
|
+
});
|
|
216
|
+
response.on('end', () => {
|
|
217
|
+
clearTimeout(timer);
|
|
218
|
+
res(data);
|
|
219
|
+
});
|
|
220
|
+
response.on('error', (err) => {
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
rej(err);
|
|
223
|
+
});
|
|
224
|
+
}) : https.get(url, (response) => {
|
|
225
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
return downloadText(response.headers.location).then(res).catch(rej);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (response.statusCode !== 200) {
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
clearSpinnerLine();
|
|
233
|
+
rej(new Error("Failed to download (" + response.statusCode + "): " + url));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
updateMakeLocalProgress(url);
|
|
238
|
+
|
|
239
|
+
let data = '';
|
|
240
|
+
response.on('data', (/** @type {Buffer|string} */ chunk) => {
|
|
241
|
+
data += chunk;
|
|
242
|
+
});
|
|
243
|
+
response.on('end', () => {
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
res(data);
|
|
246
|
+
});
|
|
247
|
+
response.on('error', (err) => {
|
|
248
|
+
clearTimeout(timer);
|
|
249
|
+
rej(err);
|
|
250
|
+
});
|
|
251
|
+
}));
|
|
252
|
+
req.on('error', (err) => {
|
|
253
|
+
clearTimeout(timer);
|
|
254
|
+
clearSpinnerLine();
|
|
255
|
+
rej(err);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @param {string} url
|
|
262
|
+
* @returns {Promise<Buffer>}
|
|
263
|
+
*/
|
|
264
|
+
export function downloadBinary(url) {
|
|
265
|
+
return new Promise((res, rej) => {
|
|
266
|
+
const timer = setTimeout(() => {
|
|
267
|
+
rej(new Error("Download timed out after " + DOWNLOAD_TIMEOUT_MS + "ms: " + url));
|
|
268
|
+
}, DOWNLOAD_TIMEOUT_MS);
|
|
269
|
+
|
|
270
|
+
const req = (url.startsWith("http://") ? http.get(url, (response) => {
|
|
271
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
return downloadBinary(response.headers.location).then(res).catch(rej);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (response.statusCode !== 200) {
|
|
277
|
+
clearTimeout(timer);
|
|
278
|
+
clearSpinnerLine();
|
|
279
|
+
rej(new Error("Failed to download (" + response.statusCode + "): " + url));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
updateMakeLocalProgress(url);
|
|
284
|
+
|
|
285
|
+
const chunks = /** @type {Buffer[]} */ ([]);
|
|
286
|
+
response.on('data', (/** @type {Buffer|string} */ chunk) => {
|
|
287
|
+
chunks.push(chunk);
|
|
288
|
+
});
|
|
289
|
+
response.on('end', () => {
|
|
290
|
+
clearTimeout(timer);
|
|
291
|
+
res(Buffer.concat(chunks));
|
|
292
|
+
});
|
|
293
|
+
response.on('error', (err) => {
|
|
294
|
+
clearTimeout(timer);
|
|
295
|
+
rej(err);
|
|
296
|
+
});
|
|
297
|
+
}) : https.get(url, (response) => {
|
|
298
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
299
|
+
clearTimeout(timer);
|
|
300
|
+
return downloadBinary(response.headers.location).then(res).catch(rej);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (response.statusCode !== 200) {
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
clearSpinnerLine();
|
|
306
|
+
rej(new Error("Failed to download (" + response.statusCode + "): " + url));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
updateMakeLocalProgress(url);
|
|
311
|
+
|
|
312
|
+
const chunks = /** @type {Buffer[]} */ ([]);
|
|
313
|
+
response.on('data', (/** @type {Buffer|string} */ chunk) => {
|
|
314
|
+
chunks.push(chunk);
|
|
315
|
+
});
|
|
316
|
+
response.on('end', () => {
|
|
317
|
+
clearTimeout(timer);
|
|
318
|
+
res(Buffer.concat(chunks));
|
|
319
|
+
});
|
|
320
|
+
response.on('error', (err) => {
|
|
321
|
+
clearTimeout(timer);
|
|
322
|
+
rej(err);
|
|
323
|
+
});
|
|
324
|
+
}));
|
|
325
|
+
req.on('error', (err) => {
|
|
326
|
+
clearTimeout(timer);
|
|
327
|
+
clearSpinnerLine();
|
|
328
|
+
rej(err);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @param {LocalizationContext} context
|
|
335
|
+
* @param {string} url
|
|
336
|
+
* @param {unknown} err
|
|
337
|
+
*/
|
|
338
|
+
export function recordFailedDownload(context, url, err) {
|
|
339
|
+
if (!url || !context?.failedDownloads) return;
|
|
340
|
+
const message = err?.message ? String(err.message) : "";
|
|
341
|
+
if (!context.failedDownloads.has(url)) {
|
|
342
|
+
context.failedDownloads.set(url, message);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* @param {string} url
|
|
348
|
+
* @returns {string}
|
|
349
|
+
*/
|
|
350
|
+
export function getShortUrlName(url) {
|
|
351
|
+
try {
|
|
352
|
+
const parsed = new URL(url);
|
|
353
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
354
|
+
return parts[parts.length - 1] || parsed.host;
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return url;
|
|
358
|
+
}
|
|
359
|
+
}
|