@monoscopetech/browser 0.7.1 → 0.8.0

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.
@@ -0,0 +1,12 @@
1
+ export declare class DevOverlay {
2
+ private el;
3
+ private eventCount;
4
+ private statusDot;
5
+ private countEl;
6
+ private connEl;
7
+ constructor();
8
+ private mount;
9
+ incrementEvents(): void;
10
+ setConnectionStatus(ok: boolean): void;
11
+ private dismiss;
12
+ }
@@ -0,0 +1,63 @@
1
+ const DISMISS_KEY = "monoscope-overlay-dismissed";
2
+ export class DevOverlay {
3
+ constructor() {
4
+ this.el = null;
5
+ this.eventCount = 0;
6
+ this.statusDot = null;
7
+ this.countEl = null;
8
+ this.connEl = null;
9
+ if (typeof document === "undefined")
10
+ return;
11
+ try {
12
+ if (sessionStorage.getItem(DISMISS_KEY))
13
+ return;
14
+ }
15
+ catch { }
16
+ this.mount();
17
+ }
18
+ mount() {
19
+ const el = document.createElement("div");
20
+ Object.assign(el.style, {
21
+ position: "fixed", bottom: "12px", right: "12px", zIndex: "2147483647",
22
+ background: "#1a1a2e", color: "#e0e0e0", fontFamily: "system-ui, sans-serif",
23
+ fontSize: "12px", padding: "6px 10px", borderRadius: "6px",
24
+ boxShadow: "0 2px 8px rgba(0,0,0,0.3)", display: "flex", alignItems: "center", gap: "6px",
25
+ cursor: "default", userSelect: "none",
26
+ });
27
+ this.statusDot = document.createElement("span");
28
+ Object.assign(this.statusDot.style, {
29
+ width: "7px", height: "7px", borderRadius: "50%", background: "#22c55e", display: "inline-block",
30
+ });
31
+ this.countEl = document.createElement("span");
32
+ this.countEl.textContent = "0";
33
+ this.countEl.style.opacity = "0.7";
34
+ this.connEl = document.createElement("span");
35
+ this.connEl.style.opacity = "0.7";
36
+ const close = document.createElement("span");
37
+ close.textContent = "\u00d7";
38
+ Object.assign(close.style, { cursor: "pointer", marginLeft: "4px", opacity: "0.6", fontSize: "14px" });
39
+ close.onclick = () => this.dismiss();
40
+ el.append(this.statusDot, " Monoscope ", this.countEl, " ", this.connEl, close);
41
+ this.el = el;
42
+ (document.body || document.documentElement).appendChild(el);
43
+ }
44
+ incrementEvents() {
45
+ this.eventCount++;
46
+ if (this.countEl)
47
+ this.countEl.textContent = String(this.eventCount);
48
+ }
49
+ setConnectionStatus(ok) {
50
+ if (this.connEl)
51
+ this.connEl.textContent = ok ? "Connected" : "Connection failed";
52
+ if (this.statusDot)
53
+ this.statusDot.style.background = ok ? "#22c55e" : "#ef4444";
54
+ }
55
+ dismiss() {
56
+ this.el?.remove();
57
+ this.el = null;
58
+ try {
59
+ sessionStorage.setItem(DISMISS_KEY, "1");
60
+ }
61
+ catch { }
62
+ }
63
+ }
package/dist/react.d.ts CHANGED
@@ -2,10 +2,14 @@ import { Component } from "react";
2
2
  import type { ReactNode, ErrorInfo } from "react";
3
3
  import Monoscope from ".";
4
4
  import type { MonoscopeConfig, MonoscopeUser } from "./types";
5
- export declare function MonoscopeProvider({ config, children }: {
6
- config: MonoscopeConfig;
5
+ type ProviderProps = {
7
6
  children: ReactNode;
8
- }): import("react/jsx-runtime").JSX.Element;
7
+ } & ({
8
+ config: MonoscopeConfig;
9
+ } | ({
10
+ config?: undefined;
11
+ } & MonoscopeConfig));
12
+ export declare function MonoscopeProvider({ children, ...rest }: ProviderProps): import("react/jsx-runtime").JSX.Element;
9
13
  export declare function useMonoscope(): Monoscope | null;
10
14
  export declare function useMonoscopeUser(user: MonoscopeUser | null | undefined): void;
11
15
  type ErrorBoundaryProps = {
package/dist/react.js CHANGED
@@ -3,7 +3,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext, useRef, useEffect, Component } from "react";
4
4
  import Monoscope from ".";
5
5
  const MonoscopeContext = createContext(null);
