@interfere/react 0.0.1 → 0.0.2-alpha.1

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 (198) hide show
  1. package/dist/client.d.mts +15 -0
  2. package/dist/client.d.mts.map +1 -0
  3. package/dist/client.mjs +75 -0
  4. package/dist/client.mjs.map +1 -0
  5. package/dist/core/events/event-registry.d.mts +23 -0
  6. package/dist/core/events/event-registry.d.mts.map +1 -0
  7. package/dist/core/events/event-registry.mjs +32 -0
  8. package/dist/core/events/event-registry.mjs.map +1 -0
  9. package/dist/core/events/plugin-event-types.d.mts +92 -0
  10. package/dist/core/events/plugin-event-types.d.mts.map +1 -0
  11. package/dist/core/events/plugin-event-types.mjs +25 -0
  12. package/dist/core/events/plugin-event-types.mjs.map +1 -0
  13. package/dist/core/plugins/dom-utils.d.mts +9 -0
  14. package/dist/core/plugins/dom-utils.d.mts.map +1 -0
  15. package/dist/core/plugins/dom-utils.mjs +25 -0
  16. package/dist/core/plugins/dom-utils.mjs.map +1 -0
  17. package/dist/core/plugins/impl/ai-summary.d.mts +6 -0
  18. package/dist/core/plugins/impl/ai-summary.d.mts.map +1 -0
  19. package/dist/core/plugins/impl/ai-summary.mjs +122 -0
  20. package/dist/core/plugins/impl/ai-summary.mjs.map +1 -0
  21. package/dist/core/plugins/impl/errors.d.mts +9 -0
  22. package/dist/core/plugins/impl/errors.d.mts.map +1 -0
  23. package/dist/core/plugins/impl/errors.mjs +153 -0
  24. package/dist/core/plugins/impl/errors.mjs.map +1 -0
  25. package/dist/core/plugins/impl/page-events.d.mts +15 -0
  26. package/dist/core/plugins/impl/page-events.d.mts.map +1 -0
  27. package/dist/core/plugins/impl/page-events.mjs +131 -0
  28. package/dist/core/plugins/impl/page-events.mjs.map +1 -0
  29. package/dist/core/plugins/impl/rage-click.d.mts +6 -0
  30. package/dist/core/plugins/impl/rage-click.d.mts.map +1 -0
  31. package/dist/core/plugins/impl/rage-click.mjs +53 -0
  32. package/dist/core/plugins/impl/rage-click.mjs.map +1 -0
  33. package/dist/core/plugins/impl/replay.d.mts +9 -0
  34. package/dist/core/plugins/impl/replay.d.mts.map +1 -0
  35. package/dist/core/plugins/impl/replay.mjs +144 -0
  36. package/dist/core/plugins/impl/replay.mjs.map +1 -0
  37. package/dist/core/plugins/impl/server-tracing.d.mts +7 -0
  38. package/dist/core/plugins/impl/server-tracing.d.mts.map +1 -0
  39. package/dist/core/plugins/impl/server-tracing.mjs +160 -0
  40. package/dist/core/plugins/impl/server-tracing.mjs.map +1 -0
  41. package/dist/core/plugins/plugin-event-system.d.mts +47 -0
  42. package/dist/core/plugins/plugin-event-system.d.mts.map +1 -0
  43. package/dist/core/plugins/plugin-event-system.mjs +75 -0
  44. package/dist/core/plugins/plugin-event-system.mjs.map +1 -0
  45. package/dist/core/plugins/plugin-loader.d.mts +22 -0
  46. package/dist/core/plugins/plugin-loader.d.mts.map +1 -0
  47. package/dist/core/plugins/plugin-loader.mjs +142 -0
  48. package/dist/core/plugins/plugin-loader.mjs.map +1 -0
  49. package/dist/core/runtime/config.d.mts +14 -0
  50. package/dist/core/runtime/config.d.mts.map +1 -0
  51. package/dist/core/runtime/config.mjs +39 -0
  52. package/dist/core/runtime/config.mjs.map +1 -0
  53. package/dist/core/runtime/context.d.mts +25 -0
  54. package/dist/core/runtime/context.d.mts.map +1 -0
  55. package/dist/core/runtime/context.mjs +48 -0
  56. package/dist/core/runtime/context.mjs.map +1 -0
  57. package/dist/core/runtime/ingest-target.d.mts +10 -0
  58. package/dist/core/runtime/ingest-target.d.mts.map +1 -0
  59. package/dist/core/runtime/ingest-target.mjs +15 -0
  60. package/dist/core/runtime/ingest-target.mjs.map +1 -0
  61. package/dist/core/schemas.d.mts +85 -0
  62. package/dist/core/schemas.d.mts.map +1 -0
  63. package/dist/core/schemas.mjs +1 -0
  64. package/dist/effect/build-envelope.d.mts +9 -0
  65. package/dist/effect/build-envelope.d.mts.map +1 -0
  66. package/dist/effect/build-envelope.mjs +29 -0
  67. package/dist/effect/build-envelope.mjs.map +1 -0
  68. package/dist/effect/errors.d.mts +36 -0
  69. package/dist/effect/errors.d.mts.map +1 -0
  70. package/dist/effect/errors.mjs +10 -0
  71. package/dist/effect/errors.mjs.map +1 -0
  72. package/dist/effect/layers/config.layer.d.mts +13 -0
  73. package/dist/effect/layers/config.layer.d.mts.map +1 -0
  74. package/dist/effect/layers/config.layer.mjs +21 -0
  75. package/dist/effect/layers/config.layer.mjs.map +1 -0
  76. package/dist/effect/layers/context.layer.d.mts +12 -0
  77. package/dist/effect/layers/context.layer.d.mts.map +1 -0
  78. package/dist/effect/layers/context.layer.mjs +14 -0
  79. package/dist/effect/layers/context.layer.mjs.map +1 -0
  80. package/dist/effect/layers/http.layer.d.mts +21 -0
  81. package/dist/effect/layers/http.layer.d.mts.map +1 -0
  82. package/dist/effect/layers/http.layer.mjs +113 -0
  83. package/dist/effect/layers/http.layer.mjs.map +1 -0
  84. package/dist/effect/layers/queue.layer.d.mts +30 -0
  85. package/dist/effect/layers/queue.layer.d.mts.map +1 -0
  86. package/dist/effect/layers/queue.layer.mjs +232 -0
  87. package/dist/effect/layers/queue.layer.mjs.map +1 -0
  88. package/dist/effect/layers/session.layer.d.mts +26 -0
  89. package/dist/effect/layers/session.layer.d.mts.map +1 -0
  90. package/dist/effect/layers/session.layer.mjs +126 -0
  91. package/dist/effect/layers/session.layer.mjs.map +1 -0
  92. package/dist/effect/layers/storage.layer.d.mts +19 -0
  93. package/dist/effect/layers/storage.layer.d.mts.map +1 -0
  94. package/dist/effect/layers/storage.layer.mjs +200 -0
  95. package/dist/effect/layers/storage.layer.mjs.map +1 -0
  96. package/dist/effect/layers/tracer.layer.d.mts +9 -0
  97. package/dist/effect/layers/tracer.layer.d.mts.map +1 -0
  98. package/dist/effect/layers/tracer.layer.mjs +11 -0
  99. package/dist/effect/layers/tracer.layer.mjs.map +1 -0
  100. package/dist/effect/runtime-services.d.mts +22 -0
  101. package/dist/effect/runtime-services.d.mts.map +1 -0
  102. package/dist/effect/runtime-services.mjs +76 -0
  103. package/dist/effect/runtime-services.mjs.map +1 -0
  104. package/dist/effect/tags.d.mts +50 -0
  105. package/dist/effect/tags.d.mts.map +1 -0
  106. package/dist/effect/tags.mjs +7 -0
  107. package/dist/effect/tags.mjs.map +1 -0
  108. package/dist/hooks/use-runtime-and-plugins.d.mts +7 -0
  109. package/dist/hooks/use-runtime-and-plugins.d.mts.map +1 -0
  110. package/dist/hooks/use-runtime-and-plugins.mjs +153 -0
  111. package/dist/hooks/use-runtime-and-plugins.mjs.map +1 -0
  112. package/dist/hooks/use-session.d.mts +40 -0
  113. package/dist/hooks/use-session.d.mts.map +1 -0
  114. package/dist/hooks/use-session.mjs +96 -0
  115. package/dist/hooks/use-session.mjs.map +1 -0
  116. package/dist/package.mjs +100 -0
  117. package/dist/package.mjs.map +1 -0
  118. package/dist/provider.d.mts +17 -0
  119. package/dist/provider.d.mts.map +1 -0
  120. package/dist/provider.mjs +26 -0
  121. package/dist/provider.mjs.map +1 -0
  122. package/dist/server/auth.d.mts +11 -0
  123. package/dist/server/auth.d.mts.map +1 -0
  124. package/dist/server/auth.mjs +36 -0
  125. package/dist/server/auth.mjs.map +1 -0
  126. package/dist/server/capture.d.mts +18 -0
  127. package/dist/server/capture.d.mts.map +1 -0
  128. package/dist/server/capture.mjs +105 -0
  129. package/dist/server/capture.mjs.map +1 -0
  130. package/package.json +60 -27
  131. package/dist/__tests__/client.test.d.ts +0 -2
  132. package/dist/__tests__/client.test.d.ts.map +0 -1
  133. package/dist/__tests__/client.test.js +0 -447
  134. package/dist/__tests__/client.test.js.map +0 -1
  135. package/dist/__tests__/lib/core/error-handlers.test.d.ts +0 -2
  136. package/dist/__tests__/lib/core/error-handlers.test.d.ts.map +0 -1
  137. package/dist/__tests__/lib/core/error-handlers.test.js +0 -596
  138. package/dist/__tests__/lib/core/error-handlers.test.js.map +0 -1
  139. package/dist/__tests__/lib/core/event-queue.test.d.ts +0 -2
  140. package/dist/__tests__/lib/core/event-queue.test.d.ts.map +0 -1
  141. package/dist/__tests__/lib/core/event-queue.test.js +0 -290
  142. package/dist/__tests__/lib/core/event-queue.test.js.map +0 -1
  143. package/dist/__tests__/lib/core/runtime.test.d.ts +0 -2
  144. package/dist/__tests__/lib/core/runtime.test.d.ts.map +0 -1
  145. package/dist/__tests__/lib/core/runtime.test.js +0 -133
  146. package/dist/__tests__/lib/core/runtime.test.js.map +0 -1
  147. package/dist/__tests__/lib/core/session-manager.test.d.ts +0 -2
  148. package/dist/__tests__/lib/core/session-manager.test.d.ts.map +0 -1
  149. package/dist/__tests__/lib/core/session-manager.test.js +0 -356
  150. package/dist/__tests__/lib/core/session-manager.test.js.map +0 -1
  151. package/dist/__tests__/provider.test.d.ts +0 -2
  152. package/dist/__tests__/provider.test.d.ts.map +0 -1
  153. package/dist/__tests__/provider.test.js +0 -143
  154. package/dist/__tests__/provider.test.js.map +0 -1
  155. package/dist/client.d.ts +0 -78
  156. package/dist/client.d.ts.map +0 -1
  157. package/dist/client.js +0 -219
  158. package/dist/client.js.map +0 -1
  159. package/dist/index.d.ts +0 -6
  160. package/dist/index.d.ts.map +0 -1
  161. package/dist/index.js +0 -5
  162. package/dist/index.js.map +0 -1
  163. package/dist/lib/core/error-handlers.d.ts +0 -14
  164. package/dist/lib/core/error-handlers.d.ts.map +0 -1
  165. package/dist/lib/core/error-handlers.js +0 -191
  166. package/dist/lib/core/error-handlers.js.map +0 -1
  167. package/dist/lib/core/event-queue.d.ts +0 -90
  168. package/dist/lib/core/event-queue.d.ts.map +0 -1
  169. package/dist/lib/core/event-queue.js +0 -286
  170. package/dist/lib/core/event-queue.js.map +0 -1
  171. package/dist/lib/core/runtime.d.ts +0 -7
  172. package/dist/lib/core/runtime.d.ts.map +0 -1
  173. package/dist/lib/core/runtime.js +0 -16
  174. package/dist/lib/core/runtime.js.map +0 -1
  175. package/dist/lib/core/session-manager.d.ts +0 -96
  176. package/dist/lib/core/session-manager.d.ts.map +0 -1
  177. package/dist/lib/core/session-manager.js +0 -431
  178. package/dist/lib/core/session-manager.js.map +0 -1
  179. package/dist/lib/persistence/storage.d.ts +0 -5
  180. package/dist/lib/persistence/storage.d.ts.map +0 -1
  181. package/dist/lib/persistence/storage.js +0 -67
  182. package/dist/lib/persistence/storage.js.map +0 -1
  183. package/dist/lib/session/rage-click.d.ts +0 -2
  184. package/dist/lib/session/rage-click.d.ts.map +0 -1
  185. package/dist/lib/session/rage-click.js +0 -51
  186. package/dist/lib/session/rage-click.js.map +0 -1
  187. package/dist/lib/session/replay.d.ts +0 -3
  188. package/dist/lib/session/replay.d.ts.map +0 -1
  189. package/dist/lib/session/replay.js +0 -106
  190. package/dist/lib/session/replay.js.map +0 -1
  191. package/dist/lib/session/session-summary.d.ts +0 -3
  192. package/dist/lib/session/session-summary.d.ts.map +0 -1
  193. package/dist/lib/session/session-summary.js +0 -79
  194. package/dist/lib/session/session-summary.js.map +0 -1
  195. package/dist/provider.d.ts +0 -76
  196. package/dist/provider.d.ts.map +0 -1
  197. package/dist/provider.js +0 -138
  198. package/dist/provider.js.map +0 -1
