@klime/node 1.0.3 → 1.2.1

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 CHANGED
@@ -1,419 +1,509 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
1
+ // src/index.ts
2
+ import * as https from "https";
3
+ import * as http from "http";
4
+ import * as url from "url";
5
+
6
+ // src/types.ts
7
+ var SendError = class extends Error {
8
+ constructor(message, events) {
9
+ super(message);
10
+ this.name = "SendError";
11
+ this.events = events;
12
+ }
13
+ };
14
+
15
+ // src/index.ts
16
+ var DEFAULT_ENDPOINT = "https://i.klime.com";
17
+ var DEFAULT_FLUSH_INTERVAL = 2e3;
18
+ var DEFAULT_MAX_BATCH_SIZE = 20;
19
+ var DEFAULT_MAX_QUEUE_SIZE = 1e3;
20
+ var DEFAULT_RETRY_MAX_ATTEMPTS = 5;
21
+ var DEFAULT_RETRY_INITIAL_DELAY = 1e3;
22
+ var MAX_BATCH_SIZE = 100;
23
+ var MAX_EVENT_SIZE_BYTES = 200 * 1024;
24
+ var MAX_BATCH_SIZE_BYTES = 10 * 1024 * 1024;
25
+ var SDK_VERSION = "1.1.0";
26
+ var hasNativeFetch = typeof fetch !== "undefined";
27
+ var createDefaultLogger = () => ({
28
+ debug: (message, ...args) => console.debug(`[Klime] ${message}`, ...args),
29
+ info: (message, ...args) => console.info(`[Klime] ${message}`, ...args),
30
+ warn: (message, ...args) => console.warn(`[Klime] ${message}`, ...args),
31
+ error: (message, ...args) => console.error(`[Klime] ${message}`, ...args)
17
32
  });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
