@needle-tools/engine 5.1.0-alpha.5 → 5.1.0-canary.02ccb45

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-OPkPmdUM.umd.cjs → needle-engine.bundle-BSJwg312.umd.cjs} +146 -145
  4. package/dist/{needle-engine.bundle-C-LG00ZZ.js → needle-engine.bundle-DLNnLj9B.js} +6650 -6494
  5. package/dist/{needle-engine.bundle-D7tzaiYE.min.js → needle-engine.bundle-I8Lv85MA.min.js} +175 -174
  6. package/dist/needle-engine.d.ts +95 -17
  7. package/dist/needle-engine.js +554 -553
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/lib/engine/api.d.ts +1 -1
  11. package/lib/engine/api.js +1 -1
  12. package/lib/engine/api.js.map +1 -1
  13. package/lib/engine/engine_context.js +7 -0
  14. package/lib/engine/engine_context.js.map +1 -1
  15. package/lib/engine/engine_init.js +2 -2
  16. package/lib/engine/engine_init.js.map +1 -1
  17. package/lib/engine/engine_license.d.ts +7 -7
  18. package/lib/engine/engine_license.js +185 -57
  19. package/lib/engine/engine_license.js.map +1 -1
  20. package/lib/engine/engine_networking_blob.js +3 -3
  21. package/lib/engine/engine_networking_blob.js.map +1 -1
  22. package/lib/engine/engine_utils_format.js +20 -14
  23. package/lib/engine/engine_utils_format.js.map +1 -1
  24. package/lib/engine/engine_utils_qrcode.js +2 -2
  25. package/lib/engine/engine_utils_qrcode.js.map +1 -1
  26. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  27. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  28. package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
  29. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  30. package/lib/engine/webcomponents/needle-engine.js +2 -2
  31. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  32. package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
  33. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  34. package/lib/engine/xr/TempXRContext.js +2 -2
  35. package/lib/engine/xr/TempXRContext.js.map +1 -1
  36. package/lib/engine-components/AudioSource.js +1 -1
  37. package/lib/engine-components/AudioSource.js.map +1 -1
  38. package/lib/engine-components/DropListener.js +1 -0
  39. package/lib/engine-components/DropListener.js.map +1 -1
  40. package/lib/engine-components/OrbitControls.d.ts +1 -0
  41. package/lib/engine-components/OrbitControls.js +7 -2
  42. package/lib/engine-components/OrbitControls.js.map +1 -1
  43. package/lib/engine-components/VideoPlayer.d.ts +8 -2
  44. package/lib/engine-components/VideoPlayer.js +42 -19
  45. package/lib/engine-components/VideoPlayer.js.map +1 -1
  46. package/lib/engine-components/Voip.d.ts +16 -7
  47. package/lib/engine-components/Voip.js +90 -53
  48. package/lib/engine-components/Voip.js.map +1 -1
  49. package/lib/engine-components/api.d.ts +1 -0
  50. package/lib/engine-components/api.js +1 -0
  51. package/lib/engine-components/api.js.map +1 -1
  52. package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
  53. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  54. package/lib/engine-components/webxr/WebXRImageTracking.d.ts +62 -1
  55. package/lib/engine-components/webxr/WebXRImageTracking.js +55 -2
  56. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  57. package/package.json +2 -2
  58. package/plugins/common/license.js +50 -10
  59. package/plugins/types/userconfig.d.ts +4 -1
  60. package/plugins/vite/build-pipeline.js +57 -20
  61. package/plugins/vite/license.js +29 -12
  62. package/src/engine/api.ts +1 -1
  63. package/src/engine/engine_context.ts +11 -1
  64. package/src/engine/engine_init.ts +2 -2
  65. package/src/engine/engine_license.ts +201 -55
  66. package/src/engine/engine_networking_blob.ts +3 -3
  67. package/src/engine/engine_utils_format.ts +20 -14
  68. package/src/engine/engine_utils_qrcode.ts +2 -2
  69. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
  70. package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
  71. package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
  72. package/src/engine/webcomponents/needle-engine.ts +2 -2
  73. package/src/engine/xr/TempXRContext.ts +2 -2
  74. package/src/engine-components/AudioSource.ts +1 -1
  75. package/src/engine-components/DropListener.ts +1 -0
  76. package/src/engine-components/OrbitControls.ts +8 -2
  77. package/src/engine-components/VideoPlayer.ts +40 -17
  78. package/src/engine-components/Voip.ts +88 -53
  79. package/src/engine-components/api.ts +1 -0
  80. package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
  81. package/src/engine-components/webxr/WebXRImageTracking.ts +77 -7
  82. package/src/vite-env.d.ts +0 -16
