@needle-tools/engine 4.12.0-next.ca2cebd → 4.12.0-next.dcaf2b0

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 (216) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/components.needle.json +1 -1
  3. package/dist/generateMeshBVH.worker-iyfPIK6R.js +21 -0
  4. package/dist/{gltf-progressive-DWcmTMCh.umd.cjs → gltf-progressive-Bfpfaz84.umd.cjs} +1 -1
  5. package/dist/{gltf-progressive-DgYz5BYa.js → gltf-progressive-DPunMlEM.js} +24 -24
  6. package/dist/{gltf-progressive-DZrY8VT6.min.js → gltf-progressive-hFPACYio.min.js} +2 -2
  7. package/dist/{loader.worker-Dip-PthR.js → loader.worker-DWzfDpAl.js} +4 -4
  8. package/dist/{needle-engine.bundle-CFkbGdL5.js → needle-engine.bundle-DTXsNmA-.js} +9789 -9421
  9. package/dist/needle-engine.bundle-HRGeyfKM.umd.cjs +1647 -0
  10. package/dist/needle-engine.bundle-OAD5_P8d.min.js +1647 -0
  11. package/dist/needle-engine.d.ts +105 -33
  12. package/dist/needle-engine.js +48 -48
  13. package/dist/needle-engine.min.js +1 -1
  14. package/dist/needle-engine.umd.cjs +1 -1
  15. package/dist/{postprocessing-CMgoN5t5.umd.cjs → postprocessing-BHQvwehB.umd.cjs} +81 -81
  16. package/dist/{postprocessing-DYDtB188.min.js → postprocessing-ClLv0reO.min.js} +54 -54
  17. package/dist/{postprocessing-BTW9pD_s.js → postprocessing-DLI2N3LL.js} +450 -441
  18. package/dist/{three-DfMvBzXi.js → three-BCCkyCA5.js} +1 -7
  19. package/dist/{three-qj71I7J3.umd.cjs → three-Bf2NBxAw.umd.cjs} +2 -2
  20. package/dist/{three-B7CT31Bt.min.js → three-W7zWTcfP.min.js} +1 -1
  21. package/dist/{three-examples-D1SK93ek.js → three-examples-D4rE49Ui.js} +12 -22
  22. package/dist/{three-examples-D1P7eEhn.min.js → three-examples-DB5Uoja4.min.js} +12 -12
  23. package/dist/{three-examples-CsW4_6LI.umd.cjs → three-examples-Djbk6WA4.umd.cjs} +6 -6
  24. package/dist/{three-mesh-ui-C_uSB5dD.js → three-mesh-ui-3nSSizT4.js} +1 -1
  25. package/dist/{three-mesh-ui-LQ44s0AL.min.js → three-mesh-ui-CIez6qJQ.min.js} +1 -1
  26. package/dist/{three-mesh-ui-DpATDXwU.umd.cjs → three-mesh-ui-zsOOA5Pq.umd.cjs} +1 -1
  27. package/dist/{vendor-D0zoswDa.js → vendor-DMZcbVO1.js} +3707 -3527
  28. package/dist/vendor-sURMCFSI.min.js +1116 -0
  29. package/dist/{vendor-UCpFAwt1.umd.cjs → vendor-tyBvnMF-.umd.cjs} +39 -39
  30. package/lib/engine/codegen/register_types.js +0 -2
  31. package/lib/engine/codegen/register_types.js.map +1 -1
  32. package/lib/engine/debug/debug_console.js +403 -1
  33. package/lib/engine/debug/debug_console.js.map +1 -1
  34. package/lib/engine/engine_components.js +3 -3
  35. package/lib/engine/engine_components.js.map +1 -1
  36. package/lib/engine/engine_context.js +2 -0
  37. package/lib/engine/engine_context.js.map +1 -1
  38. package/lib/engine/engine_input.d.ts +5 -0
  39. package/lib/engine/engine_input.js +6 -0
  40. package/lib/engine/engine_input.js.map +1 -1
  41. package/lib/engine/engine_license.d.ts +18 -0
  42. package/lib/engine/engine_license.js +163 -18
  43. package/lib/engine/engine_license.js.map +1 -1
  44. package/lib/engine/engine_networking.js +20 -5
  45. package/lib/engine/engine_networking.js.map +1 -1
  46. package/lib/engine/engine_physics.js.map +1 -1
  47. package/lib/engine/engine_physics_rapier.js +1 -1
  48. package/lib/engine/engine_physics_rapier.js.map +1 -1
  49. package/lib/engine/engine_serialization_builtin_serializer.js +1 -1
  50. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  51. package/lib/engine/engine_three_utils.js +2 -2
  52. package/lib/engine/engine_three_utils.js.map +1 -1
  53. package/lib/engine/engine_utils.d.ts +4 -1
  54. package/lib/engine/engine_utils.js +28 -4
  55. package/lib/engine/engine_utils.js.map +1 -1
  56. package/lib/engine/extensions/extensions.d.ts +29 -7
  57. package/lib/engine/extensions/extensions.js.map +1 -1
  58. package/lib/engine/webcomponents/WebXRButtons.js +13 -5
  59. package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
  60. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -1
  61. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  62. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +2 -0
  63. package/lib/engine/webcomponents/needle menu/needle-menu.js +37 -6
  64. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  65. package/lib/engine/webcomponents/needle-engine.ar-overlay.js +4 -0
  66. package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
  67. package/lib/engine/webcomponents/needle-engine.js +1 -1
  68. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  69. package/lib/engine/xr/NeedleXRSession.d.ts +1 -1
  70. package/lib/engine/xr/NeedleXRSession.js +106 -22
  71. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  72. package/lib/engine/xr/TempXRContext.js +12 -2
  73. package/lib/engine/xr/TempXRContext.js.map +1 -1
  74. package/lib/engine/xr/usdz.js +6 -2
  75. package/lib/engine/xr/usdz.js.map +1 -1
  76. package/lib/engine-components/AlignmentConstraint.d.ts +1 -1
  77. package/lib/engine-components/AlignmentConstraint.js +1 -1
  78. package/lib/engine-components/Animation.d.ts +1 -1
  79. package/lib/engine-components/Animation.js +1 -1
  80. package/lib/engine-components/Animator.d.ts +1 -1
  81. package/lib/engine-components/Animator.js +1 -1
  82. package/lib/engine-components/AudioListener.d.ts +1 -1
  83. package/lib/engine-components/AudioListener.js +1 -1
  84. package/lib/engine-components/AudioSource.d.ts +1 -1
  85. package/lib/engine-components/AudioSource.js +1 -1
  86. package/lib/engine-components/Camera.d.ts +1 -1
  87. package/lib/engine-components/Camera.js +5 -2
  88. package/lib/engine-components/Camera.js.map +1 -1
  89. package/lib/engine-components/CharacterController.d.ts +6 -2
  90. package/lib/engine-components/CharacterController.js +6 -2
  91. package/lib/engine-components/CharacterController.js.map +1 -1
  92. package/lib/engine-components/Collider.d.ts +1 -1
  93. package/lib/engine-components/Collider.js.map +1 -1
  94. package/lib/engine-components/Component.d.ts +2 -1
  95. package/lib/engine-components/Component.js +3 -2
  96. package/lib/engine-components/Component.js.map +1 -1
  97. package/lib/engine-components/DragControls.js +4 -1
  98. package/lib/engine-components/DragControls.js.map +1 -1
  99. package/lib/engine-components/DropListener.d.ts +1 -0
  100. package/lib/engine-components/DropListener.js +26 -8
  101. package/lib/engine-components/DropListener.js.map +1 -1
  102. package/lib/engine-components/EventList.js +4 -1
  103. package/lib/engine-components/EventList.js.map +1 -1
  104. package/lib/engine-components/Joints.d.ts +14 -0
  105. package/lib/engine-components/Joints.js +14 -0
  106. package/lib/engine-components/Joints.js.map +1 -1
  107. package/lib/engine-components/LookAtConstraint.d.ts +1 -1
  108. package/lib/engine-components/LookAtConstraint.js +1 -1
  109. package/lib/engine-components/OrbitControls.d.ts +1 -1
  110. package/lib/engine-components/OrbitControls.js +1 -1
  111. package/lib/engine-components/Renderer.d.ts +6 -0
  112. package/lib/engine-components/Renderer.js +6 -0
  113. package/lib/engine-components/Renderer.js.map +1 -1
  114. package/lib/engine-components/RendererInstancing.js +5 -3
  115. package/lib/engine-components/RendererInstancing.js.map +1 -1
  116. package/lib/engine-components/SceneSwitcher.d.ts +3 -2
  117. package/lib/engine-components/SceneSwitcher.js +38 -25
  118. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  119. package/lib/engine-components/SpectatorCamera.js +15 -7
  120. package/lib/engine-components/SpectatorCamera.js.map +1 -1
  121. package/lib/engine-components/SpriteRenderer.d.ts +2 -1
  122. package/lib/engine-components/SpriteRenderer.js +2 -1
  123. package/lib/engine-components/SpriteRenderer.js.map +1 -1
  124. package/lib/engine-components/api.d.ts +1 -0
  125. package/lib/engine-components/api.js +1 -0
  126. package/lib/engine-components/api.js.map +1 -1
  127. package/lib/engine-components/codegen/components.d.ts +0 -1
  128. package/lib/engine-components/codegen/components.js +0 -1
  129. package/lib/engine-components/codegen/components.js.map +1 -1
  130. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +8 -0
  131. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  132. package/lib/engine-components/timeline/SignalAsset.d.ts +1 -1
  133. package/lib/engine-components/timeline/SignalAsset.js +1 -1
  134. package/lib/engine-components/ui/Raycaster.d.ts +3 -2
  135. package/lib/engine-components/ui/Raycaster.js +3 -2
  136. package/lib/engine-components/ui/Raycaster.js.map +1 -1
  137. package/lib/engine-components/ui/RectTransform.d.ts +6 -0
  138. package/lib/engine-components/ui/RectTransform.js +6 -0
  139. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  140. package/lib/engine-components/utils/LookAt.d.ts +2 -1
  141. package/lib/engine-components/utils/LookAt.js +2 -1
  142. package/lib/engine-components/utils/LookAt.js.map +1 -1
  143. package/lib/engine-components/web/CursorFollow.d.ts +1 -1
  144. package/lib/engine-components/web/CursorFollow.js +1 -1
  145. package/lib/engine-components/web/HoverAnimation.d.ts +1 -1
  146. package/lib/engine-components/web/HoverAnimation.js +1 -1
  147. package/lib/engine-components/web/ViewBox.d.ts +1 -1
  148. package/lib/engine-components/web/ViewBox.js +1 -1
  149. package/lib/engine-components/webxr/Avatar.js +2 -0
  150. package/lib/engine-components/webxr/Avatar.js.map +1 -1
  151. package/lib/engine-components/webxr/WebARSessionRoot.d.ts +5 -2
  152. package/lib/engine-components/webxr/WebARSessionRoot.js +5 -2
  153. package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
  154. package/lib/engine-components/webxr/WebXR.d.ts +3 -1
  155. package/lib/engine-components/webxr/WebXR.js +21 -13
  156. package/lib/engine-components/webxr/WebXR.js.map +1 -1
  157. package/package.json +6 -6
  158. package/plugins/vite/poster-client.js +8 -1
  159. package/src/engine/codegen/register_types.ts +0 -2
  160. package/src/engine/debug/debug_console.ts +449 -1
  161. package/src/engine/engine_components.ts +4 -4
  162. package/src/engine/engine_context.ts +2 -0
  163. package/src/engine/engine_input.ts +7 -0
  164. package/src/engine/engine_license.ts +180 -17
  165. package/src/engine/engine_networking.ts +20 -5
  166. package/src/engine/engine_physics.ts +3 -3
  167. package/src/engine/engine_physics_rapier.ts +1 -1
  168. package/src/engine/engine_serialization_builtin_serializer.ts +1 -1
  169. package/src/engine/engine_three_utils.ts +4 -2
  170. package/src/engine/engine_utils.ts +23 -4
  171. package/src/engine/extensions/extensions.ts +30 -6
  172. package/src/engine/webcomponents/WebXRButtons.ts +15 -5
  173. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -1
  174. package/src/engine/webcomponents/needle menu/needle-menu.ts +39 -7
  175. package/src/engine/webcomponents/needle-engine.ar-overlay.ts +6 -0
  176. package/src/engine/webcomponents/needle-engine.ts +2 -2
  177. package/src/engine/xr/NeedleXRSession.ts +120 -24
  178. package/src/engine/xr/TempXRContext.ts +12 -2
  179. package/src/engine/xr/usdz.ts +6 -1
  180. package/src/engine-components/AlignmentConstraint.ts +1 -1
  181. package/src/engine-components/Animation.ts +1 -1
  182. package/src/engine-components/Animator.ts +1 -1
  183. package/src/engine-components/AudioListener.ts +1 -1
  184. package/src/engine-components/AudioSource.ts +1 -1
  185. package/src/engine-components/Camera.ts +5 -2
  186. package/src/engine-components/CharacterController.ts +6 -2
  187. package/src/engine-components/Collider.ts +1 -1
  188. package/src/engine-components/Component.ts +5 -4
  189. package/src/engine-components/DragControls.ts +5 -1
  190. package/src/engine-components/DropListener.ts +29 -8
  191. package/src/engine-components/EventList.ts +5 -1
  192. package/src/engine-components/Joints.ts +14 -0
  193. package/src/engine-components/LookAtConstraint.ts +1 -1
  194. package/src/engine-components/OrbitControls.ts +1 -1
  195. package/src/engine-components/Renderer.ts +6 -0
  196. package/src/engine-components/RendererInstancing.ts +6 -3
  197. package/src/engine-components/SceneSwitcher.ts +40 -30
  198. package/src/engine-components/SpectatorCamera.ts +21 -10
  199. package/src/engine-components/SpriteRenderer.ts +2 -1
  200. package/src/engine-components/api.ts +2 -1
  201. package/src/engine-components/codegen/components.ts +0 -1
  202. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +11 -0
  203. package/src/engine-components/timeline/SignalAsset.ts +1 -1
  204. package/src/engine-components/ui/Raycaster.ts +3 -2
  205. package/src/engine-components/ui/RectTransform.ts +6 -0
  206. package/src/engine-components/utils/LookAt.ts +2 -1
  207. package/src/engine-components/web/CursorFollow.ts +1 -1
  208. package/src/engine-components/web/HoverAnimation.ts +1 -1
  209. package/src/engine-components/web/ViewBox.ts +1 -1
  210. package/src/engine-components/webxr/Avatar.ts +4 -0
  211. package/src/engine-components/webxr/WebARSessionRoot.ts +7 -3
  212. package/src/engine-components/webxr/WebXR.ts +23 -13
  213. package/dist/generateMeshBVH.worker-mO20N_b8.js +0 -21
  214. package/dist/needle-engine.bundle-4A5NjznD.min.js +0 -1647
  215. package/dist/needle-engine.bundle-BJg4_HhU.umd.cjs +0 -1647
  216. package/dist/vendor-BKGa4GE0.min.js +0 -1116
