@monoscopetech/browser 0.7.2 → 0.9.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/router.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- type EmitFn = (name: string, attrs: Record<string, string | number>) => void;
1
+ type NavFn = (from: string, to: string, method: string) => void;
2
2
  export declare class SPARouter {
3
- private emit;
3
+ private onNavigation;
4
4
  private currentUrl;
5
5
  private _active;
6
6
  private origPushState;
7
7
  private origReplaceState;
8
8
  private popstateHandler;
9
- constructor(emit: EmitFn);
9
+ constructor(onNavigation: NavFn);
10
10
  start(): void;
11
11
  stop(): void;
12
12
  }
package/dist/router.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { addBreadcrumb } from "./breadcrumbs";
2
2
  export class SPARouter {
3
- constructor(emit) {
3
+ constructor(onNavigation) {
4
4
  this.currentUrl = "";
5
5
  this._active = false;
6
6
  this.origPushState = null;
7
7
  this.origReplaceState = null;
8
8
  this.popstateHandler = null;
9
- this.emit = emit;
9
+ this.onNavigation = onNavigation;
10
10
  }
11
11
  start() {
12
12
  if (typeof window === "undefined" || this._active)
@@ -23,12 +23,7 @@ export class SPARouter {
23
23
  return;
24
24
  this.currentUrl = to;
25
25
  addBreadcrumb({ type: "navigation", message: `${from} → ${to}`, data: { method } });
26
- this.emit("navigation", {
27
- "navigation.from": from,
28
- "navigation.to": to,
29
- "navigation.method": method,
30
- "page.title": document.title,
31
- });
26
+ this.onNavigation(from, to, method);
32
27
  }
33
28
  catch (e) {
34
29
  try {
package/dist/tracing.d.ts CHANGED
@@ -1,30 +1,61 @@
1
1
  import { MonoscopeConfig, MonoscopeUser } from "./types";
2
2
  import { Span } from "@opentelemetry/api";
3
+ export type MonoscopeKind = "page_load" | "navigation" | "interaction" | "network" | "resource" | "web_vital" | "error" | "long_task" | "custom";
4
+ export declare function shortPath(url: string): string;
5
+ export declare function describeElement(el: EventTarget | Element | null | undefined): string;
6
+ /**
7
+ * RFC4122 v4 id with a fallback for non-secure contexts (HTTP / file:// /
8
+ * older Safari/Edge) where `crypto.randomUUID` is undefined.
9
+ */
10
+ export declare function newId(): string;
3
11
  export declare class OpenTelemetryManager {
4
12
  private config;
5
13
  private sessionId;
6
14
  private tabId;
15
+ private pageviewId;
7
16
  private provider;
17
+ private processor;
8
18
  private longTaskObserver;
9
19
  private resourceObserver;
10
20
  private _enabled;
11
21
  private _configured;
12
- private pageSpan;
13
- private pageContext;
14
- private endPageSpanHandler;
22
+ private _firstExportLogged;
23
+ private routeSpan;
24
+ private routeContext;
25
+ private routeIdleTimer;
26
+ private flushOnHideHandler;
27
+ private visibilityHandler;
28
+ onExportStatus: ((ok: boolean) => void) | null;
29
+ onSpanStart: (() => void) | null;
15
30
  constructor(config: MonoscopeConfig, sessionId: string, tabId: string);
16
31
  private createProvider;
17
32
  private commonAttrs;
18
33
  private applyCommonAttrs;
19
34
  configure(): void;
20
- private startPageSpan;
21
- getPageContext(): import("@opentelemetry/api").Context | null;
22
- private withPageContext;
35
+ getPageviewId(): string;
36
+ rotatePageview(): string;
37
+ /**
38
+ * Open a short-lived route.change root span for an SPA navigation. Closes
39
+ * any previous route span, rotates pageview.id, and publishes the span as
40
+ * the active context so async work started in the same Zone (fetch/XHR)
41
+ * inherits it as parent. Auto-closes after ROUTE_IDLE_MS or on next nav.
42
+ */
43
+ startRouteChange(from: string, to: string, method: string): void;
44
+ endRouteChange(): void;
45
+ /**
46
+ * Flush pending spans before the JS context is destroyed. Critical for
47
+ * MPAs where every navigation unloads the page, and still valuable for
48
+ * SPAs at tab close. pagehide is preferred over beforeunload (fires for
49
+ * bfcache eviction and mobile backgrounding; beforeunload does not).
50
+ */
51
+ private installFlushOnHide;
52
+ private withActiveContext;
23
53
  emitSpan(name: string, attrs: Record<string, string | number | boolean>, configure?: (span: Span) => void): void;
24
54
  private observeLongTasks;
25
55
  private observeResourceTiming;
26
56
  startSpan<T>(name: string, fn: (span: Span) => T): T;
27
57
  recordEvent(name: string, attributes?: Record<string, string | number | boolean>): void;
58
+ forceFlush(): Promise<void>;
28
59
  updateSessionId(sessionId: string): void;
29
60
  setEnabled(enabled: boolean): void;
30
61
  shutdown(): Promise<void>;