@@ -82,7 +82,10 @@ export type userSettings = {
82
82
  */
83
83
  version?: string;
84
84
 
85
- /** If defined the access token will be used to run compression on Needle Cloud */
85
+ /** If defined the access token will be used to run compression on Needle Cloud.
86
+ *
87
+ * Expected to be a Needle Cloud access token (created in the Needle Cloud UI),
88
+ * NOT a JWT. Do not pass a licensing JWT here. */
86
89
  accessToken?: string | undefined;
87
90
 
88
91
  /**
@@ -1,5 +1,6 @@
1
1
  import { ChildProcess, exec } from 'child_process';
2
2
  import { NEEDLE_CLOUD_CLI_NAME } from '../common/cloud.js';
3
+ import { resolveLicense } from '../common/license.js';
3
4
  import { getOutputDirectory, loadConfig } from './config.js';
4
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'fs';
5
6
  import { relative } from 'path';
@@ -10,6 +11,8 @@ import { needleBlue, needleDim, needleLog, needleSupportsColor, setTransientLogL
10
11
  const PIPELINE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
11
12
  const PIPELINE_STRUCTURED_LOG_PREFIX = "__needle_pipeline_log__:";
12
13
 
14
+ // #region Validation
15
+
13
16
  /**
14
17
  * @param {import('../types').userSettings} config
15
18
  * @returns {boolean}
@@ -37,8 +40,11 @@ env:
37
40
  return true;
38
41
  }
39
42
 
43
+ // #endregion
44
+
40
45
  // see https://linear.app/needle/issue/NE-3798
41
46
 
47
+ // #region State
42
48
 
43
49
  /** @type {Promise<void>|null} */
44
50
  let buildPipelineTask;
@@ -87,6 +93,10 @@ function increaseMaxWaitTime(debugLog) {
87
93
  }
88
94
  }
89
95
 
96
+ // #endregion
97
+
98
+ // #region Plugin
99
+
90
100
  /** Runs the needle build pipeline as part of the vite build process.
91
101
  * @param {"build" | "serve"} command
92
102
  * @param {import('../types/needleConfig').needleMeta | null | undefined} config
@@ -237,9 +247,12 @@ export async function needleBuildPipeline(command, config, userSettings) {
237
247
  }
238
248
  }
239
249
 
250
+ // #endregion
251
+
252
+ // #region Migration
240
253
 
241
254
  /**
242
- * Previously we did always install the build pipeline and run an extra command to invoke the build pipeline.
255
+ * Previously we did always install the build pipeline and run an extra command to invoke the build pipeline.
243
256
  * This is now done automatically by the needle build pipeline plugin - so we update all legacy projects to use the new method.
244
257
  * @param {string} packageJsonPath
245
258
  */
@@ -258,6 +271,10 @@ async function fixPackageJson(packageJsonPath) {
258
271
  writeFileSync(packageJsonPath, fixed);
259
272
  }
260
273
 
274
+ // #endregion
275
+
276
+ // #region Logging
277
+
261
278
  /** @param {...unknown} args */
