@stacksee/analytics 0.10.0 → 0.11.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,157 @@
1
+ var u = Object.defineProperty;
2
+ var l = (e, t, r) => t in e ? u(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
3
+ var c = (e, t, r) => l(e, typeof t != "symbol" ? t + "" : t, r);
4
+ var i = class extends Error {
5
+ constructor(e, t, r, a) {
6
+ super(e), this.statusCode = t, this.requestId = r, this.details = a, this.name = "EmitKitError";
7
+ }
8
+ }, y = class extends i {
9
+ constructor(e, t) {
10
+ super(
11
+ `Rate limit exceeded. Resets in ${Math.ceil(e.resetIn / 1e3)}s`,
12
+ 429,
13
+ t
14
+ ), this.rateLimit = e, this.name = "RateLimitError";
15
+ }
16
+ }, w = class extends i {
17
+ constructor(e, t, r) {
18
+ super(e, 400, r, t), this.validationErrors = t, this.name = "ValidationError";
19
+ }
20
+ }, I = class {
21
+ constructor(e, t) {
22
+ c(this, "config");
23
+ c(this, "lastRateLimit");
24
+ this.config = {
25
+ apiKey: e,
26
+ baseUrl: (t == null ? void 0 : t.baseUrl) || "https://api.emitkit.com",
27
+ timeout: (t == null ? void 0 : t.timeout) || 3e4,
28
+ fetch: (t == null ? void 0 : t.fetch) || globalThis.fetch
29
+ };
30
+ }
31
+ /**
32
+ * Get the last known rate limit information
33
+ */
34
+ get rateLimit() {
35
+ return this.lastRateLimit;
36
+ }
37
+ /**
38
+ * Events API
39
+ */
40
+ get events() {
41
+ return {
42
+ /**
43
+ * Create a new event
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const result = await client.events.create({
48
+ * channelName: 'payments',
49
+ * title: 'Payment Received',
50
+ * metadata: { amount: 99.99 }
51
+ * });
52
+ * ```
53
+ */
54
+ create: async (e, t) => this.request("/v1/events", {
55
+ method: "POST",
56
+ body: JSON.stringify(e),
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ ...(t == null ? void 0 : t.idempotencyKey) && {
60
+ "Idempotency-Key": t.idempotencyKey
61
+ },
62
+ ...t == null ? void 0 : t.headers
63
+ }
64
+ })
65
+ };
66
+ }
67
+ /**
68
+ * Identify a user with properties and aliases
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * const result = await client.identify({
73
+ * user_id: 'user_123',
74
+ * properties: {
75
+ * email: 'user@example.com',
76
+ * name: 'John Doe',
77
+ * plan: 'pro'
78
+ * },
79
+ * aliases: ['user@example.com', 'johndoe']
80
+ * });
81
+ * ```
82
+ */
83
+ async identify(e, t) {
84
+ return this.request("/v1/identify", {
85
+ method: "POST",
86
+ body: JSON.stringify(e),
87
+ headers: {
88
+ "Content-Type": "application/json",
89
+ ...t == null ? void 0 : t.headers
90
+ }
91
+ });
92
+ }
93
+ /**
94
+ * Make a raw API request
95
+ */
96
+ async request(e, t) {
97
+ const r = `${this.config.baseUrl}${e}`, a = new AbortController(), n = setTimeout(() => a.abort(), this.config.timeout);
98
+ try {
99
+ const s = await this.config.fetch(r, {
100
+ ...t,
101
+ headers: {
102
+ Authorization: `Bearer ${this.config.apiKey}`,
103
+ ...t.headers
104
+ },
105
+ signal: a.signal
106
+ });
107
+ clearTimeout(n);
108
+ const o = this.extractRateLimit(s);
109
+ o && (this.lastRateLimit = o);
110
+ const d = s.headers.get("X-Idempotent-Replay") === "true";
111
+ s.ok || await this.handleErrorResponse(s, o);
112
+ const h = await s.json(), m = h.requestId || s.headers.get("X-Request-ID");
113
+ return {
114
+ data: h.success ? h.data : h,
115
+ rateLimit: o,
116
+ requestId: m || "unknown",
117
+ wasReplayed: d
118
+ };
119
+ } catch (s) {
120
+ throw clearTimeout(n), s instanceof i ? s : s instanceof Error ? s.name === "AbortError" ? new i("Request timeout", 408) : new i(
121
+ `Network error: ${s.message}`,
122
+ 0
123
+ ) : new i("Unknown error occurred", 500);
124
+ }
125
+ }
126
+ extractRateLimit(e) {
127
+ const t = e.headers.get("X-RateLimit-Limit"), r = e.headers.get("X-RateLimit-Remaining"), a = e.headers.get("X-RateLimit-Reset");
128
+ if (!t || !r || !a)
129
+ return null;
130
+ const n = parseInt(a, 10) * 1e3, s = Math.max(0, n - Date.now());
131
+ return {
132
+ limit: parseInt(t, 10),
133
+ remaining: parseInt(r, 10),
134
+ reset: parseInt(a, 10),
135
+ resetIn: s
136
+ };
137
+ }
138
+ async handleErrorResponse(e, t) {
139
+ const r = await e.json().catch(() => ({})), a = r.requestId || e.headers.get("X-Request-ID") || void 0;
140
+ throw e.status === 429 && t ? new y(t, a) : e.status === 400 && r.details ? new w(
141
+ r.error || "Validation error",
142
+ r.details,
143
+ a
144
+ ) : new i(
145
+ r.error || r.message || `HTTP ${e.status}`,
146
+ e.status,
147
+ a || void 0,
148
+ r
149
+ );
150
+ }
151
+ };
152
+ export {
153
+ I as EmitKit,
154
+ i as EmitKitError,
155
+ y as RateLimitError,
156
+ w as ValidationError
157
+ };
@@ -0,0 +1,62 @@
1
+ import { BaseEvent, EventContext } from '../../core/events/types.js';
2
+ import { BaseAnalyticsProvider } from '../base.provider.js';
3
+ /**
4
+ * Configuration for EmitKit server provider
5
+ */
6
+ export interface EmitKitServerConfig {
7
+ /**
8
+ * Your EmitKit API key (starts with emitkit_)
9
+ */
10
+ apiKey: string;
11
+ /**
12
+ * Default channel name for events
13
+ * @default 'analytics'
14
+ */
15
+ channelName?: string;
16
+ /**
17
+ * Send notification for events
18
+ * @default true
19
+ */
20
+ notify?: boolean;
21
+ /**
22
+ * Display style for events
23
+ * @default 'notification'
24
+ */
25
+ displayAs?: "message" | "notification";
26
+ /**
27
+ * Enable debug logging
28
+ */
29
+ debug?: boolean;
30
+ /**
31
+ * Enable/disable the provider
32
+ */
33
+ enabled?: boolean;
34
+ }
35
+ export declare class EmitKitServerProvider extends BaseAnalyticsProvider {
36
+ name: string;
37
+ private client?;
38
+ private initialized;
39
+ private config;
40
+ private currentUserId?;
41
+ private currentUserEmail?;
42
+ constructor(config: EmitKitServerConfig);
43
+ initialize(): Promise<void>;
44
+ identify(userId: string, traits?: Record<string, unknown>): void;
45
+ track(event: BaseEvent, context?: EventContext): Promise<void>;
46
+ pageView(properties?: Record<string, unknown>, context?: EventContext): void;
47
+ reset(): Promise<void>;
48
+ shutdown(): Promise<void>;
49
+ /**
50
+ * Format event action into a human-readable title
51
+ * Converts: "user_signed_up" -> "User Signed Up"
52
+ */
53
+ private formatEventTitle;
54
+ /**
55
+ * Generate a description for the event
56
+ */
57
+ private getEventDescription;
58
+ /**
59
+ * Get an appropriate icon for the event category
60
+ */
61
+ private getEventIcon;
62
+ }
@@ -5,5 +5,7 @@ export { BentoServerProvider } from './bento/server.js';
5
5
  export type { BentoServerConfig, BentoAnalyticsOptions, } from './bento/server.js';
