@dash0/sdk-web 0.6.2

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 (194) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +496 -0
  3. package/dist/dash0.iife.js +2 -0
  4. package/dist/dash0.iife.js.map +1 -0
  5. package/dist/dash0.js +2 -0
  6. package/dist/dash0.js.map +1 -0
  7. package/dist/dash0.umd.cjs +2 -0
  8. package/dist/dash0.umd.cjs.map +1 -0
  9. package/dist/modules/api/attributes.js +16 -0
  10. package/dist/modules/api/debug.js +5 -0
  11. package/dist/modules/api/events.js +30 -0
  12. package/dist/modules/api/identify.js +29 -0
  13. package/dist/modules/api/init.js +130 -0
  14. package/dist/modules/api/report-error.js +4 -0
  15. package/dist/modules/api/session.js +88 -0
  16. package/dist/modules/attributes/common.js +26 -0
  17. package/dist/modules/attributes/index.js +2 -0
  18. package/dist/modules/attributes/url.js +25 -0
  19. package/dist/modules/entrypoint/npm-package.js +12 -0
  20. package/dist/modules/entrypoint/script.js +60 -0
  21. package/dist/modules/instrumentations/errors/async-function-wrapping.js +105 -0
  22. package/dist/modules/instrumentations/errors/event-handlers.js +52 -0
  23. package/dist/modules/instrumentations/errors/index.js +10 -0
  24. package/dist/modules/instrumentations/errors/timers.js +46 -0
  25. package/dist/modules/instrumentations/errors/unhandled-error.js +137 -0
  26. package/dist/modules/instrumentations/errors/unhandled-promise-rejection.js +29 -0
  27. package/dist/modules/instrumentations/http/fetch.js +164 -0
  28. package/dist/modules/instrumentations/http/utils.js +35 -0
  29. package/dist/modules/instrumentations/navigation/event.js +44 -0
  30. package/dist/modules/instrumentations/navigation/index.js +6 -0
  31. package/dist/modules/instrumentations/navigation/page-load.js +83 -0
  32. package/dist/modules/instrumentations/navigation/page-transition.js +72 -0
  33. package/dist/modules/instrumentations/web-vitals.js +32 -0
  34. package/dist/modules/semantic-conventions.js +79 -0
  35. package/dist/modules/transport/batcher.js +48 -0
  36. package/dist/modules/transport/fetch.js +58 -0
  37. package/dist/modules/transport/index.js +66 -0
  38. package/dist/modules/utils/constants.js +2 -0
  39. package/dist/modules/utils/debug.js +19 -0
  40. package/dist/modules/utils/fn.js +3 -0
  41. package/dist/modules/utils/globals.js +36 -0
  42. package/dist/modules/utils/id.js +16 -0
  43. package/dist/modules/utils/id_test.js +33 -0
  44. package/dist/modules/utils/ignore-rules.js +48 -0
  45. package/dist/modules/utils/index.js +16 -0
  46. package/dist/modules/utils/listeners.js +18 -0
  47. package/dist/modules/utils/local-storage.js +19 -0
  48. package/dist/modules/utils/math.js +3 -0
  49. package/dist/modules/utils/obj.js +10 -0
  50. package/dist/modules/utils/on-last-chance.js +31 -0
  51. package/dist/modules/utils/origin.js +14 -0
  52. package/dist/modules/utils/otel/attributes.js +55 -0
  53. package/dist/modules/utils/otel/attributes_test.js +158 -0
  54. package/dist/modules/utils/otel/error.js +30 -0
  55. package/dist/modules/utils/otel/http.js +7 -0
  56. package/dist/modules/utils/otel/index.js +4 -0
  57. package/dist/modules/utils/otel/span.js +56 -0
  58. package/dist/modules/utils/otel/trace-context.js +60 -0
  59. package/dist/modules/utils/performance.js +162 -0
  60. package/dist/modules/utils/pick.js +17 -0
  61. package/dist/modules/utils/pick_test.js +104 -0
  62. package/dist/modules/utils/rate-limit.js +20 -0
  63. package/dist/modules/utils/session-storage.js +21 -0
  64. package/dist/modules/utils/tab-id.js +29 -0
  65. package/dist/modules/utils/time.js +28 -0
  66. package/dist/modules/utils/timers.js +62 -0
  67. package/dist/modules/utils/url.js +11 -0
  68. package/dist/modules/utils/wrap.js +22 -0
  69. package/dist/modules/vars.js +25 -0
  70. package/dist/tsconfig.tsbuildinfo +1 -0
  71. package/dist/types/api/attributes.d.ts +12 -0
  72. package/dist/types/api/debug.d.ts +1 -0
  73. package/dist/types/api/events.d.ts +36 -0
  74. package/dist/types/api/identify.d.ts +9 -0
  75. package/dist/types/api/init.d.ts +31 -0
  76. package/dist/types/api/report-error.d.ts +2 -0
  77. package/dist/types/api/session.d.ts +3 -0
  78. package/dist/types/attributes/common.d.ts +6 -0
  79. package/dist/types/attributes/index.d.ts +2 -0
  80. package/dist/types/attributes/url.d.ts +2 -0
  81. package/dist/types/entrypoint/npm-package.d.ts +8 -0
  82. package/dist/types/entrypoint/script.d.ts +1 -0
  83. package/dist/types/instrumentations/errors/async-function-wrapping.d.ts +8 -0
  84. package/dist/types/instrumentations/errors/event-handlers.d.ts +1 -0
  85. package/dist/types/instrumentations/errors/index.d.ts +1 -0
  86. package/dist/types/instrumentations/errors/timers.d.ts +1 -0
  87. package/dist/types/instrumentations/errors/unhandled-error.d.ts +5 -0
  88. package/dist/types/instrumentations/errors/unhandled-promise-rejection.d.ts +2 -0
  89. package/dist/types/instrumentations/http/fetch.d.ts +1 -0
  90. package/dist/types/instrumentations/http/utils.d.ts +5 -0
  91. package/dist/types/instrumentations/navigation/event.d.ts +1 -0
  92. package/dist/types/instrumentations/navigation/index.d.ts +1 -0
  93. package/dist/types/instrumentations/navigation/page-load.d.ts +7 -0
  94. package/dist/types/instrumentations/navigation/page-transition.d.ts +7 -0
  95. package/dist/types/instrumentations/web-vitals.d.ts +1 -0
  96. package/dist/types/semantic-conventions.d.ts +68 -0
  97. package/dist/types/transport/batcher.d.ts +4 -0
  98. package/dist/types/transport/fetch.d.ts +1 -0
  99. package/dist/types/transport/index.d.ts +3 -0
  100. package/dist/types/utils/constants.d.ts +2 -0
  101. package/dist/types/utils/debug.d.ts +7 -0
  102. package/dist/types/utils/fn.d.ts +1 -0
  103. package/dist/types/utils/globals.d.ts +14 -0
  104. package/dist/types/utils/id.d.ts +6 -0
  105. package/dist/types/utils/id_test.d.ts +1 -0
  106. package/dist/types/utils/ignore-rules.d.ts +3 -0
  107. package/dist/types/utils/index.d.ts +16 -0
  108. package/dist/types/utils/listeners.d.ts +2 -0
  109. package/dist/types/utils/local-storage.d.ts +4 -0
  110. package/dist/types/utils/math.d.ts +1 -0
  111. package/dist/types/utils/obj.d.ts +5 -0
  112. package/dist/types/utils/on-last-chance.d.ts +5 -0
  113. package/dist/types/utils/origin.d.ts +1 -0
  114. package/dist/types/utils/otel/attributes.d.ts +9 -0
  115. package/dist/types/utils/otel/attributes_test.d.ts +1 -0
  116. package/dist/types/utils/otel/error.d.ts +24 -0
  117. package/dist/types/utils/otel/http.d.ts +2 -0
  118. package/dist/types/utils/otel/index.d.ts +4 -0
  119. package/dist/types/utils/otel/span.d.ts +10 -0
  120. package/dist/types/utils/otel/trace-context.d.ts +8 -0
  121. package/dist/types/utils/performance.d.ts +45 -0
  122. package/dist/types/utils/pick.d.ts +9 -0
  123. package/dist/types/utils/pick_test.d.ts +1 -0
  124. package/dist/types/utils/rate-limit.d.ts +5 -0
  125. package/dist/types/utils/session-storage.d.ts +4 -0
  126. package/dist/types/utils/tab-id.d.ts +8 -0
  127. package/dist/types/utils/time.d.ts +6 -0
  128. package/dist/types/utils/timers.d.ts +6 -0
  129. package/dist/types/utils/url.d.ts +6 -0
  130. package/dist/types/utils/wrap.d.ts +1 -0
  131. package/dist/types/vars.d.ts +123 -0
  132. package/package.json +124 -0
  133. package/src/api/attributes.ts +19 -0
  134. package/src/api/debug.ts +6 -0
  135. package/src/api/events.ts +67 -0
  136. package/src/api/identify.ts +43 -0
  137. package/src/api/init.ts +221 -0
  138. package/src/api/report-error.ts +6 -0
  139. package/src/api/session.ts +113 -0
  140. package/src/attributes/common.ts +42 -0
  141. package/src/attributes/index.ts +2 -0
  142. package/src/attributes/url.ts +25 -0
  143. package/src/entrypoint/npm-package.ts +14 -0
  144. package/src/entrypoint/script-tag.html +23 -0
  145. package/src/entrypoint/script.ts +82 -0
  146. package/src/instrumentations/errors/async-function-wrapping.ts +157 -0
  147. package/src/instrumentations/errors/event-handlers.ts +77 -0
  148. package/src/instrumentations/errors/index.ts +11 -0
  149. package/src/instrumentations/errors/timers.ts +52 -0
  150. package/src/instrumentations/errors/unhandled-error.ts +187 -0
  151. package/src/instrumentations/errors/unhandled-promise-rejection.ts +30 -0
  152. package/src/instrumentations/http/fetch.ts +194 -0
  153. package/src/instrumentations/http/utils.ts +49 -0
  154. package/src/instrumentations/navigation/event.ts +66 -0
  155. package/src/instrumentations/navigation/index.ts +7 -0
  156. package/src/instrumentations/navigation/page-load.ts +96 -0
  157. package/src/instrumentations/navigation/page-transition.ts +100 -0
  158. package/src/instrumentations/web-vitals.ts +37 -0
  159. package/src/semantic-conventions.ts +91 -0
  160. package/src/transport/batcher.ts +59 -0
  161. package/src/transport/fetch.ts +72 -0
  162. package/src/transport/index.ts +77 -0
  163. package/src/utils/constants.ts +3 -0
  164. package/src/utils/debug.ts +25 -0
  165. package/src/utils/fn.ts +3 -0
  166. package/src/utils/globals.ts +42 -0
  167. package/src/utils/id.ts +17 -0
  168. package/src/utils/id_test.ts +41 -0
  169. package/src/utils/ignore-rules.ts +59 -0
  170. package/src/utils/index.ts +16 -0
  171. package/src/utils/listeners.ts +18 -0
  172. package/src/utils/local-storage.ts +25 -0
  173. package/src/utils/math.ts +3 -0
  174. package/src/utils/obj.ts +12 -0
  175. package/src/utils/on-last-chance.ts +36 -0
  176. package/src/utils/origin.ts +15 -0
  177. package/src/utils/otel/attributes.ts +67 -0
  178. package/src/utils/otel/attributes_test.ts +181 -0
  179. package/src/utils/otel/error.ts +63 -0
  180. package/src/utils/otel/http.ts +9 -0
  181. package/src/utils/otel/index.ts +4 -0
  182. package/src/utils/otel/span.ts +70 -0
  183. package/src/utils/otel/trace-context.ts +84 -0
  184. package/src/utils/performance.ts +203 -0
  185. package/src/utils/pick.ts +19 -0
  186. package/src/utils/pick_test.ts +134 -0
  187. package/src/utils/rate-limit.ts +34 -0
  188. package/src/utils/session-storage.ts +27 -0
  189. package/src/utils/tab-id.ts +33 -0
  190. package/src/utils/time.ts +37 -0
  191. package/src/utils/timers.ts +79 -0
  192. package/src/utils/url.ts +12 -0
  193. package/src/utils/wrap.ts +33 -0
  194. package/src/vars.ts +169 -0