262
279
  function log(...args) {
263
280
  needleLog("needle-buildpipeline", args.join(" "));
@@ -267,11 +284,15 @@ function warn(...args) {
267
284
  needleLog("needle-buildpipeline", args.join(" "), "warn");
268
285
  }
269
286
 
287
+ // #endregion
288
+
289
+ // #region Execution
290
+
270
291
  /**
271
292
  * @typedef {{ event?: string, phase?: string, target?: string, message?: string, level?: string }} BuildPipelinePayload
272
293
  */
273
294
  /**
274
- * @param {import('../types').userSettings} opts
295
+ * @param {import('../types').userSettings} opts
275
296
  * @param {{verbose?:boolean}} [options]
276
297
  * @returns {Promise<boolean>}
277
298
  */
@@ -283,12 +304,7 @@ async function invokeBuildPipeline(opts, options = {}) {
283
304
  const supportsColor = needleSupportsColor();
284
305
  const key = (/** @type {string} */ text) => supportsColor ? needleBlue(text) : text;
285
306
 
286
- const installPath = "node_modules/@needle-tools/gltf-build-pipeline";
287
- const fullInstallPath = process.cwd() + "/" + installPath;
288
- const existsLocally = existsSync(fullInstallPath);
289
- if (existsLocally) {
290
- log("Found local installation at " + fullInstallPath);
291
- }
307
+ // #region Wait for output
292
308
  await delay(500);
293
309
  const outputDirectory = getOutputDirectory() + "/assets";
294
310
  const startWaitTime = Date.now();
@@ -332,17 +348,18 @@ async function invokeBuildPipeline(opts, options = {}) {
332
348
  `${key("Files to process")}: ${files.length} in ${rel(outputDirectory)}, ${formatBytes(filesBytes)}`,
333
349
  existsSync(process.cwd() + "/node_modules/.needle/build-pipeline/output") ? needleDim("Removing temporary output directory") : undefined,
334
350
  ].filter(Boolean), "log", { dimBody: false });
351
+ // #endregion
352
+
353
+ // #region Setup
335
354
 
336
355
  /** @type {null | ChildProcess} */
337
356
  let proc = null;
338
357
 
339
- let cloudAccessToken = opts.buildPipeline?.accessToken || opts.license?.accessToken;
340
- if (!cloudAccessToken) {
341
- cloudAccessToken = process.env.NEEDLE_CLOUD_TOKEN;
342
- }
358
+ // Cloud token used by `${NEEDLE_CLOUD_CLI_NAME} optimize --token <token>`.
359
+ // This is a Needle Cloud access token (created in the Needle Cloud UI), NOT a JWT.
360
+ const cloudAccessToken = opts.buildPipeline?.accessToken;
343
361
  const runInCloud = typeof cloudAccessToken === "string" && cloudAccessToken.length > 0;
344
- // if a user has defined the build pipeline settings object but not passed in a token we should print out some information
345
- // or perhaps log an error / prevent the build from running completely
362
+
346
363
  if (opts.buildPipeline && !runInCloud && process.env.CI) {
347
364
  warn(`No cloud access token found. Please set it via process.env.NEEDLE_CLOUD_TOKEN`);
348
365
  return false;
@@ -367,6 +384,10 @@ async function invokeBuildPipeline(opts, options = {}) {
367
384
  }
368
385
  }
369
386
 
387
+ // #endregion
388
+
389
+ // #region Run
390
+
370
391
  // allow running the build pipeline in the cloud. It requires and access token to be set in the vite.config.js
371
392
  // this can be set via e.g. process.env.NEEDLE_CLOUD_TOKEN
372
393
  const commandEnv = { ...process.env, NEEDLE_PIPELINE_STRUCTURED_LOGS: "1" };
@@ -393,11 +414,6 @@ async function invokeBuildPipeline(opts, options = {}) {
393
414
  log(`Running compression in cloud ⛅ using access token: ${obfuscatedToken}`);
394
415
  proc = exec(cmd, { env: commandEnv });
395
416
  }
396
- else if (existsLocally) {
397
- const cmd = `needle-gltf transform "${outputDirectory}" \"${tempOutputPath}\"`;
398
- log("Running command \"" + cmd + "\" at " + process.cwd() + "...");
399
- proc = exec(cmd, { cwd: installPath, env: commandEnv });
400
- }
401
417
  else {
402
418
  // First check if the user passed in a specific version to use via the vite config
403
419
  let version = opts.buildPipeline?.version;
@@ -424,10 +440,21 @@ async function invokeBuildPipeline(opts, options = {}) {
424
440
  if (!version) version = "stable";
425
441
 
426
442
  const versionInfo = versionSource ? `'${version}' (${versionSource})` : `'${version}'`;
427
- const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}" \"${tempOutputPath}\"`;
443
+ // needle-gltf 3.x requires a JWT via `--auth-token <jwt>` on every CLI invocation.
444
+ // The JWT comes from the needle license server — it is NOT the Needle Cloud access token.
445
+ const licenseResult = await resolveLicense({
446
+ team: opts.license?.team,
447
+ accessToken: opts.license?.accessToken,
448
+ loglevel: opts.debugLicense === true ? "verbose" : undefined,
449
+ });
450
+ const authTokenArg = licenseResult?.jwt ? ` --auth-token ${licenseResult.jwt}` : "";
451
+ const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}" \"${tempOutputPath}\"${authTokenArg}`;
428
452
  log(`Running compression locally using version ${versionInfo}`);
429
453
  proc = exec(cmd, { env: commandEnv });
430
454
  }