@@ -1,9 +1,14 @@
1
+ import { dof } from "three/src/nodes/TSL.js";
2
+
1
3
  import { isDevEnvironment } from "./debug/index.js";
2
4
  import { BUILD_TIME, GENERATOR, PUBLIC_KEY, VERSION } from "./engine_constants.js";
3
5
  import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
6
+ import { onInitialized } from "./engine_lifecycle_api.js";
7
+ import { isLocalNetwork } from "./engine_networking_utils.js";
4
8
  import { Context } from "./engine_setup.js";
5
9
  import type { IContext } from "./engine_types.js";
6
10
  import { getParam } from "./engine_utils.js";
11
+ import { InternalAttributeUtils } from "./engine_utils_attributes.js";
7
12
 
8
13
  const debug = getParam("debuglicense");
9
14
 
@@ -67,6 +72,174 @@ function invokeLicenseCheckResultChanged(result: boolean) {
67
72
  }
68
73
  }
69
74
 
75
+
76
+
77
+ // #region Telemetry
78
+ export namespace Telemetry {
79
+
80
+ window.addEventListener("error", (event: ErrorEvent) => {
81
+ sendError(Context.Current, "unhandled_error", event);
82
+ });
83
+ window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
84
+ sendError(Context.Current, "unhandled_promise_rejection", {
85
+ message: event.reason?.message,
86
+ stack: event.reason?.stack,
87
+ timestamp: Date.now(),
88
+ });
89
+ });
90
+
91
+ onInitialized((ctx => sendPageViewEvent(ctx)), { once: true });
92
+
93
+ function sendPageViewEvent(ctx: IContext) {
94
+ if (!isAllowed(ctx)) {
95
+ if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
96
+ return;
97
+ }
98
+ return doFetch({
99
+ site_id: "dabb8317376f",
100
+ type: "pageview",
101
+ pathname: window.location.pathname,
102
+ hostname: window.location.hostname,
103
+ page_title: document.title,
104
+ referrer: document.referrer,
105
+ user_agent: navigator.userAgent,
106
+ querystring: window.location.search,
107
+ language: navigator.language,
108
+ screenWidth: window.screen.width,
109
+ screenHeight: window.screen.height,
110
+ event_name: "page_view"
111
+ }).then(res => {
112
+ if (res instanceof Response && res.ok && isLocalNetwork()) {
113
+ const src = ctx.domElement?.getAttribute("src") || "";
114
+ const sessionKey = src + VERSION + GENERATOR + BUILD_TIME + PUBLIC_KEY;
115
+ if (window.sessionStorage.getItem("session_key") !== sessionKey) {
116
+ window.sessionStorage.setItem("session_key", sessionKey);
117
+ sendEvent(ctx, "info", {
118
+ src: ctx.domElement?.getAttribute("src") || "",
119
+ version: VERSION,
120
+ generator: GENERATOR,
121
+ build_time: BUILD_TIME,
122
+ public_key: PUBLIC_KEY,
123
+ });
124
+ }
125
+ }
126
+ })
127
+ }
128
+
129
+ export function isAllowed(context: IContext | null | undefined): boolean {
130
+ let domElement = context?.domElement as HTMLElement | null;
131
+ if (!domElement) domElement = document.querySelector<HTMLElement>("needle-engine");
132
+ if (!domElement && !context) return false;
133
+
134
+ const attribute = domElement?.getAttribute("no-telemetry");
135
+ if (attribute === "" || attribute === "true" || attribute === "1") {
136
+ if (NEEDLE_ENGINE_LICENSE_TYPE === "pro" || NEEDLE_ENGINE_LICENSE_TYPE === "enterprise") {
137
+ if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
138
+ return false;
139
+ }
140
+ }
141
+ return true;
142
+ }
143
+
144
+ const id = "dabb8317376f";
145
+
146
+ /**
147
+ * Sends a telemetry event
148
+ */
149
+ export async function sendEvent(context: IContext | null | undefined, eventName: string, properties?: Record<string, any>) {
150
+ if (!isAllowed(context)) {
151
+ if (debug) console.debug("Telemetry is disabled");
152
+ return;
153
+ }
154
+ const body = {
155
+ site_id: id,
156
+ type: "custom_event",
157
+ pathname: window.location.pathname,
158
+ event_name: eventName,
159
+ properties: properties ? JSON.stringify(properties) : undefined,
160
+ }
161
+ return doFetch(body);
162
+ }
163
+
164
+ type ErrorData = {
165
+ message?: string;
166
+ stack?: string;
167
+ filename?: string;
168
+ lineno?: number;
169
+ colno?: number;
170
+ timestamp?: number;
171
+ }
172
+
173
+ export async function sendError(context: IContext, errorName: string, error: ErrorData | ErrorEvent | Error) {
174
+
175
+ if (!isAllowed(context)) {
176
+ if (debug) console.debug("Telemetry is disabled");
177
+ return;
178
+ }
179
+
180
+ if (error instanceof ErrorEvent) {
181
+ error = {
182
+ message: error.message,
183
+ stack: error.error?.stack,
184
+ filename: error.filename,
185
+ lineno: error.lineno,
186
+ colno: error.colno,
187
+ timestamp: error.timeStamp || Date.now(),
188
+
189
+ };
190
+ }
191
+ else if (error instanceof Error) {
192
+ error = {
193
+ message: error.message,
194
+ stack: error.stack,
195
+ timestamp: Date.now(),
196
+ };
197
+ }
198
+ const body = {
199
+ site_id: id,
200
+ type: "error",
201
+ event_name: errorName || "error",
202
+ properties: JSON.stringify({
203
+ error_name: errorName,
204
+ message: error.message,
205
+ stack: error.stack,
206
+ filename: error.filename,
207
+ lineno: error.lineno,
208
+ colno: error.colno,
209
+ timestamp: error.timestamp,
210
+ })
211
+ }
212
+ return doFetch(body);
213
+ }
214
+
215
+ function doFetch(body: Record<string, any>) {
216
+ try {
217
+ const url = "https://needle.tools/api/v1/rum/t";
218
+ return fetch(url, {
219
+ method: "POST",
220
+ body: JSON.stringify(body),
221
+ headers: {
222
+ 'Content-Type': 'application/json'
223
+ },
224
+ // Ensures request completes even if page unloads
225
+ keepalive: true,
226
+ // Allow CORS requests
227
+ mode: 'cors',
228
+ // Low priority to avoid blocking other requests
229
+ // @ts-ignore
230
+ priority: 'low',
231
+ }).catch(e => {
232
+ if (debug) console.error("Failed to send telemetry", e);
233
+ })
234
+ }
235
+ catch (err) {
236
+ if (debug) console.error(err);
237
+ }
238
+ return Promise.resolve();
239
+ }
240
+ }
241
+
242
+
70
243
  ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
