Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/adapter-kit/src/report-server-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
//
// RUNTIME-PORTABLE (node + edge): uses only @bugsee/core APIs. Correlation to the session is automatic —
// `logException` snapshots the active per-request context. Fully defensive: never throws out of the hook.
import { type BugseeClient, getCarrierClient, type LogExceptionOptions } from '@bugsee/core';
import {
asInternalClient,
type BugseeClient,
getCarrierClient,
type InternalLogExceptionOptions,
} from '@bugsee/core';

export interface ReportServerErrorOptions {
/** Resolve the Bugsee client. Default: the process/isolate carrier singleton. */
getClient?: () => BugseeClient | undefined;
/** A capture event stamping the framework's route attribution (name + params) onto the recording. */
event?: { name: string; params?: Record<string, unknown> };
/** `logException` mechanism. Default `'http-error'`. */
mechanism?: NonNullable<LogExceptionOptions['mechanism']>;
mechanism?: NonNullable<InternalLogExceptionOptions['mechanism']>;
}

/**
Expand All @@ -35,7 +40,9 @@ export function reportServerError(error: unknown, options: ReportServerErrorOpti
// Attribution is best-effort; the report below must still happen.
}
}
void client.logException(error, { mechanism: options.mechanism ?? 'http-error' });
void asInternalClient(client).logException(error, {
mechanism: options.mechanism ?? 'http-error',
});
} catch {
// Never replace / disrupt the framework's own error handling.
}
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
type TraceSample,
} from '@bugsee/capture';
import {
asInternalClient,
BUGSEE_SDK_VERSION,
type BugseeApi,
type BugseeClient,
Expand Down Expand Up @@ -740,7 +741,7 @@ export async function launchCore(
const showReportDialog = createReportDialogLauncher({
// No DOM (a worker, SSR) → there is nothing to draw on; the launcher then resolves null.
document: domDocument as Document,
prepare: () => client.prepareUpload(),
prepare: () => asInternalClient(client).prepareUpload(),
isLaunched: () => domDocument !== undefined && client.isLaunched(),
getUserIdentifier: () => client.getUserIdentifier(),
setUserIdentifier: (email) => client.setUserIdentifier(email),
Expand Down
12 changes: 6 additions & 6 deletions packages/browser/src/report-dialog.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import type { PreparedUpload, UploadOptions, UploadResult } from '@bugsee/core';
import type { InternalPreparedUpload, InternalUploadOptions, UploadResult } from '@bugsee/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
createReportDialogLauncher,
Expand All @@ -21,10 +21,10 @@ const UI: ReportDialogUiOptions = {

interface Harness {
deps: ReportDialogDeps;
/** `PreparedUpload.send` — what the dialog submits. */
upload: ReturnType<typeof vi.fn<(options: UploadOptions) => Promise<UploadResult>>>;
/** `InternalPreparedUpload.send` — what the dialog submits. */
upload: ReturnType<typeof vi.fn<(options: InternalUploadOptions) => Promise<UploadResult>>>;
discard: ReturnType<typeof vi.fn<() => void>>;
prepare: ReturnType<typeof vi.fn<() => PreparedUpload | null>>;
prepare: ReturnType<typeof vi.fn<() => InternalPreparedUpload | null>>;
setUserIdentifier: ReturnType<typeof vi.fn<(email: string) => void>>;
clearUserIdentifier: ReturnType<typeof vi.fn<() => void>>;
show: ReturnType<typeof createReportDialogLauncher>;
Expand All @@ -34,11 +34,11 @@ const harness = (
overrides: Partial<ReportDialogDeps> = {},
ui: Partial<ReportDialogUiOptions> = {},
): Harness => {
const upload = vi.fn<(options: UploadOptions) => Promise<UploadResult>>(async () => ({
const upload = vi.fn<(options: InternalUploadOptions) => Promise<UploadResult>>(async () => ({
ok: true,
}));
const discard = vi.fn<() => void>();
const prepare = vi.fn<() => PreparedUpload | null>(() => ({ send: upload, discard }));
const prepare = vi.fn<() => InternalPreparedUpload | null>(() => ({ send: upload, discard }));
const setUserIdentifier = vi.fn<(email: string) => void>();
const clearUserIdentifier = vi.fn<() => void>();
const deps: ReportDialogDeps = {
Expand Down
4 changes: 2 additions & 2 deletions packages/browser/src/report-dialog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PreparedUpload, UploadResult } from '@bugsee/core';
import type { InternalPreparedUpload, UploadResult } from '@bugsee/core';
import { DEFAULT_REPORT_DIALOG_STRINGS, type ReportDialogStrings } from './report-dialog-strings';
import { REPORT_DIALOG_CLASS_PREFIX, REPORT_DIALOG_CSS } from './report-dialog-styles';

Expand Down Expand Up @@ -81,7 +81,7 @@ export interface ReportDialogUiOptions {
export interface ReportDialogDeps {
document: Document;
/** Freeze the capture and return the report-in-waiting (`client.prepareUpload`); `null` = cannot. */
prepare(): PreparedUpload | null;
prepare(): InternalPreparedUpload | null;
/** The dialog does nothing, as on mobile, while the SDK is not running. */
isLaunched(): boolean;
getUserIdentifier(): string | null;
Expand Down
3 changes: 1 addition & 2 deletions packages/bugsee/src/bun.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { launchCore } from '@bugsee/bun';
import type { Bugsee } from '@bugsee/node';
import { type BugseeServerLaunchOptions, createServerLaunch } from './server-launch';
import { type Bugsee, type BugseeServerLaunchOptions, createServerLaunch } from './server-launch';

// The `bugsee` umbrella launch() for BUN — selected via the package `exports` "bun" condition, which must
// be listed BEFORE "node" because Bun sets both.
Expand Down
3 changes: 1 addition & 2 deletions packages/bugsee/src/deno.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { launchCore } from '@bugsee/deno';
import type { Bugsee } from '@bugsee/node';
import { type BugseeServerLaunchOptions, createServerLaunch } from './server-launch';
import { type Bugsee, type BugseeServerLaunchOptions, createServerLaunch } from './server-launch';

// The `bugsee` umbrella launch() for DENO — selected via the package `exports` "deno" condition, which must
// be listed BEFORE "node" because Deno sets both under node compatibility.
Expand Down
2 changes: 1 addition & 1 deletion packages/bugsee/src/index.bun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// because Bun sets both). Re-exports the server SDK surface + the umbrella launch() bound to
// @bugsee/bun's composition root, so the Bun-specific defaults are not silently lost. See ./bun.

export type { Bugsee } from '@bugsee/bun';
// Manual-instrumentation argument types — the curated public surface every adapter re-exports so users
// can type their own event()/addBreadcrumb()/logException() calls without reaching into @bugsee/core.
export type {
Expand All @@ -14,3 +13,4 @@ export type {
} from '@bugsee/core';
export type { BugseeSpanProcessor } from '@bugsee/opentelemetry';
export { type BugseeBunLaunchOptions, launch } from './bun';
export type { Bugsee } from './server-launch';
2 changes: 1 addition & 1 deletion packages/bugsee/src/index.deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export type {
LogExceptionOptions,
UploadOptions,
} from '@bugsee/core';
export type { Bugsee } from '@bugsee/deno';
export type { BugseeSpanProcessor } from '@bugsee/opentelemetry';
export { type BugseeDenoLaunchOptions, launch } from './deno';
export type { Bugsee } from './server-launch';
16 changes: 15 additions & 1 deletion packages/bugsee/src/index.edge-light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,18 @@ export type {
UploadOptions,
} from '@bugsee/core';
export * from '@bugsee/vercel-edge';
export { launchEdge as launch } from '@bugsee/vercel-edge';

import type { PerformanceFacade } from '@bugsee/performance';
import {
type BugseeEdgeLaunchOptions,
type Bugsee as EdgeBugsee,
launchEdge,
} from '@bugsee/vercel-edge';
import { withPerformanceFacade } from './performance-facade';

// The edge runtime collects no performance data, so the top-level performance calls are no-ops here (as
// on the mobile SDKs with performance off). Explicit exports win over the `export *` above.
export type Bugsee = EdgeBugsee & PerformanceFacade;
export function launch(appToken: string, options?: BugseeEdgeLaunchOptions): Bugsee {
return withPerformanceFacade(launchEdge(appToken, options));
}
3 changes: 2 additions & 1 deletion packages/bugsee/src/index.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ export type {
// F-6: the `HttpTransport` primitive (+ its request/response option shapes), so a custom transport (a
// proxy, a queueing shim, a test double) passed to the `transport` launch option types without an
// unsafe cast.
export type { Bugsee, HttpRequestOptions, HttpResponse, HttpTransport } from '@bugsee/node';
export type { HttpRequestOptions, HttpResponse, HttpTransport } from '@bugsee/node';
// F-5: the per-request context store — needed by app code that wants to call `store.setAttribute()`
// directly (e.g. the per-request-attribute concurrency pattern) without reaching into @bugsee/node's
// internal DI container (`client.getServiceProvider(...)`) or taking a direct dependency on it purely
// to reach this token, as every framework adapter's own hooks.ts already does internally.
export { type RequestContextStore, RequestContextStoreToken } from '@bugsee/node';
export type { BugseeSpanProcessor } from '@bugsee/opentelemetry';
export { type BugseeNodeLaunchOptions, launch } from './node';
export type { Bugsee } from './server-launch';
6 changes: 4 additions & 2 deletions packages/bugsee/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Umbrella package; the batteries-included entry (Tier 5). It re-exports the platform SDK surface and
// adds launch() = the platform composition root + the on-by-default extensions (@bugsee/performance:
// web-vitals + transactions + http spans) wired in. See docs/design/sdk-design.md §5.
export type { Bugsee, ReportDialogOptions, ReportDialogStrings } from '@bugsee/browser';
export type { ReportDialogOptions, ReportDialogStrings } from '@bugsee/browser';
// Manual-instrumentation argument types — the curated public surface every adapter re-exports so users
// can type their own event()/addBreadcrumb()/logException() calls without reaching into @bugsee/core.
export type {
Expand All @@ -20,12 +20,14 @@ export type { BugseeSpanProcessor } from '@bugsee/opentelemetry';
// package. Nothing else here referenced it, so the published .d.ts never mentioned it and
// `client.ext('performance')` had NO usable type for anyone installing from npm — it worked inside
// this monorepo only because source-based `exports` make the whole graph visible.
// The top-level performance calls (mobile parity) and the no-op-safe facade type.
export type {
PerformanceApi,
PerformanceFacade,
Span,
SpanStatus,
StartTransactionOptions,
Transaction,
TransactionNameSource,
} from '@bugsee/performance';
export { type BugseeLaunchOptionsWithPerformance, launch } from './launch';
export { type Bugsee, type BugseeLaunchOptionsWithPerformance, launch } from './launch';
16 changes: 16 additions & 0 deletions packages/bugsee/src/index.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ export type {
UploadOptions,
} from '@bugsee/core';
export * from '@bugsee/webworker';

import type { PerformanceFacade } from '@bugsee/performance';
import {
type BugseeWorkerLaunchOptions,
launch as launchWorker,
type Bugsee as WorkerBugsee,
} from '@bugsee/webworker';
import { withPerformanceFacade } from './performance-facade';

// Workers collect no performance data, so the top-level performance calls are no-ops here (as on the
// mobile SDKs with performance off). They exist so one code base can call them on every runtime.
// Explicit exports win over the `export *` above.
export type Bugsee = WorkerBugsee & PerformanceFacade;
export function launch(appToken: string, options?: BugseeWorkerLaunchOptions): Bugsee {
return withPerformanceFacade(launchWorker(appToken, options));
}
15 changes: 15 additions & 0 deletions packages/bugsee/src/index.workerd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@
// no pageload or startup transaction to wire.

export * from '@bugsee/cloudflare';

import {
type BugseeEdgeLaunchOptions,
type Bugsee as CloudflareBugsee,
launch as launchCloudflare,
} from '@bugsee/cloudflare';
import type { PerformanceFacade } from '@bugsee/performance';
import { withPerformanceFacade } from './performance-facade';

// The workerd runtime collects no performance data, so the top-level performance calls are no-ops here (as
// on the mobile SDKs with performance off). Explicit exports win over the `export *` above.
export type Bugsee = CloudflareBugsee & PerformanceFacade;
export function launch(appToken: string, options?: BugseeEdgeLaunchOptions): Bugsee {
return withPerformanceFacade(launchCloudflare(appToken, options));
}
export type {
AttributeValue,
Breadcrumb,
Expand Down
30 changes: 28 additions & 2 deletions packages/bugsee/src/launch.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { BrowserProbe, Bugsee, DeviceIdLocalStorage } from '@bugsee/browser';
import type { BrowserProbe, DeviceIdLocalStorage } from '@bugsee/browser';
import { launchCore, resetBrowserDeviceIdCache } from '@bugsee/browser';
import type { HttpRequestOptions, HttpResponse, HttpTransport } from '@bugsee/core';
import type { BugseeSpanProcessor } from '@bugsee/opentelemetry';
import { serializeTransaction, type Transaction } from '@bugsee/performance';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type BugseeLaunchOptionsWithPerformance, launch } from './launch';
import { type Bugsee, type BugseeLaunchOptionsWithPerformance, launch } from './launch';

// --- fakes ---------------------------------------------------------------------------------------

Expand Down Expand Up @@ -174,6 +174,32 @@ describe('bugsee umbrella launch', () => {
expect(client.ext('performance').getActiveSpan()).toBeDefined(); // the pageload transaction is active
});

it('exposes startTransaction / startSpan / getActiveSpan straight on the launched client', async () => {
const client = await track(await launch('tok', base({ carrier: {} })));
const txn = client.startTransaction('Checkout', 'ui.load', { cart: 2 });
expect(txn.getName()).toBe('Checkout');
expect(txn.getAttributes()).toMatchObject({ cart: 2 });
expect(client.getActiveSpan()).toBe(txn);
expect(client.ext('performance').getActiveSpan()).toBe(txn);
expect(client.startSpan('db.query', 'select').getTraceId()).toBe(txn.getTraceId());
});

it('the calls are harmless no-ops when performanceMonitoring is false', async () => {
const client = await track(
await launch('tok', base({ carrier: {}, performanceMonitoring: false })),
);
expect(client.startTransaction('Checkout', 'ui.load').isSampled()).toBe(false);
expect(client.startSpan('db').getOperation()).toBe('db');
expect(client.getActiveSpan()).toBeUndefined();
});

it('a repeat launch still returns a client carrying the calls', async () => {
const carrier = {};
await track(await launch('tok', base({ carrier })));
const second = await launch('tok', base({ carrier }));
expect(typeof second.startTransaction).toBe('function');
});

it('does NOT wire performance when performanceMonitoring is false', async () => {
const client = await track(
await launch('tok', base({ carrier: {}, performanceMonitoring: false })),
Expand Down
9 changes: 7 additions & 2 deletions packages/bugsee/src/launch.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import {
type Bugsee,
type Bugsee as BrowserBugsee,
type BugseeLaunchOptions,
createBrowserInteractionSource,
createBrowserNavigationSource,
launchCore,
readMetaTraceContinuation,
} from '@bugsee/browser';
import type { PerformanceFacade } from '@bugsee/performance';
import { withPerformanceFacade } from './performance-facade';
import { type UmbrellaExtensionOptions, wireUmbrella } from './wire';

/** The launched client in the browser: the platform client plus the top-level performance calls. */
export type Bugsee = BrowserBugsee & PerformanceFacade;

// The `bugsee` umbrella launch() for the BROWSER — the batteries-included entry. It runs the browser
// composition root (@bugsee/browser's launchCore, which returns the client PLUS its internal wiring) and
// then wires the on-by-default extensions (@bugsee/performance + @bugsee/opentelemetry) via the shared,
Expand All @@ -27,7 +32,7 @@ export async function launch(
// Browser: pageload transaction. The browser-only capture sources are injected here (as factories) so
// the shared wireUmbrella — and therefore the node umbrella entry — never imports @bugsee/browser.
return internals === undefined
? client
? withPerformanceFacade(client)
: wireUmbrella(
client,
internals,
Expand Down
32 changes: 31 additions & 1 deletion packages/bugsee/src/node.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Clock, HttpRequestOptions, HttpResponse, HttpTransport } from '@bugsee/core';
import {
type Bugsee,
type LaunchResult,
launchCore,
type NodeRuntime,
Expand All @@ -11,6 +10,7 @@ import type { BugseeSpanProcessor } from '@bugsee/opentelemetry';
import type { ActiveSpanStore } from '@bugsee/performance';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { type BugseeNodeLaunchOptions, launch } from './node';
import type { Bugsee } from './server-launch';

// --- node fakes (no real process / network / perf_hooks) ----------------------------------------

Expand Down Expand Up @@ -247,6 +247,36 @@ describe('bugsee node umbrella launch', () => {
expect(body.resourceSpans[0].scopeSpans[0].scope.name).toBe('com.bugsee.nodejs/performance');
});

it('exposes startTransaction / startSpan / getActiveSpan straight on the launched client', () => {
const client = track(launch('tok', base({ carrier: {}, appStartTimeMs: 1000 })));
const txn = client.startTransaction('Checkout', 'ui.load', { cart: 2 });
expect(txn.isSampled()).toBe(true);
expect(txn.getAttributes()).toMatchObject({ cart: 2 });
expect(client.getActiveSpan()).toBe(client.ext('performance').getActiveSpan());
expect(client.getActiveSpan()).toBe(txn);
const child = client.startSpan('db.query', 'select');
expect(child.getTraceId()).toBe(txn.getTraceId());
txn.finish();
});

it('the calls are harmless no-ops when performanceMonitoring is off', () => {
const client = track(
launch('tok', base({ carrier: {}, appStartTimeMs: 1000, performanceMonitoring: false })),
);
const txn = client.startTransaction('Checkout', 'ui.load');
expect(txn.isSampled()).toBe(false);
expect(txn.getName()).toBe('Checkout');
expect(client.startSpan('db').getOperation()).toBe('db');
expect(client.getActiveSpan()).toBeUndefined();
});

it('a repeat launch still returns a client carrying the calls', () => {
const carrier = {};
track(launch('tok', base({ carrier, appStartTimeMs: 1000 })));
const second = launch('tok', base({ carrier, appStartTimeMs: 1000 }));
expect(typeof second.startTransaction).toBe('function');
});

it('does not mint a device id on node launch', () => {
const { internals } = launchCore('tok-nomint', base({ carrier: {} }));
expect((internals?.getEnvironment().hardware as { device_id: unknown }).device_id).toBeNull();
Expand Down
3 changes: 1 addition & 2 deletions packages/bugsee/src/node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Bugsee } from '@bugsee/node';
import { launchCore } from '@bugsee/node';
import { type BugseeServerLaunchOptions, createServerLaunch } from './server-launch';
import { type Bugsee, type BugseeServerLaunchOptions, createServerLaunch } from './server-launch';

// The `bugsee` umbrella launch() for NODE — the batteries-included server entry, selected via the package
// `exports` "node" condition. The composition itself lives in ./server-launch, shared with the bun and deno
Expand Down
Loading
Loading