@needle-tools/engine 5.1.4 → 5.1.5

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 (72) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{needle-engine.bundle-CEba8eTv.js → needle-engine.bundle-CxsZfV6C.js} +5245 -5166
  3. package/dist/{needle-engine.bundle-BvxvjlSN.min.js → needle-engine.bundle-DUb8msPR.min.js} +140 -140
  4. package/dist/{needle-engine.bundle-GUaSFX8u.umd.cjs → needle-engine.bundle-D_h1sDs-.umd.cjs} +137 -137
  5. package/dist/needle-engine.d.ts +39 -30
  6. package/dist/needle-engine.js +242 -242
  7. package/dist/needle-engine.min.js +1 -1
  8. package/dist/needle-engine.umd.cjs +1 -1
  9. package/lib/engine/api.d.ts +1 -1
  10. package/lib/engine/api.js +1 -1
  11. package/lib/engine/api.js.map +1 -1
  12. package/lib/engine/debug/debug_console.js +39 -17
  13. package/lib/engine/debug/debug_console.js.map +1 -1
  14. package/lib/engine/debug/debug_spatial_console.js +14 -0
  15. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  16. package/lib/engine/engine_init.js +2 -2
  17. package/lib/engine/engine_init.js.map +1 -1
  18. package/lib/engine/engine_license.d.ts +7 -7
  19. package/lib/engine/engine_license.js +71 -71
  20. package/lib/engine/engine_license.js.map +1 -1
  21. package/lib/engine/engine_networking_blob.js +3 -3
  22. package/lib/engine/engine_utils_qrcode.js +2 -2
  23. package/lib/engine/engine_utils_screenshot.js +1 -0
  24. package/lib/engine/engine_utils_screenshot.js.map +1 -1
  25. package/lib/engine/engine_utils_screenshot.xr.js +1 -1
  26. package/lib/engine/engine_utils_screenshot.xr.js.map +1 -1
  27. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  28. package/lib/engine/webcomponents/needle menu/needle-menu.js +18 -5
  29. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  30. package/lib/engine/webcomponents/needle-engine.ar-overlay.js +33 -7
  31. package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
  32. package/lib/engine/webcomponents/needle-engine.js +2 -2
  33. package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
  34. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  35. package/lib/engine/xr/NeedleXRSession.js +10 -4
  36. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  37. package/lib/engine/xr/NeedleXRSync.js +47 -47
  38. package/lib/engine/xr/NeedleXRSync.js.map +1 -1
  39. package/lib/engine/xr/TempXRContext.js +2 -2
  40. package/lib/engine-components/DragControls.d.ts +9 -0
  41. package/lib/engine-components/DragControls.js +174 -73
  42. package/lib/engine-components/DragControls.js.map +1 -1
  43. package/lib/engine-components/DragControlsConstraints.d.ts +11 -2
  44. package/lib/engine-components/DragControlsConstraints.js +18 -11
  45. package/lib/engine-components/DragControlsConstraints.js.map +1 -1
  46. package/lib/engine-components/OrbitControls.js +32 -6
  47. package/lib/engine-components/OrbitControls.js.map +1 -1
  48. package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
  49. package/package.json +1 -1
  50. package/plugins/common/license.js +4 -4
  51. package/plugins/vite/license.js +4 -4
  52. package/src/engine/api.ts +1 -1
  53. package/src/engine/debug/debug_console.ts +33 -17
  54. package/src/engine/debug/debug_spatial_console.ts +15 -0
  55. package/src/engine/engine_init.ts +2 -2
  56. package/src/engine/engine_license.ts +68 -68
  57. package/src/engine/engine_networking_blob.ts +3 -3
  58. package/src/engine/engine_utils_qrcode.ts +2 -2
  59. package/src/engine/engine_utils_screenshot.ts +1 -0
  60. package/src/engine/engine_utils_screenshot.xr.ts +1 -1
  61. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
  62. package/src/engine/webcomponents/needle menu/needle-menu.ts +19 -5
  63. package/src/engine/webcomponents/needle-engine.ar-overlay.ts +33 -5
  64. package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
  65. package/src/engine/webcomponents/needle-engine.ts +2 -2
  66. package/src/engine/xr/NeedleXRSession.ts +10 -3
  67. package/src/engine/xr/NeedleXRSync.ts +47 -50
  68. package/src/engine/xr/TempXRContext.ts +2 -2
  69. package/src/engine-components/DragControls.ts +189 -80
  70. package/src/engine-components/DragControlsConstraints.ts +20 -12
  71. package/src/engine-components/OrbitControls.ts +31 -4
  72. package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
@@ -9,19 +9,19 @@ import { DeviceUtilities, getParam } from "./engine_utils.js";
9
9
 
10
10
  const debug = getParam("__debuglic__");
11
11
 
12
- const _$mvpTzyNE: ((result: boolean) => void)[] = [];
12
+ const $qnPTnU: ((result: boolean) => void)[] = [];
13
13
 
14
14
  // DO NOT EDIT MANUALLY
15
- let __GWcsL: string = "";
15
+ let __IsXDNQb: string = "";
16
16
  // eslint-disable-next-line prefer-const
