@apinator/server 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,424 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ Apinator: () => Apinator,
25
+ AuthenticationError: () => AuthenticationError,
26
+ RealtimeError: () => RealtimeError,
27
+ ValidationError: () => ValidationError,
28
+ authenticateChannel: () => authenticateChannel,
29
+ md5Hex: () => md5Hex,
30
+ signChannel: () => signChannel,
31
+ signRequest: () => signRequest,
32
+ signWebhookPayload: () => signWebhookPayload,
33
+ verifyWebhook: () => verifyWebhook
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/crypto.ts
38
+ var import_crypto = require("crypto");
39
+ function md5Hex(data) {
40
+ return (0, import_crypto.createHash)("md5").update(data, "utf8").digest("hex");
41
+ }
42
+ function signRequest(secret, method, path, body, timestamp) {
43
+ const bodyMD5 = body === "" ? "" : md5Hex(body);
44
+ const sigString = `${timestamp}
45
+ ${method}
46
+ ${path}
47
+ ${bodyMD5}`;
48
+ return (0, import_crypto.createHmac)("sha256", secret).update(sigString, "utf8").digest("hex");
49
+ }
50
+ function signChannel(secret, socketId, channelName, channelData) {
51
+ const sigString = channelData ? `${socketId}:${channelName}:${channelData}` : `${socketId}:${channelName}`;
52
+ return (0, import_crypto.createHmac)("sha256", secret).update(sigString, "utf8").digest("hex");
53
+ }
54
+ function signWebhookPayload(secret, timestamp, payload) {
55
+ const input = `${timestamp}.${payload}`;
56
+ return (0, import_crypto.createHmac)("sha256", secret).update(input, "utf8").digest("hex");
57
+ }
58
+
59
+ // src/auth.ts
60
+ function authenticateChannel(secret, key, socketId, channelName, channelData) {
61
+ const signature = signChannel(secret, socketId, channelName, channelData);
62
+ const response = {
63
+ auth: `${key}:${signature}`
64
+ };
65
+ if (channelData !== void 0) {
66
+ response.channel_data = channelData;
67
+ }
68
+ return response;
69
+ }
70
+
71
+ // src/webhook.ts
72
+ var import_crypto3 = require("crypto");
73
+ function verifyWebhook(secret, headers, body, maxAge) {
74
+ const signatureHeader = getHeaderCaseInsensitive(
75
+ headers,
76
+ "x-realtime-signature"
77
+ );
78
+ if (!signatureHeader) {
79
+ return false;
80
+ }
81
+ const actualSignature = signatureHeader.startsWith("sha256=") ? signatureHeader.slice(7) : signatureHeader;
82
+ const timestamp = getHeaderCaseInsensitive(headers, "x-realtime-timestamp");
83
+ if (!timestamp) {
84
+ return false;
85
+ }
86
+ if (maxAge !== void 0) {
87
+ const webhookTime = parseInt(timestamp, 10);
88
+ if (isNaN(webhookTime)) {
89
+ return false;
90
+ }
91
+ const currentTime = Math.floor(Date.now() / 1e3);
92
+ const age = currentTime - webhookTime;
93
+ if (age > maxAge || age < 0) {
94
+ return false;
95
+ }
96
+ }
97
+ const expectedSignature = signWebhookPayload(secret, timestamp, body);
98
+ try {
99
+ const actualBuffer = Buffer.from(actualSignature, "hex");
100
+ const expectedBuffer = Buffer.from(expectedSignature, "hex");
101
+ if (actualBuffer.length !== expectedBuffer.length) {
102
+ return false;
103
+ }
104
+ return (0, import_crypto3.timingSafeEqual)(actualBuffer, expectedBuffer);
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+ function getHeaderCaseInsensitive(headers, key) {
110
+ const lowerKey = key.toLowerCase();
111
+ for (const [k, v] of Object.entries(headers)) {
112
+ if (k.toLowerCase() === lowerKey) {
113
+ if (typeof v === "string") {
114
+ return v;
115
+ }
116
+ if (Array.isArray(v)) {
117
+ return v.find((entry) => typeof entry === "string");
118
+ }
119
+ return void 0;
120
+ }
121
+ }
122
+ return void 0;
123
+ }
124
+
125
+ // src/errors.ts
126
+ var RealtimeError = class _RealtimeError extends Error {
127
+ constructor(message) {
128
+ super(message);
129
+ this.name = "RealtimeError";
130
+ Object.setPrototypeOf(this, _RealtimeError.prototype);
131
+ }
132
+ };
133
+ var AuthenticationError = class _AuthenticationError extends RealtimeError {
134
+ constructor(message) {
135
+ super(message);
136
+ this.name = "AuthenticationError";
137
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
138
+ }
139
+ };
140
+ var ValidationError = class _ValidationError extends RealtimeError {
141
+ constructor(message) {
142
+ super(message);
143
+ this.name = "ValidationError";
144
+ Object.setPrototypeOf(this, _ValidationError.prototype);
145
+ }
146
+ };
147
+ var ApiError = class _ApiError extends RealtimeError {
148
+ constructor(message, status, body) {
149
+ super(message);
150
+ this.name = "ApiError";
151
+ this.status = status;
152
+ this.body = body;
153
+ Object.setPrototypeOf(this, _ApiError.prototype);
154
+ }
155
+ };
156
+
157
+ // src/client.ts
158
+ var Apinator = class {
159
+ constructor(options) {
160
+ this.appId = options.appId;
161
+ this.key = options.key;
162
+ this.secret = options.secret;
163
+ this.host = `https://ws-${options.cluster}.apinator.io`;
164
+ }
165
+ /**
166
+ * Trigger an event on one or more channels.
167
+ *
168
+ * @param params - Event parameters
169
+ * @throws {ValidationError} If the request is invalid (400)
170
+ * @throws {AuthenticationError} If authentication fails (401)
171
+ * @throws {ApiError} For other API errors
172
+ *
173
+ * @example Single channel
174
+ * ```ts
175
+ * await client.trigger({
176
+ * name: 'message',
177
+ * channel: 'chat',
178
+ * data: JSON.stringify({ text: 'Hello!' }),
179
+ * });
180
+ * ```
181
+ *
182
+ * @example Multiple channels
183
+ * ```ts
184
+ * await client.trigger({
185
+ * name: 'notification',
186
+ * channels: ['user-1', 'user-2'],
187
+ * data: JSON.stringify({ message: 'New update' }),
188
+ * });
189
+ * ```
190
+ *
191
+ * @example Exclude socket
192
+ * ```ts
193
+ * await client.trigger({
194
+ * name: 'message',
195
+ * channel: 'chat',
196
+ * data: JSON.stringify({ text: 'Hello!' }),
197
+ * socketId: '12345.67890', // Don't send to this connection
198
+ * });
199
+ * ```
200
+ */
201
+ async trigger(params) {
202
+ if (params.channel && params.channels) {
203
+ throw new ValidationError(
204
+ "Cannot specify both 'channel' and 'channels' parameters"
205
+ );
206
+ }
207
+ if (!params.channel && !params.channels) {
208
+ throw new ValidationError(
209
+ "Must specify either 'channel' or 'channels' parameter"
210
+ );
211
+ }
212
+ const body = {
213
+ name: params.name,
214
+ data: params.data
215
+ };
216
+ if (params.channel) {
217
+ body.channel = params.channel;
218
+ }
219
+ if (params.channels) {
220
+ body.channels = params.channels;
221
+ }
222
+ if (params.socketId) {
223
+ body.socket_id = params.socketId;
224
+ }
225
+ const bodyString = JSON.stringify(body);
226
+ const path = `/apps/${this.appId}/events`;
227
+ await this.request("POST", path, bodyString);
228
+ }
229
+ /**
230
+ * Authenticate a channel subscription request.
231
+ *
232
+ * This generates the authentication signature required for private and presence channels.
233
+ * The returned auth object should be sent back to the client for channel subscription.
234
+ *
235
+ * @param socketId - WebSocket connection socket ID from the client
236
+ * @param channelName - Channel name to authenticate
237
+ * @param channelData - Optional channel data for presence channels (JSON string with user info)
238
+ * @returns Channel authentication response with auth signature
239
+ *
240
+ * @example Private channel
241
+ * ```ts
242
+ * const auth = client.authenticateChannel('12345.67890', 'private-chat');
243
+ * // Send auth back to client
244
+ * res.json(auth);
245
+ * ```
246
+ *
247
+ * @example Presence channel
248
+ * ```ts
249
+ * const channelData = JSON.stringify({
250
+ * user_id: 'user1',
251
+ * user_info: { name: 'Alice' }
252
+ * });
253
+ * const auth = client.authenticateChannel('12345.67890', 'presence-room', channelData);
254
+ * res.json(auth);
255
+ * ```
256
+ */
257
+ authenticateChannel(socketId, channelName, channelData) {
258
+ return authenticateChannel(this.secret, this.key, socketId, channelName, channelData);
259
+ }
260
+ /**
261
+ * Get a list of channels, optionally filtered by prefix.
262
+ *
263
+ * @param prefix - Optional prefix to filter channels (e.g., "private-", "presence-")
264
+ * @returns Array of channel information
265
+ * @throws {AuthenticationError} If authentication fails (401)
266
+ * @throws {ApiError} For other API errors
267
+ *
268
+ * @example Get all channels
269
+ * ```ts
270
+ * const channels = await client.getChannels();
271
+ * channels.forEach(ch => console.log(ch.name, ch.subscription_count));
272
+ * ```
273
+ *
274
+ * @example Get presence channels only
275
+ * ```ts
276
+ * const presenceChannels = await client.getChannels('presence-');
277
+ * ```
278
+ */
279
+ async getChannels(prefix) {
280
+ let path = `/apps/${this.appId}/channels`;
281
+ if (prefix) {
282
+ const encodedPrefix = encodeURIComponent(prefix);
283
+ path += `?filter_by_prefix=${encodedPrefix}`;
284
+ }
285
+ const response = await this.request("GET", path, "");
286
+ return response.channels;
287
+ }
288
+ /**
289
+ * Get information about a specific channel.
290
+ *
291
+ * @param channelName - Channel name
292
+ * @returns Channel information
293
+ * @throws {AuthenticationError} If authentication fails (401)
294
+ * @throws {ApiError} For other API errors (including 404 if channel not found)
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * try {
299
+ * const channel = await client.getChannel('chat');
300
+ * console.log(`Channel has ${channel.subscription_count} subscribers`);
301
+ * } catch (err) {
302
+ * if (err instanceof ApiError && err.status === 404) {
303
+ * console.log('Channel not found');
304
+ * }
305
+ * }
306
+ * ```
307
+ */
308
+ async getChannel(channelName) {
309
+ const encodedChannel = encodeURIComponent(channelName);
310
+ const path = `/apps/${this.appId}/channels/${encodedChannel}`;
311
+ const response = await this.request("GET", path, "");
312
+ return response;
313
+ }
314
+ /**
315
+ * Verify the authenticity of a webhook request.
316
+ *
317
+ * This function verifies that the webhook was sent by the Realtime service
318
+ * by validating the HMAC signature in the request headers.
319
+ *
320
+ * @param headers - HTTP headers from the webhook request
321
+ * @param body - Raw request body as string
322
+ * @param maxAge - Optional maximum age in seconds for the webhook timestamp
323
+ * @returns true if the webhook is valid, false otherwise
324
+ *
325
+ * @example Express.js webhook handler
326
+ * ```ts
327
+ * app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
328
+ * const body = req.body.toString('utf8');
329
+ *
330
+ * if (!client.verifyWebhook(req.headers, body, 300)) {
331
+ * return res.status(401).send('Invalid signature');
332
+ * }
333
+ *
334
+ * const event = JSON.parse(body);
335
+ * // Process webhook...
336
+ * });
337
+ * ```
338
+ */
339
+ verifyWebhook(headers, body, maxAge) {
340
+ return verifyWebhook(this.secret, headers, body, maxAge);
341
+ }
342
+ /**
343
+ * Make an authenticated API request.
344
+ */
345
+ async request(method, path, body) {
346
+ const timestamp = Math.floor(Date.now() / 1e3);
347
+ const [signPath] = path.split("?");
348
+ const signature = signRequest(this.secret, method, signPath, body, timestamp);
349
+ const url = `${this.host}${path}`;
350
+ const headers = {
351
+ "X-Realtime-Key": this.key,
352
+ "X-Realtime-Timestamp": timestamp.toString(),
353
+ "X-Realtime-Signature": signature
354
+ };
355
+ if (body !== "") {
356
+ headers["Content-Type"] = "application/json";
357
+ }
358
+ try {
359
+ const response = await fetch(url, {
360
+ method,
361
+ headers,
362
+ body: body !== "" ? body : void 0
363
+ });
364
+ const responseText = await response.text();
365
+ if (!response.ok) {
366
+ let errorMessage = responseText;
367
+ let problem = null;
368
+ try {
369
+ problem = JSON.parse(responseText);
370
+ if (typeof problem.detail === "string" && problem.detail.length > 0) {
371
+ errorMessage = problem.detail;
372
+ } else if (typeof problem.title === "string" && problem.title.length > 0) {
373
+ errorMessage = problem.title;
374
+ }
375
+ } catch {
376
+ problem = null;
377
+ }
378
+ if (response.status === 401 || response.status === 403) {
379
+ throw new AuthenticationError(
380
+ errorMessage || "Authentication failed"
381
+ );
382
+ }
383
+ if (response.status === 400 || response.status === 422) {
384
+ throw new ValidationError(errorMessage || "Validation failed");
385
+ }
386
+ throw new ApiError(
387
+ errorMessage || `Request failed with status ${response.status}`,
388
+ response.status,
389
+ responseText
390
+ );
391
+ }
392
+ if (responseText === "") {
393
+ return {};
394
+ }
395
+ try {
396
+ return JSON.parse(responseText);
397
+ } catch {
398
+ throw new RealtimeError(`Failed to parse response: ${responseText}`);
399
+ }
400
+ } catch (error) {
401
+ if (error instanceof ApiError || error instanceof AuthenticationError || error instanceof ValidationError || error instanceof RealtimeError) {
402
+ throw error;
403
+ }
404
+ throw new RealtimeError(
405
+ `Network error: ${error instanceof Error ? error.message : String(error)}`
406
+ );
407
+ }
408
+ }
409
+ };
410
+ // Annotate the CommonJS export names for ESM import in node:
411
+ 0 && (module.exports = {
412
+ ApiError,
413
+ Apinator,
414
+ AuthenticationError,
415
+ RealtimeError,
416
+ ValidationError,
417
+ authenticateChannel,
418
+ md5Hex,
419
+ signChannel,
420
+ signRequest,
421
+ signWebhookPayload,
422
+ verifyWebhook
423
+ });
424
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/crypto.ts","../src/auth.ts","../src/webhook.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * @apinator/server - Node.js server SDK for Apinator\n *\n * This package provides a server-side SDK for interacting with the Apinator API.\n * It supports triggering events, authenticating channel subscriptions, managing channels,\n * and verifying webhook signatures.\n *\n * @example Basic usage\n * ```ts\n * import { Apinator } from '@apinator/server';\n *\n * const client = new Apinator({\n * appId: 'my-app-id',\n * key: 'my-key',\n * secret: 'my-secret',\n * cluster: 'eu', // or 'us'\n * });\n *\n * // Trigger an event\n * await client.trigger({\n * name: 'order-placed',\n * channel: 'orders',\n * data: JSON.stringify({ orderId: '123' }),\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nexport { Apinator } from \"./client\";\nexport { authenticateChannel } from \"./auth\";\nexport { verifyWebhook } from \"./webhook\";\nexport {\n signRequest,\n signChannel,\n signWebhookPayload,\n md5Hex,\n} from \"./crypto\";\n\nexport type {\n RealtimeOptions,\n TriggerParams,\n ChannelAuthResponse,\n ChannelInfo,\n WebhookEvent,\n} from \"./types\";\n\nexport {\n RealtimeError,\n AuthenticationError,\n ValidationError,\n ApiError,\n} from \"./errors\";\n","import { createHmac, createHash } from \"crypto\";\n\n/**\n * Compute MD5 hash of data and return as hex string.\n */\nexport function md5Hex(data: string): string {\n return createHash(\"md5\").update(data, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Sign an API request using HMAC-SHA256.\n *\n * @param secret - API secret\n * @param method - HTTP method (e.g., \"POST\", \"GET\")\n * @param path - Request path (e.g., \"/apps/123/events\")\n * @param body - Request body as string\n * @param timestamp - Unix timestamp in seconds\n * @returns HMAC signature as hex string\n */\nexport function signRequest(\n secret: string,\n method: string,\n path: string,\n body: string,\n timestamp: number\n): string {\n // If body is empty string, bodyMD5 = \"\" (NOT md5 of empty bytes)\n const bodyMD5 = body === \"\" ? \"\" : md5Hex(body);\n\n // sigString = ${timestamp}\\n${method}\\n${path}\\n${bodyMD5}\n const sigString = `${timestamp}\\n${method}\\n${path}\\n${bodyMD5}`;\n\n // Return hex(hmac-sha256(secret, sigString))\n return createHmac(\"sha256\", secret).update(sigString, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Sign a channel authentication request using HMAC-SHA256.\n *\n * @param secret - API secret\n * @param socketId - WebSocket connection socket ID\n * @param channelName - Channel name to authenticate\n * @param channelData - Optional channel data for presence channels (JSON string)\n * @returns HMAC signature as hex string\n */\nexport function signChannel(\n secret: string,\n socketId: string,\n channelName: string,\n channelData?: string\n): string {\n // sigString = ${socketId}:${channelName} or ${socketId}:${channelName}:${channelData}\n const sigString = channelData\n ? `${socketId}:${channelName}:${channelData}`\n : `${socketId}:${channelName}`;\n\n // Return hex(hmac-sha256(secret, sigString))\n return createHmac(\"sha256\", secret).update(sigString, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Sign a webhook payload using HMAC-SHA256.\n *\n * @param secret - Webhook secret\n * @param timestamp - Timestamp from X-Realtime-Timestamp header\n * @param payload - Raw webhook body\n * @returns HMAC signature as hex string\n */\nexport function signWebhookPayload(\n secret: string,\n timestamp: string,\n payload: string\n): string {\n // input = ${timestamp}.${payload} (dot-separated)\n const input = `${timestamp}.${payload}`;\n\n // Return hex(hmac-sha256(secret, input))\n return createHmac(\"sha256\", secret).update(input, \"utf8\").digest(\"hex\");\n}\n","import { signChannel } from \"./crypto\";\nimport type { ChannelAuthResponse } from \"./types\";\n\n/**\n * Authenticate a channel subscription request.\n *\n * This generates the authentication signature required for private and presence channels.\n * The returned auth string should be sent back to the client for channel subscription.\n *\n * @param secret - API secret\n * @param key - API key\n * @param socketId - WebSocket connection socket ID from the client\n * @param channelName - Channel name to authenticate\n * @param channelData - Optional channel data for presence channels (JSON string with user info)\n * @returns Channel authentication response with auth signature\n *\n * @example\n * ```ts\n * const authResponse = authenticateChannel(\n * \"my-secret\",\n * \"my-key\",\n * \"12345.67890\",\n * \"private-chat\"\n * );\n * // Returns: { auth: \"my-key:abc123...\" }\n * ```\n *\n * @example Presence channel with user data\n * ```ts\n * const authResponse = authenticateChannel(\n * \"my-secret\",\n * \"my-key\",\n * \"12345.67890\",\n * \"presence-room\",\n * JSON.stringify({ user_id: \"user1\", user_info: { name: \"Alice\" } })\n * );\n * // Returns: { auth: \"my-key:abc123...\", channel_data: \"...\" }\n * ```\n */\nexport function authenticateChannel(\n secret: string,\n key: string,\n socketId: string,\n channelName: string,\n channelData?: string\n): ChannelAuthResponse {\n const signature = signChannel(secret, socketId, channelName, channelData);\n\n const response: ChannelAuthResponse = {\n auth: `${key}:${signature}`,\n };\n\n if (channelData !== undefined) {\n response.channel_data = channelData;\n }\n\n return response;\n}\n","import { timingSafeEqual } from \"crypto\";\nimport { signWebhookPayload } from \"./crypto\";\n\n/**\n * Verify the authenticity of a webhook request.\n *\n * This function verifies that the webhook was sent by the Realtime service\n * by validating the HMAC signature in the request headers.\n *\n * @param secret - Webhook secret\n * @param headers - HTTP headers from the webhook request (case-insensitive keys)\n * @param body - Raw request body as string\n * @param maxAge - Optional maximum age in seconds for the webhook timestamp\n * @returns true if the webhook is valid, false otherwise\n *\n * @example\n * ```ts\n * // Express.js webhook handler\n * app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {\n * const headers = req.headers;\n * const body = req.body.toString('utf8');\n *\n * if (!verifyWebhook('my-webhook-secret', headers, body, 300)) {\n * return res.status(401).send('Invalid signature');\n * }\n *\n * // Process webhook\n * const event = JSON.parse(body);\n * // ...\n * });\n * ```\n */\nexport function verifyWebhook(\n secret: string,\n headers: Record<string, string | string[] | undefined>,\n body: string,\n maxAge?: number\n): boolean {\n // Extract signature from X-Realtime-Signature header (case-insensitive)\n const signatureHeader = getHeaderCaseInsensitive(\n headers,\n \"x-realtime-signature\"\n );\n if (!signatureHeader) {\n return false;\n }\n\n // Strip \"sha256=\" prefix if present\n const actualSignature = signatureHeader.startsWith(\"sha256=\")\n ? signatureHeader.slice(7)\n : signatureHeader;\n\n // Extract timestamp from X-Realtime-Timestamp header (case-insensitive)\n const timestamp = getHeaderCaseInsensitive(headers, \"x-realtime-timestamp\");\n if (!timestamp) {\n return false;\n }\n\n // If maxAge provided, check timestamp is within maxAge of current time\n if (maxAge !== undefined) {\n const webhookTime = parseInt(timestamp, 10);\n if (isNaN(webhookTime)) {\n return false;\n }\n\n const currentTime = Math.floor(Date.now() / 1000);\n const age = currentTime - webhookTime;\n\n if (age > maxAge || age < 0) {\n return false;\n }\n }\n\n // Compute expected signature\n const expectedSignature = signWebhookPayload(secret, timestamp, body);\n\n // Timing-safe comparison\n try {\n const actualBuffer = Buffer.from(actualSignature, \"hex\");\n const expectedBuffer = Buffer.from(expectedSignature, \"hex\");\n\n // Both buffers must be same length for timingSafeEqual\n if (actualBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(actualBuffer, expectedBuffer);\n } catch {\n return false;\n }\n}\n\n/**\n * Get header value with case-insensitive key lookup.\n */\nfunction getHeaderCaseInsensitive(\n headers: Record<string, string | string[] | undefined>,\n key: string\n): string | undefined {\n const lowerKey = key.toLowerCase();\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === lowerKey) {\n if (typeof v === \"string\") {\n return v;\n }\n if (Array.isArray(v)) {\n return v.find((entry): entry is string => typeof entry === \"string\");\n }\n return undefined;\n }\n }\n return undefined;\n}\n","/**\n * Base error class for all Realtime SDK errors.\n */\nexport class RealtimeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"RealtimeError\";\n Object.setPrototypeOf(this, RealtimeError.prototype);\n }\n}\n\n/**\n * Error thrown when authentication fails (401 responses).\n */\nexport class AuthenticationError extends RealtimeError {\n constructor(message: string) {\n super(message);\n this.name = \"AuthenticationError\";\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n}\n\n/**\n * Error thrown when request validation fails (400 responses).\n */\nexport class ValidationError extends RealtimeError {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\n/**\n * Generic API error with status code and response body.\n */\nexport class ApiError extends RealtimeError {\n public readonly status: number;\n public readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, ApiError.prototype);\n }\n}\n","import { signRequest } from \"./crypto\";\nimport { authenticateChannel as authChannel } from \"./auth\";\nimport { verifyWebhook as verifyWebhookSignature } from \"./webhook\";\nimport {\n ApiError,\n AuthenticationError,\n ValidationError,\n RealtimeError,\n} from \"./errors\";\nimport type {\n RealtimeOptions,\n TriggerParams,\n ChannelAuthResponse,\n ChannelInfo,\n} from \"./types\";\n\ntype ProblemDetails = {\n type?: string;\n title?: string;\n status?: number;\n detail?: string;\n code?: string;\n details?: unknown;\n};\n\n/**\n * Server-side client for triggering events and managing channels.\n *\n * @example\n * ```ts\n * import { Apinator } from '@apinator/server';\n *\n * const client = new Apinator({\n * appId: 'my-app-id',\n * key: 'my-key',\n * secret: 'my-secret',\n * cluster: 'eu', // or 'us'\n * });\n *\n * // Trigger an event\n * await client.trigger({\n * name: 'order-placed',\n * channel: 'orders',\n * data: JSON.stringify({ orderId: '123' }),\n * });\n *\n * // Authenticate a channel subscription\n * const auth = client.authenticateChannel(socketId, 'private-chat');\n *\n * // Get channel info\n * const channel = await client.getChannel('orders');\n * console.log(channel.subscription_count);\n * ```\n */\nexport class Apinator {\n private readonly appId: string;\n private readonly key: string;\n private readonly secret: string;\n private readonly host: string;\n\n constructor(options: RealtimeOptions) {\n this.appId = options.appId;\n this.key = options.key;\n this.secret = options.secret;\n this.host = `https://ws-${options.cluster}.apinator.io`;\n }\n\n /**\n * Trigger an event on one or more channels.\n *\n * @param params - Event parameters\n * @throws {ValidationError} If the request is invalid (400)\n * @throws {AuthenticationError} If authentication fails (401)\n * @throws {ApiError} For other API errors\n *\n * @example Single channel\n * ```ts\n * await client.trigger({\n * name: 'message',\n * channel: 'chat',\n * data: JSON.stringify({ text: 'Hello!' }),\n * });\n * ```\n *\n * @example Multiple channels\n * ```ts\n * await client.trigger({\n * name: 'notification',\n * channels: ['user-1', 'user-2'],\n * data: JSON.stringify({ message: 'New update' }),\n * });\n * ```\n *\n * @example Exclude socket\n * ```ts\n * await client.trigger({\n * name: 'message',\n * channel: 'chat',\n * data: JSON.stringify({ text: 'Hello!' }),\n * socketId: '12345.67890', // Don't send to this connection\n * });\n * ```\n */\n async trigger(params: TriggerParams): Promise<void> {\n // Validate mutually exclusive channel/channels\n if (params.channel && params.channels) {\n throw new ValidationError(\n \"Cannot specify both 'channel' and 'channels' parameters\"\n );\n }\n\n if (!params.channel && !params.channels) {\n throw new ValidationError(\n \"Must specify either 'channel' or 'channels' parameter\"\n );\n }\n\n // Build request body\n const body: Record<string, unknown> = {\n name: params.name,\n data: params.data,\n };\n\n if (params.channel) {\n body.channel = params.channel;\n }\n\n if (params.channels) {\n body.channels = params.channels;\n }\n\n if (params.socketId) {\n body.socket_id = params.socketId;\n }\n\n const bodyString = JSON.stringify(body);\n const path = `/apps/${this.appId}/events`;\n\n await this.request(\"POST\", path, bodyString);\n }\n\n /**\n * Authenticate a channel subscription request.\n *\n * This generates the authentication signature required for private and presence channels.\n * The returned auth object should be sent back to the client for channel subscription.\n *\n * @param socketId - WebSocket connection socket ID from the client\n * @param channelName - Channel name to authenticate\n * @param channelData - Optional channel data for presence channels (JSON string with user info)\n * @returns Channel authentication response with auth signature\n *\n * @example Private channel\n * ```ts\n * const auth = client.authenticateChannel('12345.67890', 'private-chat');\n * // Send auth back to client\n * res.json(auth);\n * ```\n *\n * @example Presence channel\n * ```ts\n * const channelData = JSON.stringify({\n * user_id: 'user1',\n * user_info: { name: 'Alice' }\n * });\n * const auth = client.authenticateChannel('12345.67890', 'presence-room', channelData);\n * res.json(auth);\n * ```\n */\n authenticateChannel(\n socketId: string,\n channelName: string,\n channelData?: string\n ): ChannelAuthResponse {\n return authChannel(this.secret, this.key, socketId, channelName, channelData);\n }\n\n /**\n * Get a list of channels, optionally filtered by prefix.\n *\n * @param prefix - Optional prefix to filter channels (e.g., \"private-\", \"presence-\")\n * @returns Array of channel information\n * @throws {AuthenticationError} If authentication fails (401)\n * @throws {ApiError} For other API errors\n *\n * @example Get all channels\n * ```ts\n * const channels = await client.getChannels();\n * channels.forEach(ch => console.log(ch.name, ch.subscription_count));\n * ```\n *\n * @example Get presence channels only\n * ```ts\n * const presenceChannels = await client.getChannels('presence-');\n * ```\n */\n async getChannels(prefix?: string): Promise<ChannelInfo[]> {\n let path = `/apps/${this.appId}/channels`;\n\n if (prefix) {\n const encodedPrefix = encodeURIComponent(prefix);\n path += `?filter_by_prefix=${encodedPrefix}`;\n }\n\n const response = await this.request(\"GET\", path, \"\");\n return response.channels as ChannelInfo[];\n }\n\n /**\n * Get information about a specific channel.\n *\n * @param channelName - Channel name\n * @returns Channel information\n * @throws {AuthenticationError} If authentication fails (401)\n * @throws {ApiError} For other API errors (including 404 if channel not found)\n *\n * @example\n * ```ts\n * try {\n * const channel = await client.getChannel('chat');\n * console.log(`Channel has ${channel.subscription_count} subscribers`);\n * } catch (err) {\n * if (err instanceof ApiError && err.status === 404) {\n * console.log('Channel not found');\n * }\n * }\n * ```\n */\n async getChannel(channelName: string): Promise<ChannelInfo> {\n const encodedChannel = encodeURIComponent(channelName);\n const path = `/apps/${this.appId}/channels/${encodedChannel}`;\n\n const response = await this.request(\"GET\", path, \"\");\n return response as ChannelInfo;\n }\n\n /**\n * Verify the authenticity of a webhook request.\n *\n * This function verifies that the webhook was sent by the Realtime service\n * by validating the HMAC signature in the request headers.\n *\n * @param headers - HTTP headers from the webhook request\n * @param body - Raw request body as string\n * @param maxAge - Optional maximum age in seconds for the webhook timestamp\n * @returns true if the webhook is valid, false otherwise\n *\n * @example Express.js webhook handler\n * ```ts\n * app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {\n * const body = req.body.toString('utf8');\n *\n * if (!client.verifyWebhook(req.headers, body, 300)) {\n * return res.status(401).send('Invalid signature');\n * }\n *\n * const event = JSON.parse(body);\n * // Process webhook...\n * });\n * ```\n */\n verifyWebhook(\n headers: Record<string, string | string[] | undefined>,\n body: string,\n maxAge?: number\n ): boolean {\n return verifyWebhookSignature(this.secret, headers, body, maxAge);\n }\n\n /**\n * Make an authenticated API request.\n */\n private async request(\n method: string,\n path: string,\n body: string\n ): Promise<any> {\n const timestamp = Math.floor(Date.now() / 1000);\n // Sign only the path portion (before query string) — server uses r.URL.Path\n const [signPath] = path.split(\"?\");\n const signature = signRequest(this.secret, method, signPath, body, timestamp);\n\n const url = `${this.host}${path}`;\n const headers: Record<string, string> = {\n \"X-Realtime-Key\": this.key,\n \"X-Realtime-Timestamp\": timestamp.toString(),\n \"X-Realtime-Signature\": signature,\n };\n\n if (body !== \"\") {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: body !== \"\" ? body : undefined,\n });\n\n const responseText = await response.text();\n\n if (!response.ok) {\n let errorMessage = responseText;\n let problem: ProblemDetails | null = null;\n try {\n problem = JSON.parse(responseText) as ProblemDetails;\n if (typeof problem.detail === \"string\" && problem.detail.length > 0) {\n errorMessage = problem.detail;\n } else if (typeof problem.title === \"string\" && problem.title.length > 0) {\n errorMessage = problem.title;\n }\n } catch {\n problem = null;\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new AuthenticationError(\n errorMessage || \"Authentication failed\"\n );\n }\n\n if (response.status === 400 || response.status === 422) {\n throw new ValidationError(errorMessage || \"Validation failed\");\n }\n\n throw new ApiError(\n errorMessage || `Request failed with status ${response.status}`,\n response.status,\n responseText\n );\n }\n\n // Parse successful response\n if (responseText === \"\") {\n return {};\n }\n\n try {\n return JSON.parse(responseText);\n } catch {\n throw new RealtimeError(`Failed to parse response: ${responseText}`);\n }\n } catch (error) {\n // Re-throw our custom errors\n if (\n error instanceof ApiError ||\n error instanceof AuthenticationError ||\n error instanceof ValidationError ||\n error instanceof RealtimeError\n ) {\n throw error;\n }\n\n // Wrap network errors\n throw new RealtimeError(\n `Network error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n}\n"],"mappings":"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","names":["import_crypto"]}