@@ -0,0 +1,153 @@
1
+ import { createEffectPlugin, defineEvent } from "../plugin-event-system.mjs";
2
+ import { Effect } from "effect";
3
+
4
+ //#region src/core/plugins/impl/errors.ts
5
+ const STACK_TRACE_REGEX = /at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?/;
6
+ function parseStackTrace(stack) {
7
+ if (!stack) return [];
8
+ const lines = stack.split("\n");
9
+ const frames = [];
10
+ for (const line of lines) {
11
+ const match = line.match(STACK_TRACE_REGEX);
12
+ if (match) frames.push({
13
+ file: { reported: match[2] || null },
14
+ line: { reported: match[3] ? Number.parseInt(match[3], 10) : null },
15
+ column: { reported: match[4] ? Number.parseInt(match[4], 10) : null },
16
+ fn: { reported: match[1] || null }
17
+ });
18
+ }
19
+ return frames.length > 0 ? frames : [{
20
+ file: { reported: null },
21
+ line: { reported: null },
22
+ column: { reported: null },
23
+ fn: { reported: null }
24
+ }];
25
+ }
26
+ function buildErrorPayload(error) {
27
+ return {
28
+ frames: error.stack ? parseStackTrace(error.stack) : [{
29
+ file: { reported: error.filename || null },
30
+ line: { reported: error.lineno || null },
31
+ column: { reported: error.colno || null },
32
+ fn: { reported: null }
33
+ }],
34
+ message: error.message || null,
35
+ name: error.name || null,
36
+ stack: error.stack || null,
37
+ errorSource: "client"
38
+ };
39
+ }
40
+ var errors_default = createEffectPlugin("errors", {
41
+ name: "errors",
42
+ events: [defineEvent("error").value()],
43
+ setup: (ctx) => Effect.gen(function* () {
44
+ if (typeof window === "undefined") {
45
+ ctx.log.debug("errors: SSR - skipping setup");
46
+ return;
47
+ }
48
+ const recent = /* @__PURE__ */ new Set();
49
+ const DEDUPE_TTL_MS = 2e3;
50
+ const STACK_PREFIX_SLICE = 120;
51
+ const makeKey = (input) => `${input.name ?? ""}|${input.message ?? ""}|${input.stack?.slice(0, STACK_PREFIX_SLICE) ?? ""}`;
52
+ const recordOnce = (payload, span) => {
53
+ const key = makeKey(payload);
54
+ if (recent.has(key)) return;
55
+ recent.add(key);
56
+ setTimeout(() => recent.delete(key), DEDUPE_TTL_MS);
57
+ ctx.span(span, ctx.capture("error", payload));
58
+ };
59
+ const api = { captureError: (err, meta) => {
60
+ const payload = buildErrorPayload(err instanceof Error ? {
61
+ message: err.message,
62
+ stack: err.stack,
63
+ name: err.name
64
+ } : {
65
+ message: String(err),
66
+ name: "UnknownError"
67
+ });
68
+ recordOnce(meta ? {
69
+ ...payload,
70
+ ...meta
71
+ } : payload, "plugin.error.capture");
72
+ } };
73
+ const errorHandler = (event) => {
74
+ recordOnce(buildErrorPayload({
75
+ message: event.message,
76
+ stack: event.error?.stack,
77
+ filename: event.filename,
78
+ lineno: event.lineno,
79
+ colno: event.colno,
80
+ name: event.error?.name
81
+ }), "plugin.error.window");
82
+ };
83
+ const rejectionHandler = (event) => {
84
+ const reason = event.reason;
85
+ recordOnce(buildErrorPayload(reason instanceof Error ? {
86
+ message: `Unhandled Promise Rejection: ${reason.message}`,
87
+ stack: reason.stack,
88
+ name: reason.name
89
+ } : {
90
+ message: `Unhandled Promise Rejection: ${String(reason)}`,
91
+ name: "UnhandledRejection"
92
+ }), "plugin.error.rejection");
93
+ };
94
+ const originalFetch = window.fetch.bind(window);
95
+ const patchedFetch = (...args) => originalFetch(...args).catch((err) => {
96
+ recordOnce(buildErrorPayload(err instanceof Error ? {
97
+ message: `Fetch failed: ${err.message}`,
98
+ stack: err.stack,
99
+ name: err.name
100
+ } : {
101
+ message: `Fetch failed: ${String(err)}`,
102
+ name: "FetchError"
103
+ }), "plugin.error.fetch");
104
+ throw err;
105
+ });
106
+ window.fetch = patchedFetch;
107
+ let inConsoleCapture = false;
108
+ const originalConsoleError = console.error.bind(console);
109
+ console.error = (...args) => {
110
+ try {
111
+ originalConsoleError(...args);
112
+ } finally {
113
+ if (!inConsoleCapture) {
114
+ inConsoleCapture = true;
115
+ try {
116
+ const firstError = args.find((a) => a instanceof Error);
117
+ const toStringSafe = (a) => {
118
+ if (typeof a === "string") return a;
119
+ if (a instanceof Error) return a.message;
120
+ try {
121
+ return JSON.stringify(a);
122
+ } catch {
123
+ return String(a);
124
+ }
125
+ };
126
+ recordOnce(buildErrorPayload({
127
+ message: args.map(toStringSafe).join(" "),
128
+ name: firstError?.name ?? "ConsoleError",
129
+ stack: firstError?.stack
130
+ }), "plugin.error.console_error");
131
+ } finally {
132
+ inConsoleCapture = false;
133
+ }
134
+ }
135
+ }
136
+ };
137
+ window.addEventListener("error", errorHandler, { capture: true });
138
+ window.addEventListener("unhandledrejection", rejectionHandler);
139
+ return {
140
+ key: "errors",
141
+ api,
142
+ cleanup: () => {
143
+ window.removeEventListener("error", errorHandler, { capture: true });
144
+ window.removeEventListener("unhandledrejection", rejectionHandler);
145
+ window.fetch = originalFetch;
146
+ console.error = originalConsoleError;
147
+ }
148
+ };
149
+ })
150
+ });
151
+
152
+ //#endregion
153
+ export { errors_default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.mjs","names":["frames: IngestedFrame[]","api: ErrorsAPI","originalFetch: typeof window.fetch","patchedFetch: typeof window.fetch","originalConsoleError: typeof console.error"],"sources":["../../../../src/core/plugins/impl/errors.ts"],"sourcesContent":["import type { IngestedFrame } from \"@interfere/types/data/frame\";\nimport type { ErrorEnvelopePayload } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { Effect } from \"effect\";\n\nimport { createEffectPlugin, defineEvent } from \"../plugin-event-system.js\";\n\n// Regex defined at top level for performance\nconst STACK_TRACE_REGEX = /at\\s+(?:(.+?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?/;\n\nfunction parseStackTrace(stack?: string): IngestedFrame[] {\n if (!stack) {\n return [];\n }\n\n // Basic stack trace parsing - can be enhanced with error-stack-parser library\n const lines = stack.split(\"\\n\");\n const frames: IngestedFrame[] = [];\n\n for (const line of lines) {\n // Match patterns like: \"at functionName (file:line:column)\" or \"at file:line:column\"\n const match = line.match(STACK_TRACE_REGEX);\n\n if (match) {\n frames.push({\n file: { reported: match[2] || null },\n line: { reported: match[3] ? Number.parseInt(match[3], 10) : null },\n column: { reported: match[4] ? Number.parseInt(match[4], 10) : null },\n fn: { reported: match[1] || null },\n });\n }\n }\n\n return frames.length > 0\n ? frames\n : [\n // Fallback frame if we can't parse the stack\n {\n file: { reported: null },\n line: { reported: null },\n column: { reported: null },\n fn: { reported: null },\n },\n ];\n}\n\nfunction buildErrorPayload(error: {\n message?: string;\n name?: string;\n stack?: string;\n filename?: string;\n lineno?: number;\n colno?: number;\n}): ErrorEnvelopePayload {\n const frames = error.stack\n ? parseStackTrace(error.stack)\n : [\n {\n file: { reported: error.filename || null },\n line: { reported: error.lineno || null },\n column: { reported: error.colno || null },\n fn: { reported: null },\n },\n ];\n\n return {\n frames,\n message: error.message || null,\n name: error.name || null,\n stack: error.stack || null,\n errorSource: \"client\",\n };\n}\n\nexport interface ErrorsAPI {\n captureError: (err: unknown, meta?: Record<string, unknown>) => void;\n}\n\nexport default createEffectPlugin(\"errors\", {\n name: \"errors\",\n events: [defineEvent(\"error\").value()],\n setup: (ctx) =>\n Effect.gen(function* () {\n if (typeof window === \"undefined\") {\n ctx.log.debug(\"errors: SSR - skipping setup\");\n return;\n }\n\n // Simple in-memory de-dupe to avoid double reports from multiple hooks\n const recent = new Set<string>();\n\n const DEDUPE_TTL_MS = 2000;\n const STACK_PREFIX_SLICE = 120;\n\n const makeKey = (input: {\n message?: string | null;\n stack?: string | null;\n name?: string | null;\n }) =>\n `${input.name ?? \"\"}|${input.message ?? \"\"}|${input.stack?.slice(0, STACK_PREFIX_SLICE) ?? \"\"}`;\n\n const recordOnce = (\n payload: ReturnType<typeof buildErrorPayload>,\n span: string\n ) => {\n const key = makeKey(payload);\n\n if (recent.has(key)) {\n return;\n }\n\n recent.add(key);\n\n setTimeout(() => recent.delete(key), DEDUPE_TTL_MS);\n\n ctx.span(span, ctx.capture(\"error\", payload));\n };\n\n const api: ErrorsAPI = {\n captureError: (err: unknown, meta?: Record<string, unknown>) => {\n const errorInfo =\n err instanceof Error\n ? { message: err.message, stack: err.stack, name: err.name }\n : { message: String(err), name: \"UnknownError\" };\n\n const payload = buildErrorPayload(errorInfo);\n recordOnce(\n (meta\n ? ({ ...payload, ...meta } as unknown)\n : payload) as ErrorEnvelopePayload,\n \"plugin.error.capture\"\n );\n },\n };\n\n const errorHandler = (event: ErrorEvent) => {\n const payload = buildErrorPayload({\n message: event.message,\n stack: event.error?.stack,\n filename: event.filename,\n lineno: event.lineno,\n colno: event.colno,\n name: event.error?.name,\n });\n\n recordOnce(payload, \"plugin.error.window\");\n };\n\n const rejectionHandler = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n\n const payload = buildErrorPayload(\n reason instanceof Error\n ? {\n message: `Unhandled Promise Rejection: ${reason.message}`,\n stack: reason.stack,\n name: reason.name,\n }\n : {\n message: `Unhandled Promise Rejection: ${String(reason)}`,\n name: \"UnhandledRejection\",\n }\n );\n\n recordOnce(payload, \"plugin.error.rejection\");\n };\n\n // Network errors: patch fetch to capture rejected requests\n const originalFetch: typeof window.fetch = window.fetch.bind(window);\n\n const patchedFetch: typeof window.fetch = (...args) =>\n originalFetch(...args).catch((err: unknown) => {\n const errorInfo =\n err instanceof Error\n ? {\n message: `Fetch failed: ${err.message}`,\n stack: err.stack,\n name: err.name,\n }\n : { message: `Fetch failed: ${String(err)}`, name: \"FetchError\" };\n const payload = buildErrorPayload(errorInfo);\n recordOnce(payload, \"plugin.error.fetch\");\n throw err;\n });\n\n window.fetch = patchedFetch;\n\n let inConsoleCapture = false;\n\n const originalConsoleError: typeof console.error =\n console.error.bind(console);\n\n console.error = (...args: unknown[]) => {\n try {\n originalConsoleError(...(args as Parameters<typeof console.error>));\n } finally {\n if (!inConsoleCapture) {\n inConsoleCapture = true;\n try {\n const firstError = args.find(\n (a): a is Error => a instanceof Error\n );\n const toStringSafe = (a: unknown): string => {\n if (typeof a === \"string\") {\n return a;\n }\n if (a instanceof Error) {\n return a.message;\n }\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n };\n const message = args.map(toStringSafe).join(\" \");\n\n const payload = buildErrorPayload({\n message,\n name: firstError?.name ?? \"ConsoleError\",\n stack: firstError?.stack,\n });\n\n recordOnce(payload, \"plugin.error.console_error\");\n } finally {\n inConsoleCapture = false;\n }\n }\n }\n };\n\n window.addEventListener(\"error\", errorHandler, {\n capture: true,\n });\n\n window.addEventListener(\"unhandledrejection\", rejectionHandler);\n\n return {\n key: \"errors\",\n api,\n cleanup: () => {\n window.removeEventListener(\"error\", errorHandler, {\n capture: true,\n } as EventListenerOptions);\n\n window.removeEventListener(\"unhandledrejection\", rejectionHandler);\n\n window.fetch = originalFetch;\n console.error = originalConsoleError as typeof console.error;\n },\n };\n }),\n});\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { Plugin } from "../../schemas.mjs";
2
+
3
+ //#region src/core/plugins/impl/page-events.d.ts
4
+
5
+ /**
6
+ * Page Events Plugin
7
+ *
8
+ * Handles tracking of page navigation and UI interaction events:
9
+ * - pageview: When a user views a page
10
+ * - pageleave: When a user leaves a page
11
+ * - ui_event: General UI interaction events (clicks, form submits, etc.)
12
+ */
13
+ declare const _default: Plugin<"pageEvents", unknown>;
14
+ //#endregion
15
+ export { _default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-events.d.mts","names":[],"sources":["../../../../src/core/plugins/impl/page-events.ts"],"sourcesContent":[],"mappings":""}
@@ -0,0 +1,131 @@
1
+ import { getElementSelector, getElementText } from "../dom-utils.mjs";
2
+ import { createEffectPlugin, defineEvent } from "../plugin-event-system.mjs";
3
+ import { Effect } from "effect";
4
+
5
+ //#region src/core/plugins/impl/page-events.ts
6
+ /**
7
+ * Page Events Plugin
8
+ *
9
+ * Handles tracking of page navigation and UI interaction events:
10
+ * - pageview: When a user views a page
11
+ * - pageleave: When a user leaves a page
12
+ * - ui_event: General UI interaction events (clicks, form submits, etc.)
13
+ */
14
+ var page_events_default = createEffectPlugin("pageEvents", {
15
+ name: "page-events",
16
+ events: [
17
+ defineEvent("pageview").value(),
18
+ defineEvent("pageleave").value(),
19
+ defineEvent("ui_event").value()
20
+ ],
21
+ setup: (ctx) => Effect.gen(function* () {
22
+ if (typeof window === "undefined") {
23
+ ctx.log.debug("page-events: SSR - skipping setup");
24
+ return;
25
+ }
26
+ let lastKey = null;
27
+ const shouldSkip = (key) => lastKey === key;
28
+ const remember = (key) => {
29
+ lastKey = key;
30
+ };
31
+ const handlePageView = () => {
32
+ const url = window.location.href;
33
+ const title = document.title;
34
+ ctx.log.trace("Page view", {
35
+ url,
36
+ title
37
+ });
38
+ const key = `pageview:${url}`;
39
+ if (shouldSkip(key)) return;
40
+ remember(key);
41
+ ctx.run(ctx.capture("pageview", {
42
+ url,
43
+ title
44
+ }).pipe(Effect.catchAll((error) => Effect.gen(function* () {
45
+ yield* Effect.logError("Failed to capture pageview", {
46
+ error: String(error),
47
+ url,
48
+ title
49
+ });
50
+ }))));
51
+ };
52
+ const handlePageLeave = () => {
53
+ const url = window.location.href;
54
+ const duration = performance.now();
55
+ ctx.log.trace("Page leave", {
56
+ url,
57
+ duration
58
+ });
59
+ const key = `pageleave:${url}`;
60
+ if (shouldSkip(key)) return;
61
+ remember(key);
62
+ ctx.captureNow("pageleave", {
63
+ url,
64
+ durationMs: Math.round(duration)
65
+ });
66
+ };
67
+ const handleUIEvent = (event) => {
68
+ const target = event.target;
69
+ if (!isInteractiveElement(target)) return;
70
+ const selector = getElementSelector(target);
71
+ const text = getElementText(target);
72
+ const TRUNCATE_LENGTH = 100;
73
+ const key = `ui_event:${selector}:${text}`;
74
+ if (shouldSkip(key)) return;
75
+ remember(key);
76
+ ctx.captureNow("ui_event", { event: {
77
+ type: "click",
78
+ selector,
79
+ text: text ? text.substring(0, TRUNCATE_LENGTH) : void 0,
80
+ url: window.location.href,
81
+ timestamp: Date.now()
82
+ } });
83
+ };
84
+ function isInteractiveElement(element) {
85
+ const isInteractiveTag = [
86
+ "BUTTON",
87
+ "A",
88
+ "INPUT",
89
+ "SELECT",
90
+ "TEXTAREA"
91
+ ].includes(element.tagName);
92
+ const hasClickHandler = element.onclick !== null;
93
+ const hasRole = element.getAttribute("role") === "button";
94
+ return isInteractiveTag || hasClickHandler || hasRole;
95
+ }
96
+ handlePageView();
97
+ const originalPushState = history.pushState;
98
+ const originalReplaceState = history.replaceState;
99
+ history.pushState = (...args) => {
100
+ const result = originalPushState.apply(history, args);
101
+ handlePageView();
102
+ return result;
103
+ };
104
+ history.replaceState = (...args) => {
105
+ const result = originalReplaceState.apply(history, args);
106
+ handlePageView();
107
+ return result;
108
+ };
109
+ window.addEventListener("popstate", handlePageView);
110
+ window.addEventListener("beforeunload", handlePageLeave);
111
+ const onVisibility = () => {
112
+ if (document.visibilityState === "hidden") handlePageLeave();
113
+ };
114
+ document.addEventListener("visibilitychange", onVisibility);
115
+ document.addEventListener("click", handleUIEvent, true);
116
+ return {
117
+ key: "pageEvents",
118
+ cleanup: () => {
119
+ history.pushState = originalPushState;
120
+ history.replaceState = originalReplaceState;
121
+ window.removeEventListener("popstate", handlePageView);
122
+ window.removeEventListener("beforeunload", handlePageLeave);
123
+ document.removeEventListener("visibilitychange", onVisibility);
124
+ document.removeEventListener("click", handleUIEvent, true);
125
+ }
126
+ };
127
+ })
128
+ });
129
+
130
+ //#endregion
131
+ export { page_events_default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-events.mjs","names":["lastKey: string | null"],"sources":["../../../../src/core/plugins/impl/page-events.ts"],"sourcesContent":["import { Effect } from \"effect\";\n\nimport { getElementSelector, getElementText } from \"../dom-utils.js\";\nimport { createEffectPlugin, defineEvent } from \"../plugin-event-system.js\";\n\n/**\n * Page Events Plugin\n *\n * Handles tracking of page navigation and UI interaction events:\n * - pageview: When a user views a page\n * - pageleave: When a user leaves a page\n * - ui_event: General UI interaction events (clicks, form submits, etc.)\n */\nexport default createEffectPlugin(\"pageEvents\", {\n name: \"page-events\",\n events: [\n defineEvent(\"pageview\").value(),\n defineEvent(\"pageleave\").value(),\n defineEvent(\"ui_event\").value(),\n ],\n setup: (ctx) =>\n Effect.gen(function* () {\n if (typeof window === \"undefined\") {\n ctx.log.debug(\"page-events: SSR - skipping setup\");\n return;\n }\n\n // Deduplicate consecutive identical events (best-effort, last-in-queue proxy)\n let lastKey: string | null = null;\n\n const shouldSkip = (key: string) => lastKey === key;\n\n const remember = (key: string) => {\n lastKey = key;\n };\n\n // Track page views on navigation\n const handlePageView = () => {\n const url = window.location.href;\n const title = document.title;\n\n ctx.log.trace(\"Page view\", { url, title });\n\n const key = `pageview:${url}`;\n\n if (shouldSkip(key)) {\n return;\n }\n\n remember(key);\n\n ctx.run(\n ctx.capture(\"pageview\", { url, title }).pipe(\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logError(\"Failed to capture pageview\", {\n error: String(error),\n url,\n title,\n });\n })\n )\n )\n );\n };\n\n // Track page leaves\n const handlePageLeave = () => {\n const url = window.location.href;\n const duration = performance.now();\n\n ctx.log.trace(\"Page leave\", { url, duration });\n\n const key = `pageleave:${url}`;\n\n if (shouldSkip(key)) {\n return;\n }\n\n remember(key);\n\n ctx.captureNow(\"pageleave\", {\n url,\n durationMs: Math.round(duration),\n });\n };\n\n // Track UI events (example: clicks on interactive elements)\n const handleUIEvent = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n\n // Only track clicks on interactive elements\n if (!isInteractiveElement(target)) {\n return;\n }\n\n const selector = getElementSelector(target);\n const text = getElementText(target);\n const TRUNCATE_LENGTH = 100;\n\n const key = `ui_event:${selector}:${text}`;\n\n if (shouldSkip(key)) {\n return;\n }\n\n remember(key);\n\n ctx.captureNow(\"ui_event\", {\n event: {\n type: \"click\",\n selector,\n text: text ? text.substring(0, TRUNCATE_LENGTH) : undefined,\n url: window.location.href,\n timestamp: Date.now(),\n },\n });\n };\n\n // Helper to determine if element is interactive\n function isInteractiveElement(element: HTMLElement): boolean {\n const interactiveTags = [\"BUTTON\", \"A\", \"INPUT\", \"SELECT\", \"TEXTAREA\"];\n const isInteractiveTag = interactiveTags.includes(element.tagName);\n const hasClickHandler = element.onclick !== null;\n const hasRole = element.getAttribute(\"role\") === \"button\";\n\n return isInteractiveTag || hasClickHandler || hasRole;\n }\n\n // Selector and text helpers are imported from dom-utils\n\n // Set up event listeners\n // Initial page view\n handlePageView();\n\n // Listen for navigation changes (for SPAs)\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n\n history.pushState = (...args) => {\n const result = originalPushState.apply(history, args);\n handlePageView();\n return result;\n };\n\n history.replaceState = (...args) => {\n const result = originalReplaceState.apply(history, args);\n handlePageView();\n return result;\n };\n\n window.addEventListener(\"popstate\", handlePageView);\n\n // Page leave events\n window.addEventListener(\"beforeunload\", handlePageLeave);\n\n const onVisibility = () => {\n if (document.visibilityState === \"hidden\") {\n handlePageLeave();\n }\n };\n document.addEventListener(\"visibilitychange\", onVisibility);\n\n // UI interaction events\n document.addEventListener(\"click\", handleUIEvent, true);\n\n return {\n key: \"pageEvents\",\n cleanup: () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n window.removeEventListener(\"popstate\", handlePageView);\n window.removeEventListener(\"beforeunload\", handlePageLeave);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n document.removeEventListener(\"click\", handleUIEvent, true);\n },\n };\n }),\n});\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Plugin } from "../../schemas.mjs";
2
+
3
+ //#region src/core/plugins/impl/rage-click.d.ts
4
+ declare const _default: Plugin<"rageClick", unknown>;
5
+ //#endregion
6
+ export { _default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rage-click.d.mts","names":[],"sources":["../../../../src/core/plugins/impl/rage-click.ts"],"sourcesContent":[],"mappings":""}
@@ -0,0 +1,53 @@
1
+ import { getElementSelector } from "../dom-utils.mjs";
2
+ import { createEffectPlugin, defineEvent } from "../plugin-event-system.mjs";
3
+ import { Effect } from "effect";
4
+
5
+ //#region src/core/plugins/impl/rage-click.ts
6
+ var rage_click_default = createEffectPlugin("rageClick", {
7
+ name: "rage-click",
8
+ events: [defineEvent("rage_click").value()],
9
+ config: {
10
+ timeWindow: 1e3,
11
+ threshold: 3
12
+ },
13
+ setup: (ctx, config) => Effect.gen(function* () {
14
+ if (typeof window === "undefined") {
15
+ ctx.log.debug("rage-click: SSR - skipping setup");
16
+ return;
17
+ }
18
+ let clickTimestamps = [];
19
+ function handleClick(event) {
20
+ const now = Date.now();
21
+ clickTimestamps = clickTimestamps.filter((ts) => now - ts < config.timeWindow);
22
+ clickTimestamps.push(now);
23
+ if (clickTimestamps.length >= config.threshold) {
24
+ const target = event.target;
25
+ const TRUNCATE_LENGTH = 100;
26
+ ctx.log.debug("Rage click detected", {
27
+ count: clickTimestamps.length,
28
+ selector: target ? getElementSelector(target) : "unknown"
29
+ });
30
+ ctx.span("plugin.rage_click", ctx.capture("rage_click", {
31
+ count: clickTimestamps.length,
32
+ timeWindow: config.timeWindow,
33
+ selector: target ? getElementSelector(target) : "unknown",
34
+ text: target?.textContent?.substring(0, TRUNCATE_LENGTH) || "",
35
+ x: event.clientX,
36
+ y: event.clientY,
37
+ timestamp: now
38
+ }));
39
+ clickTimestamps = [];
40
+ }
41
+ }
42
+ window.addEventListener("click", handleClick, true);
43
+ return {
44
+ key: "rageClick",
45
+ cleanup: () => {
46
+ window.removeEventListener("click", handleClick, true);
47
+ }
48
+ };
49
+ })
50
+ });
51
+
52
+ //#endregion
53
+ export { rage_click_default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rage-click.mjs","names":["clickTimestamps: number[]"],"sources":["../../../../src/core/plugins/impl/rage-click.ts"],"sourcesContent":["import { Effect } from \"effect\";\n\nimport type { PluginContext } from \"../../schemas.js\";\nimport { getElementSelector } from \"../dom-utils.js\";\nimport { createEffectPlugin, defineEvent } from \"../plugin-event-system.js\";\n\nexport default createEffectPlugin(\"rageClick\", {\n name: \"rage-click\",\n events: [defineEvent(\"rage_click\").value()],\n config: {\n timeWindow: 1000,\n threshold: 3,\n },\n setup: (\n ctx: PluginContext,\n config: { timeWindow: number; threshold: number }\n ) =>\n Effect.gen(function* () {\n if (typeof window === \"undefined\") {\n ctx.log.debug(\"rage-click: SSR - skipping setup\");\n return;\n }\n\n let clickTimestamps: number[] = [];\n\n function handleClick(event: MouseEvent) {\n const now = Date.now();\n clickTimestamps = clickTimestamps.filter(\n (ts) => now - ts < config.timeWindow\n );\n clickTimestamps.push(now);\n\n if (clickTimestamps.length >= config.threshold) {\n const target = event.target as Element;\n const TRUNCATE_LENGTH = 100;\n\n ctx.log.debug(\"Rage click detected\", {\n count: clickTimestamps.length,\n selector: target ? getElementSelector(target) : \"unknown\",\n });\n\n ctx.span(\n \"plugin.rage_click\",\n ctx.capture(\"rage_click\", {\n count: clickTimestamps.length,\n timeWindow: config.timeWindow,\n selector: target ? getElementSelector(target) : \"unknown\",\n text: target?.textContent?.substring(0, TRUNCATE_LENGTH) || \"\",\n x: event.clientX,\n y: event.clientY,\n timestamp: now,\n })\n );\n\n clickTimestamps = [];\n }\n }\n\n window.addEventListener(\"click\", handleClick, true);\n\n return {\n key: \"rageClick\",\n cleanup: () => {\n window.removeEventListener(\"click\", handleClick, true);\n },\n };\n }),\n});\n"],"mappings":";;;;;AAMA,yBAAe,mBAAmB,aAAa;CAC7C,MAAM;CACN,QAAQ,CAAC,YAAY,aAAa,CAAC,OAAO,CAAC;CAC3C,QAAQ;EACN,YAAY;EACZ,WAAW;EACZ;CACD,QACE,KACA,WAEA,OAAO,IAAI,aAAa;AACtB,MAAI,OAAO,WAAW,aAAa;AACjC,OAAI,IAAI,MAAM,mCAAmC;AACjD;;EAGF,IAAIA,kBAA4B,EAAE;EAElC,SAAS,YAAY,OAAmB;GACtC,MAAM,MAAM,KAAK,KAAK;AACtB,qBAAkB,gBAAgB,QAC/B,OAAO,MAAM,KAAK,OAAO,WAC3B;AACD,mBAAgB,KAAK,IAAI;AAEzB,OAAI,gBAAgB,UAAU,OAAO,WAAW;IAC9C,MAAM,SAAS,MAAM;IACrB,MAAM,kBAAkB;AAExB,QAAI,IAAI,MAAM,uBAAuB;KACnC,OAAO,gBAAgB;KACvB,UAAU,SAAS,mBAAmB,OAAO,GAAG;KACjD,CAAC;AAEF,QAAI,KACF,qBACA,IAAI,QAAQ,cAAc;KACxB,OAAO,gBAAgB;KACvB,YAAY,OAAO;KACnB,UAAU,SAAS,mBAAmB,OAAO,GAAG;KAChD,MAAM,QAAQ,aAAa,UAAU,GAAG,gBAAgB,IAAI;KAC5D,GAAG,MAAM;KACT,GAAG,MAAM;KACT,WAAW;KACZ,CAAC,CACH;AAED,sBAAkB,EAAE;;;AAIxB,SAAO,iBAAiB,SAAS,aAAa,KAAK;AAEnD,SAAO;GACL,KAAK;GACL,eAAe;AACb,WAAO,oBAAoB,SAAS,aAAa,KAAK;;GAEzD;GACD;CACL,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { Plugin } from "../../schemas.mjs";
2
+
3
+ //#region src/core/plugins/impl/replay.d.ts
4
+ interface ReplayAPI {
5
+ stop: () => void;
6
+ }
7
+ declare const _default: Plugin<"replay", unknown>;
8
+ //#endregion
9
+ export { ReplayAPI, _default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.mts","names":[],"sources":["../../../../src/core/plugins/impl/replay.ts"],"sourcesContent":[],"mappings":";;;UAYiB,SAAA;;;AAAjB,cAEC,QAFyB,EAEzB,MAFyB,CAAA,QAAA,EAAA,OAAA,CAAA"}
@@ -0,0 +1,144 @@
1
+ import { createEffectPlugin } from "../plugin-event-system.mjs";
2
+ import { Effect } from "effect";
3
+ import { v7 } from "uuid";
4
+
5
+ //#region src/core/plugins/impl/replay.ts
6
+ let replayStarted = false;
7
+ let replayStopFn = null;
8
+ var replay_default = createEffectPlugin("replay", {
9
+ name: "replay",
10
+ setup: (ctx) => Effect.gen(function* () {
11
+ if (typeof window === "undefined" || replayStarted || ctx.config.features.replay !== true) {
12
+ ctx.log.trace("Skipping replay setup in a non-browser environment");
13
+ return;
14
+ }
15
+ const startRecording = async () => {
16
+ ctx.log.trace("Starting replay recording");
17
+ const chunk = [];
18
+ let sessionId = v7();
19
+ const sendChunk = () => {
20
+ if (chunk.length === 0) return;
21
+ ctx.log.trace("Sending replay chunk", {
22
+ sessionId,
23
+ count: chunk.length
24
+ });
25
+ ctx.span("plugin.replay.send_chunk", ctx.capture("replay_chunk", {
26
+ ts: Date.now(),
27
+ count: chunk.length,
28
+ events: chunk.map((e) => JSON.stringify(e))
29
+ }));
30
+ chunk.length = 0;
31
+ };
32
+ const flushNow = () => {
33
+ try {
34
+ sendChunk();
35
+ } catch (error) {
36
+ ctx.log.error("Failed to flush replay chunk", { error });
37
+ }
38
+ };
39
+ const beforeUnload = () => flushNow();
40
+ const visibility = () => {
41
+ if (document.visibilityState === "hidden") flushNow();
42
+ };
43
+ const detach = () => {
44
+ try {
45
+ window.removeEventListener("beforeunload", beforeUnload);
46
+ document.removeEventListener("visibilitychange", visibility);
47
+ } catch (error) {
48
+ ctx.log.debug("Failed to remove listeners after flushing replay chunk", { error });
49
+ }
50
+ };
51
+ window.addEventListener("beforeunload", beforeUnload);
52
+ document.addEventListener("visibilitychange", visibility);
53
+ const rrweb = await import("rrweb");
54
+ if (typeof rrweb.record !== "function") return () => {
55
+ ctx.log.error("Failed to start recording", { error: "rrweb is not a function" });
56
+ };
57
+ const MAX_CHUNK_AGE_MS = 6e4;
58
+ const MAX_CHUNK_EVENTS = 100;
59
+ const MAX_CHUNK_SIZE_BYTES = 1e6;
60
+ const stopRecord = rrweb.record({
61
+ emit: (event) => {
62
+ if (chunk.length > 0) {
63
+ const first = chunk.at(0);
64
+ if (first && event.timestamp - first.timestamp > MAX_CHUNK_AGE_MS) {
65
+ sendChunk();
66
+ sessionId = v7();
67
+ }
68
+ }
69
+ chunk.push(event);
70
+ const estimatedSize = JSON.stringify(chunk).length;
71
+ if (chunk.length >= MAX_CHUNK_EVENTS || estimatedSize > MAX_CHUNK_SIZE_BYTES) sendChunk();
72
+ },
73
+ sampling: {
74
+ mousemove: false,
75
+ mouseInteraction: false,
76
+ scroll: 150,
77
+ media: 800,
78
+ input: "last"
79
+ },
80
+ slimDOMOptions: "all",
81
+ inlineStylesheet: false,
82
+ collectFonts: false,
83
+ recordCanvas: false
84
+ });
85
+ const intervalId = window.setInterval(() => {
86
+ try {
87
+ sendChunk();
88
+ } catch (error) {
89
+ ctx.log.warn("Failed to send replay chunk after recording event", {
90
+ error,
91
+ chunk
92
+ });
93
+ }
94
+ }, 1e4);
95
+ return () => {
96
+ try {
97
+ if (typeof stopRecord === "function") stopRecord();
98
+ } catch (error) {
99
+ ctx.log.debug("Failed to stop recording on cleanup", {
100
+ error,
101
+ stopRecord,
102
+ intervalId
103
+ });
104
+ }
105
+ try {
106
+ window.clearInterval(intervalId);
107
+ } catch (error) {
108
+ ctx.log.debug("Failed to clear interval on cleanup", {
109
+ error,
110
+ intervalId
111
+ });
112
+ }
113
+ detach();
114
+ };
115
+ };
116
+ try {
117
+ replayStopFn = yield* Effect.promise(() => startRecording());
118
+ replayStarted = true;
119
+ } catch (error) {
120
+ ctx.log.error("Failed to start session replay recording", { error });
121
+ replayStarted = false;
122
+ }
123
+ return {
124
+ key: "replay",
125
+ api: { stop: () => {
126
+ if (replayStopFn) {
127
+ replayStopFn();
128
+ replayStopFn = null;
129
+ replayStarted = false;
130
+ }
131
+ } },
132
+ cleanup: () => {
133
+ if (replayStopFn) {
134
+ replayStopFn();
135
+ replayStopFn = null;
136
+ replayStarted = false;
137
+ }
138
+ }
139
+ };
140
+ })
141
+ });
142
+
143
+ //#endregion
144
+ export { replay_default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.mjs","names":["replayStopFn: (() => void) | null","chunk: eventWithTime[]","uuidv7"],"sources":["../../../../src/core/plugins/impl/replay.ts"],"sourcesContent":["import type { eventWithTime } from \"@rrweb/types\";\nimport { Effect } from \"effect\";\nimport type { recordOptions } from \"rrweb\";\nimport { v7 as uuidv7 } from \"uuid\";\n\nimport type { PluginExpose } from \"../../schemas.js\";\nimport { createEffectPlugin } from \"../plugin-event-system.js\";\n\n// Ensure recording starts only once across multiple setup() calls\nlet replayStarted = false;\nlet replayStopFn: (() => void) | null = null;\n\nexport interface ReplayAPI {\n stop: () => void;\n}\n\nexport default createEffectPlugin(\"replay\", {\n name: \"replay\",\n setup: (ctx) =>\n Effect.gen(function* () {\n // Respect config.features.replay; schema should default based on env\n if (\n typeof window === \"undefined\" ||\n replayStarted ||\n ctx.config.features.replay !== true\n ) {\n ctx.log.trace(\"Skipping replay setup in a non-browser environment\");\n\n return;\n }\n\n const startRecording = async () => {\n ctx.log.trace(\"Starting replay recording\");\n\n const chunk: eventWithTime[] = [];\n let sessionId = uuidv7();\n\n const sendChunk = () => {\n if (chunk.length === 0) {\n return;\n }\n\n ctx.log.trace(\"Sending replay chunk\", {\n sessionId,\n count: chunk.length,\n });\n\n ctx.span(\n \"plugin.replay.send_chunk\",\n ctx.capture(\"replay_chunk\", {\n ts: Date.now(),\n count: chunk.length,\n events: chunk.map((e) => JSON.stringify(e)),\n })\n );\n\n chunk.length = 0;\n };\n\n const flushNow = () => {\n try {\n sendChunk();\n } catch (error) {\n ctx.log.error(\"Failed to flush replay chunk\", { error });\n }\n };\n\n const beforeUnload = () => flushNow();\n const visibility = () => {\n if (document.visibilityState === \"hidden\") {\n flushNow();\n }\n };\n\n const detach = () => {\n try {\n window.removeEventListener(\"beforeunload\", beforeUnload);\n document.removeEventListener(\"visibilitychange\", visibility);\n } catch (error) {\n ctx.log.debug(\n \"Failed to remove listeners after flushing replay chunk\",\n { error }\n );\n }\n };\n\n window.addEventListener(\"beforeunload\", beforeUnload);\n document.addEventListener(\"visibilitychange\", visibility);\n\n // Dynamically import rrweb to reduce initial bundle size\n const rrweb = await import(\"rrweb\");\n\n if (typeof rrweb.record !== \"function\") {\n return () => {\n ctx.log.error(\"Failed to start recording\", {\n error: \"rrweb is not a function\",\n });\n };\n }\n\n const MAX_CHUNK_AGE_MS = 60_000;\n const MAX_CHUNK_EVENTS = 100;\n const MAX_CHUNK_SIZE_BYTES = 1_000_000;\n\n const options: recordOptions<eventWithTime> = {\n emit: (event) => {\n // Start a new session if the chunk is too old\n if (chunk.length > 0) {\n const first = chunk.at(0);\n if (\n first &&\n event.timestamp - first.timestamp > MAX_CHUNK_AGE_MS\n ) {\n sendChunk();\n sessionId = uuidv7();\n }\n }\n\n chunk.push(event);\n\n // Send chunk if it's getting large\n const estimatedSize = JSON.stringify(chunk).length;\n if (\n chunk.length >= MAX_CHUNK_EVENTS ||\n estimatedSize > MAX_CHUNK_SIZE_BYTES\n ) {\n sendChunk();\n }\n },\n sampling: {\n mousemove: false,\n mouseInteraction: false,\n scroll: 150,\n media: 800,\n input: \"last\",\n },\n slimDOMOptions: \"all\",\n inlineStylesheet: false,\n collectFonts: false,\n recordCanvas: false,\n };\n\n const stopRecord = rrweb.record(options);\n\n const FLUSH_INTERVAL_MS = 10_000;\n const intervalId = window.setInterval(() => {\n try {\n sendChunk();\n } catch (error) {\n ctx.log.warn(\"Failed to send replay chunk after recording event\", {\n error,\n chunk,\n });\n }\n }, FLUSH_INTERVAL_MS);\n\n return () => {\n try {\n if (typeof stopRecord === \"function\") {\n stopRecord();\n }\n } catch (error) {\n ctx.log.debug(\"Failed to stop recording on cleanup\", {\n error,\n stopRecord,\n intervalId,\n });\n }\n try {\n window.clearInterval(intervalId);\n } catch (error) {\n ctx.log.debug(\"Failed to clear interval on cleanup\", {\n error,\n intervalId,\n });\n }\n detach();\n };\n };\n\n try {\n replayStopFn = yield* Effect.promise(() => startRecording());\n replayStarted = true;\n } catch (error) {\n ctx.log.error(\"Failed to start session replay recording\", { error });\n\n replayStarted = false;\n }\n\n const expose: PluginExpose<\"replay\", ReplayAPI> = {\n key: \"replay\",\n api: {\n stop: () => {\n if (replayStopFn) {\n replayStopFn();\n replayStopFn = null;\n replayStarted = false;\n }\n },\n },\n cleanup: () => {\n if (replayStopFn) {\n replayStopFn();\n replayStopFn = null;\n replayStarted = false;\n }\n },\n };\n\n return expose;\n }),\n});\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Plugin } from "../../schemas.mjs";
2
+
3
+ //#region src/core/plugins/impl/server-tracing.d.ts
4
+ type ServerTracingAPI = null;
5
+ declare const _default: Plugin<"serverTracing", unknown>;
6
+ //#endregion
7
+ export { ServerTracingAPI, _default as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-tracing.d.mts","names":[],"sources":["../../../../src/core/plugins/impl/server-tracing.ts"],"sourcesContent":[],"mappings":";;;KAqGY,gBAAA;cAAwB,UAAA"}