6
6
  export { PirschServerProvider } from './pirsch/server.js';
7
7
  export type { PirschServerConfig } from './pirsch/server.js';
8
+ export { EmitKitServerProvider } from './emitkit/server.js';
9
+ export type { EmitKitServerConfig } from './emitkit/server.js';
8
10
  export { ingestProxyEvents, createProxyHandler } from './proxy/server.js';
9
11
  export type { IngestProxyEventsConfig } from './proxy/server.js';
@@ -1,16 +1,16 @@
1
- var q = Object.defineProperty;
2
- var D = (p, u, i) => u in p ? q(p, u, { enumerable: !0, configurable: !0, writable: !0, value: i }) : p[u] = i;
3
- var t = (p, u, i) => D(p, typeof u != "symbol" ? u + "" : u, i);
4
- import { B as V } from "../base.provider-AfFL5W_P.js";
5
- import { P as Q } from "../server-DjEk1fUD.js";
6
- class N extends V {
1
+ var O = Object.defineProperty;
2
+ var q = (g, t, i) => t in g ? O(g, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : g[t] = i;
3
+ var o = (g, t, i) => q(g, typeof t != "symbol" ? t + "" : t, i);
4
+ import { B } from "../base.provider-AfFL5W_P.js";
5
+ import { P as X } from "../server-DjEk1fUD.js";
6
+ class H extends B {
7
7
  constructor(i) {
8
8
  super({ debug: i.debug, enabled: i.enabled });
9
- t(this, "name", "Bento-Server");
10
- t(this, "client");
11
- t(this, "initialized", !1);
12
- t(this, "config");
13
- t(this, "currentUserEmail");
9
+ o(this, "name", "Bento-Server");
10
+ o(this, "client");
11
+ o(this, "initialized", !1);
12
+ o(this, "config");
13
+ o(this, "currentUserEmail");
14
14
  this.config = i;
15
15
  }
16
16
  async initialize() {
@@ -23,7 +23,7 @@ class N extends V {
23
23
  if (!((e = this.config.authentication) != null && e.secretKey) || typeof this.config.authentication.secretKey != "string")
24
24
  throw new Error("Bento requires authentication.secretKey");
25
25
  try {
26
- const { Analytics: r } = await import("../bento-node-sdk.esm-CWEAoj97.js"), { debug: h, enabled: s, ...d } = this.config;
26
+ const { Analytics: r } = await import("../bento-node-sdk.esm-CWEAoj97.js"), { debug: a, enabled: s, ...d } = this.config;
27
27
  this.client = new r(d), this.initialized = !0, this.log("Initialized successfully", {
28
28
  siteUuid: this.config.siteUuid
29
29
  });
@@ -46,25 +46,25 @@ class N extends V {
46
46
  return;
47
47
  }
48
48
  this.currentUserEmail = r;
49
- const h = e ? { ...e } : {};
50
- h.email = void 0, this.client.V1.addSubscriber({
49
+ const a = e ? { ...e } : {};
50
+ a.email = void 0, this.client.V1.addSubscriber({
51
51
  email: r,
52
- fields: h
52
+ fields: a
53
53
  }).catch((s) => {
54
54
  console.error("[Bento-Server] Failed to identify user:", s);
55
55
  }), this.log("Identified user", { userId: i, email: r, traits: e });
56
56
  }
57
57
  async track(i, e) {
58
- var d, g, n, l;
58
+ var d, p, u, l;
59
59
  if (!this.isEnabled() || !this.initialized || !this.client) return;
60
- const r = ((d = e == null ? void 0 : e.user) == null ? void 0 : d.email) || this.currentUserEmail || ((g = e == null ? void 0 : e.user) == null ? void 0 : g.userId) || i.userId;
60
+ const r = ((d = e == null ? void 0 : e.user) == null ? void 0 : d.email) || this.currentUserEmail || ((p = e == null ? void 0 : e.user) == null ? void 0 : p.userId) || i.userId;
61
61
  if (!r || !r.includes("@")) {
62
62
  console.warn(
63
63
  "[Bento-Server] Skipping event - Bento requires an email address. Anonymous events are not currently supported by the Bento Node SDK. For now, use the Bento client provider for anonymous tracking. If you're using a proxy, use the hybrid pattern as described in the docs. For identified users, call identify() with a valid email before tracking events."
64
64
  );
65
65
  return;
66
66
  }
67
- const h = {
67
+ const a = {
68
68
  ...i.properties,
69
69
  category: i.category,
70
70
  timestamp: i.timestamp || Date.now(),
@@ -83,21 +83,21 @@ class N extends V {
83
83
  ...(e == null ? void 0 : e.device) && { device: e.device },
84
84
  ...(e == null ? void 0 : e.utm) && { utm: e.utm },
85
85
  site: this.config.siteUuid,
86
- ...((n = e == null ? void 0 : e.user) == null ? void 0 : n.userId) && { visitor: e.user.userId }
86
+ ...((u = e == null ? void 0 : e.user) == null ? void 0 : u.userId) && { visitor: e.user.userId }
87
87
  }, s = ((l = e == null ? void 0 : e.user) == null ? void 0 : l.traits) || {};
88
88
  try {
89
89
  await this.client.V1.track({
90
90
  email: r,
91
91
  type: `$${i.action}`,
92
- details: h,
92
+ details: a,
93
93
  fields: s
94
94
  }), this.log("Tracked event", { event: i, context: e });
95
- } catch (a) {
96
- console.error("[Bento-Server] Failed to track event:", a);
95
+ } catch (h) {
96
+ console.error("[Bento-Server] Failed to track event:", h);
97
97
  }
98
98
  }
99
99
  pageView(i, e) {
100
- var d, g, n;
100
+ var d, p, u;
101
101
  if (!this.isEnabled() || !this.initialized || !this.client) return;
102
102
  const r = ((d = e == null ? void 0 : e.user) == null ? void 0 : d.email) || this.currentUserEmail;
103
103
  if (!r || !r.includes("@")) {
@@ -106,7 +106,7 @@ class N extends V {
106
106
  );
107
107
  return;
108
108
  }
109
- const h = {
109
+ const a = {
110
110
  ...i,
111
111
  date: (/* @__PURE__ */ new Date()).toISOString(),
112
112
  ...(e == null ? void 0 : e.page) && {
@@ -121,12 +121,12 @@ class N extends V {
121
121
  }
122
122
  },
123
123
  site: this.config.siteUuid,
124
- ...((g = e == null ? void 0 : e.user) == null ? void 0 : g.userId) && { visitor: e.user.userId }
125
- }, s = ((n = e == null ? void 0 : e.user) == null ? void 0 : n.traits) || {};
124
+ ...((p = e == null ? void 0 : e.user) == null ? void 0 : p.userId) && { visitor: e.user.userId }
125
+ }, s = ((u = e == null ? void 0 : e.user) == null ? void 0 : u.traits) || {};
126
126
  this.client.V1.track({
127
127
  email: r,
128
128
  type: "$view",
129
- details: h,
129
+ details: a,
130
130
  fields: s
131
131
  }).catch((l) => {
132
132
  console.error("[Bento-Server] Failed to track page view:", l);
@@ -139,13 +139,13 @@ class N extends V {
139
139
  this.client = void 0, this.initialized = !1, this.log("Shutdown complete");
140
140
  }
141
141
  }
142
- class W extends V {
142
+ class W extends B {
143
143
  constructor(i) {
144
144
  super({ debug: i.debug, enabled: i.enabled });
145
- t(this, "name", "Pirsch-Server");
146
- t(this, "client");
147
- t(this, "initialized", !1);
148
- t(this, "config");
145
+ o(this, "name", "Pirsch-Server");
146
+ o(this, "client");
147
+ o(this, "initialized", !1);
148
+ o(this, "config");
149
149
  this.config = i;
150
150
  }
151
151
  async initialize() {
@@ -160,7 +160,7 @@ class W extends V {
160
160
  "Pirsch requires a clientId when using OAuth authentication (clientSecret doesn't start with 'pa_'). Either provide a clientId or use an access key (starts with 'pa_') as clientSecret."
161
161
  );
162
162
  try {
163
- const { Pirsch: e } = await import("../index-zS7gy63J.js").then((d) => d.i), { debug: r, enabled: h, ...s } = this.config;
163
+ const { Pirsch: e } = await import("../index-zS7gy63J.js").then((d) => d.i), { debug: r, enabled: a, ...s } = this.config;
164
164
  this.client = new e(s), this.initialized = !0, this.log("Initialized successfully", {
165
165
  hostname: this.config.hostname,
166
166
  authMode: i ? "access-key" : "oauth"
@@ -178,7 +178,7 @@ class W extends V {
178
178
  url: "https://identify",
179
179
  ip: "0.0.0.0",
180
180
  user_agent: "analytics-library"
181
- }, h = {
181
+ }, a = {
182
182
  userId: i,
183
183
  ...e && Object.fromEntries(
184
184
  Object.entries(e).filter(
@@ -186,113 +186,113 @@ class W extends V {
186
186
  )
187
187
  )
188
188
  };
189
- this.client.event("user_identified", r, 0, h).catch((s) => {
189
+ this.client.event("user_identified", r, 0, a).catch((s) => {
190
190
  console.error("[Pirsch-Server] Failed to track identify event:", s);
191
191
  }), this.log("Identified user via event", { userId: i, traits: e });
192
192
  }
193
193
  async track(i, e) {
194
- var a, o, f, y, c, w, m, b, S, k, I, _, E, P, z, B, A, F, v, $, K, j, O;
194
+ var h, n, c, v, y, m, w, b, E, I, k, S, _, z, P, U, K, A, f, $, D, j, V;
195
195
  if (!this.isEnabled() || !this.initialized || !this.client) return;
196
- const r = e, h = ((a = r == null ? void 0 : r.device) == null ? void 0 : a.ip) || ((o = r == null ? void 0 : r.server) == null ? void 0 : o.ip), s = ((f = r == null ? void 0 : r.server) == null ? void 0 : f.userAgent) || ((y = r == null ? void 0 : r.device) == null ? void 0 : y.userAgent);
197
- if (!h || !s) {
196
+ const r = e, a = ((h = r == null ? void 0 : r.device) == null ? void 0 : h.ip) || ((n = r == null ? void 0 : r.server) == null ? void 0 : n.ip), s = ((c = r == null ? void 0 : r.server) == null ? void 0 : c.userAgent) || ((v = r == null ? void 0 : r.device) == null ? void 0 : v.userAgent);
197
+ if (!a || !s) {
198
198
  this.log(
199
199
  "Skipping event - missing required IP or user-agent from context",
200
200
  {
201
- hasIp: !!h,
201
+ hasIp: !!a,
202
202
  hasUserAgent: !!s,
203
203
  event: i.action
204
204
  }
205
205
  );
206
206
  return;
207
207
  }
208
- const g = {
209
- url: ((c = e == null ? void 0 : e.page) == null ? void 0 : c.url) || ((w = e == null ? void 0 : e.page) != null && w.protocol && ((m = e == null ? void 0 : e.page) != null && m.host) && ((b = e == null ? void 0 : e.page) != null && b.path) ? `${e.page.protocol}://${e.page.host}${e.page.path}` : (S = e == null ? void 0 : e.page) != null && S.path ? `https://${this.config.hostname}${e.page.path}` : "https://event"),
210
- ip: h,
208
+ const p = {
209
+ url: ((y = e == null ? void 0 : e.page) == null ? void 0 : y.url) || ((m = e == null ? void 0 : e.page) != null && m.protocol && ((w = e == null ? void 0 : e.page) != null && w.host) && ((b = e == null ? void 0 : e.page) != null && b.path) ? `${e.page.protocol}://${e.page.host}${e.page.path}` : (E = e == null ? void 0 : e.page) != null && E.path ? `https://${this.config.hostname}${e.page.path}` : "https://event"),
210
+ ip: a,
211
211
  user_agent: s,
212
- ...((k = e == null ? void 0 : e.page) == null ? void 0 : k.title) && { title: e.page.title },
213
- ...((I = e == null ? void 0 : e.page) == null ? void 0 : I.referrer) && { referrer: e.page.referrer },
214
- ...((E = (_ = e == null ? void 0 : e.device) == null ? void 0 : _.screen) == null ? void 0 : E.width) && {
212
+ ...((I = e == null ? void 0 : e.page) == null ? void 0 : I.title) && { title: e.page.title },
213
+ ...((k = e == null ? void 0 : e.page) == null ? void 0 : k.referrer) && { referrer: e.page.referrer },
214
+ ...((_ = (S = e == null ? void 0 : e.device) == null ? void 0 : S.screen) == null ? void 0 : _.width) && {
215
215
  screen_width: e.device.screen.width
216
216
  },
217
- ...((z = (P = e == null ? void 0 : e.device) == null ? void 0 : P.screen) == null ? void 0 : z.height) && {
217
+ ...((P = (z = e == null ? void 0 : e.device) == null ? void 0 : z.screen) == null ? void 0 : P.height) && {
218
218
  screen_height: e.device.screen.height
219
219
  },
220
- ...((A = (B = e == null ? void 0 : e.device) == null ? void 0 : B.viewport) == null ? void 0 : A.width) && {
220
+ ...((K = (U = e == null ? void 0 : e.device) == null ? void 0 : U.viewport) == null ? void 0 : K.width) && {
221
221
  sec_ch_viewport_width: String(e.device.viewport.width)
222
222
  },
223
- ...((F = e == null ? void 0 : e.device) == null ? void 0 : F.language) && {
223
+ ...((A = e == null ? void 0 : e.device) == null ? void 0 : A.language) && {
224
224
  accept_language: e.device.language
225
225
  },
226
- ...((v = e == null ? void 0 : e.device) == null ? void 0 : v.type) && {
226
+ ...((f = e == null ? void 0 : e.device) == null ? void 0 : f.type) && {
227
227
  sec_ch_ua_mobile: e.device.type === "mobile" || e.device.type === "tablet" ? "?1" : "?0"
228
228
  },
229
229
  ...(($ = e == null ? void 0 : e.device) == null ? void 0 : $.os) && { sec_ch_ua_platform: e.device.os }
230
230
  }, l = {
231
231
  ...Object.fromEntries(
232
232
  Object.entries(i.properties).filter(
233
- ([, U]) => typeof U == "string" || typeof U == "number" || typeof U == "boolean"
233
+ ([, F]) => typeof F == "string" || typeof F == "number" || typeof F == "boolean"
234
234
  )
235
235
  ),
236
236
  category: i.category,
237
237
  timestamp: String(i.timestamp || Date.now()),
238
238
  ...i.userId && { userId: i.userId },
239
239
  ...i.sessionId && { sessionId: i.sessionId },
240
- ...((K = e == null ? void 0 : e.user) == null ? void 0 : K.email) && { user_email: e.user.email },
240
+ ...((D = e == null ? void 0 : e.user) == null ? void 0 : D.email) && { user_email: e.user.email },
241
241
  ...((j = e == null ? void 0 : e.device) == null ? void 0 : j.timezone) && { timezone: e.device.timezone },
242
- ...((O = e == null ? void 0 : e.device) == null ? void 0 : O.browser) && { browser: e.device.browser }
242
+ ...((V = e == null ? void 0 : e.device) == null ? void 0 : V.browser) && { browser: e.device.browser }
243
243
  };
244
244
  try {
245
- await this.client.event(i.action, g, 0, l), this.log("Tracked event", { event: i, context: e });
246
- } catch (U) {
247
- console.error("[Pirsch-Server] Failed to track event:", U);
245
+ await this.client.event(i.action, p, 0, l), this.log("Tracked event", { event: i, context: e });
246
+ } catch (F) {
247
+ console.error("[Pirsch-Server] Failed to track event:", F);
248
248
  }
249
249
  }
250
250
  pageView(i, e) {
251
- var n, l, a, o, f, y, c, w, m, b, S, k, I, _, E, P, z, B, A, F;
251
+ var u, l, h, n, c, v, y, m, w, b, E, I, k, S, _, z, P, U, K, A;
252
252
  if (!this.isEnabled() || !this.initialized || !this.client) return;
253
- const r = e, h = ((n = r == null ? void 0 : r.device) == null ? void 0 : n.ip) || ((l = r == null ? void 0 : r.server) == null ? void 0 : l.ip), s = ((a = r == null ? void 0 : r.server) == null ? void 0 : a.userAgent) || ((o = r == null ? void 0 : r.device) == null ? void 0 : o.userAgent);
254
- if (!h || !s) {
253
+ const r = e, a = ((u = r == null ? void 0 : r.device) == null ? void 0 : u.ip) || ((l = r == null ? void 0 : r.server) == null ? void 0 : l.ip), s = ((h = r == null ? void 0 : r.server) == null ? void 0 : h.userAgent) || ((n = r == null ? void 0 : r.device) == null ? void 0 : n.userAgent);
254
+ if (!a || !s) {
255
255
  this.log(
256
256
  "Skipping pageView - missing required IP or user-agent from context",
257
257
  {
258
- hasIp: !!h,
258
+ hasIp: !!a,
259
259
  hasUserAgent: !!s
260
260
  }
261
261
  );
262
262
  return;
263
263
  }
264
- const g = {
265
- url: ((f = e == null ? void 0 : e.page) == null ? void 0 : f.url) || ((y = e == null ? void 0 : e.page) != null && y.protocol && ((c = e == null ? void 0 : e.page) != null && c.host) && ((w = e == null ? void 0 : e.page) != null && w.path) ? `${e.page.protocol}://${e.page.host}${e.page.path}` : (m = e == null ? void 0 : e.page) != null && m.path ? `https://${this.config.hostname}${e.page.path}` : "https://pageview"),
266
- ip: h,
264
+ const p = {
265
+ url: ((c = e == null ? void 0 : e.page) == null ? void 0 : c.url) || ((v = e == null ? void 0 : e.page) != null && v.protocol && ((y = e == null ? void 0 : e.page) != null && y.host) && ((m = e == null ? void 0 : e.page) != null && m.path) ? `${e.page.protocol}://${e.page.host}${e.page.path}` : (w = e == null ? void 0 : e.page) != null && w.path ? `https://${this.config.hostname}${e.page.path}` : "https://pageview"),
266
+ ip: a,
267
267
  user_agent: s,
268
268
  ...((b = e == null ? void 0 : e.page) == null ? void 0 : b.title) && { title: e.page.title },
269
- ...((S = e == null ? void 0 : e.page) == null ? void 0 : S.referrer) && { referrer: e.page.referrer },
270
- ...((I = (k = e == null ? void 0 : e.device) == null ? void 0 : k.screen) == null ? void 0 : I.width) && {
269
+ ...((E = e == null ? void 0 : e.page) == null ? void 0 : E.referrer) && { referrer: e.page.referrer },
270
+ ...((k = (I = e == null ? void 0 : e.device) == null ? void 0 : I.screen) == null ? void 0 : k.width) && {
271
271
  screen_width: e.device.screen.width
272
272
  },
273
- ...((E = (_ = e == null ? void 0 : e.device) == null ? void 0 : _.screen) == null ? void 0 : E.height) && {
273
+ ...((_ = (S = e == null ? void 0 : e.device) == null ? void 0 : S.screen) == null ? void 0 : _.height) && {
274
274
  screen_height: e.device.screen.height
275
275
  },
276
- ...((z = (P = e == null ? void 0 : e.device) == null ? void 0 : P.viewport) == null ? void 0 : z.width) && {
276
+ ...((P = (z = e == null ? void 0 : e.device) == null ? void 0 : z.viewport) == null ? void 0 : P.width) && {
277
277
  sec_ch_viewport_width: String(e.device.viewport.width)
278
278
  },
279
- ...((B = e == null ? void 0 : e.device) == null ? void 0 : B.language) && {
279
+ ...((U = e == null ? void 0 : e.device) == null ? void 0 : U.language) && {
280
280
  accept_language: e.device.language
281
281
  },
282
- ...((A = e == null ? void 0 : e.device) == null ? void 0 : A.type) && {
282
+ ...((K = e == null ? void 0 : e.device) == null ? void 0 : K.type) && {
283
283
  sec_ch_ua_mobile: e.device.type === "mobile" || e.device.type === "tablet" ? "?1" : "?0"
284
284
  },
285
- ...((F = e == null ? void 0 : e.device) == null ? void 0 : F.os) && { sec_ch_ua_platform: e.device.os },
285
+ ...((A = e == null ? void 0 : e.device) == null ? void 0 : A.os) && { sec_ch_ua_platform: e.device.os },
286
286
  ...i && {
287
287
  tags: Object.fromEntries(
288
288
  Object.entries(i).filter(
289
- ([, v]) => typeof v == "string" || typeof v == "number" || typeof v == "boolean"
289
+ ([, f]) => typeof f == "string" || typeof f == "number" || typeof f == "boolean"
290
290
  )
291
291
  )
292
292
  }
293
293
  };
294
- this.client.hit(g).catch((v) => {
295
- console.error("[Pirsch-Server] Failed to track page view:", v);
294
+ this.client.hit(p).catch((f) => {
295
+ console.error("[Pirsch-Server] Failed to track page view:", f);
296
296
  }), this.log("Tracked page view", { properties: i, context: e });
297
297
  }
298
298
  async reset() {
@@ -310,80 +310,271 @@ class W extends V {
310
310
  this.client = void 0, this.initialized = !1, this.log("Shutdown complete");
311
311
  }
312
312
  }
313
- async function R(p, u, i) {
314
- var e, r, h, s;
313
+ class G extends B {
314
+ constructor(i) {
315
+ super({ debug: i.debug, enabled: i.enabled });
316
+ o(this, "name", "EmitKit-Server");
317
+ o(this, "client");
318
+ o(this, "initialized", !1);
319
+ o(this, "config");
320
+ o(this, "currentUserId");
321
+ o(this, "currentUserEmail");
322
+ this.config = i;
323
+ }
324
+ async initialize() {
325
+ if (this.isEnabled() && !this.initialized) {
326
+ if (!this.config.apiKey || typeof this.config.apiKey != "string")
327
+ throw new Error("EmitKit requires an apiKey");
328
+ this.config.apiKey.startsWith("emitkit_") || console.warn(
329
+ "[EmitKit-Server] API key should start with 'emitkit_'. Double check your configuration."
330
+ );
331
+ try {
332
+ const { EmitKit: i } = await import("../index-CBs091W0.js");
333
+ this.client = new i(this.config.apiKey), this.initialized = !0, this.log("Initialized successfully");
334
+ } catch (i) {
335
+ throw console.error(
336
+ "[EmitKit-Server] Failed to initialize. Make sure @emitkit/js is installed:",
337
+ i
338
+ ), i;
339
+ }
340
+ }
341
+ }
342
+ identify(i, e) {
343
+ if (!this.isEnabled() || !this.initialized || !this.client) return;
344
+ this.currentUserId = i;
345
+ const r = (e == null ? void 0 : e.email) || i;
346
+ r && r.includes("@") && (this.currentUserEmail = r);
347
+ const a = [];
348
+ i && a.push(i), r && r !== i && a.push(r), e != null && e.username && typeof e.username == "string" && a.push(e.username), this.client.identify({
349
+ user_id: i,
350
+ properties: e || {},
351
+ aliases: a.length > 0 ? a : void 0
352
+ }).then((s) => {
353
+ var d, p, u, l, h;
354
+ this.log("Identified user", {
355
+ userId: i,
356
+ email: r,
357
+ identityId: s.data.id,
358
+ aliasesCreated: ((p = (d = s.data.aliases) == null ? void 0 : d.created) == null ? void 0 : p.length) || 0,
359
+ aliasesFailed: ((l = (u = s.data.aliases) == null ? void 0 : u.failed) == null ? void 0 : l.length) || 0
360
+ }), (h = s.data.aliases) != null && h.failed && s.data.aliases.failed.length > 0 && console.warn(
361
+ "[EmitKit-Server] Some aliases failed to create:",
362
+ s.data.aliases.failed
363
+ );
364
+ }).catch((s) => {
365
+ console.error("[EmitKit-Server] Failed to identify user:", s);
366
+ });
367
+ }
368
+ async track(i, e) {
369
+ var u, l, h;
370
+ if (!this.isEnabled() || !this.initialized || !this.client) return;
371
+ const r = ((u = e == null ? void 0 : e.user) == null ? void 0 : u.email) || ((l = e == null ? void 0 : e.user) == null ? void 0 : l.userId) || i.userId || this.currentUserEmail || this.currentUserId, a = this.formatEventTitle(i.action), s = {
372
+ ...i.properties,
373
+ category: i.category,
374
+ timestamp: i.timestamp || Date.now(),
375
+ ...i.sessionId && { sessionId: i.sessionId },
376
+ ...(e == null ? void 0 : e.page) && {
377
+ page: {
378
+ url: e.page.url,
379
+ host: e.page.host,
380
+ path: e.page.path,
381
+ title: e.page.title,
382
+ protocol: e.page.protocol,
383
+ referrer: e.page.referrer,
384
+ ...e.page.search && { search: e.page.search }
385
+ }
386
+ },
387
+ ...(e == null ? void 0 : e.device) && { device: e.device },
388
+ ...(e == null ? void 0 : e.utm) && { utm: e.utm },
389
+ ...(e == null ? void 0 : e.server) && { server: e.server }
390
+ }, d = [];
391
+ i.category && d.push(i.category), (h = i.properties) != null && h.tags && Array.isArray(i.properties.tags) && i.properties.tags.every((n) => typeof n == "string") && d.push(...i.properties.tags);
392
+ const p = this.config.channelName || "analytics";
393
+ try {
394
+ const n = await this.client.events.create({
395
+ channelName: p,
396
+ title: a,
397
+ description: this.getEventDescription(i, e),
398
+ icon: this.getEventIcon(i.category),
399
+ tags: d.length > 0 ? d : void 0,
400
+ metadata: s,
401
+ userId: r || null,
402
+ notify: this.config.notify ?? !0,
403
+ displayAs: this.config.displayAs || "notification",
404
+ source: "stacksee-analytics"
405
+ });
406
+ this.log("Tracked event", {
407
+ eventId: n.data.id,
408
+ action: i.action,
409
+ userId: r,
410
+ channelName: p
411
+ });
412
+ } catch (n) {
413
+ throw console.error("[EmitKit-Server] Failed to track event:", n), n;
414
+ }
415
+ }
416
+ pageView(i, e) {
417
+ var d, p, u;
418
+ if (!this.isEnabled() || !this.initialized || !this.client) return;
419
+ const r = ((d = e == null ? void 0 : e.user) == null ? void 0 : d.email) || ((p = e == null ? void 0 : e.user) == null ? void 0 : p.userId) || this.currentUserEmail || this.currentUserId, a = {
420
+ ...i,
421
+ date: (/* @__PURE__ */ new Date()).toISOString(),
422
+ ...(e == null ? void 0 : e.page) && {
423
+ page: {
424
+ url: e.page.url,
425
+ host: e.page.host,
426
+ path: e.page.path,
427
+ title: e.page.title,
428
+ protocol: e.page.protocol,
429
+ referrer: e.page.referrer,
430
+ ...e.page.search && { search: e.page.search }
431
+ }
432
+ },
433
+ ...(e == null ? void 0 : e.device) && { device: e.device },
434
+ ...(e == null ? void 0 : e.utm) && { utm: e.utm },
435
+ ...(e == null ? void 0 : e.server) && { server: e.server }
436
+ }, s = this.config.channelName || "analytics";
437
+ this.client.events.create({
438
+ channelName: s,
439
+ title: "Page View",
440
+ description: ((u = e == null ? void 0 : e.page) == null ? void 0 : u.path) || "User viewed a page",
441
+ icon: "👁️",
442
+ tags: ["page_view", "navigation"],
443
+ metadata: a,
444
+ userId: r || null,
445
+ notify: !1,
446
+ // Don't notify for page views by default
447
+ displayAs: "message",
448
+ source: "stacksee-analytics"
449
+ }).then((l) => {
450
+ var h;
451
+ this.log("Tracked page view", {
452
+ eventId: l.data.id,
453
+ path: (h = e == null ? void 0 : e.page) == null ? void 0 : h.path,
454
+ userId: r
455
+ });
456
+ }).catch((l) => {
457
+ console.error("[EmitKit-Server] Failed to track page view:", l);
458
+ });
459
+ }
460
+ async reset() {
461
+ !this.isEnabled() || !this.initialized || !this.client || (this.currentUserId = void 0, this.currentUserEmail = void 0, this.log("Reset user session"));
462
+ }
463
+ async shutdown() {
464
+ this.client = void 0, this.initialized = !1, this.log("Shutdown complete");
465
+ }
466
+ // ============================================================================
467
+ // Helper Methods
468
+ // ============================================================================
469
+ /**
470
+ * Format event action into a human-readable title
471
+ * Converts: "user_signed_up" -> "User Signed Up"
472
+ */
473
+ formatEventTitle(i) {
474
+ return i.split("_").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" ");
475
+ }
476
+ /**
477
+ * Generate a description for the event
478
+ */
479
+ getEventDescription(i, e) {
480
+ var a;
481
+ return (a = i.properties) != null && a.description && typeof i.properties.description == "string" ? i.properties.description : {
482
+ engagement: "User interaction event",
483
+ user: "User lifecycle event",
484
+ navigation: "Navigation event",
485
+ error: "Error or exception occurred",
486
+ performance: "Performance metric",
487
+ conversion: "Conversion event"
488
+ }[i.category] || void 0;
489
+ }
490
+ /**
491
+ * Get an appropriate icon for the event category
492
+ */
493
+ getEventIcon(i) {
494
+ return {
495
+ engagement: "👆",
496
+ user: "👤",
497
+ navigation: "🧭",
498
+ error: "❌",
499
+ performance: "⚡",
500
+ conversion: "💰"
501
+ }[i];
502
+ }
503
+ }
504
+ async function T(g, t, i) {
505
+ var e, r, a, s;
315
506
  try {
316
- const d = await p.json();
507
+ const d = await g.json();
317
508
  if (!d.events || !Array.isArray(d.events))
318
509
  throw new Error("Invalid payload: missing events array");
319
- const g = i != null && i.extractIp ? i.extractIp(p) : T(p), n = p.headers.get("user-agent"), l = i != null && i.enrichContext ? i.enrichContext(p) : {};
320
- for (const a of d.events)
510
+ const p = i != null && i.extractIp ? i.extractIp(g) : N(g), u = g.headers.get("user-agent"), l = i != null && i.enrichContext ? i.enrichContext(g) : {};
511
+ for (const h of d.events)
321
512
  try {
322
- switch (a.type) {
513
+ switch (h.type) {
323
514
  case "track": {
324
- const o = {
325
- ...a.context,
515
+ const n = {
516
+ ...h.context,
326
517
  ...l,
327
518
  server: {
328
- ...(e = a.context) == null ? void 0 : e.server,
519
+ ...(e = h.context) == null ? void 0 : e.server,
329
520
  ...typeof (l == null ? void 0 : l.server) == "object" && l.server !== null ? l.server : {},
330
- ...n ? { userAgent: n } : {}
521
+ ...u ? { userAgent: u } : {}
331
522
  },
332
523
  device: {
333
- ...(r = a.context) == null ? void 0 : r.device,
334
- ...g ? { ip: g } : {}
524
+ ...(r = h.context) == null ? void 0 : r.device,
525
+ ...p ? { ip: p } : {}
335
526
  }
336
527
  };
337
- await u.track(
338
- a.event.action,
528
+ await t.track(
529
+ h.event.action,
339
530
  // biome-ignore lint/suspicious/noExplicitAny: Properties from JSON cannot be type-checked against TEventMap at compile time
340
- a.event.properties,
531
+ h.event.properties,
341
532
  {
342
- userId: a.event.userId,
343
- sessionId: a.event.sessionId,
344
- context: o
533
+ userId: h.event.userId,
534
+ sessionId: h.event.sessionId,
535
+ context: n
345
536
  }
346
537
  );
347
538
  break;
348
539
  }
349
540
  case "identify": {
350
- u.identify(a.userId, a.traits);
541
+ t.identify(h.userId, h.traits);
351
542
  break;
352
543
  }
353
544
  case "pageView": {
354
- const o = {
355
- ...a.context,
545
+ const n = {
546
+ ...h.context,
356
547
  ...l,
357
548
  server: {
358
- ...(h = a.context) == null ? void 0 : h.server,
549
+ ...(a = h.context) == null ? void 0 : a.server,
359
550
  ...typeof (l == null ? void 0 : l.server) == "object" && l.server !== null ? l.server : {},
360
- ...n ? { userAgent: n } : {}
551
+ ...u ? { userAgent: u } : {}
361
552
  },
362
553
  device: {
363
- ...(s = a.context) == null ? void 0 : s.device,
364
- ...g ? { ip: g } : {}
554
+ ...(s = h.context) == null ? void 0 : s.device,
555
+ ...p ? { ip: p } : {}
365
556
  }
366
557
  };
367
- u.pageView(a.properties, {
368
- context: o
558
+ t.pageView(h.properties, {
559
+ context: n
369
560
  });
370
561
  break;
371
562
  }
372
563
  case "reset":
373
564
  break;
374
565
  default:
375
- console.warn("[Proxy] Unknown event type:", a);
566
+ console.warn("[Proxy] Unknown event type:", h);
376
567
  }
377
- } catch (o) {
378
- i != null && i.onError ? i.onError(o) : console.error("[Proxy] Failed to process event:", o);
568
+ } catch (n) {
569
+ i != null && i.onError ? i.onError(n) : console.error("[Proxy] Failed to process event:", n);
379
570
  }
380
571
  } catch (d) {
381
572
  throw i != null && i.onError ? i.onError(d) : console.error("[Proxy] Failed to ingest events:", d), d;
382
573
  }
383
574
  }
384
- function T(p) {
575
+ function N(g) {
385
576
  var i;
386
- const u = [
577
+ const t = [
387
578
  "x-forwarded-for",
388
579
  "x-real-ip",
389
580
  "cf-connecting-ip",
@@ -391,26 +582,27 @@ function T(p) {
391
582
  "x-client-ip",
392
583
  "x-cluster-client-ip"
393
584
  ];
394
- for (const e of u) {
395
- const r = p.headers.get(e);
585
+ for (const e of t) {
586
+ const r = g.headers.get(e);
396
587
  if (r)
397
588
  return (i = r.split(",")[0]) == null ? void 0 : i.trim();
398
589
  }
399
590
  }
400
- function G(p, u) {
591
+ function J(g, t) {
401
592
  return async (i) => {
402
593
  try {
403
- return await R(i, p, u), new Response("OK", { status: 200 });
594
+ return await T(i, g, t), new Response("OK", { status: 200 });
404
595
  } catch (e) {
405
596
  return console.error("[Proxy] Handler error:", e), new Response("Internal Server Error", { status: 500 });
406
597
  }
407
598
  };
408
599
  }
409
600
  export {
410
- V as BaseAnalyticsProvider,
411
- N as BentoServerProvider,
601
+ B as BaseAnalyticsProvider,
602
+ H as BentoServerProvider,
603
+ G as EmitKitServerProvider,
412
604
  W as PirschServerProvider,
413
- Q as PostHogServerProvider,
414
- G as createProxyHandler,
415
- R as ingestProxyEvents
605
+ X as PostHogServerProvider,
606
+ J as createProxyHandler,
607
+ T as ingestProxyEvents
416
608
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacksee/analytics",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "A highly typed, provider-agnostic analytics library for TypeScript applications",
5
5
  "type": "module",
6
6
  "exports": {
@@ -52,7 +52,7 @@
52
52
  "@biomejs/biome": "1.9.4",
53
53
  "@changesets/cli": "^2.29.4",
54
54
  "@svitejs/changesets-changelog-github-compact": "^1.2.0",
55
- "@types/node": "^20.14.11",
55
+ "@types/node": "^20.17.51",
56
56
  "@vitest/coverage-v8": "3.1.4",
57
57
  "globals": "^15.8.0",
58
58
  "jsdom": "^26.1.0",
@@ -64,6 +64,7 @@
64
64
  },
65
65
  "optionalDependencies": {
66
66
  "@bentonow/bento-node-sdk": "^0.2.1",
67
+ "@emitkit/js": "^2.1.0",
67
68
  "pirsch-sdk": "^2.9.1",
68
69
  "posthog-js": "^1.268.2",
69
70
  "posthog-node": "^5.9.0"