455
+ // #endregion
456
+
457
+ // #region Output
431
458
  let pipelineSpinnerIndex = 0;
432
459
  let pipelineSpinnerActive = false;
433
460
  let transformStepCount = 0;
@@ -550,6 +577,9 @@ async function invokeBuildPipeline(opts, options = {}) {
550
577
  }
551
578
  proc.stdout?.on('data', onLog);
552
579
  proc.stderr?.on('data', onLog);
580
+ // #endregion
581
+
582
+ // #region Exit
553
583
  return new Promise((resolve, reject) => {
554
584
  proc.on('exit', (code) => {
555
585
  clearPipelineProgress();
@@ -570,8 +600,13 @@ async function invokeBuildPipeline(opts, options = {}) {
570
600
  resolve(success);
571
601
  });
572
602
  });
603
+ // #endregion
573
604
  }
574
605
 
606
+ // #endregion
607
+
608
+ // #region Helpers
609
+
575
610
  /** @param {string | null | undefined} directory */
576
611
  function getDirectoryStats(directory) {
577
612
  if (!directory || !existsSync(directory)) return { fileCount: 0, totalBytes: 0 };
@@ -597,3 +632,5 @@ function getDirectoryStats(directory) {
597
632
  }
598
633
  return { fileCount, totalBytes };
599
634
  }
635
+
636
+ // #endregion
@@ -8,7 +8,8 @@ import { loadConfig } from './config.js';
8
8
  * @returns {import('vite').Plugin}
9
9
  */