71
244
  showLicenseInfo(evt.context);
72
245
  handleForbidden(evt.context);
@@ -81,7 +254,7 @@ async function checkLicense() {
81
254
  if (runtimeLicenseCheckPromise) return runtimeLicenseCheckPromise;
82
255
  if (NEEDLE_ENGINE_LICENSE_TYPE === "basic") {
83
256
  try {
84
- const licenseUrl = "https://engine.needle.tools/licensing/check?location=" + encodeURIComponent(window.location.href) + "&version=" + VERSION + "&generator=" + encodeURIComponent(GENERATOR);
257
+ const licenseUrl = "https://needle.tools/api/v1/needle-engine/check?location=" + encodeURIComponent(window.location.href) + "&version=" + VERSION + "&generator=" + encodeURIComponent(GENERATOR);
85
258
  const res = await fetch(licenseUrl, {
86
259
  method: "GET",
87
260
  }).catch(_err => {
@@ -341,28 +514,18 @@ async function sendUsageMessageToAnalyticsBackend(context: IContext) {
341
514
  // We can't send beacons from cross-origin isolated pages
342
515
  if (window.crossOriginIsolated) return;
343
516
 
344
- const licenseType = NEEDLE_ENGINE_LICENSE_TYPE;
345
- if (licenseType === "pro" || licenseType === "enterprise") {
346
- const attribute = context?.domElement?.getAttribute("no-telemetry");
347
- if (attribute === "" || attribute === "true" || attribute === "1") {
348
- if (debug) console.debug("Telemetry is disabled");
349
- return;
350
- }
351
- if (debug) console.debug("Telemetry attribute: " + attribute);
517
+ if (!Telemetry.isAllowed(context)) {
518
+ if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
519
+ return;
352
520
  }
353
521
 
354
522
  try {
355
- const analyticsUrl = "htt" + "ps://nee" + "dle.tools/api/v1/ana" + "lytics";
523
+ const analyticsUrl = "htt" + "ps://" + "needle" + ".tools/" + "api/v1/needle-engine/ping";
356
524
  if (analyticsUrl) {
357
525
 
358
526
  // current url without query parameters
359
527
  const currentUrl = window.location.href.split("?")[0];
360
-
361
- let endpoint = "api/v2/new/request";
362
- if (!analyticsUrl.endsWith("/")) endpoint = "/" + endpoint;
363
528
  const license = NEEDLE_ENGINE_LICENSE_TYPE;
364
- const finalUrl = `${analyticsUrl}${endpoint}`;
365
- if (debug) console.debug("Sending beacon");
366
529
 
367
530
  const beaconData = {
368
531
  license,
@@ -376,7 +539,7 @@ async function sendUsageMessageToAnalyticsBackend(context: IContext) {
376
539
  build_time: BUILD_TIME,
377
540
  public_key: PUBLIC_KEY,
378
541
  };
379
- const res = navigator.sendBeacon?.(finalUrl, JSON.stringify(beaconData));
542
+ const res = navigator.sendBeacon?.(analyticsUrl, JSON.stringify(beaconData));
380
543
  if (debug) console.debug("Sent beacon: " + res);
381
544
  }
382
545
  }
@@ -384,4 +547,4 @@ async function sendUsageMessageToAnalyticsBackend(context: IContext) {
384
547
  if (debug)
385
548
  console.log("Failed to send non-commercial usage message to analytics backend", err);
386
549
  }
387
- }
550
+ }
@@ -6,6 +6,7 @@ import { type Websocket } from 'websocket-ts';
6
6
 
7
7
  import * as schemes from "../engine-schemes/schemes.js";
8
8
  import { isDevEnvironment } from './debug/index.js';
9
+ import { Telemetry } from './engine_license.js';
9
10
  import { PeerNetworking } from './engine_networking_peer.js';
10
11
  import { type IModel, type INetworkConnection, SendQueue } from './engine_networking_types.js';
11
12
  import { isHostedOnGlitch } from './engine_networking_utils.js';
@@ -629,7 +630,7 @@ export class NetworkConnection implements INetworkConnection {
629
630
  return;
630
631
  }
631
632
 
632
- console.debug("Connecting to networking backend on\n" + networkingServerUrl)
633
+ console.debug("Connecting to networking backend on\n" + networkingServerUrl)
633
634
  const pkg = await import('websocket-ts');
634
635
  // @ts-ignore
635
636
  const WebsocketBuilder = pkg.default?.WebsocketBuilder ?? pkg.WebsocketBuilder;
@@ -641,8 +642,8 @@ export class NetworkConnection implements INetworkConnection {
641
642
  this._connectingToWebsocketPromise = null;
642
643
  this._ws = ws;
643
644
  this.connected = true;
644
- if (isDevEnvironment() || debugNet) console.log("Connected to networking backend\n" + networkingServerUrl);
645
- else console.debug("Connected to networking backend", networkingServerUrl);
645
+ if (isDevEnvironment() || debugNet) console.log("Connected to networking backend\n" + networkingServerUrl);
646
+ else console.debug("Connected to networking backend", networkingServerUrl);
646
647
  resolve(true);
647
648
  this.onSendQueued(SendQueue.OnConnection);
648
649
  })
@@ -656,10 +657,13 @@ export class NetworkConnection implements INetworkConnection {
656
657
  console.error(msg);
657
658
  })
658
659
  .onError((_e) => {
659
- console.error("Websocket connection failed...");
660
+ console.error("Websocket connection failed...");
660
661
  resolve(false);
662
+ Telemetry.sendEvent(this.context, "networking", {
663
+ event: "connection_error",
664
+ });
661
665
  })
662
- .onRetry(() => { console.log("Retry connecting to networking websocket") })
666
+ .onRetry(() => { console.log("Retry connecting to networking websocket") })
663
667
  .build();
664
668
  ws.addEventListener(pkg.WebsocketEvent.message, (socket, msg) => {
665
669
  this.onMessage(socket, msg);
@@ -727,6 +731,9 @@ export class NetworkConnection implements INetworkConnection {
727
731
  "server did not send connection id", connection.id);
728
732
  console.debug("Your id is: " + connection.id, this.context.alias ?? "");
729
733
  this._connectionId = connection.id;
734
+ Telemetry.sendEvent(this.context, "networking", {
735
+ event: "connected",
736
+ });
730
737
  }
731
738
  }
732
739
  else console.warn("Expected connection id in " + message.key);
@@ -754,6 +761,10 @@ export class NetworkConnection implements INetworkConnection {
754
761
  }
755
762
 
756
763
  this.onSendQueued(SendQueue.OnRoomJoin);
764
+ Telemetry.sendEvent(this.context, "networking", {
765
+ event: "joined_room",
766
+ room: this._currentRoomName,
767
+ });
757
768
  break;
758
769
 
759
770
  case RoomEvents.LeftRoom:
@@ -765,6 +776,10 @@ export class NetworkConnection implements INetworkConnection {
765
776
  this._currentInRoom.length = 0;
766
777
  if (debugnetBin || isDevEnvironment()) console.debug("Left Needle Engine Room: " + model.room);
767
778
  }
779
+ Telemetry.sendEvent(this.context, "networking", {
780
+ event: "left_room",
781
+ room: model.room,
782
+ });
768
783
  break;
769
784
  case RoomEvents.UserJoinedRoom:
770
785
  if (message.data) {
@@ -1,7 +1,7 @@
1
1
  import { getRaycastMesh } from '@needle-tools/gltf-progressive';
2
2
  import { ArrayCamera, Box3, BufferGeometry, Camera, type Intersection, Layers, Line, Matrix3, Matrix4, Mesh, Object3D, PerspectiveCamera, Plane, Ray, Raycaster, SkinnedMesh, Sphere, SphereGeometry, Vector2, Vector3 } from 'three'
3
3
  import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
4
- import type { MeshBVH, MeshBVHOptions, StaticGeometryGenerator } from 'three-mesh-bvh';
4
+ import type { ComputeBVHOptions, GeometryBVH, MeshBVH, StaticGeometryGenerator } from 'three-mesh-bvh';
5
5
  import type { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
6
6
 
7
7
  import { isDevEnvironment } from './debug/index.js';
@@ -762,7 +762,7 @@ namespace NEMeshBVH {
762
762
  return true;
763
763
  }
764
764
  else if (method instanceof Sphere) {
765
- const bvh = geom.boundsTree;
765
+ const bvh = geom.boundsTree as MeshBVH | undefined;
766
766
  if (bvh) {
767
767
  const sphere = method;
768
768
  // Gizmos.DrawWireSphere(sphere.center, sphere.radius, 0xdddd00, 1, false);
@@ -789,7 +789,7 @@ namespace NEMeshBVH {
789
789
  let _acceleratedRaycast: Function | null = null;
790
790
  let _MeshBVH: ConstructorConcrete<MeshBVH> | null = null;
791
791
  let _StaticGeometryGenerator: ConstructorConcrete<StaticGeometryGenerator> | null = null;
792
- let _computeBoundsTree: ((_opt?: MeshBVHOptions) => MeshBVH) | null = null;
792
+ let _computeBoundsTree: (( options?: ComputeBVHOptions ) => GeometryBVH) | null = null;
793
793
 
794
794
  function loadMeshBVHLibrary() {
795
795
  if (didLoadMeshBVHLibrary) return;
@@ -273,7 +273,7 @@ export class RapierPhysics implements IPhysicsEngine {
273
273
  if (MODULES.RAPIER_PHYSICS.MAYBEMODULE == undefined) {
274
274
  if (debugPhysics) console.trace("Loading rapier physics engine");
275
275
  const module = await MODULES.RAPIER_PHYSICS.load();
276
- // https://github.com/dimforge/rapier/issues/811
276
+ // https://github.com/dimforge/rapier/issues/811 < but single object parameter still warns
277
277
  await module.init();
278
278
  }
279
279
  if (debugPhysics) console.log("Physics engine initialized, creating world...");
@@ -337,7 +337,7 @@ class EventListSerializer extends TypeSerializer {
337
337
  args = call.arguments.map(deserializeArgument);
338
338
  }
339
339
  const method = target[call.method];
340
- if (!method) {
340
+ if (method === undefined) {
341
341
  console.warn(`EventList method not found: \"${call.method}\" on ${target?.name}`);
342
342
  }
343
343
  else {
@@ -242,8 +242,10 @@ export function setWorldPosition(obj: Object3D, val: Vector3): Object3D {
242
242
  const wp = _worldPositions.get();
243
243
  if (val !== wp)
244
244
  wp.copy(val);
245
- const obj2 = obj?.parent ?? obj;
246
- obj2.worldToLocal(wp);
245
+
246
+ if (obj.parent !== null)
247
+ obj.parent.worldToLocal(wp);
248
+
247
249
  obj.position.set(wp.x, wp.y, wp.z);
248
250
  return obj;
249
251
  }
@@ -606,7 +606,8 @@ export namespace DeviceUtilities {
606
606
  /** @returns `true` if we're currently on an iPad */
607
607
  export function isiPad() {
608
608
  if (__isiPad !== undefined) return __isiPad;
609
- return __isiPad = /iPad/.test(navigator.userAgent);
609
+ const userAgent = navigator.userAgent.toLowerCase();
610
+ return __isiPad = /iPad/.test(navigator.userAgent) || userAgent.includes("macintosh") && "ontouchend" in document;
610
611
  }
611
612
 
612
613
  let __isAndroidDevice: boolean | undefined;
@@ -623,17 +624,25 @@ export namespace DeviceUtilities {
623
624
  return __isMozillaXR = /WebXRViewer\//i.test(navigator.userAgent);
624
625
  }
625
626
 
627
+ let __isNeedleAppClip: boolean | undefined;
628
+ /** @returns `true` if we're currently in the Needle App Clip */
629
+ export function isNeedleAppClip() {
630
+ if (__isNeedleAppClip !== undefined) return __isNeedleAppClip;
631
+ return __isNeedleAppClip = /NeedleAppClip\//i.test(navigator.userAgent);
632
+ }
633
+
626
634
  let __isMacOS: boolean | undefined;
627
635
  // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
628
636
  /** @returns `true` for MacOS devices */
629
637
  export function isMacOS() {
630
638
  if (__isMacOS !== undefined) return __isMacOS;
639
+ if (isiOS() || isiPad()) return __isMacOS = false;
640
+ const userAgent = navigator.userAgent.toLowerCase();
631
641
  if (navigator.userAgentData) {
632
642
  // Use modern UA Client Hints API if available
633
643
  return __isMacOS = navigator.userAgentData.platform === 'macOS';
634
644
  } else {
635
645
  // Fallback to user agent string parsing
636
- const userAgent = navigator.userAgent.toLowerCase();
637
646
  return __isMacOS = userAgent.includes('mac os x') || userAgent.includes('macintosh');
638
647
  }
639
648
  }
@@ -642,13 +651,13 @@ export namespace DeviceUtilities {
642
651
  /** @returns `true` for VisionOS devices */
643
652
  export function isVisionOS() {
644
653
  if (__isVisionOS !== undefined) return __isVisionOS;
645
- return __isVisionOS = (isMacOS() && "xr" in navigator);
654
+ return __isVisionOS = (isiPad() && "xr" in navigator && supportsQuickLookAR());
646
655
  }
647
656
 
648
657
  let __isiOS: boolean | undefined;
649
658
  const iosDevices = ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'];
650
659
 
651
- /** @returns `true` for iOS devices like iPad, iPhone, iPod... */
660
+ /** @returns `true` for mobile Apple devices like iPad, iPhone, iPod, Vision Pro, ... */
652
661
  export function isiOS() {
653
662
  if (__isiOS !== undefined) return __isiOS;
654
663
  // eslint-disable-next-line deprecation/deprecation
@@ -725,6 +734,16 @@ export namespace DeviceUtilities {
725
734
  else __chromeVersion = null;
726
735
  return __chromeVersion;
727
736
  }
737
+
738
+ let __safariVersion: string | null | undefined;
739
+ export function getSafariVersion() {
740
+ if (__safariVersion !== undefined) return __safariVersion;
741
+ const match = navigator.userAgent.match(/Version\/(\d+\.\d+)/);
742
+ if (match && isSafari()) {
743
+ __safariVersion = match[1];
744
+ } else __safariVersion = null;
745
+ return __safariVersion;
746
+ }
728
747
  }
729
748
 
730
749
  /**
@@ -30,20 +30,44 @@ const KHR_ANIMATIONPOINTER_IMPORT = import("@needle-tools/three-animation-pointe
30
30
  });
31
31
 
32
32
 
33
- declare type OnImportCallback = (loader: GLTFLoader, url: string, context: Context) => void;
34
- declare type OnExportCallback = (exporter: GLTFExporter, context: Context) => void;
33
+ /**
34
+ * Callback type for glTF import plugins. See {@link INeedleGLTFExtensionPlugin.onImport}
35
+ */
36
+ export type OnImportCallback = (loader: GLTFLoader, url: string, context: Context) => void;
37
+
38
+ /**
39
+ * Callback type for glTF export plugins. See {@link INeedleGLTFExtensionPlugin.onExport}
40
+ */
41
+ export type OnExportCallback = (exporter: GLTFExporter, context: Context) => void;
35
42
 
36
43
  /**
37
- * Interface for registering custom glTF extensions to the Needle Engine GLTFLoaders. Register your plugin via {@link addCustomExtensionPlugin}
44
+ * Interface for registering custom glTF extensions to the Needle Engine GLTFLoaders.
45
+ * Register your plugin using the {@link addCustomExtensionPlugin} method
38
46
  */
39
47
  export interface INeedleGLTFExtensionPlugin {
40
48
  /** The Name of your plugin */
41
49
  name: string;
42
- /** Called before starting to load a glTF file. This callback can be used to add custom extensions to the GLTFLoader */
50
+ /** Called before starting to load a glTF file. This callback can be used to add custom extensions to the [GLTFLoader](https://threejs.org/docs/#GLTFLoader.register)
51
+ *
52
+ * @example Add a custom extension to the GLTFloader
53
+ * ```ts
54
+ * onImport: (loader, url, context) => {
55
+ * loader.register((parser) => new MyCustomExtension(parser));
56
+ * }
57
+ * ```
58
+ */
43
59
  onImport?: OnImportCallback;
44
- /** Called after the glTF has been loaded */
60
+ /** Called after a glTF file has been loaded */
45
61
  onLoaded?: (url: string, gltf: GLTF, context: Context) => void;
46
- /** Called before starting to export a glTF file. This callback can be used to add custom extensions to the GLTFExporter */
62
+ /** Called before starting to export a glTF file. This callback can be used to add custom extensions to the [GLTFExporter](https://threejs.org/docs/#examples/en/exporters/GLTFExporter.register)
63
+ *
64
+ * @example Add a custom extension to the GLTFExporter
65
+ * ```ts
66
+ * onExport: (exporter, context) => {
67
+ * exporter.register((writer) => new MyCustomExportExtension(writer));
68
+ * }
69
+ *
70
+ */
47
71
  onExport?: OnExportCallback;
48
72
  }
49
73
 
@@ -57,13 +57,16 @@ export class WebXRButtonFactory {
57
57
  this._quicklookButton = button;
58
58
  button.dataset["needle"] = "quicklook-button";
59
59
  const supportsQuickLook = DeviceUtilities.supportsQuickLookAR();
60
+ let buttonText = "View in AR";
60
61
  // we can immediately enter this scene, because the platform supports rel="ar" links
61
- if (supportsQuickLook) {
62
- button.innerText = "View in AR";
62
+ if (DeviceUtilities.isVisionOS()) {
63
+ buttonText = "View in AR";
63
64
  }
64
- else {
65
- button.innerText = "View in AR";
65
+ else if (supportsQuickLook || DeviceUtilities.isiOS()) {
66
+ buttonText = "Open in Quicklook";
66
67
  }
68
+
69
+ button.innerText = buttonText;
67
70
  button.prepend(getIconElement("view_in_ar"));
68
71
 
69
72
  let createdExporter = false;
@@ -209,13 +212,20 @@ export class WebXRButtonFactory {
209
212
  }
210
213
 
211
214
  private updateSessionSupported(button: HTMLButtonElement, mode: XRSessionMode) {
215
+ if (mode === "immersive-ar" && DeviceUtilities.isiOS() && !DeviceUtilities.isVisionOS()) {
216
+ // on iOS, we can forward to the AppClip experience from the same button,
217
+ // so we always show the button. No AppClip support on VisionOS for now, so
218
+ // button state depends on WebXR support there.
219
+ return;
220
+ }
221
+
212
222
  if (!("xr" in navigator)) {
213
223
  button.style.display = "none";
214
224
  return;
215
225
  }
216
226
  NeedleXRSession.isSessionSupported(mode).then(supported => {
217
227
  button.style.display = !supported ? "none" : "";
218
- if (isDevEnvironment() && !supported) console.log("[WebXR] \"" + mode + "\" is not supported on this device make sure your server runs using HTTPS and you have a device connected that supports " + mode);
228
+ if (isDevEnvironment() && !supported) console.log("[WebXR] \"" + mode + "\" is not supported on this device. Make sure your server runs using HTTPS and you have a device connected that supports " + mode);
219
229
  });
220
230
  }
221
231
 
@@ -104,7 +104,8 @@ export class NeedleSpatialMenu {
104
104
  }
105
105
 
106
106
  const xr = this._context.xr;
107
- if (!xr?.running) {
107
+ const isImmersiveXR = xr?.running && (xr?.isPassThrough || xr?.isVR)
108
+ if (!isImmersiveXR) {
108
109
  if (this._wasInXR) {
109
110
  this._wasInXR = false;
110
111
  this.onExitXR();
@@ -1,5 +1,5 @@
1
1
  import type { Context } from "../../engine_context.js";
2
- import { hasCommercialLicense, onLicenseCheckResultChanged } from "../../engine_license.js";
2
+ import { hasCommercialLicense, onLicenseCheckResultChanged, Telemetry } from "../../engine_license.js";
3
3
  import { isLocalNetwork } from "../../engine_networking_utils.js";
4
4
  import { DeviceUtilities, getParam } from "../../engine_utils.js";
5
5
  import { onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
@@ -149,6 +149,9 @@ export class NeedleMenu {
149
149
  else console.error("NeedleMenu: onclick is not a valid link", buttoninfo.onclick);
150
150
  }
151
151
  }
152
+ Telemetry.sendEvent(this._context, "needle-menu", {
153
+ action: "button_added_via_postmessage",
154
+ });
152
155
  this._menu.appendChild(button);
153
156
  }
154
157
  else if (debug) console.error("NeedleMenu: unknown postMessage event", data);
@@ -601,16 +604,15 @@ export class NeedleMenuElement extends HTMLElement {
601
604
  bottom: calc(100% + 5px);
602
605
  z-index: 100;
603
606
  width: auto;
604
- left: .2rem;
605
- right: .2rem;
606
- padding: .2rem;
607
+ max-width: 90vw;
608
+ left: 50%;
609
+ transform: translateX(-50%);
610
+ padding: .2rem 1em;
607
611
 
608
612
  }
609
613
  .compact.logo-hidden .foldout {
610
614
  /** for when there's no logo we want to center the foldout **/
611
615
  min-width: 24ch;
612
- margin-left: 50%;
613
- transform: translateX(calc(-50% - .2rem));
614
616
  }
615
617
 
616
618
  .compact.top .foldout {
@@ -936,12 +938,39 @@ export class NeedleMenuElement extends HTMLElement {
936
938
  /** @private foldout container used in compact mode */
937
939
  private readonly foldout: HTMLDivElement;
938
940
 
941
+
942
+ private readonly trackedElements: WeakSet<Node> = new WeakSet();
943
+ private trackElement(el: Node) {
944
+ if (this.trackedElements.has(el)) return;
945
+ this.trackedElements.add(el);
946
+ el.addEventListener("click", (evt) => {
947
+ Telemetry.sendEvent(this._context, "needle-menu", {
948
+ action: "button_clicked",
949
+ element: evt.target instanceof Node ? evt.target.nodeName : el.nodeName,
950
+ label: el.textContent,
951
+ title: (el instanceof HTMLElement) ? el.title : undefined,
952
+ pointerid: (evt instanceof PointerEvent) ? evt.pointerId : undefined,
953
+ });
954
+ });
955
+ // el.addEventListener("pointerenter", (evt) => {
956
+ // Telemetry.sendEvent(this._context, "needle-menu", {
957
+ // action: "button_hovered",
958
+ // element: evt.target instanceof Node ? evt.target.nodeName : el.nodeName,
959
+ // label: el.textContent,
960
+ // title: (el instanceof HTMLElement) ? el.title : undefined,
961
+ // pointerid: (evt instanceof PointerEvent) ? evt.pointerId : undefined,
962
+ // });
963
+ // });
964
+ }
965
+
939
966
  append(...nodes: (string | Node)[]): void {
940
967
  for (const node of nodes) {
941
968
  if (typeof node === "string") {
942
969
  const element = document.createTextNode(node);
970
+ this.trackElement(element);
943
971
  this.options.appendChild(element);
944
972
  } else {
973
+ this.trackElement(node);
945
974
  this.options.appendChild(node);
946
975
  }
947
976
  }
@@ -988,9 +1017,10 @@ export class NeedleMenuElement extends HTMLElement {
988
1017
  if (node.class) {
989
1018
  button.classList.add(node.class);
990
1019
  }
1020
+
991
1021
  node = button as unknown as T;
992
1022
  }
993
-
1023
+ this.trackElement(node);
994
1024
  const res = this.options.appendChild(node);
995
1025
  return res;
996
1026
  }
@@ -998,8 +1028,10 @@ export class NeedleMenuElement extends HTMLElement {
998
1028
  for (const node of nodes) {
999
1029
  if (typeof node === "string") {
1000
1030
  const element = document.createTextNode(node);
1031
+ this.trackElement(element);
1001
1032
  this.options.prepend(element);
1002
1033
  } else {
1034
+ this.trackElement(node);
1003
1035
  this.options.prepend(node);
1004
1036
  }
1005
1037
  }