@needle-tools/engine 4.12.0-next.5d44f6c → 4.12.0-next.99c3b94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,12 @@
1
+ import { dof } from "three/src/nodes/TSL.js";
1
2
  import { isDevEnvironment } from "./debug/index.js";
2
3
  import { BUILD_TIME, GENERATOR, PUBLIC_KEY, VERSION } from "./engine_constants.js";
3
4
  import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
5
+ import { onInitialized } from "./engine_lifecycle_api.js";
4
6
  import { Context } from "./engine_setup.js";
5
7
  import type { IContext } from "./engine_types.js";
6
8
  import { getParam } from "./engine_utils.js";
9
+ import { InternalAttributeUtils } from "./engine_utils_attributes.js";
7
10
 
8
11
  const debug = getParam("debuglicense");
9
12
 
@@ -67,6 +70,166 @@ function invokeLicenseCheckResultChanged(result: boolean) {
67
70
  }
68
71
  }
69
72
 
73
+
74
+
75
+ // #region Telemetry
76
+ export namespace Telemetry {
77
+
78
+ window.addEventListener("error", (event: ErrorEvent) => {
79
+ sendError(Context.Current, "unhandled_error", event);
80
+ });
81
+ window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
82
+ sendError(Context.Current, "unhandled_promise_rejection", {
83
+ message: event.reason?.message,
84
+ stack: event.reason?.stack,
85
+ timestamp: Date.now(),
86
+ });
87
+ });
88
+
89
+ onInitialized((ctx => sendPageViewEvent(ctx)), { once: true });
90
+
91
+ function sendPageViewEvent(ctx: IContext) {
92
+ if (!isAllowed(ctx)) {
93
+ if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
94
+ return;
95
+ }
96
+ return doFetch({
97
+ site_id: "dabb8317376f",
98
+ type: "pageview",
99
+ pathname: window.location.pathname,
100
+ hostname: window.location.hostname,
101
+ page_title: document.title,
102
+ referrer: document.referrer,
103
+ user_agent: navigator.userAgent,
104
+ querystring: window.location.search,
105
+ language: navigator.language,
106
+ screenWidth: window.screen.width,
107
+ screenHeight: window.screen.height,
108
+ event_name: "page_view",
109
+ properties: JSON.stringify({
110
+ version: VERSION,
111
+ generator: GENERATOR,
112
+ build_time: BUILD_TIME,
113
+ public_key: PUBLIC_KEY,
114
+ src: ctx.domElement?.getAttribute("src") || "",
115
+ })
116
+ })
117
+ }
118
+
119
+ export function isAllowed(context: IContext | null | undefined): boolean {
120
+ let domElement = context?.domElement as HTMLElement | null;
121
+ if (!domElement) domElement = document.querySelector<HTMLElement>("needle-engine");
122
+ if (!domElement && !context) return false;
123
+
124
+ const attribute = domElement?.getAttribute("no-telemetry");
125
+ if (attribute === "" || attribute === "true" || attribute === "1") {
126
+ if (NEEDLE_ENGINE_LICENSE_TYPE === "pro" || NEEDLE_ENGINE_LICENSE_TYPE === "enterprise") {
127
+ if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
128
+ return false;
129
+ }
130
+ }
131
+ return true;
132
+ }
133
+
134
+ const id = "dabb8317376f";
135
+
136
+ /**
137
+ * Sends a telemetry event
138
+ */
139
+ export async function sendEvent(context: IContext | null | undefined, eventName: string, properties?: Record<string, any>) {
140
+ if (!isAllowed(context)) {
141
+ if (debug) console.debug("Telemetry is disabled");
142
+ return;
143
+ }
144
+ const body = {
145
+ site_id: id,
146
+ type: "custom_event",
147
+ pathname: window.location.pathname,
148
+ event_name: eventName,
149
+ properties: properties ? JSON.stringify(properties) : undefined,
150
+ }
151
+ return doFetch(body);
152
+ }
153
+
154
+ type ErrorData = {
155
+ message?: string;
156
+ stack?: string;
157
+ filename?: string;
158
+ lineno?: number;
159
+ colno?: number;
160
+ timestamp?: number;
161
+ }
162
+
163
+ export async function sendError(context: IContext, errorName: string, error: ErrorData | ErrorEvent | Error) {
164
+
165
+ if (!isAllowed(context)) {
166
+ if (debug) console.debug("Telemetry is disabled");
167
+ return;
168
+ }
169
+
170
+ if (error instanceof ErrorEvent) {
171
+ error = {
172
+ message: error.message,
173
+ stack: error.error?.stack,
174
+ filename: error.filename,
175
+ lineno: error.lineno,
176
+ colno: error.colno,
177
+ timestamp: error.timeStamp || Date.now(),
178
+
179
+ };
180
+ }
181
+ else if (error instanceof Error) {
182
+ error = {
183
+ message: error.message,
184
+ stack: error.stack,
185
+ timestamp: Date.now(),
186
+ };
187
+ }
188
+ const body = {
189
+ site_id: id,
190
+ type: "error",
191
+ event_name: errorName || "error",
192
+ properties: JSON.stringify({
193
+ error_name: errorName,
194
+ message: error.message,
195
+ stack: error.stack,
196
+ filename: error.filename,
197
+ lineno: error.lineno,
198
+ colno: error.colno,
199
+ timestamp: error.timestamp,
200
+ })
201
+ }
202
+ return doFetch(body);
203
+ }
204
+
205
+ function doFetch(body: Record<string, any>) {
206
+ try {
207
+ const url = "https://needle.tools/api/v1/rum/t";
208
+ return fetch(url, {
209
+ method: "POST",
210
+ body: JSON.stringify(body),
211
+ headers: {
212
+ 'Content-Type': 'application/json'
213
+ },
214
+ // Ensures request completes even if page unloads
215
+ keepalive: true,
216
+ // Allow CORS requests
217
+ mode: 'cors',
218
+ // Low priority to avoid blocking other requests
219
+ // @ts-ignore
220
+ priority: 'low',
221
+ }).catch(e => {
222
+ if (debug) console.error("Failed to send telemetry", e);
223
+ })
224
+ }
225
+ catch (err) {
226
+ if (debug) console.error(err);
227
+ }
228
+ return Promise.resolve();
229
+ }
230
+ }
231
+
232
+
70
233
  ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