17
- let MbNaU: string = "";
17
+ let $ZDwS: string = "";
18
18
 
19
19
  if (debug) {
20
- console.log("License Type: " + __GWcsL);
21
- if (MbNaU) {
22
- console.log("License JWT: " + MbNaU);
20
+ console.log("License Type: " + __IsXDNQb);
21
+ if ($ZDwS) {
22
+ console.log("License JWT: " + $ZDwS);
23
23
  try {
24
- const payload = JSON.parse(atob(MbNaU.split(".")[1].replace(/-/g, '+').replace(/_/g, '/')));
24
+ const payload = JSON.parse(atob($ZDwS.split(".")[1].replace(/-/g, '+').replace(/_/g, '/')));
25
25
  console.log("License JWT payload:", payload);
26
26
  }
27
27
  catch { console.log("License JWT payload: (failed to decode)"); }
@@ -32,8 +32,8 @@ if (debug) {
32
32
  }
33
33
 
34
34
  /** @internal */
35
- export function KLaEb() {
36
- switch (__GWcsL) {
35
+ export function otIKt() {
36
+ switch (__IsXDNQb) {
37
37
  case "pro":
38
38
  case "enterprise":
39
39
  return true;
@@ -42,8 +42,8 @@ export function KLaEb() {
42
42
  }
43
43
 
44
44
  /** @internal */
45
- export function wPA() {
46
- switch (__GWcsL) {
45
+ export function _gNHPUn() {
46
+ switch (__IsXDNQb) {
47
47
  case "indie":
48
48
  return true;
49
49
  }
@@ -51,8 +51,8 @@ export function wPA() {
51
51
  }
52
52
 
53
53
  /** @internal */
54
- export function _bEUa() {
55
- switch (__GWcsL) {
54
+ export function $aiMWPAef() {
55
+ switch (__IsXDNQb) {
56
56
  case "edu":
57
57
  return true;
58
58
  }
@@ -60,20 +60,20 @@ export function _bEUa() {
60
60
  }
61
61
 
62
62
  /** @internal */
63
- export function __QhCnOwd() {
64
- return KLaEb() || wPA() || _bEUa();
63
+ export function $PLKRCQIf() {
64
+ return otIKt() || _gNHPUn() || $aiMWPAef();
65
65
  }
66
66
 
67
67
 
68
68
  /** @internal */
69
- export function __jFACNo(cb: (result: boolean) => void) {
70
- if (KLaEb() || wPA() || _bEUa())
69
+ export function _$sDvKPqp(cb: (result: boolean) => void) {
70
+ if (otIKt() || _gNHPUn() || $aiMWPAef())
71
71
  return cb(true);
72
- _$mvpTzyNE.push(cb);
72
+ $qnPTnU.push(cb);
73
73
  }
74
74
 
75
- function $VtYVpv(result: boolean) {
76
- for (const cb of _$mvpTzyNE) {
75
+ function _$wgFzq(result: boolean) {
76
+ for (const cb of $qnPTnU) {
77
77
  try {
78
78
  cb(result);
79
79
  }
@@ -88,7 +88,7 @@ function $VtYVpv(result: boolean) {
88
88
 
89
89
  // ECDSA P-256 public key for verifying license JWTs (verification-only, safe to ship)
90
90
  /* eslint-disable no-secrets/no-secrets -- public key, not a secret */
91
- const _$LkmsJYaP = {
91
+ const __lgCVJdwb = {
92
92
  kty: "EC",
93
93
  crv: "P-256",
94
94
  x: "A34nyKMjhQYVgzeE4tyLUYdx34TAKogDa7v7PRaO9Lg",
@@ -112,7 +112,7 @@ function base64urlDecode(str: string): Uint8Array {
112
112
  * Verify a JWT license token and return the `type` claim if valid.
113
113
  * Returns null if the JWT is missing, malformed, or has an invalid signature.
114
114
  */
115
- async function _crQsWky(jwt: string): Promise<string | null> {
115
+ async function __ZcVBPZ(jwt: string): Promise<string | null> {
116
116
  if (!jwt) return null;
117
117
  try {
118
118
  const parts = jwt.split(".");
@@ -122,7 +122,7 @@ async function _crQsWky(jwt: string): Promise<string | null> {
122
122
 
123
123
  const key = await crypto.subtle.importKey(
124
124
  "jwk",
125
- _$LkmsJYaP,
125
+ __lgCVJdwb,
126
126
  { name: "ECDSA", namedCurve: "P-256" },
127
127
  false,
128
128
  ["verify"]
@@ -182,24 +182,24 @@ async function _crQsWky(jwt: string): Promise<string | null> {
182
182
  }
183
183
 
184
184
  /** Verify the injected JWT and update the license type if valid.
185
- * The engine ONLY trusts the JWT — the plain __GWcsL string is ignored. */
185
+ * The engine ONLY trusts the JWT — the plain __IsXDNQb string is ignored. */
186
186
  let _jwtVerificationPromise: Promise<void> | undefined = undefined;
187
- async function $yHAwAkQ(): Promise<void> {
188
- // NOTE: do NOT add an `if (!MbNaU) return` guard here.
187
+ async function _$BXZmXmG(): Promise<void> {
188
+ // NOTE: do NOT add an `if (!$ZDwS) return` guard here.
189
189
  // esbuild (Vite dependency pre-bundling) would see the variable as constant ""
190
190
  // and tree-shake the entire JWT verification code path.
191
- const jwt = MbNaU;
191
+ const jwt = $ZDwS;
192
192
  // Clear after reading — this reassignment also prevents esbuild from
193
193
  // constant-folding the variable (it now has multiple assignment sites).
194
- MbNaU = "";
195
- const verifiedType = await _crQsWky(jwt);
194
+ $ZDwS = "";
195
+ const verifiedType = await __ZcVBPZ(jwt);
196
196
  if (verifiedType) {
197
- __GWcsL = verifiedType;
197
+ __IsXDNQb = verifiedType;
198
198
  if (debug) console.log("License type set from verified JWT: " + verifiedType);
199
- $VtYVpv(__QhCnOwd());
199
+ _$wgFzq($PLKRCQIf());
200
200
  }
201
201
  else {
202
- __GWcsL = "basic";
202
+ __IsXDNQb = "basic";
203
203
  if (debug && jwt) console.warn("JWT verification failed — license reset to basic");
204
204
  }
205
205
  }
@@ -650,18 +650,18 @@ export namespace Telemetry {
650
650
  }
651
651
 
652
652
 
653
- export function _$auU() {
653
+ export function _$zEuYwGe() {
654
654
 
655
- if(__GWcsL === "") __GWcsL = "basic";
655
+ if(__IsXDNQb === "") __IsXDNQb = "basic";
656
656
 
657
657
  // Start JWT verification — must be here (not top-level) to avoid tree-shaking
658
- _jwtVerificationPromise = $yHAwAkQ();
658
+ _jwtVerificationPromise = _$BXZmXmG();
659
659
 
660
660
  Telemetry.init();
661
661
  ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
662
- $aCj(evt.context);
663
- __DWQeDNnb(evt.context);
664
- setTimeout(() => Zanq(evt.context), 2000);
662
+ __mJiL(evt.context);
663
+ $NDvkjBNe(evt.context);
664
+ setTimeout(() => $vIjopOW(evt.context), 2000);
665
665
  });
666
666
  // ContextCreated fires after the renderer exists, so the canvas is available for context-loss monitoring
667
667
  ContextRegistry.registerCallback(ContextEvent.ContextCreated, evt => {
@@ -670,17 +670,17 @@ export function _$auU() {
670
670
  });
671
671
  }
672
672
 
673
- export let _$OuASD: Promise<void> | undefined = undefined;
673
+ export let $oUFilKP: Promise<void> | undefined = undefined;
674
674
  let applicationIsForbidden = false;
675
675
  let applicationForbiddenText = "";
676
- async function _iRYfLx() {
676
+ async function $IWNB() {
677
677
  // Only perform the runtime license check once
678
- if (_$OuASD) return _$OuASD;
678
+ if ($oUFilKP) return $oUFilKP;
679
679
  // Wait for JWT verification to complete first (if running)
680
680
  if (_jwtVerificationPromise) {
681
681
  await _jwtVerificationPromise;
682
682
  }
683
- if (__GWcsL === "basic") {
683
+ if (__IsXDNQb === "basic") {
684
684
  try {
685
685
  const licenseUrl = "https://needle.tools/api/v1/needle-engine/check?location=" + encodeURIComponent(window.location.href) + "&version=" + VERSION + "&generator=" + encodeURIComponent(GENERATOR);
686
686
  const res = await fetch(licenseUrl, {
@@ -692,29 +692,29 @@ async function _iRYfLx() {
692
692
  if (res?.status === 200) {
693
693
  applicationIsForbidden = false;
694
694
  if (debug) console.log("License check succeeded");
695
- __GWcsL = "pro";
696
- $VtYVpv(true);
695
+ __IsXDNQb = "pro";
696
+ _$wgFzq(true);
697
697
  }
698
698
  else if (res?.status === 403) {
699
- $VtYVpv(false);
699
+ _$wgFzq(false);
700
700
  applicationIsForbidden = true;
701
701
  applicationForbiddenText = await res.text();
702
702
  }
703
703
  else {
704
- $VtYVpv(false);
704
+ _$wgFzq(false);
705
705
  if (debug) console.log("License check failed with status " + res?.status);
706
706
  }
707
707
  }
708
708
  catch (err) {
709
- $VtYVpv(false);
709
+ _$wgFzq(false);
710
710
  if (debug) console.error("License check failed", err);
711
711
  }
712
712
  }
713
- else if (debug) console.log("Runtime license check is skipped because license is already applied as \"" + __GWcsL + "\"");
713
+ else if (debug) console.log("Runtime license check is skipped because license is already applied as \"" + __IsXDNQb + "\"");
714
714
  }
715
- _$OuASD = _iRYfLx();
715
+ $oUFilKP = $IWNB();
716
716
 
717
- async function __DWQeDNnb(ctx: IContext) {
717
+ async function $NDvkjBNe(ctx: IContext) {
718
718
  function createForbiddenElement() {
719
719
  const div = document.createElement("div");
720
720
  div.className = "needle-forbidden";
@@ -779,40 +779,40 @@ async function __DWQeDNnb(ctx: IContext) {
779
779
  }, 500)
780
780
  }
781
781
 
782
- async function $aCj(ctx: IContext) {
782
+ async function __mJiL(ctx: IContext) {
783
783
  try {
784
- if (!KLaEb() && !wPA()) {
785
- return __POTiHWtw(ctx);
784
+ if (!otIKt() && !_gNHPUn()) {
785
+ return $pTRKOi(ctx);
786
786
  }
787
787
  }
788
788
  catch (err) {
789
789
  if (debug) console.log("License check failed", err)
790
- return __POTiHWtw(ctx)
790
+ return $pTRKOi(ctx)
791
791
  }
792
- if (debug) __POTiHWtw(ctx)
792
+ if (debug) $pTRKOi(ctx)
793
793
  }
794
794
 
795
795
 
796
796
 
797
- async function __POTiHWtw(ctx: IContext) {
797
+ async function $pTRKOi(ctx: IContext) {
798
798
 
799
799
  // if the engine loads faster than the license check, we need to capture the ready event here
800
800
  let isReady = false;
801
801
  ctx.domElement.addEventListener("ready", () => isReady = true);
802
802
 
803
- await _$OuASD?.catch(() => { });
803
+ await $oUFilKP?.catch(() => { });
804
804
 
805
805
 
806
- if (KLaEb() || wPA()) return;
807
- if (__QhCnOwd() === false) __Pexfzvu();
806
+ if (otIKt() || _gNHPUn()) return;
807
+ if ($PLKRCQIf() === false) __GWmHmYep();
808
808
 
809
809
  // check if the engine is already ready (meaning has finished loading)
810
810
  if (isReady) {
811
- _fZxtASpV(ctx);
811
+ xbmulpf(ctx);
812
812
  }
813
813
  else {
814
814
  ctx.domElement.addEventListener("ready", () => {
815
- _fZxtASpV(ctx);
815
+ xbmulpf(ctx);
816
816
  });
817
817
  }
818
818
  }
@@ -820,7 +820,7 @@ async function __POTiHWtw(ctx: IContext) {
820
820
  // const licenseElementIdentifier = "needle-license-element";
821
821
  // const licenseDuration = 10000;
822
822
  // const licenseDelay = 1200;
823
- function _fZxtASpV(ctx: IContext) {
823
+ function xbmulpf(ctx: IContext) {
824
824
 
825
825
  const style = `
826
826
  position: relative;
@@ -833,7 +833,7 @@ function _fZxtASpV(ctx: IContext) {
833
833
  padding: 10px;
834
834
  padding-left: 30px;
835
835
  `;
836
- if (__GWcsL === "edu") {
836
+ if (__IsXDNQb === "edu") {
837
837
  if (navigator.webdriver) {
838
838
  console.log("This project is supported by Needle for Education – https://needle.tools");
839
839
  }
@@ -890,7 +890,7 @@ function _fZxtASpV(ctx: IContext) {
890
890
  }
891
891
  }, 1000);
892
892
 
893
- if (_bEUa()) {
893
+ if ($aiMWPAef()) {
894
894
  const removeDelay = 20_000;
895
895
  setTimeout(() => {
896
896
  clearInterval(interval);
@@ -899,7 +899,7 @@ function _fZxtASpV(ctx: IContext) {
899
899
  const intervalInMinutes = 5;
900
900
  setTimeout(() => {
901
901
  if (ctx.domElement.parentNode)
902
- _fZxtASpV(ctx);
902
+ xbmulpf(ctx);
903
903
  }, 1000 * 60 * intervalInMinutes)
904
904
  }, removeDelay);
905
905
  }
@@ -910,7 +910,7 @@ function _fZxtASpV(ctx: IContext) {
910
910
  const base64Logo = "data:image/webp;base64,UklGRrABAABXRUJQVlA4WAoAAAAQAAAAHwAAHwAAQUxQSKEAAAARN6CmbSM4WR7vdARON11EBDq3fLiNbVtVzpMCPlKAEzsx0Y/x+Ovuv4dn0EFE/ydAvz6YggXzgh5sVgXM/zOC/4sii7qgGvB5N7hmuQYwkvazWAu1JPW41FXSHq6pnaQWvqYH18Fc0j1hO/BFTtIeSBlJi5w6qIIO7IOrwhFsB2Yxukif0FTRLpXswHR8MxbslKe9VZsn/Ub5C7YFOpqSTABWUDgg6AAAAFAGAJ0BKiAAIAA+7VyoTqmkpCI3+qgBMB2JbACdMt69DwMIQBLhkTO6XwY00UEDK6cNIDnuNibPf0EgAP7Y1myuiQHLDsF/0h5unrGh6WAbv7aegg2ZMd3uRKfT/3SJztcaujYfTvMXspfCTmYcoO6a+vhC3ss4M8uM58t4siiu59I4aOl59e9Sr6xoxYlHf2v+NnBNpJYeJf8jABQAId/PXuBkLEFkiCucgSGEcfhvajql/j3reCGl0M5/9gQWy7ayNPs+wlvIxFnNfSlfuND4CZOCyxOHhRqOmHN4ULHo3tCSrUNvgAA=";
911
911
 
912
912
  let lastLogTime = 0;
913
- async function __Pexfzvu(_logo?: string) {
913
+ async function __GWmHmYep(_logo?: string) {
914
914
  const now = Date.now();
915
915
  if (now - lastLogTime < 2000) return;
916
916
  lastLogTime = now;
@@ -943,7 +943,7 @@ async function __Pexfzvu(_logo?: string) {
943
943
  }
944
944
 
945
945
 
946
- async function Zanq(context: IContext) {
946
+ async function $vIjopOW(context: IContext) {
947
947
  // isAllowed() now also covers cross-origin-isolated pages (where beacons can't be sent)
948
948
  // and Do Not Track / GPC.
949
949
  if (!Telemetry.isAllowed(context)) {
@@ -957,7 +957,7 @@ async function Zanq(context: IContext) {
957
957
 
958
958
  // current url without query parameters
959
959
  const currentUrl = window.location.href.split("?")[0];
960
- const license = __GWcsL;
960
+ const license = __IsXDNQb;
961
961
 
962
962
  const beaconData = {
963
963
  license,
@@ -1,7 +1,7 @@
1
1
  import { FileLoader } from "three";
2
2
 
3
3
  import { showBalloonWarning } from "./debug/index.js";
4
- import { __QhCnOwd } from "./engine_license.js";
4
+ import { $PLKRCQIf } from "./engine_license.js";
5
5
  import { delay } from "./engine_utils.js";
6
6
  import { md5AsBytes, md5Hex, sha256Base64 } from "./engine_utils_hash.js";
7
7
 
@@ -45,7 +45,7 @@ export namespace BlobStorage {
45
45
  */
46
46
  export function canUpload(info: { filesize: number }) {
47
47
  const sizeInMB = info.filesize / 1024 / 1024;
48
- if (__QhCnOwd()) {
48
+ if ($PLKRCQIf()) {
49
49
  return sizeInMB < maxSizeInMB;
50
50
  }
51
51
  return sizeInMB < maxFreeSizeInMB;
@@ -100,7 +100,7 @@ export namespace BlobStorage {
100
100
  console.warn(`Your file is too large for uploading (${filesizeInMB.toFixed(1)}MB). Max allowed size is ${maxSizeInMB}MB`);
101
101
  return null;
102
102
  }
103
- else if (!__QhCnOwd() && filesizeInMB > maxFreeSizeInMB) {
103
+ else if (!$PLKRCQIf() && filesizeInMB > maxFreeSizeInMB) {
104
104
  if (opts?.silent !== true) showBalloonWarning(`File is too large for uploading. Please get a <a href=\"https://needle.tools/pricing\" target=\"_blank\">commercial license</a> to upload files larger than 5MB`);
105
105
  console.warn(`Your file is too large for uploading (${filesizeInMB.toFixed(1)}MB). Max size is 5MB for non-commercial users. Please get a commercial license at https://needle.tools/pricing for larger files (up to 50MB)`);
106
106
  return null;
@@ -4,7 +4,7 @@ import { Quaternion, Vector2, Vector3, Vector4 } from "three";
4
4
 
5
5
  import { needleLogoOnlySVG } from "./assets/index.js";
6
6
  import { isDevEnvironment } from "./debug/debug.js";
7
- import { __QhCnOwd } from "./engine_license.js";
7
+ import { $PLKRCQIf } from "./engine_license.js";
8
8
  import { InternalAttributeUtils } from "./engine_utils_attributes.js";
9
9
  import type { NeedleEngineWebComponent } from "./webcomponents/needle-engine.js";
10
10
 
@@ -135,7 +135,7 @@ async function internalRenderQRCodeOverlays(canvas: HTMLCanvasElement, args: { s
135
135
  console.debug("[QR Code] No web component found")
136
136
  }
137
137
 
138
- const canUseCustomLogo = __QhCnOwd();
138
+ const canUseCustomLogo = $PLKRCQIf();
139
139
 
140
140
  // Query logo src from needle-engine attribute.
141
141
  // For any supported attribute it's possible to use "falsey" values (e.g. "0" or "false" to disable the logo in the QR code)
@@ -380,6 +380,7 @@ export function screenshot2(opts: ScreenshotOptionsShare): Promise<ScreenshotOpt
380
380
  * @see {@link saveImage} for downloading data URLs
381
381
  * @see {@link ScreenshotOptions} for all available options
382
382
  */
383
+ // #region screenshot 2
383
384
  export function screenshot2(opts: ScreenshotOptionsDataUrl | ScreenshotOptionsTexture | ScreenshotOptionsBlob | ScreenshotOptionsShare)
384
385
  : Texture | string | null | Promise<Blob | null> | Promise<ScreenshotOptionsShareReturnType> {
385
386
 
@@ -29,7 +29,7 @@ NeedleXRSession.onSessionRequestStart(evt => {
29
29
  });
30
30
  `);
31
31
  if (isDevEnvironment()) {
32
- showBalloonError("No camera feed available - please request the 'camera-access' feature before starting WebXR or add the ARCameraBackground component to your scene");
32
+ showBalloonError("No camera feed available - please request the 'camera-access' feature before starting WebXR or add the ARCameraBackground component to your scene", { duration: 300_000 });
33
33
  }
34
34
  return false;
35
35
  }
@@ -2,7 +2,7 @@ import { Mesh, Object3D, TextureLoader, Vector4 } from "three";
2
2
  import ThreeMeshUI from "three-mesh-ui";
3
3
 
4
4
  import { addNewComponent } from "../../engine_components.js";
5
- import { __QhCnOwd } from "../../engine_license.js";
5
+ import { $PLKRCQIf } from "../../engine_license.js";
6
6
  import { OneEuroFilterXYZ } from "../../engine_math.js";
7
7
  import type { Context } from "../../engine_setup.js";
8
8
  import { lookAtObject } from "../../engine_three_utils.js";
@@ -321,7 +321,7 @@ export class NeedleSpatialMenu {
321
321
  }
322
322
  if (this.menu) {
323
323
  const index = this.menu.children.indexOf(this._poweredByNeedleElement as any);
324
- if (!this._showNeedleLogo && __QhCnOwd()) {
324
+ if (!this._showNeedleLogo && $PLKRCQIf()) {
325
325
  if (index >= 0) {
326
326
  this._poweredByNeedleElement.removeFromParent();
327
327
  this.markDirty();
@@ -1,6 +1,6 @@
1
1
  import { showBalloonMessage } from "../../debug/debug.js";
2
2
  import type { Context } from "../../engine_context.js";
3
- import { __QhCnOwd, __jFACNo, Telemetry } from "../../engine_license.js";
3
+ import { $PLKRCQIf, _$sDvKPqp, Telemetry } from "../../engine_license.js";
4
4
  import { isLocalNetwork } from "../../engine_networking_utils.js";
5
5
  import { HTMLElementBase } from "../../engine_ssr.js";
6
6
  import { DeviceUtilities, getParam } from "../../engine_utils.js";
@@ -186,6 +186,11 @@ export class NeedleMenu {
186
186
  if (args.session.isScreenBasedAR) {
187
187
  this._menu["previousParent"] = this._menu.parentNode;
188
188
  this._context.arOverlayElement.appendChild(this._menu);
189
+ // The host defaults to z-index 0 (to not overlay elements outside <needle-engine> in 2D).
190
+ // In screen-based AR the overlay may be reparented to document.body (e.g. Needle Go App Clip /
191
+ // Mozilla XR) where the WebXR polyfill's fullscreen canvas wrapper sits at z-index 1 and would
192
+ // render on top of the menu — visible through the transparent canvas but swallowing all taps.
193
+ this._menu.style.zIndex = "9999";
189
194
  args.session.session.addEventListener("end", this.onExitXR);
190
195
 
191
196
  // Close the foldout if it's open on entering AR
@@ -194,6 +199,7 @@ export class NeedleMenu {
194
199
  }
195
200
 
196
201
  private onExitXR = () => {
202
+ this._menu.style.zIndex = "";
197
203
  if (this._menu["previousParent"]) {
198
204
  this._menu["previousParent"].appendChild(this._menu);
199
205
  delete this._menu["previousParent"];
@@ -778,8 +784,8 @@ export class NeedleMenuElement extends HTMLElementBase {
778
784
  try {
779
785
  // if the user has a license then we CAN hide the needle logo
780
786
  // calling this method immediately will cause an issue with vite bundling tho
781
- window.requestAnimationFrame(() => __jFACNo(res => {
782
- if (res == true && __QhCnOwd() && !debugNonCommercial) {
787
+ window.requestAnimationFrame(() => _$sDvKPqp(res => {
788
+ if (res == true && $PLKRCQIf() && !debugNonCommercial) {
783
789
  let visible = this._userRequestedLogoVisible;
784
790
  if (visible === undefined) visible = false;
785
791
  this.___onSetLogoVisible(visible);
@@ -830,7 +836,7 @@ export class NeedleMenuElement extends HTMLElementBase {
830
836
  // ensure the menu is not hidden or removed
831
837
  const requiredParent = this?.parentNode;
832
838
  if (this.style.display != "flex" || this.style.visibility != "visible" || this.style.opacity != "1" || requiredParent != this._domElement?.shadowRoot) {
833
- if (!__QhCnOwd()) {
839
+ if (!$PLKRCQIf()) {
834
840
  const change = changeEventCounter++;
835
841
  // if a user doesn't have a local pro license *but* for development the menu is hidden then we show a warning
836
842
  if (isLocalNetwork() && this._userRequestedMenuVisible === false) {
@@ -880,6 +886,7 @@ export class NeedleMenuElement extends HTMLElementBase {
880
886
  connectedCallback() {
881
887
  this.ensureInitialized();
882
888
  window.addEventListener("resize", this.handleSizeChange);
889
+ this.addEventListener("beforexrselect", this.#onBeforeXRSelect);
883
890
  this.handleMenuVisible();
884
891
  this._sizeChangeInterval = setInterval(() => this.handleSizeChange(undefined, false), 5000);
885
892
  // the dom element is set after initialization runs
@@ -890,11 +897,18 @@ export class NeedleMenuElement extends HTMLElementBase {
890
897
  }
891
898
  disconnectedCallback() {
892
899
  window.removeEventListener("resize", this.handleSizeChange);
900
+ this.removeEventListener("beforexrselect", this.#onBeforeXRSelect);
893
901
  clearInterval(this._sizeChangeInterval);
894
902
  this._domElement?.removeEventListener("resize", this.handleSizeChange);
895
903
  this._context?.domElement.removeEventListener("click", this.#onClick);
896
904
  }
897
905
 
906
+ /** Prevent XR select from firing into the scene (e.g. tap-to-place) when the user
907
+ * interacts with the menu during screen-based AR (dom-overlay / Needle Go App Clip) */
908
+ #onBeforeXRSelect = (e: Event) => {
909
+ e.preventDefault();
910
+ }
911
+
898
912
  #onClick = (e: Event) => {
899
913
  // detect a click outside the opened foldout to automatically close it
900
914
  if (!e.defaultPrevented
@@ -918,7 +932,7 @@ export class NeedleMenuElement extends HTMLElementBase {
918
932
  showNeedleLogo(visible: boolean) {
919
933
  this._userRequestedLogoVisible = visible;
920
934
  if (!visible) {
921
- if (!__QhCnOwd() || debugNonCommercial) {
935
+ if (!$PLKRCQIf() || debugNonCommercial) {
922
936
  console.warn("[Needle Engine] You need a commercial license to hide the Needle Engine logo in production.");
923
937
  const localNetwork = isLocalNetwork()
924
938
  if (!localNetwork) return;
@@ -29,11 +29,11 @@ export class AROverlayHandler {
29
29
  this.arContainer = overlayContainer;
30
30
 
31
31
  if (DeviceUtilities.isMozillaXR() || DeviceUtilities.isNeedleAppClip()) {
32
- const arElements = context.domElement!.children;
33
- for (let i = 0; i < arElements?.length; i++) {
34
- const el = arElements[i];
35
- if (!el) return;
36
- if (el === this.arContainer) return;
32
+ // Copy the live HTMLCollection: appendChild removes elements from it while
33
+ // iterating, which previously skipped every other element
34
+ const arElements = Array.from(context.domElement!.children);
35
+ for (const el of arElements) {
36
+ if (!el || el === this.arContainer) continue;
37
37
  this._reparentedObjects.push({ el: el, previousParent: el.parentElement });
38
38
  this.arContainer?.appendChild(el);
39
39
  }
@@ -47,6 +47,17 @@ export class AROverlayHandler {
47
47
  overlayContainer.style.display = "";
48
48
  overlayContainer.style.visibility = "";
49
49
  document.body.appendChild(overlayContainer);
50
+ // Outside the shadow root the container loses its shadow CSS (fullscreen, z-index).
51
+ // Without an explicit position it stays static, its z-index is ignored and it doesn't
52
+ // create a stacking context — overlay children then compete individually with the
53
+ // WebXR polyfill's fullscreen canvas wrapper (z-index 1) and can end up behind the
54
+ // transparent canvas: visible, but unable to receive any taps.
55
+ overlayContainer.style.position = "fixed";
56
+ overlayContainer.style.top = "0";
57
+ overlayContainer.style.left = "0";
58
+ overlayContainer.style.width = "100%";
59
+ overlayContainer.style.height = "100%";
60
+ overlayContainer.style.zIndex = "10000";
50
61
  }
51
62
  }
52
63
  else {
@@ -71,6 +82,23 @@ export class AROverlayHandler {
71
82
  }
72
83
  this._reparentedObjects.length = 0;
73
84
 
85
+ // Restore the overlay container that was reparented to document.body in onBegin
86
+ // (the polyfill only restores it when it received the domOverlay root) and remove
87
+ // the inline styles that replaced its shadow CSS while it was outside the shadow root
88
+ if (this.arContainer && this.originalDomOverlayParent) {
89
+ const style = this.arContainer.style;
90
+ style.position = "";
91
+ style.top = "";
92
+ style.left = "";
93
+ style.width = "";
94
+ style.height = "";
95
+ style.zIndex = "";
96
+ if (this.arContainer.parentNode !== this.originalDomOverlayParent) {
97
+ this.originalDomOverlayParent.appendChild(this.arContainer);
98
+ }
99
+ this.originalDomOverlayParent = null;
100
+ }
101
+
74
102
  // mozilla XR exit AR fixes
75
103
  if (DeviceUtilities.isMozillaXR()) {
76
104
  // without the timeout we get errors in mozillas code and can not enter XR again
@@ -1,6 +1,6 @@
1
1
  import { needleLogoOnlySVG } from "../assets/index.js"
2
2
  import { isDevEnvironment, showBalloonWarning } from "../debug/index.js";
3
- import { __QhCnOwd, KLaEb, _$OuASD } from "../engine_license.js";
3
+ import { $PLKRCQIf, otIKt, $oUFilKP } from "../engine_license.js";
4
4
  import { Mathf } from "../engine_math.js";
5
5
  import { LoadingProgressArgs } from "../engine_setup.js";
6
6
  import { getParam } from "../engine_utils.js";
@@ -205,7 +205,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
205
205
  }
206
206
 
207
207
 
208
- const hasLicense = KLaEb();
208
+ const hasLicense = otIKt();
209
209
  if (!existing) {
210
210
  this._loadingElement.style.position = "absolute";
211
211
  this._loadingElement.style.width = "100%";
@@ -373,7 +373,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
373
373
 
374
374
  // private async handleRuntimeLicense(loadingElement: HTMLElement) {
375
375
  // // First check if we have a commercial license
376
- // let commercialLicense = __QhCnOwd();
376
+ // let commercialLicense = $PLKRCQIf();
377
377
  // // if it's the case then we don't need to perform a runtime check
378
378
  // if (commercialLicense) return;
379
379
 
@@ -393,10 +393,10 @@ export class EngineLoadingView implements ILoadingViewHandler {
393
393
  // loadingElement.appendChild(nonCommercialContainer);
394
394
 
395
395
  // // Use the runtime license check
396
- // if (!isDevEnvironment() && _$OuASD) {
396
+ // if (!isDevEnvironment() && $oUFilKP) {
397
397
  // if (debugLicense) console.log("Waiting for runtime license check");
398
- // await _$OuASD;
399
- // commercialLicense = __QhCnOwd();
398
+ // await $oUFilKP;
399
+ // commercialLicense = $PLKRCQIf();
400
400
  // }
401
401
  // if (commercialLicense) return;
402
402
  // nonCommercialContainer.style.transition = "opacity .5s ease-in-out";
@@ -3,7 +3,7 @@ import type { Texture } from "three";
3
3
  import { isDevEnvironment, showBalloonWarning } from "../debug/index.js";
4
4
  import { PUBLIC_KEY, VERSION } from "../engine_constants.js";
5
5
  import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
6
- import { __QhCnOwd, Telemetry } from "../engine_license.js";
6
+ import { $PLKRCQIf, Telemetry } from "../engine_license.js";
7
7
  import { onStart } from "../engine_lifecycle_api.js";
8
8
  import { setDracoDecoderPath, setDracoDecoderType, setKtx2TranscoderPath } from "../engine_loaders.gltf.js";
9
9
  import { Context, ContextCreateArgs } from "../engine_setup.js";
@@ -579,7 +579,7 @@ export class NeedleEngineWebComponent extends HTMLElementBase implements INeedle
579
579
 
580
580
 
581
581
  // Loading start events
582
- const allowOverridingDefaultLoading = __QhCnOwd();
582
+ const allowOverridingDefaultLoading = $PLKRCQIf();
583
583
  // default loading can be overriden by calling preventDefault in the onload start event
584
584
  this.ensureLoadStartIsRegistered();
585
585
  let useDefaultLoading = this.dispatchEvent(new CustomEvent("loadstart", {
@@ -772,11 +772,18 @@ export class NeedleXRSession implements INeedleXRSession {
772
772
  this._currentSessionRequestMode = mode;
773
773
  /**@type {XRSystem} */
774
774
  const newSession = await (this._currentSessionRequest)?.catch(e => {
775
- console.error(e, "Code: " + e?.code);
776
- if (e?.code === 9) showBalloonWarning("Couldn't start XR session. Make sure you allow the required permissions.")
775
+ console.error(e, "Code: " + e?.code, "requiredFeatures:", init.requiredFeatures, "optionalFeatures:", init.optionalFeatures);
776
+ if (e?.code === 9) {
777
+ // NotSupportedError: surface the platform's reason if it provides one
778
+ // (e.g. which required feature is unsupported) instead of only a generic hint
779
+ const reason = typeof e?.message === "string" && e.message.length > 0
780
+ ? e.message
781
+ : "Make sure you allow the required permissions.";
782
+ showBalloonWarning(`Couldn't start XR session: ${reason}`, { duration: 30_000 });
783
+ }
777
784
  console.log("If the specified XR configuration is not supported (e.g. entering AR doesnt work) - make sure you access the website on a secure connection (HTTPS) and your device has the required permissions (e.g. camera access)");
778
785
  const notSecure = location.protocol === 'http:';
779
- if (notSecure) showBalloonWarning("XR requires a secure connection (HTTPS)");
786
+ if (notSecure) showBalloonWarning("XR requires a secure connection (HTTPS)", { duration: 30_000 });
780
787
  });
781
788
  this._currentSessionRequest = undefined;
782
789
  this._currentSessionRequestMode = null;