@pulsealarm/pulse-event-sdk
v0.1.9
Published
Non-blocking event/error SDK for browser and Node.js
Maintainers
Readme
Pulse Event SDK (JavaScript)
Framework-agnostic, non-blocking event and error tracking SDK for:
- Browser apps
- Node.js services
- React / Vue
- Express / Fastify
- Next.js / Nuxt
Features
- Non-blocking event delivery (queue + background flush)
- Token-based authentication (
/event/:token) - Server-to-server secret key support (
X-Pulse-Secret-Keyheader) - Manual custom event tracking
- Built-in page view tracking (
pageView) - Manual exception capture (
captureException,captureError) - Global runtime error handlers (browser + Node.js)
- Automatic
session_idgeneration - Batched sending with configurable batch size
- Queue overflow protection with callback
- Retry with configurable max retries
- Lightweight adapters for modern frameworks
- TypeScript support
- Fully tested
Installation
npm install @pulsealarm/pulse-event-sdkBrowser (No Bundler / CDN)
If you are not using a bundler (Vite, webpack, etc.), load the SDK directly from a CDN with a plain <script> tag. No build step or import required.
<!-- jsDelivr (recommended) -->
<script src="https://cdn.jsdelivr.net/npm/@pulsealarm/pulse-event-sdk/dist/pulse.min.js"></script>
<!-- or unpkg -->
<script src="https://unpkg.com/@pulsealarm/pulse-event-sdk/dist/pulse.min.js"></script>
<script>
const pulse = new Pulse.PulseClient({
token: "your_webhook_token_here",
autoPageView: true,
defaults: {
environment: "production",
platform: "web"
}
});
pulse.installGlobalHandlers();
// Manual page view
pulse.pageView();
// Custom event
pulse.track({
title: "Payment failed",
level: "error",
type: "exception",
customer_id: "cust_456"
});
</script>The SDK is exposed as window.Pulse. All exports are accessible under the Pulse namespace:
| Export | Usage |
|--------|-------|
| Pulse.PulseClient | new Pulse.PulseClient({ token }) |
| Pulse.createPulseClient | Pulse.createPulseClient({ token }) |
Tip: For bundler-based projects (Vite, webpack, Next.js, etc.), use
npm installabove — tree-shaking and TypeScript types are included automatically.
Quick Start
Browser
import { createPulseClient } from "@pulsealarm/pulse-event-sdk";
const pulse = createPulseClient({
token: "your_webhook_token_here",
autoPageView: true,
defaults: {
environment: "production",
app_version: "2.3.1",
platform: "web"
}
});
pulse.installGlobalHandlers();
pulse.track({
title: "Payment failed for order #1234",
description: "Stripe returned card_declined error during checkout",
level: "error",
type: "exception",
data: {
stack_trace: "Error: card_declined\n at processPayment (checkout.js:42)",
any_key: "any_value"
},
session_id: "sess_abc123",
customer_id: "cust_456",
ip_address: "203.0.113.10",
tags: ["payment", "critical-path"],
revenue: { currency: "USD", price: 49.99 }
});Node.js
import { createPulseClient } from "@pulsealarm/pulse-event-sdk";
const pulse = createPulseClient({
token: "your_webhook_token_here",
secretKey: "sk_your_secret_key", // required for server-to-server
defaults: {
platform: "server",
environment: "production"
}
});
pulse.installGlobalHandlers();
pulse.event("worker.started");
process.on("SIGTERM", () => {
pulse.close();
process.exit(0);
});Authentication
Browser (Client-side)
Only token is needed. Domain validation is handled on the server.
const pulse = createPulseClient({
token: "your_webhook_token_here"
});Server-to-Server
Requires secretKey in addition to token. The SDK sends it as X-Pulse-Secret-Key header.
const pulse = createPulseClient({
token: "your_webhook_token_here",
secretKey: "sk_your_secret_key"
});Endpoints
The SDK automatically builds endpoint URLs from the token:
| Type | URL Pattern |
|------|------------|
| Event | https://pulsealarm.dev/event/:token |
| Page View | https://pulsealarm.dev/page-view/:token |
Custom base URL:
const pulse = createPulseClient({
token: "your_token",
baseUrl: "https://your-self-hosted-domain.com"
});
// → https://your-self-hosted-domain.com/event/your_token
// → https://your-self-hosted-domain.com/page-view/your_tokenYou can also pass a fully custom endpoint:
const pulse = createPulseClient({
endpoint: "https://custom-domain.com/my-ingest"
});Runtime Token Updates
// Update token (and optionally secretKey) at runtime
pulse.setToken("new_token", "new_secret_key");
// Update just the endpoint URL
pulse.setEndpoint("https://custom-domain.com/event/new_token");Session Tracking
- If
session_idis not provided, SDK auto-generatessess_*. - In browsers, session ID is persisted in both cookie (
pulse_sdk_session_id) andsessionStorage. - Priority: explicit
session_id> cookie >sessionStorage> generated value.
console.log(pulse.getSessionId()); // "sess_a1b2c3d4..."Custom Event Tracking
// Simple event
pulse.event("checkout.step.completed", {
level: "info",
tags: ["checkout"]
});
// Full track with all fields
pulse.track({
title: "Order placed",
level: "info",
type: "event",
customer_id: "cust_456",
revenue: { currency: "USD", price: 99.99 },
tags: ["orders"]
});In browser runtime, track() auto-fills these fields when missing:
request_urldurationdata.referrerdata.user_agent
If you provide them explicitly, your values override SDK defaults.
Exception Capture
try {
doPayment();
} catch (error) {
pulse.captureException(error, {
tags: ["payment"],
customer_id: "cust_456",
data: { order_id: "ord_123" }
});
}captureException automatically extracts:
titlefrom error messagestack_traceintodata.stack_traceplatformas"web"(browser) or"server"(Node.js)typeas"exception",levelas"error"
Page View Tracking
pageView() sends data to the dedicated /page-view/:token endpoint.
Auto-filled fields (when available in browser):
url— current page URLduration— page load time (Navigation Timing API)session_id— from SDK sessionreferrer— document referreruser_agent— navigator user agenttimestamp— ISO-8601
// Automatic (uses browser APIs)
pulse.pageView();
// With overrides
pulse.pageView({
url: "https://myapp.com/checkout",
duration: 1200,
customer_id: "cust_456",
ip_address: "203.0.113.10"
});Auto page view on load:
const pulse = createPulseClient({
token: "your_token",
autoPageView: true
});Global Error Handlers
pulse.installGlobalHandlers();- Browser:
errorandunhandledrejectionevents - Node.js:
uncaughtExceptionandunhandledRejectionevents
Selective:
pulse.installGlobalHandlers({
browser: true,
node: false,
uncaughtException: true,
unhandledRejection: true
});Queue & Stats
const stats = pulse.getStats();
// { sent: 42, failed: 1, dropped: 0, queued: 3 }Queue overflow callback:
const pulse = createPulseClient({
token: "your_token",
maxQueueSize: 500,
onQueueOverflow: (totalDropped) => {
console.warn(`${totalDropped} events dropped due to queue overflow`);
}
});Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| token | string | — | Webhook token (used in URL: /event/:token) |
| secretKey | string | null | Server secret key (sent as X-Pulse-Secret-Key header) |
| endpoint | string | — | Custom endpoint URL (overrides token) |
| baseUrl | string | https://pulsealarm.dev | Base URL for endpoint building |
| defaults | object | {} | Default fields merged into every event |
| flushIntervalMs | number | 1000 | Background flush interval |
| maxQueueSize | number | 500 | Max queue size before dropping oldest |
| maxRetries | number | 2 | Retry count for failed sends |
| batchSize | number | 10 | Events sent per flush cycle |
| timeoutMs | number | 10000 | HTTP request timeout |
| autoPageView | boolean | false | Auto-send page view on load |
| enableBrowserContext | boolean | false | Add browser context to events |
| onError | function | — | Error callback (error, context) => {} |
| onQueueOverflow | function | — | Queue overflow callback (totalDropped) => {} |
| debug | boolean | false | Enable debug logging |
React Integration
import { usePulse, PulseErrorBoundary, withPulseErrorBoundary } from "@pulsealarm/pulse-event-sdk/react";
// Hook
const pulse = usePulse({ token: "your_token" });
// Error Boundary
<PulseErrorBoundary pulse={pulse} fallback={<div>Something went wrong</div>}>
<App />
</PulseErrorBoundary>
// HOC
const SafeCheckout = withPulseErrorBoundary(Checkout, { pulse });Vue Integration
import { createPulseVuePlugin } from "@pulsealarm/pulse-event-sdk/vue";
app.use(
createPulseVuePlugin({
token: "your_token",
defaults: { platform: "web", environment: "production" }
})
);The plugin also configures app.config.errorHandler automatically.
Express Integration
import { createPulseClient } from "@pulsealarm/pulse-event-sdk";
import {
createExpressPulseRequestMiddleware,
createExpressPulseErrorMiddleware
} from "@pulsealarm/pulse-event-sdk/express";
const pulse = createPulseClient({
token: "your_token",
secretKey: "sk_your_secret_key"
});
app.use(createExpressPulseRequestMiddleware(pulse));
// ... your routes
app.use(createExpressPulseErrorMiddleware(pulse));Fastify Integration
import { createFastifyPulsePlugin } from "@pulsealarm/pulse-event-sdk/fastify";
await fastify.register(createFastifyPulsePlugin(pulse));Next.js Integration
Pages Router API handler:
import { withPulseApiHandler } from "@pulsealarm/pulse-event-sdk/next";
export default withPulseApiHandler(async function handler(req, res) {
// your handler
}, pulse);App Router route handler:
import { withPulseRouteHandler } from "@pulsealarm/pulse-event-sdk/next";
export const POST = withPulseRouteHandler(async function POST(request) {
// your handler
}, pulse);Nuxt Integration
import { createNuxtPulsePlugin } from "@pulsealarm/pulse-event-sdk/nuxt";
export default defineNuxtPlugin((nuxtApp) => {
createNuxtPulsePlugin(pulse)(nuxtApp);
});Event Payload Schema
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| title | string | Yes | Event title |
| description | string | No | Detailed description |
| level | string | No | Dynamic value from API (must be non-empty) |
| type | string | No | Dynamic value from API (must be non-empty) |
| timestamp | string | No | ISO-8601 timestamp |
| data | object | No | Custom key-value data |
| session_id | string | No | Session identifier |
| customer_id | string | No | Customer identifier |
| ip_address | string | No | Client IP address |
| request_url | string | No | Request URL |
| duration | number | No | Duration in milliseconds |
| country_code | string | No | ISO-2 country code (e.g. TR) |
| tags | string[] | No | Tags for categorization |
| revenue | object | No | { currency: "USD", price: 49.99 } |
| group | boolean | No | Enable deduplication grouping |
| resolved | boolean | No | Mark as resolved |
| environment | string | No | production, staging, development, test |
| app_version | string | No | Application version |
| platform | string | No | web, ios, android, server, desktop |
Page View Payload Schema
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| url | string | Yes | Page URL |
| duration | number | No | Load time in milliseconds |
| session_id | string | No | Session identifier |
| customer_id | string | No | Customer identifier |
| timestamp | string | No | ISO-8601 timestamp |
| ip_address | string | No | Client IP address |
| referrer | string | No | Referring URL |
| user_agent | string | No | Browser user agent |
Testing
npm testCLI Quick Test
TOKEN="your_webhook_token_here"
BASE_URL="https://pulsealarm.dev"
curl -X POST "${BASE_URL}/event/${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"title":"CLI test event","level":"info"}'With secret key (server-to-server):
curl -X POST "${BASE_URL}/event/${TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Pulse-Secret-Key: sk_your_secret_key" \
-d '{"title":"Server test event","level":"info"}'Notes
track()is non-blocking: events go to queue, then flush in background.- Events are sent in batches (default: 10 per flush cycle).
- Browser attempts best-effort flush via
sendBeaconon page exit/hidden state. - In Node.js, call
pulse.close()before shutdown for clean flush. - Failed sends are retried up to
maxRetriestimes before being dropped.