33
+ var KlimeClient = class {
34
+ constructor(config) {
35
+ this.queue = [];
36
+ this.flushTimer = null;
37
+ this.isShutdown = false;
38
+ this.flushPromise = null;
39
+ this.shutdownHandlers = [];
40
+ if (!config.writeKey) {
41
+ throw new Error("writeKey is required");
42
+ }
43
+ this.config = {
44
+ writeKey: config.writeKey,
45
+ endpoint: config.endpoint || DEFAULT_ENDPOINT,
46
+ flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
47
+ maxBatchSize: Math.min(
48
+ config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
49
+ MAX_BATCH_SIZE
50
+ ),
51
+ maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
52
+ retryMaxAttempts: config.retryMaxAttempts ?? DEFAULT_RETRY_MAX_ATTEMPTS,
53
+ retryInitialDelay: config.retryInitialDelay ?? DEFAULT_RETRY_INITIAL_DELAY,
54
+ flushOnShutdown: config.flushOnShutdown ?? true,
55
+ logger: config.logger ?? createDefaultLogger(),
56
+ onError: config.onError,
57
+ onSuccess: config.onSuccess
26
58
  };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
59
+ if (this.config.flushOnShutdown) {
60
+ const shutdownHandler = async () => {
61
+ await this.shutdown();
62
+ };
63
+ process.on("SIGTERM", shutdownHandler);
64
+ process.on("SIGINT", shutdownHandler);
65
+ this.shutdownHandlers.push(() => {
66
+ process.removeListener("SIGTERM", shutdownHandler);
67
+ process.removeListener("SIGINT", shutdownHandler);
68
+ });
69
+ }
70
+ this.scheduleFlush();
71
+ }
72
+ track(event, properties, options) {
73
+ if (this.isShutdown) {
74
+ return;
75
+ }
76
+ const eventObj = {
77
+ type: "track",
78
+ messageId: this.generateUUID(),
79
+ event,
80
+ timestamp: this.generateTimestamp(),
81
+ properties: properties || {},
82
+ context: this.getContext()
33
83
  };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.KlimeClient = void 0;
37
- const https = __importStar(require("https"));
38
- const http = __importStar(require("http"));
39
- const url = __importStar(require("url"));
40
- const DEFAULT_ENDPOINT = "https://i.klime.com";
41
- const DEFAULT_FLUSH_INTERVAL = 2000;
42
- const DEFAULT_MAX_BATCH_SIZE = 20;
43
- const DEFAULT_MAX_QUEUE_SIZE = 1000;
44
- const DEFAULT_RETRY_MAX_ATTEMPTS = 5;
45
- const DEFAULT_RETRY_INITIAL_DELAY = 1000;
46
- const MAX_BATCH_SIZE = 100;
47
- const MAX_EVENT_SIZE_BYTES = 200 * 1024; // 200KB
48
- const MAX_BATCH_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
49
- const SDK_VERSION = "1.0.1";
50
- // Check if fetch is available (Node 18+)
51
- const hasNativeFetch = typeof fetch !== "undefined";
52
- class KlimeClient {
53
- constructor(config) {
54
- this.queue = [];
55
- this.flushTimer = null;
56
- this.isShutdown = false;
57
- this.flushPromise = null;
58
- this.shutdownHandlers = [];
59
- if (!config.writeKey) {
60
- throw new Error("writeKey is required");
61
- }
62
- this.config = {
63
- writeKey: config.writeKey,
64
- endpoint: config.endpoint || DEFAULT_ENDPOINT,
65
- flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
66
- maxBatchSize: Math.min(config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE, MAX_BATCH_SIZE),
67
- maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
68
- retryMaxAttempts: config.retryMaxAttempts ?? DEFAULT_RETRY_MAX_ATTEMPTS,
69
- retryInitialDelay: config.retryInitialDelay ?? DEFAULT_RETRY_INITIAL_DELAY,
70
- flushOnShutdown: config.flushOnShutdown ?? true,
71
- };
72
- if (this.config.flushOnShutdown) {
73
- const shutdownHandler = async () => {
74
- await this.shutdown();
75
- };
76
- process.on("SIGTERM", shutdownHandler);
77
- process.on("SIGINT", shutdownHandler);
78
- this.shutdownHandlers.push(() => {
79
- process.removeListener("SIGTERM", shutdownHandler);
80
- process.removeListener("SIGINT", shutdownHandler);
81
- });
82
- }
83
- this.scheduleFlush();
84
+ if (options?.userId) {
85
+ eventObj.userId = options.userId;
84
86
  }
85
- track(event, properties, options) {
86
- if (this.isShutdown) {
87
- return;
88
- }
89
- const eventObj = {
90
- type: "track",
91
- messageId: this.generateUUID(),
92
- event,
93
- timestamp: this.generateTimestamp(),
94
- properties: properties || {},
95
- context: this.getContext(options?.ip),
96
- };
97
- if (options?.userId) {
98
- eventObj.userId = options.userId;
99
- }
100
- if (options?.groupId) {
101
- eventObj.groupId = options.groupId;
102
- }
103
- this.enqueue(eventObj);
87
+ if (options?.groupId) {
88
+ eventObj.groupId = options.groupId;
104
89
  }
105
- identify(userId, traits, options) {
106
- if (this.isShutdown) {
107
- return;
108
- }
109
- const eventObj = {
110
- type: "identify",
111
- messageId: this.generateUUID(),
112
- userId,
113
- timestamp: this.generateTimestamp(),
114
- traits: traits || {},
115
- context: this.getContext(options?.ip),
116
- };
117
- this.enqueue(eventObj);
118
- }
119
- group(groupId, traits, options) {
120
- if (this.isShutdown) {
121
- return;
122
- }
123
- const eventObj = {
124
- type: "group",
125
- messageId: this.generateUUID(),
126
- groupId,
127
- timestamp: this.generateTimestamp(),
128
- traits: traits || {},
129
- context: this.getContext(options?.ip),
130
- };
131
- if (options?.userId) {
132
- eventObj.userId = options.userId;
133
- }
134
- this.enqueue(eventObj);
90
+ this.enqueue(eventObj);
91
+ }
92
+ identify(userId, traits, options) {
93
+ if (this.isShutdown) {
94
+ return;
135
95
  }
136
- async flush() {
137
- if (this.flushPromise) {
138
- return this.flushPromise;
139
- }
140
- this.flushPromise = this.doFlush();
141
- try {
142
- await this.flushPromise;
143
- }
144
- finally {
145
- this.flushPromise = null;
146
- }
96
+ const eventObj = {
97
+ type: "identify",
98
+ messageId: this.generateUUID(),
99
+ userId,
100
+ timestamp: this.generateTimestamp(),
101
+ traits: traits || {},
102
+ context: this.getContext()
103
+ };
104
+ this.enqueue(eventObj);
105
+ }
106
+ group(groupId, traits, options) {
107
+ if (this.isShutdown) {
108
+ return;
147
109
  }
148
- async shutdown() {
149
- if (this.isShutdown) {
150
- return;
151
- }
152
- this.isShutdown = true;
153
- // Remove shutdown handlers
154
- this.shutdownHandlers.forEach((handler) => handler());
155
- this.shutdownHandlers = [];
156
- if (this.flushTimer) {
157
- clearTimeout(this.flushTimer);
158
- this.flushTimer = null;
159
- }
160
- await this.flush();
161
- }
162
- enqueue(event) {
163
- // Check event size
164
- const eventSize = this.estimateEventSize(event);
165
- if (eventSize > MAX_EVENT_SIZE_BYTES) {
166
- console.error(`Klime: Event size (${eventSize} bytes) exceeds ${MAX_EVENT_SIZE_BYTES} bytes limit`);
167
- return;
168
- }
169
- // Drop oldest if queue is full
170
- if (this.queue.length >= this.config.maxQueueSize) {
171
- this.queue.shift();
172
- }
173
- this.queue.push(event);
174
- // Check if we should flush immediately
175
- if (this.queue.length >= this.config.maxBatchSize) {
176
- this.flush();
177
- }
110
+ const eventObj = {
111
+ type: "group",
112
+ messageId: this.generateUUID(),
113
+ groupId,
114
+ timestamp: this.generateTimestamp(),
115
+ traits: traits || {},
116
+ context: this.getContext()
117
+ };
118
+ if (options?.userId) {
119
+ eventObj.userId = options.userId;
120
+ }
121
+ this.enqueue(eventObj);
122
+ }
123
+ /**
124
+ * Track an event synchronously. Returns BatchResponse or throws SendError.
125
+ */
126
+ async trackSync(event, properties, options) {
127
+ if (this.isShutdown) {
128
+ throw new SendError("Client is shutdown", []);
178
129
  }
179
- async doFlush() {
180
- if (this.queue.length === 0) {
181
- return;
130
+ const eventObj = {
131
+ type: "track",
132
+ messageId: this.generateUUID(),
133
+ event,
134
+ timestamp: this.generateTimestamp(),
135
+ properties: properties || {},
136
+ context: this.getContext()
137
+ };
138
+ if (options?.userId) {
139
+ eventObj.userId = options.userId;
140
+ }
141
+ if (options?.groupId) {
142
+ eventObj.groupId = options.groupId;
143
+ }
144
+ return this.sendSync([eventObj]);
145
+ }
146
+ /**
147
+ * Identify a user synchronously. Returns BatchResponse or throws SendError.
148
+ */
149
+ async identifySync(userId, traits, options) {
150
+ if (this.isShutdown) {
151
+ throw new SendError("Client is shutdown", []);
152
+ }
153
+ const eventObj = {
154
+ type: "identify",
155
+ messageId: this.generateUUID(),
156
+ userId,
157
+ timestamp: this.generateTimestamp(),
158
+ traits: traits || {},
159
+ context: this.getContext()
160
+ };
161
+ return this.sendSync([eventObj]);
162
+ }
163
+ /**
164
+ * Associate a user with a group synchronously. Returns BatchResponse or throws SendError.
165
+ */
166
+ async groupSync(groupId, traits, options) {
167
+ if (this.isShutdown) {
168
+ throw new SendError("Client is shutdown", []);
169
+ }
170
+ const eventObj = {
171
+ type: "group",
172
+ messageId: this.generateUUID(),
173
+ groupId,
174
+ timestamp: this.generateTimestamp(),
175
+ traits: traits || {},
176
+ context: this.getContext()
177
+ };
178
+ if (options?.userId) {
179
+ eventObj.userId = options.userId;
180
+ }
181
+ return this.sendSync([eventObj]);
182
+ }
183
+ /**
184
+ * Return the number of events currently in the queue.
185
+ */
186
+ getQueueSize() {
187
+ return this.queue.length;
188
+ }
189
+ async flush() {
190
+ if (this.flushPromise) {
191
+ return this.flushPromise;
192
+ }
193
+ this.flushPromise = this.doFlush();
194
+ try {
195
+ await this.flushPromise;
196
+ } finally {
197
+ this.flushPromise = null;
198
+ }
199
+ }
200
+ async shutdown() {
201
+ if (this.isShutdown) {
202
+ return;
203
+ }
204
+ this.isShutdown = true;
205
+ this.shutdownHandlers.forEach((handler) => handler());
206
+ this.shutdownHandlers = [];
207
+ if (this.flushTimer) {
208
+ clearTimeout(this.flushTimer);
209
+ this.flushTimer = null;
210
+ }
211
+ await this.flush();
212
+ }
213
+ enqueue(event) {
214
+ const eventSize = this.estimateEventSize(event);
215
+ if (eventSize > MAX_EVENT_SIZE_BYTES) {
216
+ this.config.logger.warn(
217
+ `Event size (${eventSize} bytes) exceeds ${MAX_EVENT_SIZE_BYTES} bytes limit`
218
+ );
219
+ return;
220
+ }
221
+ if (this.queue.length >= this.config.maxQueueSize) {
222
+ const dropped = this.queue.shift();
223
+ this.config.logger.warn(`Queue full, dropping oldest event: ${dropped?.type}`);
224
+ }
225
+ this.queue.push(event);
226
+ this.config.logger.debug(`Enqueued ${event.type} event, queue size: ${this.queue.length}`);
227
+ if (this.queue.length >= this.config.maxBatchSize) {
228
+ this.flush();
229
+ }
230
+ }
231
+ async doFlush() {
232
+ if (this.queue.length === 0) {
233
+ return;
234
+ }
235
+ if (this.flushTimer) {
236
+ clearTimeout(this.flushTimer);
237
+ this.flushTimer = null;
238
+ }
239
+ while (this.queue.length > 0) {
240
+ const batch = this.extractBatch();
241
+ if (batch.length === 0) {
242
+ break;
243
+ }
244
+ await this.sendBatch(batch);
245
+ }
246
+ this.scheduleFlush();
247
+ }
248
+ extractBatch() {
249
+ const batch = [];
250
+ let batchSize = 0;
251
+ while (this.queue.length > 0 && batch.length < MAX_BATCH_SIZE) {
252
+ const event = this.queue[0];
253
+ const eventSize = this.estimateEventSize(event);
254
+ if (batchSize + eventSize > MAX_BATCH_SIZE_BYTES) {
255
+ break;
256
+ }
257
+ batch.push(this.queue.shift());
258
+ batchSize += eventSize;
259
+ }
260
+ return batch;
261
+ }
262
+ async sendBatch(batch) {
263
+ if (batch.length === 0) {
264
+ return;
265
+ }
266
+ this.config.logger.debug(`Sending batch of ${batch.length} events`);
267
+ const result = await this.doSend(batch);
268
+ if (result === null) {
269
+ const error = new Error(`Failed to send batch of ${batch.length} events after retries`);
270
+ this.invokeOnError(error, batch);
271
+ } else if (result.failed > 0 && result.errors) {
272
+ this.config.logger.warn(
273
+ `Batch partially failed. Accepted: ${result.accepted}, Failed: ${result.failed}`,
274
+ result.errors
275
+ );
276
+ this.invokeOnSuccess(result);
277
+ } else {
278
+ this.config.logger.debug(`Batch sent successfully. Accepted: ${result.accepted}`);
279
+ this.invokeOnSuccess(result);
280
+ }
281
+ }
282
+ async sendSync(events) {
283
+ this.config.logger.debug(`Sending ${events.length} events synchronously`);
284
+ const result = await this.doSend(events);
285
+ if (result === null) {
286
+ throw new SendError(`Failed to send ${events.length} events after retries`, events);
287
+ }
288
+ return result;
289
+ }
290
+ async doSend(batch) {
291
+ const request = { batch };
292
+ const requestBody = JSON.stringify(request);
293
+ if (hasNativeFetch) {
294
+ return this.doSendWithFetch(batch, requestBody);
295
+ } else {
296
+ return this.doSendWithHttps(batch, requestBody);
297
+ }
298
+ }
299
+ async doSendWithFetch(batch, requestBody) {
300
+ const requestUrl = `${this.config.endpoint}/v1/batch`;
301
+ let attempt = 0;
302
+ let delay = this.config.retryInitialDelay;
303
+ while (attempt < this.config.retryMaxAttempts) {
304
+ try {
305
+ const response = await fetch(requestUrl, {
306
+ method: "POST",
307
+ headers: {
308
+ "Content-Type": "application/json",
309
+ Authorization: `Bearer ${this.config.writeKey}`
310
+ },
311
+ body: requestBody
312
+ });
313
+ const data = await response.json();
314
+ if (response.ok) {
315
+ return data;
316
+ }
317
+ if (response.status === 400 || response.status === 401) {
318
+ this.config.logger.error(`Permanent error (${response.status}):`, data);
319
+ return null;
182
320
  }
183
- // Clear the flush timer
184
- if (this.flushTimer) {
185
- clearTimeout(this.flushTimer);
186
- this.flushTimer = null;
321
+ if (response.status === 429 || response.status === 503) {
322
+ const retryAfter = response.headers.get("Retry-After");
323
+ if (retryAfter) {
324
+ delay = parseInt(retryAfter, 10) * 1e3;
325
+ }
326
+ attempt++;
327
+ if (attempt < this.config.retryMaxAttempts) {
328
+ this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
329
+ await this.sleep(delay);
330
+ delay = Math.min(delay * 2, 16e3);
331
+ continue;
332
+ }
187
333
  }
188
- // Process batches
189
- while (this.queue.length > 0) {
190
- const batch = this.extractBatch();
191
- if (batch.length === 0) {
192
- break;
193
- }
194
- await this.sendBatch(batch);
334
+ attempt++;
335
+ if (attempt < this.config.retryMaxAttempts) {
336
+ this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
337
+ await this.sleep(delay);
338
+ delay = Math.min(delay * 2, 16e3);
195
339
  }
196
- // Schedule next flush
197
- this.scheduleFlush();
198
- }
199
- extractBatch() {
200
- const batch = [];
201
- let batchSize = 0;
202
- while (this.queue.length > 0 && batch.length < MAX_BATCH_SIZE) {
203
- const event = this.queue[0];
204
- const eventSize = this.estimateEventSize(event);
205
- // Check if adding this event would exceed batch size limit
206
- if (batchSize + eventSize > MAX_BATCH_SIZE_BYTES) {
207
- break;
208
- }
209
- batch.push(this.queue.shift());
210
- batchSize += eventSize;
340
+ } catch (error) {
341
+ attempt++;
342
+ if (attempt < this.config.retryMaxAttempts) {
343
+ this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
344
+ await this.sleep(delay);
345
+ delay = Math.min(delay * 2, 16e3);
346
+ } else {
347
+ this.config.logger.error("Failed to send batch after retries:", error);
211
348
  }
212
- return batch;
349
+ }
213
350
  }
214
- async sendBatch(batch) {
215
- if (batch.length === 0) {
216
- return;
351
+ return null;
352
+ }
353
+ async doSendWithHttps(batch, requestBody) {
354
+ const parsedUrl = url.parse(this.config.endpoint);
355
+ const isHttps = parsedUrl.protocol === "https:";
356
+ const client = isHttps ? https : http;
357
+ const port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : isHttps ? 443 : 80;
358
+ const hostname = parsedUrl.hostname || "";
359
+ let attempt = 0;
360
+ let delay = this.config.retryInitialDelay;
361
+ while (attempt < this.config.retryMaxAttempts) {
362
+ try {
363
+ const result = await this.makeRequest(
364
+ client,
365
+ hostname,
366
+ port,
367
+ "/v1/batch",
368
+ requestBody
369
+ );
370
+ if (result.statusCode === 200) {
371
+ const data = JSON.parse(result.body);
372
+ return data;
217
373
  }
218
- const request = { batch };
219
- const requestBody = JSON.stringify(request);
220
- if (hasNativeFetch) {
221
- await this.sendBatchWithFetch(batch, requestBody);
374
+ if (result.statusCode === 400 || result.statusCode === 401) {
375
+ const data = JSON.parse(result.body);
376
+ this.config.logger.error(`Permanent error (${result.statusCode}):`, data);
377
+ return null;
222
378
  }
223
- else {
224
- await this.sendBatchWithHttps(batch, requestBody);
379
+ if (result.statusCode === 429 || result.statusCode === 503) {
380
+ const retryAfter = result.headers["retry-after"];
381
+ if (retryAfter) {
382
+ delay = parseInt(
383
+ Array.isArray(retryAfter) ? retryAfter[0] : retryAfter,
384
+ 10
385
+ ) * 1e3;
386
+ }
387
+ attempt++;
388
+ if (attempt < this.config.retryMaxAttempts) {
389
+ this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
390
+ await this.sleep(delay);
391
+ delay = Math.min(delay * 2, 16e3);
392
+ continue;
393
+ }
225
394
  }
226
- }
227
- async sendBatchWithFetch(batch, requestBody) {
228
- const requestUrl = `${this.config.endpoint}/v1/batch`;
229
- let attempt = 0;
230
- let delay = this.config.retryInitialDelay;
231
- while (attempt < this.config.retryMaxAttempts) {
232
- try {
233
- const response = await fetch(requestUrl, {
234
- method: "POST",
235
- headers: {
236
- "Content-Type": "application/json",
237
- Authorization: `Bearer ${this.config.writeKey}`,
238
- },
239
- body: requestBody,
240
- });
241
- const data = (await response.json());
242
- if (response.ok) {
243
- if (data.failed > 0 && data.errors) {
244
- console.warn(`Klime: Batch partially failed. Accepted: ${data.accepted}, Failed: ${data.failed}`, data.errors);
245
- }
246
- return;
247
- }
248
- if (response.status === 400 || response.status === 401) {
249
- console.error(`Klime: Permanent error (${response.status}):`, data);
250
- return;
251
- }
252
- if (response.status === 429 || response.status === 503) {
253
- const retryAfter = response.headers.get("Retry-After");
254
- if (retryAfter) {
255
- delay = parseInt(retryAfter, 10) * 1000;
256
- }
257
- attempt++;
258
- if (attempt < this.config.retryMaxAttempts) {
259
- await this.sleep(delay);
260
- delay = Math.min(delay * 2, 16000);
261
- continue;
262
- }
263
- }
264
- attempt++;
265
- if (attempt < this.config.retryMaxAttempts) {
266
- await this.sleep(delay);
267
- delay = Math.min(delay * 2, 16000);
268
- }
269
- }
270
- catch (error) {
271
- attempt++;
272
- if (attempt < this.config.retryMaxAttempts) {
273
- await this.sleep(delay);
274
- delay = Math.min(delay * 2, 16000);
275
- }
276
- else {
277
- console.error("Klime: Failed to send batch after retries:", error);
278
- }
279
- }
395
+ attempt++;
396
+ if (attempt < this.config.retryMaxAttempts) {
397
+ this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
398
+ await this.sleep(delay);
399
+ delay = Math.min(delay * 2, 16e3);
280
400
  }
281
- }
282
- async sendBatchWithHttps(batch, requestBody) {
283
- const parsedUrl = url.parse(this.config.endpoint);
284
- const isHttps = parsedUrl.protocol === "https:";
285
- const client = isHttps ? https : http;
286
- const port = parsedUrl.port
287
- ? parseInt(parsedUrl.port, 10)
288
- : isHttps
289
- ? 443
290
- : 80;
291
- const hostname = parsedUrl.hostname || "";
292
- let attempt = 0;
293
- let delay = this.config.retryInitialDelay;
294
- while (attempt < this.config.retryMaxAttempts) {
295
- try {
296
- const result = await this.makeRequest(client, hostname, port, "/v1/batch", requestBody);
297
- if (result.statusCode === 200) {
298
- const data = JSON.parse(result.body);
299
- if (data.failed > 0 && data.errors) {
300
- console.warn(`Klime: Batch partially failed. Accepted: ${data.accepted}, Failed: ${data.failed}`, data.errors);
301
- }
302
- return;
303
- }
304
- if (result.statusCode === 400 || result.statusCode === 401) {
305
- const data = JSON.parse(result.body);
306
- console.error(`Klime: Permanent error (${result.statusCode}):`, data);
307
- return;
308
- }
309
- if (result.statusCode === 429 || result.statusCode === 503) {
310
- const retryAfter = result.headers["retry-after"];
311
- if (retryAfter) {
312
- delay =
313
- parseInt(Array.isArray(retryAfter) ? retryAfter[0] : retryAfter, 10) * 1000;
314
- }
315
- attempt++;
316
- if (attempt < this.config.retryMaxAttempts) {
317
- await this.sleep(delay);
318
- delay = Math.min(delay * 2, 16000);
319
- continue;
320
- }
321
- }
322
- attempt++;
323
- if (attempt < this.config.retryMaxAttempts) {
324
- await this.sleep(delay);
325
- delay = Math.min(delay * 2, 16000);
326
- }
327
- }
328
- catch (error) {
329
- attempt++;
330
- if (attempt < this.config.retryMaxAttempts) {
331
- await this.sleep(delay);
332
- delay = Math.min(delay * 2, 16000);
333
- }
334
- else {
335
- console.error("Klime: Failed to send batch after retries:", error);
336
- }
337
- }
401
+ } catch (error) {
402
+ attempt++;
403
+ if (attempt < this.config.retryMaxAttempts) {
404
+ this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
405
+ await this.sleep(delay);
406
+ delay = Math.min(delay * 2, 16e3);
407
+ } else {
408
+ this.config.logger.error("Failed to send batch after retries:", error);
338
409
  }
410
+ }
339
411
  }
340
- makeRequest(client, hostname, port, path, body) {
341
- return new Promise((resolve, reject) => {
342
- const options = {
343
- hostname,
344
- port,
345
- path,
346
- method: "POST",
347
- headers: {
348
- "Content-Type": "application/json",
349
- Authorization: `Bearer ${this.config.writeKey}`,
350
- "Content-Length": Buffer.byteLength(body),
351
- },
352
- };
353
- const req = client.request(options, (res) => {
354
- let data = "";
355
- res.on("data", (chunk) => {
356
- data += chunk;
357
- });
358
- res.on("end", () => {
359
- resolve({
360
- statusCode: res.statusCode || 500,
361
- body: data,
362
- headers: res.headers,
363
- });
364
- });
365
- });
366
- req.on("error", (error) => {
367
- reject(error);
368
- });
369
- req.write(body);
370
- req.end();
371
- });
412
+ return null;
413
+ }
414
+ invokeOnError(error, batch) {
415
+ if (this.config.onError) {
416
+ try {
417
+ this.config.onError(error, batch);
418
+ } catch (e) {
419
+ this.config.logger.error("Error in onError callback:", e);
420
+ }
372
421
  }
373
- scheduleFlush() {
374
- if (this.isShutdown || this.flushTimer) {
375
- return;
376
- }
377
- this.flushTimer = setTimeout(() => {
378
- this.flush();
379
- }, this.config.flushInterval);
422
+ }
423
+ invokeOnSuccess(response) {
424
+ if (this.config.onSuccess) {
425
+ try {
426
+ this.config.onSuccess(response);
427
+ } catch (e) {
428
+ this.config.logger.error("Error in onSuccess callback:", e);
429
+ }
380
430
  }
381
- generateUUID() {
382
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
383
- return crypto.randomUUID();
431
+ }
432
+ makeRequest(client, hostname, port, path, body) {
433
+ return new Promise((resolve, reject) => {
434
+ const options = {
435
+ hostname,
436
+ port,
437
+ path,
438
+ method: "POST",
439
+ headers: {
440
+ "Content-Type": "application/json",
441
+ Authorization: `Bearer ${this.config.writeKey}`,
442
+ "Content-Length": Buffer.byteLength(body)
384
443
  }
385
- // Fallback for Node < 15
386
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
387
- const r = (Math.random() * 16) | 0;
388
- const v = c === "x" ? r : (r & 0x3) | 0x8;
389
- return v.toString(16);
444
+ };
445
+ const req = client.request(options, (res) => {
446
+ let data = "";
447
+ res.on("data", (chunk) => {
448
+ data += chunk;
449
+ });
450
+ res.on("end", () => {
451
+ resolve({
452
+ statusCode: res.statusCode || 500,
453
+ body: data,
454
+ headers: res.headers
455
+ });
390
456
  });
457
+ });
458
+ req.on("error", (error) => {
459
+ reject(error);
460
+ });
461
+ req.write(body);
462
+ req.end();
463
+ });
464
+ }
465
+ scheduleFlush() {
466
+ if (this.isShutdown || this.flushTimer) {
467
+ return;
391
468
  }
392
- generateTimestamp() {
393
- return new Date().toISOString();
394
- }
395
- getContext(ip) {
396
- const context = {
397
- library: {
398
- name: "node-sdk",
399
- version: SDK_VERSION,
400
- },
401
- };
402
- if (ip) {
403
- context.ip = ip;
404
- }
405
- return context;
469
+ this.flushTimer = setTimeout(() => {
470
+ this.flush();
471
+ }, this.config.flushInterval);
472
+ }
473
+ generateUUID() {
474
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
475
+ return crypto.randomUUID();
406
476
  }
407
- estimateEventSize(event) {
408
- try {
409
- return JSON.stringify(event).length;
410
- }
411
- catch {
412
- return 500;
413
- }
414
- }
415
- sleep(ms) {
416
- return new Promise((resolve) => setTimeout(resolve, ms));
477
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
478
+ const r = Math.random() * 16 | 0;
479
+ const v = c === "x" ? r : r & 3 | 8;
480
+ return v.toString(16);
481
+ });
482
+ }
483
+ generateTimestamp() {
484
+ return (/* @__PURE__ */ new Date()).toISOString();
485
+ }
486
+ getContext() {
487
+ return {
488
+ library: {
489
+ name: "node-sdk",
490
+ version: SDK_VERSION
491
+ }
492
+ };
493
+ }
494
+ estimateEventSize(event) {
495
+ try {
496
+ return JSON.stringify(event).length;
497
+ } catch {
498
+ return 500;
417
499
  }
418
- }
419
- exports.KlimeClient = KlimeClient;
500
+ }
501
+ sleep(ms) {
502
+ return new Promise((resolve) => setTimeout(resolve, ms));
503
+ }
504
+ };
505
+ export {
506
+ KlimeClient,
507
+ SendError
508
+ };
509
+ //# sourceMappingURL=index.js.map