@channlize/core 0.2.1 → 0.4.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.mjs CHANGED
@@ -1,6 +1,47 @@
1
1
  // src/http.ts
2
2
  import fetch from "cross-fetch";
3
3
 
4
+ // src/shared.ts
5
+ var DEFAULT_BASE_URL = "https://api.channlize.com";
6
+ var DEFAULT_TIMEOUT = 1e4;
7
+ var EVENT_SEVERITIES = ["info", "warning", "error"];
8
+ var EVENT_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
9
+ function isValidEventKey(event) {
10
+ return typeof event === "string" && event.length <= 100 && EVENT_KEY_PATTERN.test(event);
11
+ }
12
+ function keyKind(key) {
13
+ return key.startsWith("wk_") ? "widget" : "server";
14
+ }
15
+ function clean(value) {
16
+ const v = value?.trim();
17
+ return v ? v : void 0;
18
+ }
19
+ function readChannlizeEnv(env = typeof process !== "undefined" ? process.env : void 0) {
20
+ return {
21
+ serverApiKey: clean(env?.CHANNLIZE_SERVER_API_KEY),
22
+ widgetKey: clean(env?.CHANNLIZE_WIDGET_KEY),
23
+ apiKey: clean(env?.CHANNLIZE_API_KEY),
24
+ projectId: clean(env?.CHANNLIZE_PROJECT_ID),
25
+ baseUrl: clean(env?.CHANNLIZE_BASE_URL),
26
+ timeout: clean(env?.CHANNLIZE_TIMEOUT)
27
+ };
28
+ }
29
+ function parseApiError(status, body, statusText = "") {
30
+ const data = body && typeof body === "object" ? body : {};
31
+ const raw = data.message;
32
+ const joined = Array.isArray(raw) ? raw.filter((m) => typeof m === "string").join(", ") : typeof raw === "string" ? raw : "";
33
+ const error = typeof data.error === "string" ? data.error : void 0;
34
+ const code = typeof data.code === "string" ? data.code : error;
35
+ const message = joined || (error && /\s/.test(error) ? error : "") || `HTTP ${status}: ${statusText}`.replace(/: $/, "");
36
+ return {
37
+ status,
38
+ message,
39
+ code,
40
+ requestId: typeof data.requestId === "string" ? data.requestId : void 0,
41
+ body: data
42
+ };
43
+ }
44
+
4
45
  // src/types.ts