@@ -0,0 +1,221 @@
1
+ import { Endpoint, Vars, vars } from "../vars";
2
+ import {
3
+ DEPLOYMENT_ENVIRONMENT_NAME,
4
+ DEPLOYMENT_ID,
5
+ DEPLOYMENT_NAME,
6
+ PAGE_LOAD_ID,
7
+ SERVICE_NAME,
8
+ SERVICE_VERSION,
9
+ USER_AGENT,
10
+ } from "../semantic-conventions";
11
+ import {
12
+ fetch,
13
+ generateUniqueId,
14
+ PAGE_LOAD_ID_BYTES,
15
+ warn,
16
+ debug,
17
+ perf,
18
+ nav,
19
+ win,
20
+ NO_VALUE_FALLBACK,
21
+ pick,
22
+ } from "../utils";
23
+ import { trackSessions } from "./session";
24
+ import { startWebVitalsInstrumentation } from "../instrumentations/web-vitals";
25
+ import { startErrorInstrumentation } from "../instrumentations/errors";
26
+ import { addAttribute, AttributeValueType } from "../utils/otel";
27
+ import { instrumentFetch } from "../instrumentations/http/fetch";
28
+ import { AnyValue } from "../../types/otlp";
29
+ import { startNavigationInstrumentation } from "../instrumentations/navigation";
30
+ import { merge } from "ts-deepmerge";
31
+ import { initializeTabId } from "../utils/tab-id";
32
+
33
+ export type InitOptions = {
34
+ serviceName: string;
35
+ serviceVersion?: string;
36
+ environment?: string;
37
+ deploymentName?: string;
38
+ deploymentId?: string;
39
+
40
+ /**
41
+ * Additional attributes to include with transmitted signals
42
+ */
43
+ additionalSignalAttributes?: Record<string, AttributeValueType | AnyValue>;
44
+
45
+ /**
46
+ * OTLP endpoints to which the generated telemetry should be sent to.
47
+ */
48
+ endpoint: Endpoint | Endpoint[];
49
+
50
+ /**
51
+ * The session inactivity timeout. Session inactivity is the maximum
52
+ * allowed time to pass between two page loads before the session is considered
53
+ * to be expired. Also think of cache time-to-idle configuration options.
54
+ */
55
+ sessionInactivityTimeoutMillis?: number;
56
+
57
+ /**
58
+ * The default session termination timeout. Session termination is the maximum
59
+ * allowed time to pass since session start before the session is considered
60
+ * to be expired. Also think of cache time-to-live configuration options.
61
+ */
62
+ sessionTerminationTimeoutMillis?: number;
63
+ } & Partial<
64
+ Pick<
65
+ Vars,
66
+ | "ignoreUrls"
67
+ | "ignoreErrorMessages"
68
+ | "wrapEventHandlers"
69
+ | "wrapTimers"
70
+ | "propagateTraceHeadersCorsURLs"
71
+ | "maxWaitForResourceTimingsMillis"
72
+ | "maxToleranceForResourceTimingsMillis"
73
+ | "headersToCapture"
74
+ | "pageViewInstrumentation"
75
+ >
76
+ >;
77
+
78
+ let hasBeenInitialised: boolean = false;
79
+
80
+ export function init(opts: InitOptions) {
81
+ if (hasBeenInitialised) {
82
+ debug("Dash0 SDK is being reinitialized, skipping ...");
83
+ return;
84
+ }
85
+
86
+ if (!isClient()) {
87
+ debug("Looks like we are not running in a browser context. Stopping Dash0 Web SDK initialization.");
88
+ return;
89
+ }
90
+
91
+ if (!isSupported()) {
92
+ debug("Stopping Dash0 Web SDK initialization. This browser does not support the necessary APIs");
93
+ return;
94
+ }
95
+
96
+ vars.endpoints = opts.endpoint instanceof Array ? opts.endpoint : [opts.endpoint];
97
+ if (vars.endpoints.length === 0) {
98
+ warn("No telemetry endpoint configured. Aborting Dash0 Web SDK initialization process.");
99
+ return;
100
+ }
101
+
102
+ Object.assign(
103
+ vars,
104
+ merge(
105
+ vars,
106
+ pick(opts, [
107
+ "ignoreUrls",
108
+ "ignoreErrorMessages",
109
+ "wrapEventHandlers",
110
+ "wrapTimers",
111
+ "propagateTraceHeadersCorsURLs",
112
+ "maxWaitForResourceTimingsMillis",
113
+ "maxToleranceForResourceTimingsMillis",
114
+ "headersToCapture",
115
+ "pageViewInstrumentation",
116
+ ])
117
+ )
118
+ );
119
+
120
+ initializeResourceAttributes(opts);
121
+ initializeSignalAttributes(opts);
122
+ initializeTabId();
123
+ trackSessions(opts.sessionInactivityTimeoutMillis, opts.sessionTerminationTimeoutMillis);
124
+ startNavigationInstrumentation();
125
+ startWebVitalsInstrumentation();
126
+ startErrorInstrumentation();
127
+ instrumentFetch();
128
+
129
+ hasBeenInitialised = true;
130
+ }
131
+
132
+ function initializeResourceAttributes(opts: InitOptions) {
133
+ addAttribute(vars.resource.attributes, SERVICE_NAME, opts["serviceName"]);
134
+
135
+ if (opts.serviceVersion) {
136
+ addAttribute(vars.resource.attributes, SERVICE_VERSION, opts["serviceVersion"]);
137
+ }
138
+
139
+ const env = detectEnvironment(opts);
140
+ if (env) {
141
+ addAttribute(vars.resource.attributes, DEPLOYMENT_ENVIRONMENT_NAME, env);
142
+ }
143
+
144
+ const deploymentName = detectDeploymentName(opts);
145
+ if (deploymentName) {
146
+ addAttribute(vars.resource.attributes, DEPLOYMENT_NAME, deploymentName);
147
+ }
148
+
149
+ const deploymentId = detectDeploymentId(opts);
150
+ if (deploymentId) {
151
+ addAttribute(vars.resource.attributes, DEPLOYMENT_ID, deploymentId);
152
+ }
153
+ }
154
+
155
+ function initializeSignalAttributes(opts: InitOptions) {
156
+ addAttribute(vars.signalAttributes, PAGE_LOAD_ID, generateUniqueId(PAGE_LOAD_ID_BYTES));
157
+ addAttribute(vars.signalAttributes, USER_AGENT, nav?.userAgent ?? NO_VALUE_FALLBACK);
158
+
159
+ if (opts.additionalSignalAttributes) {
160
+ Object.entries(opts.additionalSignalAttributes).forEach(([key, value]) => {
161
+ addAttribute(vars.signalAttributes, key, value);
162
+ });
163
+ }
164
+ }
165
+
166
+ function isSupported() {
167
+ return typeof fetch === "function" && perf && perf.getEntriesByType;
168
+ }
169
+
170
+ function isClient() {
171
+ return win != null;
172
+ }
173
+
174
+ function detectEnvironment(opts: InitOptions): string | undefined {
175
+ // if there is a manually specified value we use that
176
+ if (opts.environment) {
177
+ return opts.environment;
178
+ }
179
+
180
+ // if process isn't defined access to it causes an exception, but we can't check for its present due to how
181
+ // plugins like webpack define work.
182
+ try {
183
+ // vercel
184
+ // @ts-expect-error -- we need to access like this to allow webpack in the nextjs build to replace this
185
+ return process?.env?.NEXT_PUBLIC_VERCEL_ENV;
186
+ } catch (_ignored) {
187
+ return undefined;
188
+ }
189
+ }
190
+
191
+ function detectDeploymentName(opts: InitOptions): string | undefined {
192
+ if (opts.deploymentName) {
193
+ return opts.deploymentName;
194
+ }
195
+
196
+ // if process isn't defined access to it causes an exception, but we can't check for its present due to how
197
+ // plugins like webpack define work.
198
+ try {
199
+ // vercel
200
+ // @ts-expect-error -- we need to access like this to allow webpack in the nextjs build to replace this
201
+ return process?.env?.NEXT_PUBLIC_VERCEL_TARGET_ENV;
202
+ } catch (_ignored) {
203
+ return undefined;
204
+ }
205
+ }
206
+
207
+ function detectDeploymentId(opts: InitOptions): string | undefined {
208
+ if (opts.deploymentId) {
209
+ return opts.deploymentId;
210
+ }
211
+
212
+ // if process isn't defined access to it causes an exception, but we can't check for its present due to how
213
+ // plugins like webpack define work.
214
+ try {
215
+ // vercel
216
+ // @ts-expect-error -- we need to access like this to allow webpack in the nextjs build to replace this
217
+ return process?.env?.NEXT_PUBLIC_VERCEL_BRANCH_URL;
218
+ } catch (_ignored) {
219
+ return undefined;
220
+ }
221
+ }
@@ -0,0 +1,6 @@
1
+ import { ErrorLike, ReportErrorOpts } from "../../types/errors";
2
+ import { reportError as reportErrorInternal } from "../instrumentations/errors/unhandled-error";
3
+
4
+ export function reportError(error: string | ErrorLike, opts?: ReportErrorOpts) {
5
+ reportErrorInternal(error, opts);
6
+ }
@@ -0,0 +1,113 @@
1
+ import { isSupported, getItem, setItem, removeItem, SESSION_ID_BYTES } from "../utils";
2
+ import { debug, generateUniqueId, now } from "../utils";
3
+ import { info, warn } from "../utils";
4
+
5
+ interface Session {
6
+ id: string;
7
+ startTime: number;
8
+ lastActivityTime: number;
9
+ }
10
+
11
+ const SESSION_STORAGE_KEY = "d0_session";
12
+ const STORAGE_SEPARATOR_KEY = "#";
13
+ const DEFAULT_SESSION_INACTIVITY_TIMEOUT_MILLIS = 1000 * 60 * 60 * 3;
14
+ const DEFAULT_SESSION_TERMINATION_TIMEOUT_MILLIS = 1000 * 60 * 60 * 6;
15
+ const MAX_ALLOWED_SESSION_TIMEOUT_MILLIS = 1000 * 60 * 60 * 24;
16
+
17
+ export let sessionId: string | null = null;
18
+
19
+ export function trackSessions(sessionInactivityTimeoutMillis?: number, sessionTerminationTimeoutMillis?: number): void {
20
+ if (!isSupported) {
21
+ debug("Storage API is not available and session tracking is therefore not supported.");
22
+ return;
23
+ }
24
+
25
+ if (!sessionInactivityTimeoutMillis) {
26
+ sessionInactivityTimeoutMillis = DEFAULT_SESSION_INACTIVITY_TIMEOUT_MILLIS;
27
+ }
28
+ if (!sessionTerminationTimeoutMillis) {
29
+ sessionTerminationTimeoutMillis = DEFAULT_SESSION_TERMINATION_TIMEOUT_MILLIS;
30
+ }
31
+ sessionInactivityTimeoutMillis = Math.min(sessionInactivityTimeoutMillis, MAX_ALLOWED_SESSION_TIMEOUT_MILLIS);
32
+ sessionTerminationTimeoutMillis = Math.min(sessionTerminationTimeoutMillis, MAX_ALLOWED_SESSION_TIMEOUT_MILLIS);
33
+
34
+ try {
35
+ const storedValue = getItem(SESSION_STORAGE_KEY);
36
+
37
+ let session = parseSession(storedValue);
38
+ if (session && !isSessionValid(session, sessionInactivityTimeoutMillis, sessionTerminationTimeoutMillis)) {
39
+ session = null;
40
+ }
41
+
42
+ if (session) {
43
+ session.lastActivityTime = now();
44
+ } else {
45
+ session = {
46
+ id: generateUniqueId(SESSION_ID_BYTES),
47
+ startTime: now(),
48
+ lastActivityTime: now(),
49
+ };
50
+ }
51
+
52
+ setItem(SESSION_STORAGE_KEY, serializeSession(session));
53
+ sessionId = session.id;
54
+ } catch (e) {
55
+ warn("Failed to record session information", e);
56
+ }
57
+ }
58
+
59
+ export function terminateSession() {
60
+ sessionId = null;
61
+
62
+ if (!isSupported) {
63
+ return;
64
+ }
65
+
66
+ try {
67
+ removeItem(SESSION_STORAGE_KEY);
68
+ } catch (e) {
69
+ info("Failed to terminate session", e);
70
+ }
71
+ }
72
+
73
+ function parseSession(storedValue?: string | null): Session | null {
74
+ if (!storedValue) {
75
+ return null;
76
+ }
77
+
78
+ const values = storedValue.split(STORAGE_SEPARATOR_KEY);
79
+ if (values.length < 3) {
80
+ return null;
81
+ }
82
+
83
+ const id = values[0];
84
+ const startTime = parseInt(values[1]!, 10);
85
+ const lastActivityTime = parseInt(values[2]!, 10);
86
+ if (!id || isNaN(startTime) || isNaN(lastActivityTime)) {
87
+ return null;
88
+ }
89
+
90
+ return {
91
+ id,
92
+ startTime,
93
+ lastActivityTime,
94
+ };
95
+ }
96
+
97
+ function serializeSession(session: Session): string {
98
+ return session.id + STORAGE_SEPARATOR_KEY + session.startTime + STORAGE_SEPARATOR_KEY + session.lastActivityTime;
99
+ }
100
+
101
+ function isSessionValid(
102
+ session: Session,
103
+ sessionInactivityTimeoutMillis: number,
104
+ sessionTerminationTimeoutMillis: number
105
+ ): boolean {
106
+ const minAllowedLastActivityTime = now() - sessionInactivityTimeoutMillis;
107
+ if (session.lastActivityTime < minAllowedLastActivityTime) {
108
+ return false;
109
+ }
110
+
111
+ const minAllowedStartTime = now() - sessionTerminationTimeoutMillis;
112
+ return session.startTime >= minAllowedStartTime;
113
+ }
@@ -0,0 +1,42 @@
1
+ import { KeyValue } from "../../types/otlp";
2
+ import { nav, NO_VALUE_FALLBACK, win } from "../utils";
3
+ import {
4
+ BROWSER_TAB_ID,
5
+ NETWORK_CONNECTION_TYPE,
6
+ SESSION_ID,
7
+ WINDOW_HEIGHT,
8
+ WINDOW_WIDTH,
9
+ } from "../semantic-conventions";
10
+ import { sessionId } from "../api/session";
11
+ import { vars } from "../vars";
12
+ import { addAttribute } from "../utils/otel";
13
+ import { addUrlAttributes } from "./url";
14
+ import { tabId } from "../utils/tab-id";
15
+
16
+ type Options = {
17
+ url?: string | URL;
18
+ };
19
+
20
+ export function addCommonAttributes(attributes: KeyValue[], options?: Options): void {
21
+ for (let i = 0; i < vars.signalAttributes.length; i++) {
22
+ attributes.push(vars.signalAttributes[i]!);
23
+ }
24
+
25
+ addUrlAttributes(attributes, options?.url ?? win?.location.href ?? NO_VALUE_FALLBACK);
26
+
27
+ if (sessionId) {
28
+ addAttribute(attributes, SESSION_ID, sessionId);
29
+ }
30
+ if (tabId) {
31
+ addAttribute(attributes, BROWSER_TAB_ID, tabId);
32
+ }
33
+
34
+ addAttribute(attributes, WINDOW_WIDTH, win?.innerWidth ?? NO_VALUE_FALLBACK);
35
+ addAttribute(attributes, WINDOW_HEIGHT, win?.innerHeight ?? NO_VALUE_FALLBACK);
36
+
37
+ // @ts-expect-error -- TypeScript is not aware of navigator.connection.effectiveType
38
+ const connectionType = nav?.connection?.effectiveType;
39
+ if (connectionType) {
40
+ addAttribute(attributes, NETWORK_CONNECTION_TYPE, connectionType);
41
+ }
42
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./common";
2
+ export * from "./url";
@@ -0,0 +1,25 @@
1
+ import { KeyValue } from "../../types/otlp";
2
+ import { addAttribute } from "../utils/otel";
3
+ import { URL_DOMAIN, URL_FRAGMENT, URL_FULL, URL_PATH, URL_QUERY, URL_SCHEME } from "../semantic-conventions";
4
+ import { parseUrl } from "../utils/url";
5
+
6
+ export function addUrlAttributes(attributes: KeyValue[], url: string | URL) {
7
+ try {
8
+ const parsed = parseUrl(url);
9
+ if (parsed.username) parsed.username = "REDACTED";
10
+ if (parsed.password) parsed.password = "REDACTED";
11
+
12
+ addAttribute(attributes, URL_FULL, parsed.href);
13
+ addAttribute(attributes, URL_PATH, parsed.pathname);
14
+ addAttribute(attributes, URL_DOMAIN, parsed.hostname);
15
+ addAttribute(attributes, URL_SCHEME, parsed.protocol.replace(":", ""));
16
+ if (parsed.hash) {
17
+ addAttribute(attributes, URL_FRAGMENT, parsed.hash.replace("#", ""));
18
+ }
19
+ if (parsed.search) {
20
+ addAttribute(attributes, URL_QUERY, parsed.search.replace("?", ""));
21
+ }
22
+ } catch (_e) {
23
+ addAttribute(attributes, URL_FULL, String(url));
24
+ }
25
+ }
@@ -0,0 +1,14 @@
1
+ import { debug, INIT_MESSAGE } from "../utils";
2
+ import { init as initApi, InitOptions } from "../api/init";
3
+
4
+ export * from "../api/identify";
5
+ export * from "../api/debug";
6
+ export * from "../api/attributes";
7
+ export * from "../api/events";
8
+ export { terminateSession } from "../api/session";
9
+ export { reportError } from "../api/report-error";
10
+
11
+ export function init(opts: InitOptions): void {
12
+ debug(`${INIT_MESSAGE} (via package)`);
13
+ initApi(opts);
14
+ }
@@ -0,0 +1,23 @@
1
+ <script>
2
+ (function (d, a, s, h, z, e, r, o) {
3
+ d[a] ||
4
+ ((z = d[a] =
5
+ function () {
6
+ h.push(arguments);
7
+ }),
8
+ (z._t = new Date()),
9
+ (z._v = 1),
10
+ (h = z._q = []));
11
+ })(window, "dash0");
12
+ dash0("init", {
13
+ serviceName: "dash0.com",
14
+ serviceVersion: "1.2.3",
15
+ environment: "production",
16
+ endpoint: {
17
+ url: "https://ingress.eu-west-1.aws.dash0-dev.com",
18
+ authToken: "auth_abc123",
19
+ dataset: "production",
20
+ },
21
+ });
22
+ </script>
23
+ <script defer crossorigin="anonymous" src="dash0.min.js"></script>
@@ -0,0 +1,82 @@
1
+ import { win, debug, warn, INIT_MESSAGE } from "../utils";
2
+
3
+ import { reportError } from "../api/report-error";
4
+ import { identify } from "../api/identify";
5
+ import { debug as debugApi } from "../api/debug";
6
+ import { init as initApi } from "../api/init";
7
+ import { terminateSession } from "../api/session";
8
+ import { addSignalAttribute, removeSignalAttribute } from "../api/attributes";
9
+ import { sendEvent } from "../api/events";
10
+
11
+ /**
12
+ * All the APIs exposed through the script tag via `dash0('{{api name}}')`
13
+ */
14
+ const scriptApis = {
15
+ init: initApi,
16
+ debug: debugApi,
17
+ identify,
18
+ terminateSession,
19
+ reportError,
20
+ addSignalAttribute,
21
+ removeSignalAttribute,
22
+ sendEvent,
23
+ } as const;
24
+
25
+ type GlobalObject = {
26
+ /**
27
+ * Queued API calls. We will work through this queue during the initialization and then set it to `undefined`
28
+ * afterward to signal completion
29
+ */
30
+ _q?: Array<IArguments>;
31
+ };
32
+
33
+ init();
34
+
35
+ function init(): void {
36
+ debug(`${INIT_MESSAGE} (via Script)`);
37
+
38
+ const globalObject: GlobalObject = (win as any)["dash0"] as GlobalObject;
39
+
40
+ if (!globalObject) {
41
+ warn("global 'dash0' not found. Did you use the correct Dash0 Web SDK initializer?");
42
+ return;
43
+ }
44
+
45
+ if (!globalObject["_q"]) {
46
+ warn("Dash0 Web SDK command queue not defined. Did you add the script tag multiple times to your website?");
47
+ return;
48
+ }
49
+
50
+ processQueuedApiCalls(globalObject["_q"]);
51
+ addApiCallAfterInitializationSupport();
52
+ }
53
+
54
+ function processQueuedApiCalls(apiCalls: Array<any>) {
55
+ for (let i = 0, len = apiCalls.length; i < len; i++) {
56
+ processQueuedApiCall(apiCalls[i]);
57
+ }
58
+ }
59
+
60
+ function processQueuedApiCall(apiCall: IArguments) {
61
+ const apiName = apiCall[0];
62
+ // @ts-expect-error the APIs are dynamic
63
+ const apiFn = scriptApis[apiName] as (typeof scriptApis)[apiName];
64
+
65
+ if (!apiFn) {
66
+ warn("Unsupported Dash0 Web SDK api: ", apiCall[0]);
67
+ return;
68
+ }
69
+
70
+ const args: any[] = [];
71
+ for (let i = 1; i < apiCall.length; i++) {
72
+ args.push(apiCall[i]);
73
+ }
74
+
75
+ apiFn.apply(null, args);
76
+ }
77
+
78
+ function addApiCallAfterInitializationSupport() {
79
+ (win as any)["dash0"] = function () {
80
+ return processQueuedApiCall(arguments as any);
81
+ };
82
+ }
@@ -0,0 +1,157 @@
1
+ const WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY = "__dash0OriginalFunctions";
2
+
3
+ export type EventListenerOptionsOrUseCapture =
4
+ | boolean
5
+ | {
6
+ capture?: boolean;
7
+ };
8
+
9
+ // Asynchronous function wrapping: The process of wrapping a listener which goes into one function, e.g.
10
+ //
11
+ // - EventTarget#addEventListener
12
+ // - EventEmitter#on
13
+ //
14
+ // and is removed via another function, e.g.
15
+ //
16
+ // - EventTarget#removeEventListener
17
+ // - EventEmitter#off
18
+ //
19
+ // What is complicated about this, is that these methods identify registered listeners by function reference.
20
+ // When we wrap a function, we naturally change the reference. We must therefore keep track of which
21
+ // original function belongs to what wrapped function.
22
+ //
23
+ // This file provides helpers that help in the typical cases. It is removed from all browser specific APIs
24
+ // in order to allow simple unit test execution.
25
+ //
26
+ // Note that this file follows the behavior outlined in DOM specification. Among others, this means that it is not
27
+ // possible to register the same listener twice.
28
+ // http://dom.spec.whatwg.org
29
+
30
+ export function addWrappedFunction(
31
+ storageTarget: EventTarget,
32
+ wrappedFunction: EventListenerOrEventListenerObject,
33
+ valuesForEqualityCheck: any[]
34
+ ): EventListenerOrEventListenerObject {
35
+ if (!storageTarget) return wrappedFunction;
36
+
37
+ const storage = ((storageTarget as any)[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY] =
38
+ (storageTarget as any)[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY] || []);
39
+ const index = findInStorage(storageTarget, valuesForEqualityCheck);
40
+ if (index !== -1) {
41
+ // already registered. Do not allow re-registration
42
+ return storage[index].wrappedFunction;
43
+ }
44
+
45
+ storage.push({
46
+ wrappedFunction,
47
+ valuesForEqualityCheck,
48
+ });
49
+ return wrappedFunction;
50
+ }
51
+
52
+ function findInStorage(storageTarget: EventTarget, valuesForEqualityCheck: any[]): number {
53
+ const storage = (storageTarget as any)[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY];
54
+ for (let i = 0; i < storage.length; i++) {
55
+ const storageItem = storage[i];
56
+
57
+ if (matchesEqualityCheck(storageItem.valuesForEqualityCheck, valuesForEqualityCheck)) {
58
+ return i;
59
+ }
60
+ }
61
+ return -1;
62
+ }
63
+
64
+ export function popWrappedFunction(
65
+ storageTarget: EventTarget,
66
+ valuesForEqualityCheck: any[],
67
+ fallback?: EventListenerOrEventListenerObject
68
+ ): EventListenerOrEventListenerObject | undefined {
69
+ const storage = (storageTarget as any)?.[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY];
70
+ if (storage == null) {
71
+ return fallback;
72
+ }
73
+
74
+ const index = findInStorage(storageTarget, valuesForEqualityCheck);
75
+ if (index === -1) {
76
+ return fallback;
77
+ }
78
+
79
+ const storageItem = storage[index];
80
+ storage.splice(index, 1);
81
+ return storageItem.wrappedFunction;
82
+ }
83
+
84
+ function matchesEqualityCheck(valuesForEqualityCheckA: any, valuesForEqualityCheckB: any) {
85
+ if (valuesForEqualityCheckA.length !== valuesForEqualityCheckB.length) {
86
+ return false;
87
+ }
88
+
89
+ for (let i = 0; i < valuesForEqualityCheckA.length; i++) {
90
+ if (valuesForEqualityCheckA[i] !== valuesForEqualityCheckB[i]) {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ return true;
96
+ }
97
+
98
+ export function addWrappedDomEventListener(
99
+ storageTarget: EventTarget,
100
+ wrappedFunction: EventListenerOrEventListenerObject,
101
+ eventName: string,
102
+ eventListener: EventListenerOrEventListenerObject,
103
+ optionsOrCapture?: EventListenerOptionsOrUseCapture
104
+ ): EventListenerOrEventListenerObject {
105
+ return addWrappedFunction(
106
+ storageTarget,
107
+ wrappedFunction,
108
+ getDomEventListenerValuesForEqualityCheck(eventName, eventListener, optionsOrCapture)
109
+ );
110
+ }
111
+
112
+ function getDomEventListenerValuesForEqualityCheck(
113
+ eventName: string,
114
+ eventListener: EventListenerOrEventListenerObject,
115
+ optionsOrCapture?: EventListenerOptionsOrUseCapture
116
+ ): any[] {
117
+ return [eventName, eventListener, getDomEventListenerCaptureValue(optionsOrCapture)];
118
+ }
119
+
120
+ export function getDomEventListenerCaptureValue(optionsOrCapture?: EventListenerOptionsOrUseCapture): boolean {
121
+ // > Let capture, passive, and once be the result of flattening more options.
122
+ // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
123
+ //
124
+ // > To flatten more options, run these steps:
125
+ // > 1. Let capture be the result of flattening options.
126
+ // https://dom.spec.whatwg.org/#event-flatten-more
127
+ //
128
+ // > To flatten options, run these steps:
129
+ // > 1. If options is a boolean, then return options.
130
+ // > 2. Return options’s capture.
131
+ // https://dom.spec.whatwg.org/#concept-flatten-options
132
+ //
133
+ // > dictionary EventListenerOptions {
134
+ // > boolean capture = false;
135
+ // > };
136
+ // https://dom.spec.whatwg.org/#dom-eventlisteneroptions-capture
137
+ if (optionsOrCapture == null) {
138
+ return false;
139
+ } else if (typeof optionsOrCapture === "object") {
140
+ return Boolean(optionsOrCapture.capture);
141
+ }
142
+ return Boolean(optionsOrCapture);
143
+ }
144
+
145
+ export function popWrappedDomEventListener(
146
+ storageTarget: EventTarget,
147
+ eventName: string,
148
+ eventListener: EventListenerOrEventListenerObject,
149
+ optionsOrCapture?: EventListenerOptionsOrUseCapture,
150
+ fallback?: EventListenerOrEventListenerObject
151
+ ): EventListenerOrEventListenerObject | undefined {
152
+ return popWrappedFunction(
153
+ storageTarget,
154
+ getDomEventListenerValuesForEqualityCheck(eventName, eventListener, optionsOrCapture),
155
+ fallback
156
+ );
157
+ }