@metrone-io/server 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,494 @@
1
+ // src/errors.ts
2
+ var MetroneError = class extends Error {
3
+ code;
4
+ status;
5
+ retryable;
6
+ constructor(code, message, status, retryable = false) {
7
+ super(message);
8
+ this.name = "MetroneError";
9
+ this.code = code;
10
+ this.status = status;
11
+ this.retryable = retryable;
12
+ }
13
+ };
14
+ var MetroneConfigError = class extends MetroneError {
15
+ constructor(message) {
16
+ super("CONFIG_INVALID", message);
17
+ this.name = "MetroneConfigError";
18
+ }
19
+ };
20
+ var MetroneAuthError = class extends MetroneError {
21
+ constructor(message = "Invalid or missing API key") {
22
+ super("AUTH_FAILED", message, 401, false);
23
+ this.name = "MetroneAuthError";
24
+ }
25
+ };
26
+ var MetroneRateLimitError = class extends MetroneError {
27
+ retryAfterMs;
28
+ constructor(retryAfterMs) {
29
+ super("RATE_LIMITED", `Rate limit exceeded. Retry after ${retryAfterMs}ms.`, 429, true);
30
+ this.name = "MetroneRateLimitError";
31
+ this.retryAfterMs = retryAfterMs;
32
+ }
33
+ };
34
+ var MetroneQuotaError = class extends MetroneError {
35
+ constructor(message = "Monthly event quota exceeded") {
36
+ super("QUOTA_EXCEEDED", message, 429, false);
37
+ this.name = "MetroneQuotaError";
38
+ }
39
+ };
40
+ var MetroneNetworkError = class extends MetroneError {
41
+ constructor(message = "Network request failed") {
42
+ super("NETWORK_ERROR", message, void 0, true);
43
+ this.name = "MetroneNetworkError";
44
+ }
45
+ };
46
+ var MetroneTimeoutError = class extends MetroneError {
47
+ constructor(timeoutMs) {
48
+ super("TIMEOUT", `Request timed out after ${timeoutMs}ms`, void 0, true);
49
+ this.name = "MetroneTimeoutError";
50
+ }
51
+ };
52
+ var MetroneValidationError = class extends MetroneError {
53
+ fields;
54
+ constructor(fields) {
55
+ const summary = fields.map((f) => `${f.field}: ${f.message}`).join("; ");
56
+ super("VALIDATION_FAILED", `Validation failed: ${summary}`, 400, false);
57
+ this.name = "MetroneValidationError";
58
+ this.fields = fields;
59
+ }
60
+ };
61
+ var MetroneServerError = class extends MetroneError {
62
+ constructor(status, message = "Internal server error") {
63
+ super("SERVER_ERROR", message, status, true);
64
+ this.name = "MetroneServerError";
65
+ }
66
+ };
67
+
68
+ // src/http.ts
69
+ async function httpPost(config, path, body, headers) {
70
+ return httpRequest(config, "POST", path, body, headers);
71
+ }
72
+ async function httpGet(config, path, params, headers) {
73
+ let url = path;
74
+ if (params) {
75
+ const qs = new URLSearchParams();
76
+ for (const [k, v] of Object.entries(params)) {
77
+ if (v !== void 0) qs.set(k, String(v));
78
+ }
79
+ const str = qs.toString();
80
+ if (str) url += `?${str}`;
81
+ }
82
+ return httpRequest(config, "GET", url, void 0, headers);
83
+ }
84
+ async function httpRequest(config, method, path, body, extraHeaders) {
85
+ const url = `${config.endpoint}${path}`;
86
+ const fetchFn = config.fetch;
87
+ const headers = {
88
+ "X-Api-Key": config.apiKey,
89
+ "User-Agent": "metrone-server-sdk/1.0.0",
90
+ ...extraHeaders
91
+ };
92
+ if (body !== void 0) {
93
+ headers["Content-Type"] = "application/json";
94
+ }
95
+ const controller = new AbortController();
96
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
97
+ try {
98
+ const response = await fetchFn(url, {
99
+ method,
100
+ headers,
101
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
102
+ signal: controller.signal
103
+ });
104
+ clearTimeout(timer);
105
+ let data;
106
+ const ct = response.headers.get("content-type") ?? "";
107
+ if (ct.includes("application/json")) {
108
+ data = await response.json();
109
+ } else {
110
+ data = await response.text();
111
+ }
112
+ if (response.ok) {
113
+ return { ok: true, status: response.status, data };
114
+ }
115
+ const errBody = data;
116
+ const errMsg = typeof errBody?.error === "string" ? errBody.error : typeof errBody?.error === "object" && errBody.error !== null ? errBody.error.message ?? response.statusText : response.statusText;
117
+ if (response.status === 401) throw new MetroneAuthError(errMsg);
118
+ if (response.status === 429) {
119
+ const retryMs = typeof errBody?.retry_after_ms === "number" ? errBody.retry_after_ms : 6e4;
120
+ throw new MetroneRateLimitError(retryMs);
121
+ }
122
+ if (response.status >= 500) throw new MetroneServerError(response.status, errMsg);
123
+ return {
124
+ ok: false,
125
+ status: response.status,
126
+ error: { code: "API_ERROR", message: errMsg, details: errBody }
127
+ };
128
+ } catch (err) {
129
+ clearTimeout(timer);
130
+ if (err instanceof MetroneAuthError) throw err;
131
+ if (err instanceof MetroneRateLimitError) throw err;
132
+ if (err instanceof MetroneServerError) throw err;
133
+ if (err instanceof DOMException && err.name === "AbortError") {
134
+ throw new MetroneTimeoutError(config.timeoutMs);
135
+ }
136
+ if (err instanceof TypeError) {
137
+ throw new MetroneNetworkError(err.message);
138
+ }
139
+ throw new MetroneNetworkError(
140
+ err instanceof Error ? err.message : "Unknown network error"
141
+ );
142
+ }
143
+ }
144
+ async function withRetry(config, fn) {
145
+ let lastError;
146
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
147
+ try {
148
+ return await fn();
149
+ } catch (err) {
150
+ lastError = err instanceof Error ? err : new Error(String(err));
151
+ const isRetryable = err instanceof MetroneNetworkError || err instanceof MetroneTimeoutError || err instanceof MetroneServerError || err instanceof MetroneRateLimitError;
152
+ if (!isRetryable || attempt >= config.maxRetries) throw err;
153
+ const baseDelay = err instanceof MetroneRateLimitError ? err.retryAfterMs : config.retryBaseMs * Math.pow(2, attempt);
154
+ const jitter = Math.random() * baseDelay * 0.1;
155
+ const delay = baseDelay + jitter;
156
+ if (config.debug) {
157
+ const errLabel = err instanceof Error ? err.message : String(err);
158
+ console.error(
159
+ `[metrone] Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delay)}ms: ${errLabel}`
160
+ );
161
+ }
162
+ await sleep(delay);
163
+ }
164
+ }
165
+ throw lastError;
166
+ }
167
+ function sleep(ms) {
168
+ return new Promise((resolve) => setTimeout(resolve, ms));
169
+ }
170
+
171
+ // src/client.ts
172
+ var API_KEY_PATTERN = /^(metrone_(live|test)_|mk_(live|test)_)[a-f0-9]{32}$/;
173
+ var MetroneServer = class {
174
+ config;
175
+ queue = [];
176
+ flushTimer = null;
177
+ flushing = false;
178
+ destroyed = false;
179
+ constructor(config) {
180
+ if (!config.apiKey) {
181
+ throw new MetroneConfigError("apiKey is required");
182
+ }
183
+ if (!API_KEY_PATTERN.test(config.apiKey)) {
184
+ throw new MetroneConfigError(
185
+ "Invalid apiKey format. Expected metrone_live_*, metrone_test_*, mk_live_*, or mk_test_* followed by 32 hex chars."
186
+ );
187
+ }
188
+ const endpoint = (config.endpoint ?? "https://api.metrone.io").replace(/\/+$/, "");
189
+ this.config = {
190
+ apiKey: config.apiKey,
191
+ endpoint,
192
+ batchSize: config.batchSize ?? 10,
193
+ flushIntervalMs: config.flushIntervalMs ?? 5e3,
194
+ maxRetries: config.maxRetries ?? 3,
195
+ retryBaseMs: config.retryBaseMs ?? 1e3,
196
+ timeoutMs: config.timeoutMs ?? 1e4,
197
+ maxQueueSize: config.maxQueueSize ?? 1e3,
198
+ debug: config.debug ?? false,
199
+ fetch: config.fetch ?? globalThis.fetch.bind(globalThis)
200
+ };
201
+ if (this.config.batchSize > 0 && this.config.flushIntervalMs > 0) {
202
+ this.flushTimer = setInterval(() => {
203
+ this.flush().catch((err) => {
204
+ if (this.config.debug) {
205
+ console.error("[metrone] Auto-flush error:", err);
206
+ }
207
+ });
208
+ }, this.config.flushIntervalMs);
209
+ if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
210
+ this.flushTimer.unref();
211
+ }
212
+ }
213
+ if (this.config.debug) {
214
+ console.error(`[metrone] Server SDK initialized (endpoint: ${this.config.endpoint})`);
215
+ }
216
+ }
217
+ // ─── Event Tracking ────────────────────────────────────────────────────────
218
+ /**
219
+ * Track a custom event. Queued for batch sending unless batchSize is 0.
220
+ */
221
+ track(eventType, data) {
222
+ this.assertNotDestroyed();
223
+ if (!eventType) throw new MetroneValidationError([{ field: "event_type", message: "required" }]);
224
+ const event = {
225
+ api_key: this.config.apiKey,
226
+ event_type: eventType,
227
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
228
+ ...data
229
+ };
230
+ if (this.config.batchSize <= 0) {
231
+ withRetry(this.config, () => httpPost(this.config, "/v1/events", event)).catch((err) => {
232
+ if (this.config.debug) console.error("[metrone] Send error:", err);
233
+ });
234
+ return;
235
+ }
236
+ this.queue.push(event);
237
+ if (this.queue.length >= this.config.batchSize) {
238
+ this.flush().catch((err) => {
239
+ if (this.config.debug) console.error("[metrone] Flush error:", err);
240
+ });
241
+ }
242
+ }
243
+ /**
244
+ * Track a page view.
245
+ */
246
+ pageview(url, title, data) {
247
+ this.track("pageview", {
248
+ page_url: url,
249
+ page_title: title,
250
+ source: "web",
251
+ ...data
252
+ });
253
+ }
254
+ /**
255
+ * Track a conversion event.
256
+ */
257
+ conversion(data) {
258
+ this.track("conversion", {
259
+ source: "web",
260
+ properties: {
261
+ conversion_type: data.conversion_type,
262
+ value: data.value,
263
+ currency: data.currency,
264
+ ...data.properties
265
+ }
266
+ });
267
+ }
268
+ // ─── AI Tracking ───────────────────────────────────────────────────────────
269
+ /**
270
+ * Track an AI voice call.
271
+ */
272
+ trackAICall(data) {
273
+ this.track("ai_call", {
274
+ source: "voice",
275
+ channel: "phone",
276
+ ai_provider: data.provider ?? "unknown",
277
+ ai_call_id: data.call_id,
278
+ ai_intent: data.intent,
279
+ ai_duration_sec: data.duration,
280
+ session_id: data.session_id,
281
+ properties: {
282
+ transcript_snippet: data.transcript_snippet,
283
+ outcome: data.outcome,
284
+ ...data.properties
285
+ }
286
+ });
287
+ }
288
+ /**
289
+ * Track an AI chat interaction.
290
+ */
291
+ trackAIChat(data) {
292
+ this.track("ai_chat", {
293
+ source: "chat",
294
+ channel: "website",
295
+ ai_provider: data.provider ?? "unknown",
296
+ ai_session_id: data.session_id,
297
+ ai_intent: data.intent,
298
+ ai_duration_sec: data.duration,
299
+ properties: {
300
+ message_count: data.message_count,
301
+ resolved: data.resolved,
302
+ ...data.properties
303
+ }
304
+ });
305
+ }
306
+ /**
307
+ * Track an AI intent detection.
308
+ */
309
+ trackAIIntent(data) {
310
+ this.track("ai_intent", {
311
+ source: data.source ?? "assistant",
312
+ ai_intent: data.intent,
313
+ properties: {
314
+ confidence: data.confidence,
315
+ ...data.properties
316
+ }
317
+ });
318
+ }
319
+ /**
320
+ * Track an AI session lifecycle event (start, end, timeout).
321
+ */
322
+ trackAISession(data) {
323
+ this.track(`ai_session_${data.action}`, {
324
+ source: "assistant",
325
+ ai_provider: data.provider ?? "unknown",
326
+ ai_session_id: data.session_id,
327
+ ai_duration_sec: data.duration,
328
+ properties: data.properties
329
+ });
330
+ }
331
+ // ─── Read API ──────────────────────────────────────────────────────────────
332
+ /**
333
+ * Get aggregated analytics stats for a time period.
334
+ */
335
+ async getStats(params) {
336
+ this.assertNotDestroyed();
337
+ const qp = this.buildDateParams(params);
338
+ const res = await withRetry(
339
+ this.config,
340
+ () => httpGet(this.config, "/v1/api/stats", qp)
341
+ );
342
+ return res.data;
343
+ }
344
+ /**
345
+ * Get individual analytics events with pagination and filters.
346
+ */
347
+ async getEvents(params) {
348
+ this.assertNotDestroyed();
349
+ const qp = {
350
+ ...this.buildDateParams(params),
351
+ limit: params?.limit,
352
+ offset: params?.offset,
353
+ event_type: params?.event_type,
354
+ source: params?.source
355
+ };
356
+ const res = await withRetry(
357
+ this.config,
358
+ () => httpGet(this.config, "/v1/api/events", qp)
359
+ );
360
+ return res.data;
361
+ }
362
+ /**
363
+ * Get page analytics broken down by path.
364
+ */
365
+ async getPages(params) {
366
+ this.assertNotDestroyed();
367
+ const qp = {
368
+ ...this.buildDateParams(params),
369
+ limit: params?.limit
370
+ };
371
+ const res = await withRetry(
372
+ this.config,
373
+ () => httpGet(this.config, "/v1/api/pages", qp)
374
+ );
375
+ return res.data;
376
+ }
377
+ /**
378
+ * Get traffic source and referrer analytics.
379
+ */
380
+ async getSources(params) {
381
+ this.assertNotDestroyed();
382
+ const qp = this.buildDateParams(params);
383
+ const res = await withRetry(
384
+ this.config,
385
+ () => httpGet(this.config, "/v1/api/sources", qp)
386
+ );
387
+ return res.data;
388
+ }
389
+ /**
390
+ * Get real-time live stats (active visitors, today's totals).
391
+ */
392
+ async getLive() {
393
+ this.assertNotDestroyed();
394
+ const res = await withRetry(
395
+ this.config,
396
+ () => httpGet(this.config, "/v1/api/live")
397
+ );
398
+ return res.data;
399
+ }
400
+ // ─── Batch & Lifecycle ─────────────────────────────────────────────────────
401
+ /**
402
+ * Immediately flush all queued events. Returns the result.
403
+ * Safe to call multiple times concurrently — only one flush runs at a time.
404
+ */
405
+ async flush() {
406
+ if (this.flushing || this.queue.length === 0) {
407
+ return { sent: 0, failed: 0, errors: [] };
408
+ }
409
+ this.flushing = true;
410
+ const batch = this.queue.splice(0, 100);
411
+ try {
412
+ const res = await withRetry(
413
+ this.config,
414
+ () => httpPost(this.config, "/v1/events/batch", batch)
415
+ );
416
+ const body = res.data;
417
+ const accepted = typeof body?.accepted === "number" ? body.accepted : batch.length;
418
+ if (this.config.debug) {
419
+ console.error(`[metrone] Flushed ${accepted}/${batch.length} events`);
420
+ }
421
+ return { sent: accepted, failed: batch.length - accepted, errors: [] };
422
+ } catch (err) {
423
+ this.queue.unshift(...batch);
424
+ if (this.queue.length > this.config.maxQueueSize) {
425
+ this.queue.length = this.config.maxQueueSize;
426
+ }
427
+ if (this.config.debug) {
428
+ console.error("[metrone] Flush failed, re-queued:", err);
429
+ }
430
+ return {
431
+ sent: 0,
432
+ failed: batch.length,
433
+ errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }]
434
+ };
435
+ } finally {
436
+ this.flushing = false;
437
+ if (this.queue.length >= this.config.batchSize && this.config.batchSize > 0) {
438
+ this.flush().catch(() => {
439
+ });
440
+ }
441
+ }
442
+ }
443
+ /**
444
+ * Flush remaining events, stop the auto-flush timer, and mark the
445
+ * client as destroyed. Subsequent calls will throw.
446
+ */
447
+ async shutdown() {
448
+ if (this.destroyed) return { sent: 0, failed: 0, errors: [] };
449
+ if (this.flushTimer) {
450
+ clearInterval(this.flushTimer);
451
+ this.flushTimer = null;
452
+ }
453
+ const result = await this.flush();
454
+ this.destroyed = true;
455
+ if (this.config.debug) {
456
+ console.error("[metrone] Server SDK shut down");
457
+ }
458
+ return result;
459
+ }
460
+ /** Number of events currently in the send queue. */
461
+ get queueSize() {
462
+ return this.queue.length;
463
+ }
464
+ /** Whether shutdown() has been called. */
465
+ get isDestroyed() {
466
+ return this.destroyed;
467
+ }
468
+ // ─── Internal ──────────────────────────────────────────────────────────────
469
+ assertNotDestroyed() {
470
+ if (this.destroyed) {
471
+ throw new MetroneConfigError("Client has been shut down. Create a new instance.");
472
+ }
473
+ }
474
+ buildDateParams(params) {
475
+ const result = {};
476
+ if (params?.days) result.days = params.days;
477
+ if (params?.from) result.from = params.from instanceof Date ? params.from.toISOString() : params.from;
478
+ if (params?.to) result.to = params.to instanceof Date ? params.to.toISOString() : params.to;
479
+ return result;
480
+ }
481
+ };
482
+ export {
483
+ MetroneAuthError,
484
+ MetroneConfigError,
485
+ MetroneError,
486
+ MetroneNetworkError,
487
+ MetroneQuotaError,
488
+ MetroneRateLimitError,
489
+ MetroneServer,
490
+ MetroneServerError,
491
+ MetroneTimeoutError,
492
+ MetroneValidationError
493
+ };
494
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["/**\n * Structured error types for the Metrone server SDK.\n *\n * All errors carry a machine-readable `code` that agents can switch on,\n * plus a human-readable `message` for logs.\n */\n\nexport class MetroneError extends Error {\n public readonly code: string\n public readonly status?: number\n public readonly retryable: boolean\n\n constructor(code: string, message: string, status?: number, retryable = false) {\n super(message)\n this.name = 'MetroneError'\n this.code = code\n this.status = status\n this.retryable = retryable\n }\n}\n\nexport class MetroneConfigError extends MetroneError {\n constructor(message: string) {\n super('CONFIG_INVALID', message)\n this.name = 'MetroneConfigError'\n }\n}\n\nexport class MetroneAuthError extends MetroneError {\n constructor(message = 'Invalid or missing API key') {\n super('AUTH_FAILED', message, 401, false)\n this.name = 'MetroneAuthError'\n }\n}\n\nexport class MetroneRateLimitError extends MetroneError {\n public readonly retryAfterMs: number\n\n constructor(retryAfterMs: number) {\n super('RATE_LIMITED', `Rate limit exceeded. Retry after ${retryAfterMs}ms.`, 429, true)\n this.name = 'MetroneRateLimitError'\n this.retryAfterMs = retryAfterMs\n }\n}\n\nexport class MetroneQuotaError extends MetroneError {\n constructor(message = 'Monthly event quota exceeded') {\n super('QUOTA_EXCEEDED', message, 429, false)\n this.name = 'MetroneQuotaError'\n }\n}\n\nexport class MetroneNetworkError extends MetroneError {\n constructor(message = 'Network request failed') {\n super('NETWORK_ERROR', message, undefined, true)\n this.name = 'MetroneNetworkError'\n }\n}\n\nexport class MetroneTimeoutError extends MetroneError {\n constructor(timeoutMs: number) {\n super('TIMEOUT', `Request timed out after ${timeoutMs}ms`, undefined, true)\n this.name = 'MetroneTimeoutError'\n }\n}\n\nexport class MetroneValidationError extends MetroneError {\n public readonly fields: Array<{ field: string; message: string }>\n\n constructor(fields: Array<{ field: string; message: string }>) {\n const summary = fields.map(f => `${f.field}: ${f.message}`).join('; ')\n super('VALIDATION_FAILED', `Validation failed: ${summary}`, 400, false)\n this.name = 'MetroneValidationError'\n this.fields = fields\n }\n}\n\nexport class MetroneServerError extends MetroneError {\n constructor(status: number, message = 'Internal server error') {\n super('SERVER_ERROR', message, status, true)\n this.name = 'MetroneServerError'\n }\n}\n","/**\n * Runtime-agnostic HTTP layer.\n *\n * Uses the global fetch() which is available in Node 18+, Deno, Bun,\n * Cloudflare Workers, and all modern edge runtimes. A custom fetch can\n * be injected via config for environments that need it.\n */\n\nimport type { ResolvedConfig, ApiResponse } from './types.js'\nimport {\n MetroneNetworkError,\n MetroneTimeoutError,\n MetroneAuthError,\n MetroneRateLimitError,\n MetroneServerError,\n} from './errors.js'\n\nexport async function httpPost(\n config: ResolvedConfig,\n path: string,\n body: unknown,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n return httpRequest(config, 'POST', path, body, headers)\n}\n\nexport async function httpGet(\n config: ResolvedConfig,\n path: string,\n params?: Record<string, string | number | undefined>,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n let url = path\n if (params) {\n const qs = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) qs.set(k, String(v))\n }\n const str = qs.toString()\n if (str) url += `?${str}`\n }\n return httpRequest(config, 'GET', url, undefined, headers)\n}\n\nasync function httpRequest(\n config: ResolvedConfig,\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n): Promise<ApiResponse> {\n const url = `${config.endpoint}${path}`\n const fetchFn = config.fetch\n\n const headers: Record<string, string> = {\n 'X-Api-Key': config.apiKey,\n 'User-Agent': 'metrone-server-sdk/1.0.0',\n ...extraHeaders,\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), config.timeoutMs)\n\n try {\n const response = await fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n })\n\n clearTimeout(timer)\n\n let data: unknown\n const ct = response.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n data = await response.json()\n } else {\n data = await response.text()\n }\n\n if (response.ok) {\n return { ok: true, status: response.status, data }\n }\n\n const errBody = data as Record<string, unknown> | undefined\n const errMsg = typeof errBody?.error === 'string'\n ? errBody.error\n : typeof errBody?.error === 'object' && errBody.error !== null\n ? (errBody.error as Record<string, unknown>).message as string ?? response.statusText\n : response.statusText\n\n if (response.status === 401) throw new MetroneAuthError(errMsg)\n if (response.status === 429) {\n const retryMs = typeof errBody?.retry_after_ms === 'number'\n ? errBody.retry_after_ms\n : 60_000\n throw new MetroneRateLimitError(retryMs)\n }\n if (response.status >= 500) throw new MetroneServerError(response.status, errMsg)\n\n return {\n ok: false,\n status: response.status,\n error: { code: 'API_ERROR', message: errMsg, details: errBody },\n }\n } catch (err) {\n clearTimeout(timer)\n\n if (err instanceof MetroneAuthError) throw err\n if (err instanceof MetroneRateLimitError) throw err\n if (err instanceof MetroneServerError) throw err\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new MetroneTimeoutError(config.timeoutMs)\n }\n if (err instanceof TypeError) {\n throw new MetroneNetworkError(err.message)\n }\n\n throw new MetroneNetworkError(\n err instanceof Error ? err.message : 'Unknown network error',\n )\n }\n}\n\n/**\n * Retry wrapper with exponential backoff.\n * Retries on MetroneNetworkError, MetroneTimeoutError, MetroneServerError,\n * and MetroneRateLimitError. Stops on auth or validation errors.\n */\nexport async function withRetry<T>(\n config: ResolvedConfig,\n fn: () => Promise<T>,\n): Promise<T> {\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn()\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err))\n\n const isRetryable =\n err instanceof MetroneNetworkError ||\n err instanceof MetroneTimeoutError ||\n err instanceof MetroneServerError ||\n err instanceof MetroneRateLimitError\n\n if (!isRetryable || attempt >= config.maxRetries) throw err\n\n const baseDelay = err instanceof MetroneRateLimitError\n ? err.retryAfterMs\n : config.retryBaseMs * Math.pow(2, attempt)\n\n const jitter = Math.random() * baseDelay * 0.1\n const delay = baseDelay + jitter\n\n if (config.debug) {\n const errLabel = err instanceof Error ? err.message : String(err)\n console.error(\n `[metrone] Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delay)}ms: ${errLabel}`,\n )\n }\n\n await sleep(delay)\n }\n }\n\n throw lastError\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n","/**\n * MetroneServer — the main server-side SDK client.\n *\n * Zero dependencies. Uses global fetch (Node 18+, Deno, Bun, edge runtimes).\n * Runtime-agnostic: no DOM, no window, no navigator, no localStorage.\n *\n * Features:\n * - Event ingestion (single + batch) with automatic batching\n * - AI tracking (calls, chats, intents, sessions)\n * - Read API (stats, events, pages, sources, live)\n * - Retry with exponential backoff + jitter\n * - Idempotency key support\n * - Agent identity tracking\n * - Graceful shutdown with flush()\n */\n\nimport type {\n MetroneServerConfig,\n ResolvedConfig,\n EventPayload,\n AICallData,\n AIChatData,\n AIIntentData,\n AISessionData,\n ConversionData,\n StatsParams,\n EventsParams,\n PagesParams,\n SourcesParams,\n FlushResult,\n StatsResponse,\n EventsResponse,\n PagesResponse,\n SourcesResponse,\n LiveResponse,\n ApiResponse,\n} from './types.js'\nimport { MetroneConfigError, MetroneValidationError } from './errors.js'\nimport { httpPost, httpGet, withRetry } from './http.js'\n\nconst API_KEY_PATTERN = /^(metrone_(live|test)_|mk_(live|test)_)[a-f0-9]{32}$/\n\nexport class MetroneServer {\n private readonly config: ResolvedConfig\n private queue: Array<EventPayload & { api_key: string }> = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushing = false\n private destroyed = false\n\n constructor(config: MetroneServerConfig) {\n if (!config.apiKey) {\n throw new MetroneConfigError('apiKey is required')\n }\n if (!API_KEY_PATTERN.test(config.apiKey)) {\n throw new MetroneConfigError(\n 'Invalid apiKey format. Expected metrone_live_*, metrone_test_*, mk_live_*, or mk_test_* followed by 32 hex chars.',\n )\n }\n\n const endpoint = (config.endpoint ?? 'https://api.metrone.io').replace(/\\/+$/, '')\n\n this.config = {\n apiKey: config.apiKey,\n endpoint,\n batchSize: config.batchSize ?? 10,\n flushIntervalMs: config.flushIntervalMs ?? 5000,\n maxRetries: config.maxRetries ?? 3,\n retryBaseMs: config.retryBaseMs ?? 1000,\n timeoutMs: config.timeoutMs ?? 10_000,\n maxQueueSize: config.maxQueueSize ?? 1000,\n debug: config.debug ?? false,\n fetch: config.fetch ?? globalThis.fetch.bind(globalThis),\n }\n\n if (this.config.batchSize > 0 && this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n this.flush().catch(err => {\n if (this.config.debug) {\n console.error('[metrone] Auto-flush error:', err)\n }\n })\n }, this.config.flushIntervalMs)\n\n if (typeof this.flushTimer === 'object' && 'unref' in this.flushTimer) {\n (this.flushTimer as NodeJS.Timeout).unref()\n }\n }\n\n if (this.config.debug) {\n console.error(`[metrone] Server SDK initialized (endpoint: ${this.config.endpoint})`)\n }\n }\n\n // ─── Event Tracking ────────────────────────────────────────────────────────\n\n /**\n * Track a custom event. Queued for batch sending unless batchSize is 0.\n */\n track(eventType: string, data?: Partial<EventPayload>): void {\n this.assertNotDestroyed()\n if (!eventType) throw new MetroneValidationError([{ field: 'event_type', message: 'required' }])\n\n const event: EventPayload & { api_key: string } = {\n api_key: this.config.apiKey,\n event_type: eventType,\n timestamp: new Date().toISOString(),\n ...data,\n }\n\n if (this.config.batchSize <= 0) {\n withRetry(this.config, () => httpPost(this.config, '/v1/events', event)).catch(err => {\n if (this.config.debug) console.error('[metrone] Send error:', err)\n })\n return\n }\n\n this.queue.push(event)\n if (this.queue.length >= this.config.batchSize) {\n this.flush().catch(err => {\n if (this.config.debug) console.error('[metrone] Flush error:', err)\n })\n }\n }\n\n /**\n * Track a page view.\n */\n pageview(url: string, title?: string, data?: Partial<EventPayload>): void {\n this.track('pageview', {\n page_url: url,\n page_title: title,\n source: 'web',\n ...data,\n })\n }\n\n /**\n * Track a conversion event.\n */\n conversion(data: ConversionData): void {\n this.track('conversion', {\n source: 'web',\n properties: {\n conversion_type: data.conversion_type,\n value: data.value,\n currency: data.currency,\n ...data.properties,\n },\n })\n }\n\n // ─── AI Tracking ───────────────────────────────────────────────────────────\n\n /**\n * Track an AI voice call.\n */\n trackAICall(data: AICallData): void {\n this.track('ai_call', {\n source: 'voice',\n channel: 'phone',\n ai_provider: data.provider ?? 'unknown',\n ai_call_id: data.call_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n session_id: data.session_id,\n properties: {\n transcript_snippet: data.transcript_snippet,\n outcome: data.outcome,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI chat interaction.\n */\n trackAIChat(data: AIChatData): void {\n this.track('ai_chat', {\n source: 'chat',\n channel: 'website',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n properties: {\n message_count: data.message_count,\n resolved: data.resolved,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI intent detection.\n */\n trackAIIntent(data: AIIntentData): void {\n this.track('ai_intent', {\n source: data.source ?? 'assistant',\n ai_intent: data.intent,\n properties: {\n confidence: data.confidence,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI session lifecycle event (start, end, timeout).\n */\n trackAISession(data: AISessionData): void {\n this.track(`ai_session_${data.action}`, {\n source: 'assistant',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_duration_sec: data.duration,\n properties: data.properties,\n })\n }\n\n // ─── Read API ──────────────────────────────────────────────────────────────\n\n /**\n * Get aggregated analytics stats for a time period.\n */\n async getStats(params?: StatsParams): Promise<StatsResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/stats', qp),\n )\n return res.data as StatsResponse\n }\n\n /**\n * Get individual analytics events with pagination and filters.\n */\n async getEvents(params?: EventsParams): Promise<EventsResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n offset: params?.offset,\n event_type: params?.event_type,\n source: params?.source,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/events', qp),\n )\n return res.data as EventsResponse\n }\n\n /**\n * Get page analytics broken down by path.\n */\n async getPages(params?: PagesParams): Promise<PagesResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/pages', qp),\n )\n return res.data as PagesResponse\n }\n\n /**\n * Get traffic source and referrer analytics.\n */\n async getSources(params?: SourcesParams): Promise<SourcesResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/sources', qp),\n )\n return res.data as SourcesResponse\n }\n\n /**\n * Get real-time live stats (active visitors, today's totals).\n */\n async getLive(): Promise<LiveResponse> {\n this.assertNotDestroyed()\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/live'),\n )\n return res.data as LiveResponse\n }\n\n // ─── Batch & Lifecycle ─────────────────────────────────────────────────────\n\n /**\n * Immediately flush all queued events. Returns the result.\n * Safe to call multiple times concurrently — only one flush runs at a time.\n */\n async flush(): Promise<FlushResult> {\n if (this.flushing || this.queue.length === 0) {\n return { sent: 0, failed: 0, errors: [] }\n }\n\n this.flushing = true\n const batch = this.queue.splice(0, 100)\n\n try {\n const res = await withRetry(this.config, () =>\n httpPost(this.config, '/v1/events/batch', batch),\n )\n\n const body = res.data as Record<string, unknown> | undefined\n const accepted = typeof body?.accepted === 'number' ? body.accepted : batch.length\n\n if (this.config.debug) {\n console.error(`[metrone] Flushed ${accepted}/${batch.length} events`)\n }\n\n return { sent: accepted, failed: batch.length - accepted, errors: [] }\n } catch (err) {\n this.queue.unshift(...batch)\n if (this.queue.length > this.config.maxQueueSize) {\n this.queue.length = this.config.maxQueueSize\n }\n\n if (this.config.debug) {\n console.error('[metrone] Flush failed, re-queued:', err)\n }\n\n return {\n sent: 0,\n failed: batch.length,\n errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }],\n }\n } finally {\n this.flushing = false\n\n if (this.queue.length >= this.config.batchSize && this.config.batchSize > 0) {\n this.flush().catch(() => {})\n }\n }\n }\n\n /**\n * Flush remaining events, stop the auto-flush timer, and mark the\n * client as destroyed. Subsequent calls will throw.\n */\n async shutdown(): Promise<FlushResult> {\n if (this.destroyed) return { sent: 0, failed: 0, errors: [] }\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n const result = await this.flush()\n this.destroyed = true\n\n if (this.config.debug) {\n console.error('[metrone] Server SDK shut down')\n }\n\n return result\n }\n\n /** Number of events currently in the send queue. */\n get queueSize(): number {\n return this.queue.length\n }\n\n /** Whether shutdown() has been called. */\n get isDestroyed(): boolean {\n return this.destroyed\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private assertNotDestroyed(): void {\n if (this.destroyed) {\n throw new MetroneConfigError('Client has been shut down. Create a new instance.')\n }\n }\n\n private buildDateParams(\n params?: { days?: number; from?: string | Date; to?: string | Date },\n ): Record<string, string | number | undefined> {\n const result: Record<string, string | number | undefined> = {}\n if (params?.days) result.days = params.days\n if (params?.from) result.from = params.from instanceof Date ? params.from.toISOString() : params.from\n if (params?.to) result.to = params.to instanceof Date ? params.to.toISOString() : params.to\n return result\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@metrone-io/server",
3
+ "version": "1.0.0",
4
+ "description": "Metrone server-side SDK for Node.js, Deno, Bun, and edge runtimes",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "type-check": "tsc --noEmit",
31
+ "clean": "rimraf dist",
32
+ "prepublishOnly": "npm run clean && npm run build"
33
+ },
34
+ "keywords": [
35
+ "metrone",
36
+ "analytics",
37
+ "server",
38
+ "ai-agent",
39
+ "privacy",
40
+ "tracking",
41
+ "node",
42
+ "deno",
43
+ "bun",
44
+ "edge"
45
+ ],
46
+ "author": "Metrone",
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/metrone/metrone.git",
51
+ "directory": "packages/server"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "rimraf": "^5.0.5",
58
+ "tsup": "^8.0.0",
59
+ "typescript": "^5.3.2",
60
+ "vitest": "^2.0.0"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ }
65
+ }