5
46
  var ChannlizeError = class extends Error {
6
47
  constructor(message, statusCode, code) {
@@ -11,6 +52,13 @@ var ChannlizeError = class extends Error {
11
52
  }
12
53
  statusCode;
13
54
  code;
55
+ /** The API's `requestId` for a failed response, to quote to support. */
56
+ requestId;
57
+ /**
58
+ * The API's JSON error body, e.g. `limit`, `used`, `resetAt` and
59
+ * `upgradeUrl` on a plan-limit 429. Undefined for network errors.
60
+ */
61
+ details;
14
62
  };
15
63
  var ValidationError = class extends ChannlizeError {
16
64
  constructor(message, field) {
@@ -27,8 +75,8 @@ var AuthenticationError = class extends ChannlizeError {
27
75
  }
28
76
  };
29
77
  var RateLimitError = class extends ChannlizeError {
30
- constructor(message = "Rate limit exceeded") {
31
- super(message, 429, "RATE_LIMIT_ERROR");
78
+ constructor(message = "Rate limit exceeded", code = "RATE_LIMIT_ERROR") {
79
+ super(message, 429, code);
32
80
  this.name = "RateLimitError";
33
81
  }
34
82
  };
@@ -71,43 +119,54 @@ async function requestJson(http, endpoint, options = {}) {
71
119
  }
72
120
  }
73
121
  async function handleErrorResponse(response) {
74
- const contentType = response.headers.get("content-type");
75
- let errorData = {};
76
- if (contentType?.includes("application/json")) {
122
+ let body = {};
123
+ if (response.headers.get("content-type")?.includes("application/json")) {
77
124
  try {
78
- errorData = await response.json();
125
+ body = await response.json();
79
126
  } catch {
80
127
  }
81
128
  }
82
- const rawMessage = errorData.message;
83
- const message = (Array.isArray(rawMessage) ? rawMessage.join(", ") : rawMessage) || `HTTP ${response.status}: ${response.statusText}`;
84
- const code = errorData.code ?? errorData.error;
85
- switch (response.status) {
129
+ throw errorFromApi(parseApiError(response.status, body, response.statusText));
130
+ }
131
+ function errorFromApi(info) {
132
+ const { status, message, code } = info;
133
+ let error;
134
+ switch (status) {
86
135
  case 401:
87
- throw new AuthenticationError(message);
136
+ error = new AuthenticationError(message);
137
+ break;
88
138
  case 400:
89
- throw new ValidationError(message, errorData.field);
139
+ error = new ValidationError(
140
+ message,
141
+ typeof info.body.field === "string" ? info.body.field : void 0
142
+ );
143
+ break;
90
144
  case 429:
91
- throw new RateLimitError(message);
145
+ error = new RateLimitError(
146
+ message,
147
+ code === "PLAN_LIMIT_REACHED" ? code : void 0
148
+ );
149
+ break;
92
150
  default:
93
- throw new ChannlizeError(message, response.status, code);
151
+ error = new ChannlizeError(message, status, code);
94
152
  }
153
+ error.requestId = info.requestId;
154
+ error.details = info.body;
155
+ return error;
95
156
  }
96
157
 
97
158
  // src/notify.ts
98
- var EVENT_KEY = /^[a-z0-9][a-z0-9._-]*$/;
99
- var DEFAULT_BASE_URL = "https://api.channlize.com";
100
159
  async function sendNotify(http, auth, event, data, options = {}) {
101
160
  if (!auth.projectId) {
102
161
  throw new ValidationError("projectId is required for notify", "projectId");
103
162
  }
104
- if (auth.serverApiKey.startsWith("wk_")) {
163
+ if (keyKind(auth.serverApiKey) === "widget") {
105
164
  throw new ValidationError(
106
165
  "notify needs a server API key (ch_live_\u2026, write scope), not the public widget key",
107
166
  "serverApiKey"
108
167
  );
109
168
  }
110
- if (typeof event !== "string" || event.length > 100 || !EVENT_KEY.test(event)) {
169
+ if (!isValidEventKey(event)) {
111
170
  throw new ValidationError(
112
171
  `Invalid event key "${event}": use lowercase letters, digits, dots, underscores or dashes (e.g. user.subscribed)`,
113
172
  "event"
@@ -131,15 +190,11 @@ async function sendNotify(http, auth, event, data, options = {}) {
131
190
  });
132
191
  return res;
133
192
  }
134
- function readEnv(name) {
135
- const env = typeof process !== "undefined" && process.env ? process.env : void 0;
136
- const value = env?.[name];
137
- return value && value.trim() ? value.trim() : void 0;
138
- }
139
193
  function createNotifier(config = {}) {
140
194
  return (event, data, options) => {
141
- const serverApiKey = config.serverApiKey ?? readEnv("CHANNLIZE_SERVER_API_KEY") ?? readEnv("CHANNLIZE_API_KEY");
142
- const projectId = config.projectId ?? readEnv("CHANNLIZE_PROJECT_ID");
195
+ const env = readChannlizeEnv();
196
+ const serverApiKey = config.serverApiKey ?? env.serverApiKey ?? env.apiKey;
197
+ const projectId = config.projectId ?? env.projectId;
143
198
  if (!serverApiKey) {
144
199
  return Promise.reject(
145
200
  new ValidationError(
@@ -156,9 +211,12 @@ function createNotifier(config = {}) {
156
211
  )
157
212
  );
158
213
  }
159
- const baseUrl = config.baseUrl ?? readEnv("CHANNLIZE_BASE_URL") ?? DEFAULT_BASE_URL;
214
+ const baseUrl = config.baseUrl ?? env.baseUrl ?? DEFAULT_BASE_URL;
160
215
  return sendNotify(
161
- { baseUrl: baseUrl.replace(/\/$/, ""), timeout: config.timeout ?? 1e4 },
216
+ {
217
+ baseUrl: baseUrl.replace(/\/$/, ""),
218
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
219
+ },
162
220
  { serverApiKey, projectId },
163
221
  event,
164
222
  data,
@@ -174,8 +232,8 @@ var ChannlizeClient = class {
174
232
  this.config = {
175
233
  ...config,
176
234
  // After the spread: an explicit `undefined` must not drop the defaults.
177
- baseUrl: config.baseUrl || "https://api.channlize.com",
178
- timeout: config.timeout ?? 1e4
235
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
236
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
179
237
  };
180
238
  this.validateConfig();
181
239
  }
@@ -301,6 +359,9 @@ var ChannlizeClient = class {
301
359
  * `POST /public/events`). Shown under Log tracking in the dashboard.
302
360
  */
303
361
  async trackEvent(name, options) {
362
+ return this.nonFatalTracking(() => this.sendBrowserEvent(name, options));
363
+ }
364
+ async sendBrowserEvent(name, options) {
304
365
  const res = await this.makeRequest(
305
366
  "/public/events",
306
367
  {
@@ -329,6 +390,28 @@ var ChannlizeClient = class {
329
390
  "serverApiKey is required for trackServerEvent"
330
391
  );
331
392
  }
393
+ return this.nonFatalTracking(() => this.sendServerEvent(name, options));
394
+ }
395
+ /**
396
+ * Log tracking must never break the caller: a 429 (rate limit, or the
397
+ * plan's limit for the billing period) resolves with `success: false`
398
+ * and `dropped` instead of throwing, with one console warning per client.
399
+ */
400
+ async nonFatalTracking(send) {
401
+ try {
402
+ return await send();
403
+ } catch (error) {
404
+ if (!(error instanceof RateLimitError)) throw error;
405
+ const dropped = error.code === "PLAN_LIMIT_REACHED" ? "plan_limit" : "rate_limit";
406
+ if (!this.warnedDropped && typeof console !== "undefined") {
407
+ this.warnedDropped = true;
408
+ console.warn(`[channlize] log event dropped: ${error.message}`);
409
+ }
410
+ return { success: false, id: "", dropped };
411
+ }
412
+ }
413
+ warnedDropped = false;
414
+ async sendServerEvent(name, options) {
332
415
  const res = await this.makeRequest(
333
416
  "/v1/events",
334
417
  {
@@ -377,9 +460,17 @@ export {
377
460
  AuthenticationError,
378
461
  ChannlizeClient,
379
462
  ChannlizeError,
463
+ DEFAULT_BASE_URL,
464
+ DEFAULT_TIMEOUT,
465
+ EVENT_KEY_PATTERN,
466
+ EVENT_SEVERITIES,
380
467
  RateLimitError,
381
468
  ValidationError,
382
469
  createNotifier,
383
- ChannlizeClient as default
470
+ ChannlizeClient as default,
471
+ isValidEventKey,
472
+ keyKind,
473
+ parseApiError,
474
+ readChannlizeEnv
384
475
  };
385
476
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http.ts","../src/types.ts","../src/notify.ts","../src/client.ts"],"sourcesContent":["import fetch from 'cross-fetch';\nimport type { ApiResponse } from './types';\nimport {\n ChannlizeError,\n AuthenticationError,\n ValidationError,\n RateLimitError,\n} from './types';\n\n/**\n * Shared HTTP layer for `ChannlizeClient` and `createNotifier`: JSON\n * requests with a timeout, and the API's error shape mapped to typed errors.\n */\nexport async function requestJson<T>(\n http: { baseUrl: string; timeout: number },\n endpoint: string,\n options: RequestInit = {}\n): Promise<ApiResponse<T>> {\n const url = `${http.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), http.timeout);\n\n try {\n const mergedHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(options.headers as Record<string, string>),\n };\n\n // When Content-Type is explicitly empty (e.g. FormData), remove it so the\n // browser / runtime can set the correct multipart boundary automatically.\n if (mergedHeaders['Content-Type'] === '') {\n delete mergedHeaders['Content-Type'];\n }\n\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n headers: mergedHeaders,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n await handleErrorResponse(response);\n }\n\n const data = await response.json();\n return data as ApiResponse<T>;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ChannlizeError) {\n throw error;\n }\n\n if (error instanceof Error && error.name === 'AbortError') {\n throw new ChannlizeError('Request timeout', 408, 'TIMEOUT');\n }\n\n throw new ChannlizeError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\nexport async function handleErrorResponse(response: Response): Promise<never> {\n const contentType = response.headers.get('content-type');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let errorData: any = {};\n\n if (contentType?.includes('application/json')) {\n try {\n errorData = await response.json();\n } catch {\n // Ignore JSON parsing errors\n }\n }\n\n // API error shape (api/lib/api/error.ts):\n // { statusCode, error: \"BAD_REQUEST\", message: string | string[] }\n // `error` is the machine-readable code; `message` is the human text\n // (an array of field messages for validation failures).\n const rawMessage = errorData.message;\n const message =\n (Array.isArray(rawMessage) ? rawMessage.join(', ') : rawMessage) ||\n `HTTP ${response.status}: ${response.statusText}`;\n const code: string | undefined = errorData.code ?? errorData.error;\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message);\n case 400:\n throw new ValidationError(message, errorData.field);\n case 429:\n throw new RateLimitError(message);\n default:\n throw new ChannlizeError(message, response.status, code);\n }\n}\n","/**\n * Core types for Channlize SDK\n */\n\n/** Severity for log tracking (`trackEvent` / `trackServerEvent`); Slack alerts use project settings. */\nexport type EventSeverity = 'info' | 'warning' | 'error';\n\n/** Options for log tracking (`trackEvent` / `trackServerEvent`). */\nexport interface TrackEventOptions {\n properties?: Record<string, unknown>;\n severity?: EventSeverity;\n distinctId?: string;\n pageUrl?: string;\n userAgent?: string;\n}\n\nexport interface ChannlizeConfig {\n /** Widget key used for public SDK endpoints (contact form, chat, browser log tracking) */\n apiKey: string;\n /** Base URL for the Channlize API */\n baseUrl?: string;\n /** Project ID (required for server-side `trackServerEvent` and `notify`) */\n projectId: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /**\n * Organization (server) API key, `ch_live_…`, for `trackServerEvent`\n * (`POST /v1/events`) and `notify` (`POST /v1/notify`, needs the `write`\n * scope). Do not expose in browser bundles.\n */\n serverApiKey?: string;\n}\n\n/**\n * Events (notify Slack from code). Augment this interface to type event\n * keys and their data across your app:\n *\n * ```ts\n * declare module '@channlize/core' {\n * interface ChannlizeEvents {\n * 'user.subscribed': { username: string; plan: string };\n * }\n * }\n * ```\n *\n * Or pass a map as a type argument: `createNotifier<AppEvents>()`.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ChannlizeEvents {}\n\n/** A declared event key (autocompleted) or any other key. */\nexport type NotifyEventName<TEvents extends object = ChannlizeEvents> =\n (keyof TEvents & string) | (string & {});\n\n/** The declared data for a key, or any object for undeclared keys. */\nexport type NotifyEventData<\n TEvents extends object = ChannlizeEvents,\n K = string,\n> = K extends keyof TEvents ? TEvents[K] : Record<string, unknown>;\n\nexport interface NotifyOptions {\n /**\n * Retry-safe sends: a key already used on the project returns the first\n * delivery (`duplicate: true`) and posts nothing. Sent as `Idempotency-Key`.\n */\n idempotencyKey?: string;\n /** Slack channel ID to post to instead of the event's channel, for this call. */\n channel?: string;\n}\n\nexport interface NotifyResponse {\n success: boolean;\n /** Delivery ID (see Events → Recent deliveries in the dashboard). */\n id: string;\n event: string;\n /**\n * `queued`: will be posted to Slack. `skipped`: not posted (see `reason`):\n * the event is disabled, is new (saved as a draft to configure), or there\n * is no Slack workspace / channel. With `duplicate`, the first call's status.\n */\n status: 'queued' | 'skipped' | 'sent' | 'failed';\n reason?: string;\n duplicate?: boolean;\n}\n\n/** `createNotifier` options; each falls back to an environment variable. */\nexport interface NotifierConfig {\n /**\n * Server API key (`ch_live_…`, write scope). Default:\n * `CHANNLIZE_SERVER_API_KEY`, else `CHANNLIZE_API_KEY`.\n */\n serverApiKey?: string;\n /** Project ID. Default: `CHANNLIZE_PROJECT_ID`. */\n projectId?: string;\n /** API base URL. Default: `CHANNLIZE_BASE_URL`, else `https://api.channlize.com`. */\n baseUrl?: string;\n /** Request timeout in ms (default 10000). */\n timeout?: number;\n}\n\nexport interface ContactFormData {\n /** Contact's name */\n name: string;\n /** Contact's email address */\n email: string;\n /** Subject of the inquiry */\n subject?: string;\n /** Message content */\n message: string;\n /** Custom fields */\n customFields?: Record<string, unknown>;\n /** Page URL where the form was submitted */\n pageUrl?: string;\n /** User agent string */\n userAgent?: string;\n}\n\nexport interface ChatMessage {\n /** Unique message ID */\n id: string;\n /** Message content */\n content: string;\n /** Message timestamp */\n timestamp: Date;\n /** Message author (user or agent) */\n author: 'user' | 'agent';\n /** Author metadata */\n authorData?: {\n name?: string;\n email?: string;\n avatarUrl?: string;\n };\n /** Attached files */\n attachments?: FileAttachment[];\n}\n\nexport interface FileAttachment {\n /** File ID */\n id: string;\n /** Original filename */\n name: string;\n /** File size in bytes */\n size: number;\n /** MIME type */\n type: string;\n /** Download URL */\n url: string;\n}\n\nexport interface ChatSession {\n /** Session ID */\n id: string;\n /** Session status */\n status: 'active' | 'closed' | 'waiting';\n /** Messages in the session */\n messages: ChatMessage[];\n /** Session metadata */\n metadata?: Record<string, unknown>;\n /** Created timestamp */\n createdAt: Date;\n /** Updated timestamp */\n updatedAt: Date;\n}\n\nexport interface ApiResponse<T = unknown> {\n /** Response data */\n data: T;\n /** Success status */\n success: boolean;\n /** Error message if any */\n error?: string;\n /** Additional metadata */\n meta?: Record<string, unknown>;\n}\n\n/** Response of `POST /public/contact` (api/app/api/public/contact/route.ts). */\nexport interface CreateContactResponse {\n /** Whether the submission was accepted */\n success: boolean;\n /** Conversation the submission was filed under */\n conversationId: string;\n /** Human-readable confirmation to show the visitor */\n message: string;\n /** @deprecated Never returned by the API (always undefined). Use `conversationId`. */\n id: string;\n /** @deprecated Never returned by the API (always undefined). */\n status: 'received' | 'processing' | 'delivered';\n /** @deprecated Never returned by the API (always undefined). */\n slackThreadUrl?: string;\n}\n\nexport interface CreateChatSessionResponse {\n /** Chat session data */\n session: ChatSession;\n /** SSE endpoint for real-time updates */\n sseEndpoint?: string;\n}\n\nexport interface SendMessageResponse {\n /** The sent message */\n message: ChatMessage;\n /** Updated session */\n session: ChatSession;\n}\n\n/**\n * Error types\n */\nexport class ChannlizeError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public code?: string\n ) {\n super(message);\n this.name = 'ChannlizeError';\n }\n}\n\nexport class ValidationError extends ChannlizeError {\n constructor(\n message: string,\n public field?: string\n ) {\n super(message, 400, 'VALIDATION_ERROR');\n this.name = 'ValidationError';\n }\n}\n\nexport class AuthenticationError extends ChannlizeError {\n constructor(message: string = 'Authentication failed') {\n super(message, 401, 'AUTHENTICATION_ERROR');\n this.name = 'AuthenticationError';\n }\n}\n\nexport class RateLimitError extends ChannlizeError {\n constructor(message: string = 'Rate limit exceeded') {\n super(message, 429, 'RATE_LIMIT_ERROR');\n this.name = 'RateLimitError';\n }\n}\n","import { requestJson } from './http';\nimport type {\n ChannlizeEvents,\n NotifierConfig,\n NotifyEventData,\n NotifyEventName,\n NotifyOptions,\n NotifyResponse,\n} from './types';\nimport { ValidationError } from './types';\n\n/** Same rule as the API: `user.subscribed`, `invoice_paid`, `signup-done`. */\nconst EVENT_KEY = /^[a-z0-9][a-z0-9._-]*$/;\n\nconst DEFAULT_BASE_URL = 'https://api.channlize.com';\n\n/** `POST /v1/notify`, shared by `ChannlizeClient.notify` and `createNotifier`. */\nexport async function sendNotify(\n http: { baseUrl: string; timeout: number },\n auth: { serverApiKey: string; projectId: string },\n event: string,\n data: Record<string, unknown> | undefined,\n options: NotifyOptions = {}\n): Promise<NotifyResponse> {\n if (!auth.projectId) {\n throw new ValidationError('projectId is required for notify', 'projectId');\n }\n if (auth.serverApiKey.startsWith('wk_')) {\n throw new ValidationError(\n 'notify needs a server API key (ch_live_…, write scope), not the public widget key',\n 'serverApiKey'\n );\n }\n if (\n typeof event !== 'string' ||\n event.length > 100 ||\n !EVENT_KEY.test(event)\n ) {\n throw new ValidationError(\n `Invalid event key \"${event}\": use lowercase letters, digits, dots, underscores or dashes (e.g. user.subscribed)`,\n 'event'\n );\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${auth.serverApiKey}`,\n };\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const res = await requestJson<NotifyResponse>(http, '/v1/notify', {\n method: 'POST',\n headers,\n body: JSON.stringify({\n projectId: auth.projectId,\n event,\n data: data ?? {},\n channel: options.channel,\n }),\n });\n // The API answers at the top level (no `data` envelope).\n return res as unknown as NotifyResponse;\n}\n\nfunction readEnv(name: string): string | undefined {\n // `process` is absent in browsers and some edge runtimes.\n const env =\n typeof process !== 'undefined' && process.env ? process.env : undefined;\n const value = env?.[name];\n return value && value.trim() ? value.trim() : undefined;\n}\n\nexport type Notifier<TEvents extends object = ChannlizeEvents> = <\n K extends NotifyEventName<TEvents>,\n>(\n event: K,\n data?: NotifyEventData<TEvents, K>,\n options?: NotifyOptions\n) => Promise<NotifyResponse>;\n\n/**\n * Events: a standalone server-side `notify(event, data)` function. Reads\n * the server key from `CHANNLIZE_SERVER_API_KEY` or `CHANNLIZE_API_KEY`, and\n * `CHANNLIZE_PROJECT_ID`, unless given — when it is called, so creating it\n * at module load never throws.\n *\n * ```ts\n * const notify = createNotifier();\n * await notify('user.subscribed', { username: 'ada', plan: 'Pro' });\n * ```\n */\nexport function createNotifier<TEvents extends object = ChannlizeEvents>(\n config: NotifierConfig = {}\n): Notifier<TEvents> {\n return (event, data, options) => {\n const serverApiKey =\n config.serverApiKey ??\n readEnv('CHANNLIZE_SERVER_API_KEY') ??\n readEnv('CHANNLIZE_API_KEY');\n const projectId = config.projectId ?? readEnv('CHANNLIZE_PROJECT_ID');\n if (!serverApiKey) {\n return Promise.reject(\n new ValidationError(\n 'A server API key is required: pass serverApiKey or set CHANNLIZE_API_KEY (or CHANNLIZE_SERVER_API_KEY)',\n 'serverApiKey'\n )\n );\n }\n if (!projectId) {\n return Promise.reject(\n new ValidationError(\n 'A project ID is required: pass projectId or set CHANNLIZE_PROJECT_ID',\n 'projectId'\n )\n );\n }\n const baseUrl =\n config.baseUrl ?? readEnv('CHANNLIZE_BASE_URL') ?? DEFAULT_BASE_URL;\n return sendNotify(\n { baseUrl: baseUrl.replace(/\\/$/, ''), timeout: config.timeout ?? 10000 },\n { serverApiKey, projectId },\n event,\n data as Record<string, unknown> | undefined,\n options\n );\n };\n}\n","import { requestJson } from './http';\nimport { sendNotify } from './notify';\nimport type {\n ChannlizeConfig,\n ContactFormData,\n ChatMessage,\n ApiResponse,\n CreateContactResponse,\n CreateChatSessionResponse,\n SendMessageResponse,\n ChatSession,\n TrackEventOptions,\n EventSeverity,\n ChannlizeEvents,\n NotifyEventName,\n NotifyEventData,\n NotifyOptions,\n NotifyResponse,\n} from './types';\nimport { ValidationError } from './types';\n\n/**\n * Channlize API Client\n * Provides methods to interact with the Channlize platform.\n *\n * `TEvents` types `notify()` event keys and data (defaults to the\n * augmentable `ChannlizeEvents` interface; any string key is accepted).\n */\nexport class ChannlizeClient<TEvents extends object = ChannlizeEvents> {\n private readonly config: ChannlizeConfig & {\n baseUrl: string;\n timeout: number;\n };\n\n constructor(config: ChannlizeConfig) {\n this.config = {\n ...config,\n // After the spread: an explicit `undefined` must not drop the defaults.\n baseUrl: config.baseUrl || 'https://api.channlize.com',\n timeout: config.timeout ?? 10000,\n };\n\n this.validateConfig();\n }\n\n private validateConfig(): void {\n if (!this.config.apiKey) {\n throw new ValidationError('Widget key is required');\n }\n }\n\n private makeRequest<T>(\n endpoint: string,\n options: RequestInit = {}\n ): Promise<ApiResponse<T>> {\n return requestJson<T>(this.config, endpoint, options);\n }\n\n /**\n * Submit a contact form\n */\n async submitContactForm(\n data: ContactFormData\n ): Promise<CreateContactResponse> {\n const response = await this.makeRequest<CreateContactResponse>(\n '/public/contact',\n {\n method: 'POST',\n body: JSON.stringify({\n widgetKey: this.config.apiKey, // apiKey is actually the widgetKey\n subject: data.subject,\n message: data.message,\n email: data.email,\n name: data.name,\n customFields:\n data.customFields && Object.keys(data.customFields).length > 0\n ? data.customFields\n : undefined,\n pageUrl:\n data.pageUrl ||\n (typeof window !== 'undefined' ? window.location.href : undefined),\n userAgent:\n data.userAgent ||\n (typeof navigator !== 'undefined'\n ? navigator.userAgent\n : undefined),\n }),\n }\n );\n\n return response.data || response;\n }\n\n /**\n * Create a new chat session\n */\n async createChatSession(\n metadata?: Record<string, unknown>\n ): Promise<CreateChatSessionResponse> {\n const response = await this.makeRequest<CreateChatSessionResponse>(\n '/v1/chat/sessions',\n {\n method: 'POST',\n body: JSON.stringify({\n widgetKey: this.config.apiKey, // apiKey is actually the widgetKey\n metadata: {\n ...metadata,\n pageUrl:\n typeof window !== 'undefined' ? window.location.href : undefined,\n userAgent:\n typeof navigator !== 'undefined'\n ? navigator.userAgent\n : undefined,\n },\n }),\n }\n );\n\n return response.data;\n }\n\n /**\n * Send a message in a chat session.\n * When `attachments` are provided the request is sent as multipart/form-data\n * so the files are uploaded alongside the message content.\n */\n async sendMessage(\n sessionId: string,\n content: string,\n attachments?: File[]\n ): Promise<SendMessageResponse> {\n const idempotencyKey = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n\n if (attachments && attachments.length > 0) {\n const formData = new FormData();\n formData.append('content', content);\n formData.append('idempotencyKey', idempotencyKey);\n for (const file of attachments) {\n formData.append('attachments', file, file.name);\n }\n\n const response = await this.makeRequest<SendMessageResponse>(\n `/v1/chat/sessions/${sessionId}/messages`,\n {\n method: 'POST',\n body: formData,\n // Let the browser set Content-Type with boundary for multipart\n headers: { 'Content-Type': '' },\n }\n );\n\n return response.data;\n }\n\n const response = await this.makeRequest<SendMessageResponse>(\n `/v1/chat/sessions/${sessionId}/messages`,\n {\n method: 'POST',\n body: JSON.stringify({\n content,\n idempotencyKey,\n }),\n }\n );\n\n return response.data;\n }\n\n /**\n * Get chat session details\n */\n async getChatSession(sessionId: string): Promise<ChatSession> {\n const response = await this.makeRequest<ChatSession>(\n `/v1/chat/sessions/${sessionId}`\n );\n return response.data;\n }\n\n /**\n * Get messages for a chat session\n */\n async getChatMessages(\n sessionId: string,\n limit = 50,\n offset = 0\n ): Promise<ChatMessage[]> {\n const response = await this.makeRequest<ChatMessage[]>(\n `/v1/chat/sessions/${sessionId}/messages?limit=${limit}&offset=${offset}`\n );\n return response.data;\n }\n\n /**\n * Close a chat session\n */\n async closeChatSession(sessionId: string): Promise<ChatSession> {\n const response = await this.makeRequest<ChatSession>(\n `/v1/chat/sessions/${sessionId}/close`,\n { method: 'POST' }\n );\n return response.data;\n }\n\n /**\n * Log tracking: record a log line from the browser (widget key →\n * `POST /public/events`). Shown under Log tracking in the dashboard.\n */\n async trackEvent(\n name: string,\n options?: TrackEventOptions\n ): Promise<{ success: boolean; id: string }> {\n const res = await this.makeRequest<{ success: boolean; id: string }>(\n '/public/events',\n {\n method: 'POST',\n body: JSON.stringify({\n widgetKey: this.config.apiKey,\n name,\n properties: options?.properties,\n severity: options?.severity,\n distinctId: options?.distinctId,\n pageUrl:\n options?.pageUrl ??\n (typeof window !== 'undefined' ? window.location.href : undefined),\n userAgent:\n options?.userAgent ??\n (typeof navigator !== 'undefined'\n ? navigator.userAgent\n : undefined),\n }),\n }\n );\n const data =\n (res as ApiResponse<{ success: boolean; id: string }>).data ??\n (res as unknown as { success: boolean; id: string });\n return data;\n }\n\n /**\n * Log tracking from a trusted server, using the organization API key\n * (`POST /v1/events`). Shown under Log tracking in the dashboard.\n */\n async trackServerEvent(\n name: string,\n options?: { properties?: Record<string, unknown>; severity?: EventSeverity }\n ): Promise<{ success: boolean; id: string }> {\n if (!this.config.serverApiKey) {\n throw new ValidationError(\n 'serverApiKey is required for trackServerEvent'\n );\n }\n const res = await this.makeRequest<{ success: boolean; id: string }>(\n '/v1/events',\n {\n method: 'POST',\n body: JSON.stringify({\n projectId: this.config.projectId,\n name,\n properties: options?.properties,\n severity: options?.severity,\n }),\n headers: {\n Authorization: `Bearer ${this.config.serverApiKey}`,\n },\n }\n );\n const data =\n (res as ApiResponse<{ success: boolean; id: string }>).data ??\n (res as unknown as { success: boolean; id: string });\n return data;\n }\n\n /**\n * Events: post a Slack message from your server when something happens\n * (`POST /v1/notify`). The event key picks the channel and message\n * template configured under Events in the dashboard; `data` fills the\n * template's `{{field}}` placeholders. Needs `serverApiKey` (write scope)\n * and `projectId` — never call it from browser code.\n *\n * An unknown key is saved as a disabled draft and answered with\n * `status: 'skipped'`, so it never throws for a missing setup.\n */\n async notify<K extends NotifyEventName<TEvents>>(\n event: K,\n data?: NotifyEventData<TEvents, K>,\n options?: NotifyOptions\n ): Promise<NotifyResponse> {\n if (!this.config.serverApiKey) {\n throw new ValidationError('serverApiKey is required for notify');\n }\n return sendNotify(\n this.config,\n {\n serverApiKey: this.config.serverApiKey,\n projectId: this.config.projectId,\n },\n event,\n data as Record<string, unknown> | undefined,\n options\n );\n }\n}\n"],"mappings":"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","names":["response"]}
1
+ {"version":3,"sources":["../src/http.ts","../src/shared.ts","../src/types.ts","../src/notify.ts","../src/client.ts"],"sourcesContent":["import fetch from 'cross-fetch';\nimport type { ApiResponse } from './types';\nimport { parseApiError, type ApiErrorInfo } from './shared';\nimport {\n ChannlizeError,\n AuthenticationError,\n ValidationError,\n RateLimitError,\n} from './types';\n\n/**\n * Shared HTTP layer for `ChannlizeClient` and `createNotifier`: JSON\n * requests with a timeout, and the API's error shape mapped to typed errors.\n */\nexport async function requestJson<T>(\n http: { baseUrl: string; timeout: number },\n endpoint: string,\n options: RequestInit = {}\n): Promise<ApiResponse<T>> {\n const url = `${http.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), http.timeout);\n\n try {\n const mergedHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(options.headers as Record<string, string>),\n };\n\n // When Content-Type is explicitly empty (e.g. FormData), remove it so the\n // browser / runtime can set the correct multipart boundary automatically.\n if (mergedHeaders['Content-Type'] === '') {\n delete mergedHeaders['Content-Type'];\n }\n\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n headers: mergedHeaders,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n await handleErrorResponse(response);\n }\n\n const data = await response.json();\n return data as ApiResponse<T>;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ChannlizeError) {\n throw error;\n }\n\n if (error instanceof Error && error.name === 'AbortError') {\n throw new ChannlizeError('Request timeout', 408, 'TIMEOUT');\n }\n\n throw new ChannlizeError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\nexport async function handleErrorResponse(response: Response): Promise<never> {\n let body: unknown = {};\n if (response.headers.get('content-type')?.includes('application/json')) {\n try {\n body = await response.json();\n } catch {\n // Ignore JSON parsing errors\n }\n }\n throw errorFromApi(parseApiError(response.status, body, response.statusText));\n}\n\n/** Maps a parsed API error to the typed `ChannlizeError` subclasses. */\nfunction errorFromApi(info: ApiErrorInfo): ChannlizeError {\n const { status, message, code } = info;\n let error: ChannlizeError;\n switch (status) {\n case 401:\n error = new AuthenticationError(message);\n break;\n case 400:\n error = new ValidationError(\n message,\n typeof info.body.field === 'string' ? info.body.field : undefined\n );\n break;\n case 429:\n error = new RateLimitError(\n message,\n code === 'PLAN_LIMIT_REACHED' ? code : undefined\n );\n break;\n default:\n error = new ChannlizeError(message, status, code);\n }\n error.requestId = info.requestId;\n error.details = info.body;\n return error;\n}\n","/**\n * Helpers shared by `@channlize/core`, `@channlize/react` and\n * `@channlize/mcp`: defaults, key detection, environment variables, event\n * keys and the API's error shape. Core is the single source for these.\n */\n\n/** Default API base URL. */\nexport const DEFAULT_BASE_URL = 'https://api.channlize.com';\n\n/** Default request timeout, in milliseconds. */\nexport const DEFAULT_TIMEOUT = 10000;\n\n/** Log tracking severities, lowest first. */\nexport const EVENT_SEVERITIES = ['info', 'warning', 'error'] as const;\n\n/**\n * Valid Events key (same rule as the API): lowercase letters, digits,\n * dots, underscores and dashes, e.g. `user.subscribed`. At most 100\n * characters.\n */\nexport const EVENT_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;\n\n/** Whether `event` is a valid Events key for `notify`. */\nexport function isValidEventKey(event: unknown): event is string {\n return (\n typeof event === 'string' &&\n event.length <= 100 &&\n EVENT_KEY_PATTERN.test(event)\n );\n}\n\nexport type KeyKind = 'widget' | 'server';\n\n/**\n * `wk_…` is the public widget key (browser: contact form, chat, browser\n * log tracking); anything else is a server API key (`ch_live_…`).\n */\nexport function keyKind(key: string): KeyKind {\n return key.startsWith('wk_') ? 'widget' : 'server';\n}\n\n/** The `CHANNLIZE_*` environment variables, trimmed (empty → undefined). */\nexport interface ChannlizeEnv {\n /** `CHANNLIZE_SERVER_API_KEY` */\n serverApiKey?: string;\n /** `CHANNLIZE_WIDGET_KEY` */\n widgetKey?: string;\n /** `CHANNLIZE_API_KEY` (either kind of key) */\n apiKey?: string;\n /** `CHANNLIZE_PROJECT_ID` */\n projectId?: string;\n /** `CHANNLIZE_BASE_URL` */\n baseUrl?: string;\n /** `CHANNLIZE_TIMEOUT` (milliseconds, unparsed) */\n timeout?: string;\n}\n\nfunction clean(value: string | undefined): string | undefined {\n const v = value?.trim();\n return v ? v : undefined;\n}\n\n/**\n * Reads the `CHANNLIZE_*` environment variables. Safe in browsers and edge\n * runtimes without `process` (everything is undefined there).\n */\nexport function readChannlizeEnv(\n env: Record<string, string | undefined> | undefined = typeof process !==\n 'undefined'\n ? process.env\n : undefined\n): ChannlizeEnv {\n return {\n serverApiKey: clean(env?.CHANNLIZE_SERVER_API_KEY),\n widgetKey: clean(env?.CHANNLIZE_WIDGET_KEY),\n apiKey: clean(env?.CHANNLIZE_API_KEY),\n projectId: clean(env?.CHANNLIZE_PROJECT_ID),\n baseUrl: clean(env?.CHANNLIZE_BASE_URL),\n timeout: clean(env?.CHANNLIZE_TIMEOUT),\n };\n}\n\n/** A failed API response, parsed from its JSON body. */\nexport interface ApiErrorInfo {\n status: number;\n /** Human-readable text (field messages joined with `, `). */\n message: string;\n /** Machine-readable code: the body's `code`, else its `error` (e.g. `NOT_FOUND`). */\n code?: string;\n /** `requestId` from the body, to quote to support. */\n requestId?: string;\n /** The parsed body (e.g. `limit`, `used`, `resetAt` on a plan-limit 429). */\n body: Record<string, unknown>;\n}\n\n/**\n * Parses the API's error shape (`api/lib/api/error.ts`):\n * `{ statusCode, error: \"NOT_FOUND\", message: string | string[], requestId? }`.\n * A few legacy routes put the human text in `error`.\n */\nexport function parseApiError(\n status: number,\n body: unknown,\n statusText = ''\n): ApiErrorInfo {\n const data = (body && typeof body === 'object' ? body : {}) as Record<\n string,\n unknown\n >;\n const raw = data.message;\n const joined = Array.isArray(raw)\n ? raw.filter(m => typeof m === 'string').join(', ')\n : typeof raw === 'string'\n ? raw\n : '';\n const error = typeof data.error === 'string' ? data.error : undefined;\n const code = typeof data.code === 'string' ? data.code : error;\n const message =\n joined ||\n (error && /\\s/.test(error) ? error : '') ||\n `HTTP ${status}: ${statusText}`.replace(/: $/, '');\n return {\n status,\n message,\n code,\n requestId: typeof data.requestId === 'string' ? data.requestId : undefined,\n body: data,\n };\n}\n","/**\n * Core types for Channlize SDK\n */\n\nimport type { EVENT_SEVERITIES } from './shared';\n\n/** Severity for log tracking (`trackEvent` / `trackServerEvent`); Slack alerts use project settings. */\nexport type EventSeverity = (typeof EVENT_SEVERITIES)[number];\n\n/** Options for log tracking (`trackEvent` / `trackServerEvent`). */\nexport interface TrackEventOptions {\n properties?: Record<string, unknown>;\n severity?: EventSeverity;\n distinctId?: string;\n pageUrl?: string;\n userAgent?: string;\n}\n\nexport interface ChannlizeConfig {\n /** Widget key used for public SDK endpoints (contact form, chat, browser log tracking) */\n apiKey: string;\n /** Base URL for the Channlize API */\n baseUrl?: string;\n /** Project ID (required for server-side `trackServerEvent` and `notify`) */\n projectId: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /**\n * Organization (server) API key, `ch_live_…`, for `trackServerEvent`\n * (`POST /v1/events`) and `notify` (`POST /v1/notify`, needs the `write`\n * scope). Do not expose in browser bundles.\n */\n serverApiKey?: string;\n}\n\n/**\n * Events (notify Slack from code). Augment this interface to type event\n * keys and their data across your app:\n *\n * ```ts\n * declare module '@channlize/core' {\n * interface ChannlizeEvents {\n * 'user.subscribed': { username: string; plan: string };\n * }\n * }\n * ```\n *\n * Or pass a map as a type argument: `createNotifier<AppEvents>()`.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ChannlizeEvents {}\n\n/** A declared event key (autocompleted) or any other key. */\nexport type NotifyEventName<TEvents extends object = ChannlizeEvents> =\n (keyof TEvents & string) | (string & {});\n\n/** The declared data for a key, or any object for undeclared keys. */\nexport type NotifyEventData<\n TEvents extends object = ChannlizeEvents,\n K = string,\n> = K extends keyof TEvents ? TEvents[K] : Record<string, unknown>;\n\nexport interface NotifyOptions {\n /**\n * Retry-safe sends: a key already used on the project returns the first\n * delivery (`duplicate: true`) and posts nothing. Sent as `Idempotency-Key`.\n */\n idempotencyKey?: string;\n /** Slack channel ID to post to instead of the event's channel, for this call. */\n channel?: string;\n}\n\nexport interface NotifyResponse {\n success: boolean;\n /** Delivery ID (see Events → Recent deliveries in the dashboard). */\n id: string;\n event: string;\n /**\n * `queued`: will be posted to Slack. `skipped`: not posted (see `reason`):\n * the event is disabled, is new (saved as a draft to configure), or there\n * is no Slack workspace / channel. With `duplicate`, the first call's status.\n */\n status: 'queued' | 'skipped' | 'sent' | 'failed';\n reason?: string;\n duplicate?: boolean;\n}\n\n/** `createNotifier` options; each falls back to an environment variable. */\nexport interface NotifierConfig {\n /**\n * Server API key (`ch_live_…`, write scope). Default:\n * `CHANNLIZE_SERVER_API_KEY`, else `CHANNLIZE_API_KEY`.\n */\n serverApiKey?: string;\n /** Project ID. Default: `CHANNLIZE_PROJECT_ID`. */\n projectId?: string;\n /** API base URL. Default: `CHANNLIZE_BASE_URL`, else `https://api.channlize.com`. */\n baseUrl?: string;\n /** Request timeout in ms (default 10000). */\n timeout?: number;\n}\n\nexport interface ContactFormData {\n /** Contact's name */\n name: string;\n /** Contact's email address */\n email: string;\n /** Subject of the inquiry */\n subject?: string;\n /** Message content */\n message: string;\n /** Custom fields */\n customFields?: Record<string, unknown>;\n /** Page URL where the form was submitted */\n pageUrl?: string;\n /** User agent string */\n userAgent?: string;\n}\n\nexport interface ChatMessage {\n /** Unique message ID */\n id: string;\n /** Message content */\n content: string;\n /**\n * Message timestamp. The API sends an ISO 8601 string, which\n * `ChannlizeClient` passes through as is; `@channlize/react` converts it\n * to a `Date`. Wrap it in `new Date(...)` before using `Date` methods.\n */\n timestamp: Date;\n /** Message author (user or agent) */\n author: 'user' | 'agent';\n /** Author metadata (`name` / `email` can be `null` in message history) */\n authorData?: {\n name?: string;\n email?: string;\n avatarUrl?: string;\n };\n /** Attached files */\n attachments?: FileAttachment[];\n}\n\nexport interface FileAttachment {\n /** File ID */\n id: string;\n /** Original filename */\n name: string;\n /** File size in bytes */\n size: number;\n /** MIME type */\n type: string;\n /** Download URL */\n url: string;\n}\n\nexport interface ChatSession {\n /** Session ID */\n id: string;\n /**\n * Session status. `createChatSession` answers `active`; `getChatSession`\n * and `closeChatSession` answer the conversation's status (`open`,\n * `pending`, `resolved` or `closed`).\n */\n status: 'active' | 'closed' | 'waiting';\n /**\n * Messages in the session. Only `createChatSession` returns it (empty);\n * `getChatSession`, `closeChatSession` and `sendMessage` return a session\n * summary without it. Load history with `getChatMessages`.\n */\n messages: ChatMessage[];\n /** Session metadata */\n metadata?: Record<string, unknown>;\n /** Created timestamp (an ISO 8601 string on the wire; not sent by `closeChatSession`) */\n createdAt: Date;\n /** Updated timestamp (an ISO 8601 string on the wire) */\n updatedAt: Date;\n}\n\nexport interface ApiResponse<T = unknown> {\n /** Response data */\n data: T;\n /** Success status */\n success: boolean;\n /** Error message if any */\n error?: string;\n /** Additional metadata */\n meta?: Record<string, unknown>;\n}\n\n/** Response of `POST /public/contact` (api/app/api/public/contact/route.ts). */\nexport interface CreateContactResponse {\n /** Whether the submission was accepted */\n success: boolean;\n /** Conversation the submission was filed under */\n conversationId: string;\n /** Human-readable confirmation to show the visitor */\n message: string;\n /** @deprecated Never returned by the API (always undefined). Use `conversationId`. */\n id: string;\n /** @deprecated Never returned by the API (always undefined). */\n status: 'received' | 'processing' | 'delivered';\n /** @deprecated Never returned by the API (always undefined). */\n slackThreadUrl?: string;\n}\n\nexport interface CreateChatSessionResponse {\n /** Chat session data */\n session: ChatSession;\n /** SSE endpoint for real-time updates */\n sseEndpoint?: string;\n}\n\nexport interface SendMessageResponse {\n /** The sent message */\n message: ChatMessage;\n /** The session (the API sends only `id` and `status`) */\n session: ChatSession;\n}\n\n/**\n * Error types\n */\nexport class ChannlizeError extends Error {\n /** The API's `requestId` for a failed response, to quote to support. */\n requestId?: string;\n /**\n * The API's JSON error body, e.g. `limit`, `used`, `resetAt` and\n * `upgradeUrl` on a plan-limit 429. Undefined for network errors.\n */\n details?: Record<string, unknown>;\n\n constructor(\n message: string,\n public statusCode?: number,\n public code?: string\n ) {\n super(message);\n this.name = 'ChannlizeError';\n }\n}\n\nexport class ValidationError extends ChannlizeError {\n constructor(\n message: string,\n public field?: string\n ) {\n super(message, 400, 'VALIDATION_ERROR');\n this.name = 'ValidationError';\n }\n}\n\nexport class AuthenticationError extends ChannlizeError {\n constructor(message: string = 'Authentication failed') {\n super(message, 401, 'AUTHENTICATION_ERROR');\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * A 429. `code` is `PLAN_LIMIT_REACHED` when the organization is past its\n * plan's limit for the billing period (the API's 110 % hard cap), else\n * `RATE_LIMIT_ERROR`.\n */\nexport class RateLimitError extends ChannlizeError {\n constructor(\n message: string = 'Rate limit exceeded',\n code: string = 'RATE_LIMIT_ERROR'\n ) {\n super(message, 429, code);\n this.name = 'RateLimitError';\n }\n}\n\n/**\n * What `trackEvent` / `trackServerEvent` resolve with. When the API refuses\n * the event with a 429 (rate limit or plan limit), the call resolves with\n * `success: false` and `dropped` instead of throwing: log tracking never\n * breaks the app that calls it.\n */\nexport interface TrackEventResult {\n success: boolean;\n id: string;\n /** Set when the event was refused and not stored. */\n dropped?: 'plan_limit' | 'rate_limit';\n}\n","import { requestJson } from './http';\nimport type {\n ChannlizeEvents,\n NotifierConfig,\n NotifyEventData,\n NotifyEventName,\n NotifyOptions,\n NotifyResponse,\n} from './types';\nimport { ValidationError } from './types';\nimport {\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT,\n isValidEventKey,\n keyKind,\n readChannlizeEnv,\n} from './shared';\n\n/** `POST /v1/notify`, shared by `ChannlizeClient.notify` and `createNotifier`. */\nexport async function sendNotify(\n http: { baseUrl: string; timeout: number },\n auth: { serverApiKey: string; projectId: string },\n event: string,\n data: Record<string, unknown> | undefined,\n options: NotifyOptions = {}\n): Promise<NotifyResponse> {\n if (!auth.projectId) {\n throw new ValidationError('projectId is required for notify', 'projectId');\n }\n if (keyKind(auth.serverApiKey) === 'widget') {\n throw new ValidationError(\n 'notify needs a server API key (ch_live_…, write scope), not the public widget key',\n 'serverApiKey'\n );\n }\n if (!isValidEventKey(event)) {\n throw new ValidationError(\n `Invalid event key \"${event}\": use lowercase letters, digits, dots, underscores or dashes (e.g. user.subscribed)`,\n 'event'\n );\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${auth.serverApiKey}`,\n };\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const res = await requestJson<NotifyResponse>(http, '/v1/notify', {\n method: 'POST',\n headers,\n body: JSON.stringify({\n projectId: auth.projectId,\n event,\n data: data ?? {},\n channel: options.channel,\n }),\n });\n // The API answers at the top level (no `data` envelope).\n return res as unknown as NotifyResponse;\n}\n\nexport type Notifier<TEvents extends object = ChannlizeEvents> = <\n K extends NotifyEventName<TEvents>,\n>(\n event: K,\n data?: NotifyEventData<TEvents, K>,\n options?: NotifyOptions\n) => Promise<NotifyResponse>;\n\n/**\n * Events: a standalone server-side `notify(event, data)` function. Reads\n * the server key from `CHANNLIZE_SERVER_API_KEY` or `CHANNLIZE_API_KEY`, and\n * `CHANNLIZE_PROJECT_ID`, unless given — when it is called, so creating it\n * at module load never throws.\n *\n * ```ts\n * const notify = createNotifier();\n * await notify('user.subscribed', { username: 'ada', plan: 'Pro' });\n * ```\n */\nexport function createNotifier<TEvents extends object = ChannlizeEvents>(\n config: NotifierConfig = {}\n): Notifier<TEvents> {\n return (event, data, options) => {\n const env = readChannlizeEnv();\n const serverApiKey = config.serverApiKey ?? env.serverApiKey ?? env.apiKey;\n const projectId = config.projectId ?? env.projectId;\n if (!serverApiKey) {\n return Promise.reject(\n new ValidationError(\n 'A server API key is required: pass serverApiKey or set CHANNLIZE_API_KEY (or CHANNLIZE_SERVER_API_KEY)',\n 'serverApiKey'\n )\n );\n }\n if (!projectId) {\n return Promise.reject(\n new ValidationError(\n 'A project ID is required: pass projectId or set CHANNLIZE_PROJECT_ID',\n 'projectId'\n )\n );\n }\n const baseUrl = config.baseUrl ?? env.baseUrl ?? DEFAULT_BASE_URL;\n return sendNotify(\n {\n baseUrl: baseUrl.replace(/\\/$/, ''),\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n },\n { serverApiKey, projectId },\n event,\n data as Record<string, unknown> | undefined,\n options\n );\n };\n}\n","import { requestJson } from './http';\nimport { sendNotify } from './notify';\nimport type {\n ChannlizeConfig,\n ContactFormData,\n ChatMessage,\n ApiResponse,\n CreateContactResponse,\n CreateChatSessionResponse,\n SendMessageResponse,\n ChatSession,\n TrackEventOptions,\n EventSeverity,\n ChannlizeEvents,\n NotifyEventName,\n NotifyEventData,\n NotifyOptions,\n NotifyResponse,\n TrackEventResult,\n} from './types';\nimport { RateLimitError, ValidationError } from './types';\nimport { DEFAULT_BASE_URL, DEFAULT_TIMEOUT } from './shared';\n\n/**\n * Channlize API Client\n * Provides methods to interact with the Channlize platform.\n *\n * `TEvents` types `notify()` event keys and data (defaults to the\n * augmentable `ChannlizeEvents` interface; any string key is accepted).\n */\nexport class ChannlizeClient<TEvents extends object = ChannlizeEvents> {\n private readonly config: ChannlizeConfig & {\n baseUrl: string;\n timeout: number;\n };\n\n constructor(config: ChannlizeConfig) {\n this.config = {\n ...config,\n // After the spread: an explicit `undefined` must not drop the defaults.\n baseUrl: config.baseUrl || DEFAULT_BASE_URL,\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n };\n\n this.validateConfig();\n }\n\n private validateConfig(): void {\n if (!this.config.apiKey) {\n throw new ValidationError('Widget key is required');\n }\n }\n\n private makeRequest<T>(\n endpoint: string,\n options: RequestInit = {}\n ): Promise<ApiResponse<T>> {\n return requestJson<T>(this.config, endpoint, options);\n }\n\n /**\n * Submit a contact form\n */\n async submitContactForm(\n data: ContactFormData\n ): Promise<CreateContactResponse> {\n const response = await this.makeRequest<CreateContactResponse>(\n '/public/contact',\n {\n method: 'POST',\n body: JSON.stringify({\n widgetKey: this.config.apiKey, // apiKey is actually the widgetKey\n subject: data.subject,\n message: data.message,\n email: data.email,\n name: data.name,\n customFields:\n data.customFields && Object.keys(data.customFields).length > 0\n ? data.customFields\n : undefined,\n pageUrl:\n data.pageUrl ||\n (typeof window !== 'undefined' ? window.location.href : undefined),\n userAgent:\n data.userAgent ||\n (typeof navigator !== 'undefined'\n ? navigator.userAgent\n : undefined),\n }),\n }\n );\n\n return response.data || response;\n }\n\n /**\n * Create a new chat session\n */\n async createChatSession(\n metadata?: Record<string, unknown>\n ): Promise<CreateChatSessionResponse> {\n const response = await this.makeRequest<CreateChatSessionResponse>(\n '/v1/chat/sessions',\n {\n method: 'POST',\n body: JSON.stringify({\n widgetKey: this.config.apiKey, // apiKey is actually the widgetKey\n metadata: {\n ...metadata,\n pageUrl:\n typeof window !== 'undefined' ? window.location.href : undefined,\n userAgent:\n typeof navigator !== 'undefined'\n ? navigator.userAgent\n : undefined,\n },\n }),\n }\n );\n\n return response.data;\n }\n\n /**\n * Send a message in a chat session.\n * When `attachments` are provided the request is sent as multipart/form-data\n * so the files are uploaded alongside the message content.\n */\n async sendMessage(\n sessionId: string,\n content: string,\n attachments?: File[]\n ): Promise<SendMessageResponse> {\n const idempotencyKey = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n\n if (attachments && attachments.length > 0) {\n const formData = new FormData();\n formData.append('content', content);\n formData.append('idempotencyKey', idempotencyKey);\n for (const file of attachments) {\n formData.append('attachments', file, file.name);\n }\n\n const response = await this.makeRequest<SendMessageResponse>(\n `/v1/chat/sessions/${sessionId}/messages`,\n {\n method: 'POST',\n body: formData,\n // Let the browser set Content-Type with boundary for multipart\n headers: { 'Content-Type': '' },\n }\n );\n\n return response.data;\n }\n\n const response = await this.makeRequest<SendMessageResponse>(\n `/v1/chat/sessions/${sessionId}/messages`,\n {\n method: 'POST',\n body: JSON.stringify({\n content,\n idempotencyKey,\n }),\n }\n );\n\n return response.data;\n }\n\n /**\n * Get chat session details\n */\n async getChatSession(sessionId: string): Promise<ChatSession> {\n const response = await this.makeRequest<ChatSession>(\n `/v1/chat/sessions/${sessionId}`\n );\n return response.data;\n }\n\n /**\n * Get messages for a chat session\n */\n async getChatMessages(\n sessionId: string,\n limit = 50,\n offset = 0\n ): Promise<ChatMessage[]> {\n const response = await this.makeRequest<ChatMessage[]>(\n `/v1/chat/sessions/${sessionId}/messages?limit=${limit}&offset=${offset}`\n );\n return response.data;\n }\n\n /**\n * Close a chat session\n */\n async closeChatSession(sessionId: string): Promise<ChatSession> {\n const response = await this.makeRequest<ChatSession>(\n `/v1/chat/sessions/${sessionId}/close`,\n { method: 'POST' }\n );\n return response.data;\n }\n\n /**\n * Log tracking: record a log line from the browser (widget key →\n * `POST /public/events`). Shown under Log tracking in the dashboard.\n */\n async trackEvent(\n name: string,\n options?: TrackEventOptions\n ): Promise<TrackEventResult> {\n return this.nonFatalTracking(() => this.sendBrowserEvent(name, options));\n }\n\n private async sendBrowserEvent(\n name: string,\n options?: TrackEventOptions\n ): Promise<TrackEventResult> {\n const res = await this.makeRequest<{ success: boolean; id: string }>(\n '/public/events',\n {\n method: 'POST',\n body: JSON.stringify({\n widgetKey: this.config.apiKey,\n name,\n properties: options?.properties,\n severity: options?.severity,\n distinctId: options?.distinctId,\n pageUrl:\n options?.pageUrl ??\n (typeof window !== 'undefined' ? window.location.href : undefined),\n userAgent:\n options?.userAgent ??\n (typeof navigator !== 'undefined'\n ? navigator.userAgent\n : undefined),\n }),\n }\n );\n const data =\n (res as ApiResponse<{ success: boolean; id: string }>).data ??\n (res as unknown as { success: boolean; id: string });\n return data;\n }\n\n /**\n * Log tracking from a trusted server, using the organization API key\n * (`POST /v1/events`). Shown under Log tracking in the dashboard.\n */\n async trackServerEvent(\n name: string,\n options?: { properties?: Record<string, unknown>; severity?: EventSeverity }\n ): Promise<TrackEventResult> {\n if (!this.config.serverApiKey) {\n throw new ValidationError(\n 'serverApiKey is required for trackServerEvent'\n );\n }\n return this.nonFatalTracking(() => this.sendServerEvent(name, options));\n }\n\n /**\n * Log tracking must never break the caller: a 429 (rate limit, or the\n * plan's limit for the billing period) resolves with `success: false`\n * and `dropped` instead of throwing, with one console warning per client.\n */\n private async nonFatalTracking(\n send: () => Promise<TrackEventResult>\n ): Promise<TrackEventResult> {\n try {\n return await send();\n } catch (error) {\n if (!(error instanceof RateLimitError)) throw error;\n const dropped =\n error.code === 'PLAN_LIMIT_REACHED' ? 'plan_limit' : 'rate_limit';\n if (!this.warnedDropped && typeof console !== 'undefined') {\n this.warnedDropped = true;\n console.warn(`[channlize] log event dropped: ${error.message}`);\n }\n return { success: false, id: '', dropped };\n }\n }\n\n private warnedDropped = false;\n\n private async sendServerEvent(\n name: string,\n options?: { properties?: Record<string, unknown>; severity?: EventSeverity }\n ): Promise<TrackEventResult> {\n const res = await this.makeRequest<{ success: boolean; id: string }>(\n '/v1/events',\n {\n method: 'POST',\n body: JSON.stringify({\n projectId: this.config.projectId,\n name,\n properties: options?.properties,\n severity: options?.severity,\n }),\n headers: {\n Authorization: `Bearer ${this.config.serverApiKey}`,\n },\n }\n );\n const data =\n (res as ApiResponse<{ success: boolean; id: string }>).data ??\n (res as unknown as { success: boolean; id: string });\n return data;\n }\n\n /**\n * Events: post a Slack message from your server when something happens\n * (`POST /v1/notify`). The event key picks the channel and message\n * template configured under Events in the dashboard; `data` fills the\n * template's `{{field}}` placeholders. Needs `serverApiKey` (write scope)\n * and `projectId` — never call it from browser code.\n *\n * An unknown key is saved as a disabled draft and answered with\n * `status: 'skipped'`, so it never throws for a missing setup.\n */\n async notify<K extends NotifyEventName<TEvents>>(\n event: K,\n data?: NotifyEventData<TEvents, K>,\n options?: NotifyOptions\n ): Promise<NotifyResponse> {\n if (!this.config.serverApiKey) {\n throw new ValidationError('serverApiKey is required for notify');\n }\n return sendNotify(\n this.config,\n {\n serverApiKey: this.config.serverApiKey,\n projectId: this.config.projectId,\n },\n event,\n data as Record<string, unknown> | undefined,\n options\n );\n }\n}\n"],"mappings":"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","names":["response"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@channlize/core",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Channlize Core SDK - TypeScript API wrapper for Channlize platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -18,10 +18,11 @@
18
18
  "scripts": {
19
19
  "build": "tsup",
20
20
  "dev": "tsup --watch",
21
- "lint": "eslint src",
22
- "lint:fix": "eslint src --fix",
21
+ "lint": "oxlint src && prettier --check src",
22
+ "lint:fix": "oxlint --fix src && prettier --write src",
23
23
  "type-check": "tsc6 --noEmit",
24
- "clean": "rm -rf dist"
24
+ "clean": "rm -rf dist",
25
+ "test": "vitest run"
25
26
  },
26
27
  "keywords": [
27
28
  "channlize",
@@ -36,9 +37,10 @@
36
37
  },
37
38
  "devDependencies": {
38
39
  "@types/node": "^26.0.0",
39
- "eslint": "^10.0.0",
40
40
  "tsup": "^9.0.0",
41
- "typescript": "npm:@typescript/typescript6@^6.0.2"
41
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
42
+ "vite": "^8.3.5",
43
+ "vitest": "^5.1.0"
42
44
  },
43
45
  "publishConfig": {
44
46
  "access": "public"