10
10
  export function needleLicense(command, config, userSettings) {
11
- let license = undefined;
11
+ /** @type {import('../common/license.js').LicenseResult | null | undefined} */
12
+ let licenseResult = undefined;
12
13
  let appliedLicense = false;
13
14
 
14
15
  return {
@@ -24,7 +25,7 @@ export function needleLicense(command, config, userSettings) {
24
25
  }
25
26
  }
26
27
 
27
- license = await resolveLicense({
28
+ licenseResult = await resolveLicense({
28
29
  team: team,
29
30
  accessToken: userSettings?.license?.accessToken,
30
31
  loglevel: userSettings?.debugLicense === true ? "verbose" : undefined
@@ -32,10 +33,6 @@ export function needleLicense(command, config, userSettings) {
32
33
 
33
34
  },
34
35
  async transform(src, id) {
35
- if (appliedLicense === true) {
36
- return;
37
- }
38
-
39
36
  // Vite 4 and 8 handling:
40
37
  const isNeedleEngineFile = id.includes("engine/engine_license")
41
38
  || id.includes("needle-tools_engine")
@@ -45,22 +42,42 @@ export function needleLicense(command, config, userSettings) {
45
42
  const isViteChunkFile = id.includes("chunk") && id.includes(".vite");
46
43
  if (isNeedleEngineFile || isViteChunkFile) {
47
44
 
48
- if (!license) {
45
+ if (!licenseResult) {
49
46
  return;
50
47
  }
51
48
 
52
- const index = src.indexOf("NEEDLE_ENGINE_LICENSE_TYPE");
49
+ let modified = false;
50
+
51
+ // Replace license type
52
+ const index = src.indexOf("fFDtghiT");
53
53
  if (index >= 0) {
54
54
  const end = src.indexOf(";", index);
55
55
  if (end >= 0) {
56
- appliedLicense = true;
57
56
  const line = src.substring(index, end);
58
- const replaced = "NEEDLE_ENGINE_LICENSE_TYPE = \"" + license + "\"";
57
+ const replaced = "fFDtghiT = \"" + licenseResult.type + "\"";
59
58
  src = src.replace(line, replaced);
60
- return { code: src, map: null }
59
+ modified = true;
60
+ }
61
+ }
62
+
63
+ // Replace license JWT (same pattern)
64
+ if (licenseResult.jwt) {
65
+ const jwtIndex = src.indexOf("$NAqiWhRF");
66
+ if (jwtIndex >= 0) {
67
+ const jwtEnd = src.indexOf(";", jwtIndex);
68
+ if (jwtEnd >= 0) {
69
+ const jwtLine = src.substring(jwtIndex, jwtEnd);
70
+ const jwtReplaced = "$NAqiWhRF = \"" + licenseResult.jwt + "\"";
71
+ src = src.replace(jwtLine, jwtReplaced);
72
+ modified = true;
73
+ }
61
74
  }
62
75
  }
63
- // @TODO: detect local needle engine dev setup and log error if not found
76
+
77
+ if (modified) {
78
+ appliedLicense = true;
79
+ return { code: src, map: null }
80
+ }
64
81
  }
65
82
  },
66
83
  buildEnd() {
package/src/engine/api.ts CHANGED
@@ -230,7 +230,7 @@ export * from "./engine_input.js";
230
230
  export { InstancingUtil } from "./engine_instancing.js";
231
231
 
232
232
  /** License checking utilities */
233
- export { hasCommercialLicense, hasIndieLicense, hasProLicense } from "./engine_license.js";
233
+ export { _$HwXA, _$xxpfa, UsFaeEU } from "./engine_license.js";
234
234
 
235
235
 
236
236
  // ============================================================================
@@ -1808,6 +1808,7 @@ export class Context implements IContext {
1808
1808
  ? (`${((window.performance as any).memory.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`)
1809
1809
  : "n/a";
1810
1810
 
1811
+ const gl = this.renderer.getContext();
1811
1812
  console.log(this.renderer.info.render.calls + " DrawCalls", "\nRender:",
1812
1813
  {
1813
1814
  shaders: this.renderer.info.programs?.length,
@@ -1817,7 +1818,16 @@ export class Context implements IContext {
1817
1818
  {
1818
1819
  usedMemory: usedJSHeapSize,
1819
1820
  ...this.renderer.info.memory
1820
- }, "\nTarget Framerate: " + this.targetFrameRate);
1821
+ },
1822
+ "\nRenderer:",
1823
+ {
1824
+ dpr: this.renderer.getPixelRatio(),
1825
+ windowDpr: window.devicePixelRatio,
1826
+ antialias: gl.getContextAttributes()?.antialias,
1827
+ samples: gl.getParameter(gl.SAMPLES),
1828
+ resolution: `${this.renderer.domElement.width}x${this.renderer.domElement.height}`,
1829
+ },
1830
+ "\nTarget Framerate: " + this.targetFrameRate);
1821
1831
  }
1822
1832
  }
1823
1833
 
@@ -8,7 +8,7 @@ import { initBuiltinTypes } from "./codegen/register_types.js";
8
8
  import { initSpatialConsole } from "./debug/debug_spatial_console.js";
9
9
  import { initAddressableSerializers } from "./engine_addressables.js";
10
10
  import { ensureAudioContextIsResumed } from "./engine_audio.js";
11
- import { initLicense } from "./engine_license.js";
11
+ import { NJzpPtg } from "./engine_license.js";
12
12
  import { initNeedleLoader } from "./engine_loaders.js";
13
13
  import { initPhysics } from "./engine_physics_rapier.js";
14
14
  import { initBuiltinSerializers } from "./engine_serialization_builtin_serializer.js";
@@ -59,5 +59,5 @@ export function initEngine() {
59
59
  initPhysics();
60
60
  initXR();
61
61
  initSpatialConsole();
62
- initLicense();
62
+ NJzpPtg();
63
63
  }