71
234
  showLicenseInfo(evt.context);
72
235
  handleForbidden(evt.context);
@@ -341,14 +504,9 @@ async function sendUsageMessageToAnalyticsBackend(context: IContext) {
341
504
  // We can't send beacons from cross-origin isolated pages
342
505
  if (window.crossOriginIsolated) return;
343
506
 
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);
507
+ if (!Telemetry.isAllowed(context)) {
508
+ if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
509
+ return;
352
510
  }
353
511
 
354
512
  try {
@@ -379,4 +537,4 @@ async function sendUsageMessageToAnalyticsBackend(context: IContext) {
379
537
  if (debug)
380
538
  console.log("Failed to send non-commercial usage message to analytics backend", err);
381
539
  }
382
- }
540
+ }
@@ -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 {
@@ -6,6 +6,7 @@ import { Context, FrameEvent } from "../engine_context.js";
6
6
  import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
7
7
  import { isDestroyed } from "../engine_gameobject.js";
8
8
  import { Gizmos } from "../engine_gizmos.js";
9
+ import { Telemetry } from "../engine_license.js";
9
10
  import { registerFrameEventCallback, unregisterFrameEventCallback } from "../engine_lifecycle_functions_internal.js";
10
11
  import { getBoundingBox, getTempQuaternion, getTempVector, getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../engine_three_utils.js";
11
12
  import type { ICamera, IComponent, INeedleXRSession } from "../engine_types.js";
@@ -98,12 +99,12 @@ async function handleSessionGranted() {
98
99
  }
99
100
  // Check if AR is supported, otherwise we can't do anything
100
101
  if (!(await navigator.xr?.isSessionSupported("immersive-ar")) && defaultMode === "immersive-ar") {
101
- console.warn("[NeedleXRSession:granted] Neither VR nor AR supported, aborting session start.");
102
+ // console.warn("[NeedleXRSession:granted] Neither VR nor AR supported, aborting session start.");
102
103
  // showBalloonMessage("NeidleXRSession: Neither VR nor AR supported, aborting session start.");
103
104
  return;
104
105
  }
105
106
  } catch (e) {
106
- console.error("[NeedleXRSession:granted] Error while checking XR support:", e);
107
+ console.debug("[NeedleXRSession:granted] Error while checking XR support:", e);
107
108
  // showBalloonWarning("NeedleXRSession: Error while checking XR support: " + (e as Error).message);
108
109
  return;
109
110
  }
@@ -148,6 +149,7 @@ async function handleSessionGranted() {
148
149
  navigator.xr?.addEventListener('sessiongranted', async () => {
149
150
  // enableSpatialConsole(true);
150
151
 
152
+
151
153
  const lastSessionMode = sessionStorage.getItem("needle_xr_session_mode") as XRSessionMode;
152
154
  const lastSessionInit = sessionStorage.getItem("needle_xr_session_init") ?? null;
153
155
  const init = lastSessionInit ? JSON.parse(lastSessionInit) : null;
@@ -466,6 +468,10 @@ export class NeedleXRSession implements INeedleXRSession {
466
468
  }
467
469
 
468
470
  if (mode === "quicklook") {
471
+ Telemetry.sendEvent(Context.Current, "xr", {
472
+ action: "quicklook_export",
473
+ source: "NeedleXRSession.start",
474
+ });
469
475
  InternalUSDZRegistry.exportAndOpen();
470
476
  return null;
471
477
  }
@@ -478,6 +484,13 @@ export class NeedleXRSession implements INeedleXRSession {
478
484
  url.searchParams.set("url", location.href);
479
485
 
480
486
  const urlStr = url.toString();
487
+
488
+ Telemetry.sendEvent(Context.Current, "xr", {
489
+ action: "app_clip_launch",
490
+ source: "NeedleXRSession.start",
491
+ url: urlStr,
492
+ });
493
+
481
494
  // if we are in an iframe, we need to navigate the top window
482
495
  const topWindow = window.top || window;
483
496
  try {
@@ -606,6 +619,12 @@ export class NeedleXRSession implements INeedleXRSession {
606
619
  listener({ mode, init });
607
620
  }
608
621
  if (debug) showBalloonMessage("Requesting " + mode + " session (" + Date.now() + ")");
622
+ Telemetry.sendEvent(Context.Current, "xr", {
623
+ action: "session_request",
624
+ mode: mode,
625
+ features: ((init.requiredFeatures ?? []).concat(init.optionalFeatures ?? [])).join(","),
626
+ source: "NeedleXRSession.start",
627
+ });
609
628
  this._currentSessionRequest = navigator?.xr?.requestSession(mode, init);
610
629
  this._currentSessionRequestMode = mode;
611
630
  /**@type {XRSystem} */
@@ -1182,6 +1201,12 @@ export class NeedleXRSession implements INeedleXRSession {
1182
1201
 
1183
1202
  console.debug("XR Session ended");
1184
1203
 
1204
+ Telemetry.sendEvent(Context.Current, "xr", {
1205
+ action: "session_end",
1206
+ mode: this.mode,
1207
+ source: "NeedleXRSession.onEnd",
1208
+ });
1209
+
1185
1210
  deleteSessionInfo();
1186
1211
 
1187
1212
  this.onAfterRender();
@@ -447,7 +447,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
447
447
  if (!this || !this._isDragging) return;
448
448
  this._isDragging = false;
449
449
  for (const rb of this._draggingRigidbodies) {
450
- rb.setVelocity(rb.smoothedVelocity);
450
+ rb.setVelocity(rb.smoothedVelocity.multiplyScalar(this.context.time.deltaTime));
451
451
  }
452
452
  this._draggingRigidbodies.length = 0;
453
453
  this._targetObject = null;