6
- export function MonoscopeProvider({ config, children }) {
6
+ export function MonoscopeProvider({ children, ...rest }) {
7
+ const config = rest.config ?? rest;
7
8
  const ref = useRef(null);
8
9
  if (!ref.current && typeof window !== "undefined") {
9
10
  ref.current = new Monoscope(config);
package/dist/replay.js CHANGED
@@ -128,7 +128,8 @@ export class MonoscopeReplay {
128
128
  return;
129
129
  this.trimEvents();
130
130
  this.isSaving = true;
131
- const baseUrl = `${this.config.replayEventsBaseUrl || "https://app.monoscope.tech"}/rrweb/${this.config.projectId}`;
131
+ const apiKey = this.config.apiKey || this.config.projectId || "";
132
+ const baseUrl = `${this.config.replayEventsBaseUrl || "https://app.monoscope.tech"}/api/v1/rrweb`;
132
133
  const eventsToSend = [...this.events];
133
134
  this.events = [];
134
135
  const payload = {
@@ -139,6 +140,7 @@ export class MonoscopeReplay {
139
140
  eventCount: eventsToSend.length,
140
141
  user: Object.keys(this.userAttributes).length > 0 ? this.userAttributes : undefined,
141
142
  };
143
+ const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` };
142
144
  try {
143
145
  if (forceSynchronous && typeof navigator !== "undefined" && navigator.sendBeacon) {
144
146
  const blob = new Blob([JSON.stringify(payload)], {
@@ -148,7 +150,7 @@ export class MonoscopeReplay {
148
150
  if (!sent) {
149
151
  fetch(baseUrl, {
150
152
  method: "POST",
151
- headers: { "Content-Type": "application/json" },
153
+ headers,
152
154
  body: JSON.stringify(payload),
153
155
  keepalive: true,
154
156
  }).catch(() => { });
@@ -158,12 +160,19 @@ export class MonoscopeReplay {
158
160
  const body = JSON.stringify(payload);
159
161
  const response = await fetch(baseUrl, {
160
162
  method: "POST",
161
- headers: { "Content-Type": "application/json" },
163
+ headers,
162
164
  body,
163
165
  keepalive: body.length < 63000,
164
166
  });
165
167
  if (!response.ok) {
166
- throw new Error(`Failed to save replay events: ${response.status} ${response.statusText}`);
168
+ const status = response.status;
169
+ if (status === 401 || status === 403) {
170
+ console.warn("[Monoscope] Replay upload authentication failed. Your apiKey may be invalid.");
171
+ }
172
+ else {
173
+ console.warn(`[Monoscope] Replay upload failed (${status}). Check your apiKey and network.`);
174
+ }
175
+ throw new Error(`Failed to save replay events: ${status} ${response.statusText}`);
167
176
  }
168
177
  if (this.config.debug) {
169
178
  console.log(`Successfully saved ${eventsToSend.length} replay events`);
package/dist/tracing.d.ts CHANGED
@@ -5,13 +5,16 @@ export declare class OpenTelemetryManager {
5
5
  private sessionId;
6
6
  private tabId;
7
7
  private provider;
8
+ private processor;
8
9
  private longTaskObserver;
9
10
  private resourceObserver;
10
11
  private _enabled;
11
12
  private _configured;
13
+ private _firstExportLogged;
12
14
  private pageSpan;
13
15
  private pageContext;
14
16
  private endPageSpanHandler;
17
+ onExportStatus: ((ok: boolean) => void) | null;
15
18
  constructor(config: MonoscopeConfig, sessionId: string, tabId: string);
16
19
  private createProvider;
17
20
  private commonAttrs;
@@ -25,6 +28,7 @@ export declare class OpenTelemetryManager {
25
28
  private observeResourceTiming;
26
29
  startSpan<T>(name: string, fn: (span: Span) => T): T;
27
30
  recordEvent(name: string, attributes?: Record<string, string | number | boolean>): void;
31
+ forceFlush(): Promise<void>;
28
32
  updateSessionId(sessionId: string): void;
29
33
  setEnabled(enabled: boolean): void;
30
34
  shutdown(): Promise<void>;
package/dist/tracing.js CHANGED
@@ -5,6 +5,7 @@ import { ZoneContextManager } from "@opentelemetry/context-zone";
5
5
  import { registerInstrumentations } from "@opentelemetry/instrumentation";
6
6
  import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request";
7
7
  import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
8
+ import { UserInteractionInstrumentation } from "@opentelemetry/instrumentation-user-interaction";
8
9
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
9
10
  import { resourceFromAttributes } from "@opentelemetry/resources";
10
11
  import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
@@ -13,30 +14,65 @@ import { context, SpanStatusCode, trace } from "@opentelemetry/api";
13
14
  const MONOSCOPE_TRACER = "monoscope";
14
15
  export class OpenTelemetryManager {
15
16
  constructor(config, sessionId, tabId) {
17
+ this.processor = null;
16
18
  this.longTaskObserver = null;
17
19
  this.resourceObserver = null;
18
20
  this._enabled = true;
19
21
  this._configured = false;
22
+ this._firstExportLogged = false;
20
23
  this.pageSpan = null;
21
24
  this.pageContext = null;
22
25
  this.endPageSpanHandler = null;
26
+ this.onExportStatus = null;
23
27
  this.config = config;
24
28
  this.sessionId = sessionId;
25
29
  this.tabId = tabId;
26
30
  this.provider = this.createProvider();
27
31
  }
28
32
  createProvider() {
29
- const { serviceName, resourceAttributes = {}, exporterEndpoint, projectId } = this.config;
33
+ const { serviceName, resourceAttributes = {}, exporterEndpoint } = this.config;
34
+ const apiKey = this.config.apiKey || this.config.projectId || "";
35
+ const self = this;
36
+ const realExporter = new OTLPTraceExporter({
37
+ url: exporterEndpoint || "https://otelcol.monoscope.tech/v1/traces",
38
+ headers: { "x-api-key": apiKey },
39
+ });
40
+ // Wrap exporter to capture export results for diagnostics
41
+ const wrappedExporter = Object.create(realExporter, {
42
+ export: {
43
+ value(spans, resultCallback) {
44
+ return realExporter.export(spans, (result) => {
45
+ if (!self._firstExportLogged) {
46
+ self._firstExportLogged = true;
47
+ const ok = result.code === 0;
48
+ if (self.config.debug) {
49
+ console.log(ok ? "%c[Monoscope] ✓ First trace sent successfully" : "%c[Monoscope] ✗ First trace export failed", ok ? "color: #22c55e; font-weight: bold" : "color: #ef4444; font-weight: bold", ok ? "" : result.error || "");
50
+ if (!ok) {
51
+ const msg = String(result.error || "");
52
+ if (msg.includes("401") || msg.includes("403")) {
53
+ console.warn("[Monoscope] Authentication failed. Your apiKey may be invalid.");
54
+ }
55
+ else {
56
+ console.warn("[Monoscope] Could not reach Monoscope endpoint. Check your apiKey and network.");
57
+ }
58
+ }
59
+ }
60
+ self.onExportStatus?.(ok);
61
+ }
62
+ resultCallback(result);
63
+ });
64
+ },
65
+ },
66
+ });
67
+ const processor = new BatchSpanProcessor(wrappedExporter);
68
+ this.processor = processor;
30
69
  return new WebTracerProvider({
31
70
  resource: resourceFromAttributes({
32
71
  [ATTR_SERVICE_NAME]: serviceName,
33
- "at-project-id": projectId,
72
+ "x-api-key": apiKey,
34
73
  ...resourceAttributes,
35
74
  }),
36
- spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter({
37
- url: exporterEndpoint || "https://otelcol.apitoolkit.io/v1/traces",
38
- headers: {},
39
- }))],
75
+ spanProcessors: [processor],
40
76
  });
41
77
  }
42
78
  commonAttrs() {
@@ -96,6 +132,7 @@ export class OpenTelemetryManager {
96
132
  new FetchInstrumentation({
97
133
  propagateTraceHeaderCorsUrls: headerUrls, ignoreUrls, applyCustomAttributesOnSpan: addAttrs,
98
134
  }),
135
+ ...(this.config.enableUserInteraction ? [new UserInteractionInstrumentation()] : []),
99
136
  ],
100
137
  });
101
138
  this.startPageSpan();
@@ -223,6 +260,7 @@ export class OpenTelemetryManager {
223
260
  recordEvent(name, attributes = {}) {
224
261
  this.emitSpan(name, attributes);
225
262
  }
263
+ async forceFlush() { await this.processor?.forceFlush(); }
226
264
  updateSessionId(sessionId) { this.sessionId = sessionId; }
227
265
  setEnabled(enabled) { this._enabled = enabled; }
228
266
  async shutdown() {
@@ -1 +1 @@
1
- {"root":["../src/breadcrumbs.ts","../src/errors.ts","../src/index.ts","../src/react.tsx","../src/replay.ts","../src/router.ts","../src/tracing.ts","../src/types.ts","../src/web-vitals.ts"],"version":"5.9.3"}
1
+ {"root":["../src/breadcrumbs.ts","../src/errors.ts","../src/index.ts","../src/overlay.ts","../src/react.tsx","../src/replay.ts","../src/router.ts","../src/tracing.ts","../src/types.ts","../src/web-vitals.ts"],"version":"5.9.3"}
package/dist/types.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import Monoscope from ".";
2
2
  export type MonoscopeConfig = {
3
- serviceName: string;
3
+ apiKey?: string;
4
+ serviceName?: string;
4
5
  exporterEndpoint?: string;
5
6
  propagateTraceHeaderCorsUrls?: RegExp[];
6
- projectId: string;
7
+ /** @deprecated Use `apiKey` instead. */
8
+ projectId?: string;
7
9
  resourceAttributes?: Record<string, string>;
8
10
  instrumentations?: unknown[];
9
11
  replayEventsBaseUrl?: string;
@@ -14,6 +16,7 @@ export type MonoscopeConfig = {
14
16
  replaySampleRate?: number;
15
17
  enabled?: boolean;
16
18
  resourceTimingThresholdMs?: number;
19
+ enableUserInteraction?: boolean;
17
20
  };
18
21
  export type MonoscopeUser = {
19
22
  email?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoscopetech/browser",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",