@klime/browser 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/README.md +138 -3
- package/dist/index.cjs +459 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +106 -0
- package/dist/index.d.ts +83 -2
- package/dist/index.js +412 -277
- package/dist/index.js.map +1 -0
- package/package.json +17 -3
- package/dist/types.d.ts +0 -49
- package/dist/types.js +0 -1
package/dist/index.js
CHANGED
|
@@ -1,296 +1,431 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var SendError = class extends Error {
|
|
3
|
+
constructor(message, events) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "SendError";
|
|
6
|
+
this.events = events;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
var DEFAULT_ENDPOINT = "https://i.klime.com";
|
|
12
|
+
var DEFAULT_FLUSH_INTERVAL = 2e3;
|
|
13
|
+
var DEFAULT_MAX_BATCH_SIZE = 20;
|
|
14
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e3;
|
|
15
|
+
var DEFAULT_RETRY_MAX_ATTEMPTS = 5;
|
|
16
|
+
var DEFAULT_RETRY_INITIAL_DELAY = 1e3;
|
|
17
|
+
var MAX_BATCH_SIZE = 100;
|
|
18
|
+
var MAX_EVENT_SIZE_BYTES = 200 * 1024;
|
|
19
|
+
var MAX_BATCH_SIZE_BYTES = 10 * 1024 * 1024;
|
|
20
|
+
var KEEPALIVE_MAX_BODY_BYTES = 64 * 1024;
|
|
21
|
+
var SDK_VERSION = "1.1.0";
|
|
22
|
+
var createDefaultLogger = () => ({
|
|
23
|
+
debug: (message, ...args) => console.debug(`[Klime] ${message}`, ...args),
|
|
24
|
+
info: (message, ...args) => console.info(`[Klime] ${message}`, ...args),
|
|
25
|
+
warn: (message, ...args) => console.warn(`[Klime] ${message}`, ...args),
|
|
26
|
+
error: (message, ...args) => console.error(`[Klime] ${message}`, ...args)
|
|
27
|
+
});
|
|
28
|
+
var KlimeClient = class {
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.queue = [];
|
|
31
|
+
this.flushTimer = null;
|
|
32
|
+
this.isShutdown = false;
|
|
33
|
+
this.flushPromise = null;
|
|
34
|
+
this.unloadHandler = null;
|
|
35
|
+
this.visibilityChangeHandler = null;
|
|
36
|
+
if (!config.writeKey) {
|
|
37
|
+
throw new Error("writeKey is required");
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
this.config = {
|
|
40
|
+
writeKey: config.writeKey,
|
|
41
|
+
endpoint: config.endpoint || DEFAULT_ENDPOINT,
|
|
42
|
+
flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
43
|
+
maxBatchSize: Math.min(
|
|
44
|
+
config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
|
|
45
|
+
MAX_BATCH_SIZE
|
|
46
|
+
),
|
|
47
|
+
maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
48
|
+
retryMaxAttempts: config.retryMaxAttempts ?? DEFAULT_RETRY_MAX_ATTEMPTS,
|
|
49
|
+
retryInitialDelay: config.retryInitialDelay ?? DEFAULT_RETRY_INITIAL_DELAY,
|
|
50
|
+
autoFlushOnUnload: config.autoFlushOnUnload ?? true,
|
|
51
|
+
logger: config.logger ?? createDefaultLogger(),
|
|
52
|
+
onError: config.onError,
|
|
53
|
+
onSuccess: config.onSuccess
|
|
54
|
+
};
|
|
55
|
+
if (this.config.autoFlushOnUnload && typeof window !== "undefined") {
|
|
56
|
+
this.unloadHandler = () => {
|
|
57
|
+
this.flush();
|
|
58
|
+
};
|
|
59
|
+
window.addEventListener("beforeunload", this.unloadHandler);
|
|
60
|
+
window.addEventListener("pagehide", this.unloadHandler);
|
|
61
|
+
this.visibilityChangeHandler = () => {
|
|
62
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
63
|
+
this.flush();
|
|
56
64
|
}
|
|
57
|
-
|
|
65
|
+
};
|
|
66
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
58
67
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
type: "identify",
|
|
65
|
-
messageId: this.generateUUID(),
|
|
66
|
-
userId,
|
|
67
|
-
timestamp: this.generateTimestamp(),
|
|
68
|
-
traits: traits || {},
|
|
69
|
-
context: this.getContext(),
|
|
70
|
-
};
|
|
71
|
-
this.enqueue(eventObj);
|
|
72
|
-
}
|
|
73
|
-
group(groupId, traits, options) {
|
|
74
|
-
if (this.isShutdown) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const eventObj = {
|
|
78
|
-
type: "group",
|
|
79
|
-
messageId: this.generateUUID(),
|
|
80
|
-
groupId,
|
|
81
|
-
timestamp: this.generateTimestamp(),
|
|
82
|
-
traits: traits || {},
|
|
83
|
-
context: this.getContext(),
|
|
84
|
-
};
|
|
85
|
-
if (options?.userId) {
|
|
86
|
-
eventObj.userId = options.userId;
|
|
87
|
-
}
|
|
88
|
-
this.enqueue(eventObj);
|
|
68
|
+
this.scheduleFlush();
|
|
69
|
+
}
|
|
70
|
+
track(event, properties, options) {
|
|
71
|
+
if (this.isShutdown) {
|
|
72
|
+
return;
|
|
89
73
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
74
|
+
const eventObj = {
|
|
75
|
+
type: "track",
|
|
76
|
+
messageId: this.generateUUID(),
|
|
77
|
+
event,
|
|
78
|
+
timestamp: this.generateTimestamp(),
|
|
79
|
+
properties: properties || {},
|
|
80
|
+
context: this.getContext()
|
|
81
|
+
};
|
|
82
|
+
if (options?.userId) {
|
|
83
|
+
eventObj.userId = options.userId;
|
|
101
84
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
85
|
+
if (options?.groupId) {
|
|
86
|
+
eventObj.groupId = options.groupId;
|
|
87
|
+
}
|
|
88
|
+
this.enqueue(eventObj);
|
|
89
|
+
}
|
|
90
|
+
identify(userId, traits) {
|
|
91
|
+
if (this.isShutdown) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const eventObj = {
|
|
95
|
+
type: "identify",
|
|
96
|
+
messageId: this.generateUUID(),
|
|
97
|
+
userId,
|
|
98
|
+
timestamp: this.generateTimestamp(),
|
|
99
|
+
traits: traits || {},
|
|
100
|
+
context: this.getContext()
|
|
101
|
+
};
|
|
102
|
+
this.enqueue(eventObj);
|
|
103
|
+
}
|
|
104
|
+
group(groupId, traits, options) {
|
|
105
|
+
if (this.isShutdown) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const eventObj = {
|
|
109
|
+
type: "group",
|
|
110
|
+
messageId: this.generateUUID(),
|
|
111
|
+
groupId,
|
|
112
|
+
timestamp: this.generateTimestamp(),
|
|
113
|
+
traits: traits || {},
|
|
114
|
+
context: this.getContext()
|
|
115
|
+
};
|
|
116
|
+
if (options?.userId) {
|
|
117
|
+
eventObj.userId = options.userId;
|
|
118
|
+
}
|
|
119
|
+
this.enqueue(eventObj);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Track an event synchronously. Returns BatchResponse or throws SendError.
|
|
123
|
+
*/
|
|
124
|
+
async trackSync(event, properties, options) {
|
|
125
|
+
if (this.isShutdown) {
|
|
126
|
+
throw new SendError("Client is shutdown", []);
|
|
127
|
+
}
|
|
128
|
+
const eventObj = {
|
|
129
|
+
type: "track",
|
|
130
|
+
messageId: this.generateUUID(),
|
|
131
|
+
event,
|
|
132
|
+
timestamp: this.generateTimestamp(),
|
|
133
|
+
properties: properties || {},
|
|
134
|
+
context: this.getContext()
|
|
135
|
+
};
|
|
136
|
+
if (options?.userId) {
|
|
137
|
+
eventObj.userId = options.userId;
|
|
138
|
+
}
|
|
139
|
+
if (options?.groupId) {
|
|
140
|
+
eventObj.groupId = options.groupId;
|
|
141
|
+
}
|
|
142
|
+
return this.sendSync([eventObj]);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Identify a user synchronously. Returns BatchResponse or throws SendError.
|
|
146
|
+
*/
|
|
147
|
+
async identifySync(userId, traits) {
|
|
148
|
+
if (this.isShutdown) {
|
|
149
|
+
throw new SendError("Client is shutdown", []);
|
|
150
|
+
}
|
|
151
|
+
const eventObj = {
|
|
152
|
+
type: "identify",
|
|
153
|
+
messageId: this.generateUUID(),
|
|
154
|
+
userId,
|
|
155
|
+
timestamp: this.generateTimestamp(),
|
|
156
|
+
traits: traits || {},
|
|
157
|
+
context: this.getContext()
|
|
158
|
+
};
|
|
159
|
+
return this.sendSync([eventObj]);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Associate a user with a group synchronously. Returns BatchResponse or throws SendError.
|
|
163
|
+
*/
|
|
164
|
+
async groupSync(groupId, traits, options) {
|
|
165
|
+
if (this.isShutdown) {
|
|
166
|
+
throw new SendError("Client is shutdown", []);
|
|
167
|
+
}
|
|
168
|
+
const eventObj = {
|
|
169
|
+
type: "group",
|
|
170
|
+
messageId: this.generateUUID(),
|
|
171
|
+
groupId,
|
|
172
|
+
timestamp: this.generateTimestamp(),
|
|
173
|
+
traits: traits || {},
|
|
174
|
+
context: this.getContext()
|
|
175
|
+
};
|
|
176
|
+
if (options?.userId) {
|
|
177
|
+
eventObj.userId = options.userId;
|
|
178
|
+
}
|
|
179
|
+
return this.sendSync([eventObj]);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Return the number of events currently in the queue.
|
|
183
|
+
*/
|
|
184
|
+
getQueueSize() {
|
|
185
|
+
return this.queue.length;
|
|
186
|
+
}
|
|
187
|
+
async flush() {
|
|
188
|
+
if (this.flushPromise) {
|
|
189
|
+
return this.flushPromise;
|
|
190
|
+
}
|
|
191
|
+
this.flushPromise = this.doFlush();
|
|
192
|
+
try {
|
|
193
|
+
await this.flushPromise;
|
|
194
|
+
} finally {
|
|
195
|
+
this.flushPromise = null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async shutdown() {
|
|
199
|
+
if (this.isShutdown) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this.isShutdown = true;
|
|
203
|
+
if (this.unloadHandler && typeof window !== "undefined") {
|
|
204
|
+
window.removeEventListener("beforeunload", this.unloadHandler);
|
|
205
|
+
window.removeEventListener("pagehide", this.unloadHandler);
|
|
206
|
+
}
|
|
207
|
+
if (this.visibilityChangeHandler && typeof document !== "undefined") {
|
|
208
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
209
|
+
}
|
|
210
|
+
if (this.flushTimer) {
|
|
211
|
+
clearTimeout(this.flushTimer);
|
|
212
|
+
this.flushTimer = null;
|
|
213
|
+
}
|
|
214
|
+
await this.flush();
|
|
215
|
+
}
|
|
216
|
+
enqueue(event) {
|
|
217
|
+
const eventSize = this.estimateEventSize(event);
|
|
218
|
+
if (eventSize > MAX_EVENT_SIZE_BYTES) {
|
|
219
|
+
this.config.logger.warn(
|
|
220
|
+
`Event size (${eventSize} bytes) exceeds ${MAX_EVENT_SIZE_BYTES} bytes limit`
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (this.queue.length >= this.config.maxQueueSize) {
|
|
225
|
+
const dropped = this.queue.shift();
|
|
226
|
+
this.config.logger.warn(`Queue full, dropping oldest event: ${dropped?.type}`);
|
|
227
|
+
}
|
|
228
|
+
this.queue.push(event);
|
|
229
|
+
this.config.logger.debug(`Enqueued ${event.type} event, queue size: ${this.queue.length}`);
|
|
230
|
+
if (this.queue.length >= this.config.maxBatchSize) {
|
|
231
|
+
this.flush();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async doFlush() {
|
|
235
|
+
if (this.queue.length === 0) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (this.flushTimer) {
|
|
239
|
+
clearTimeout(this.flushTimer);
|
|
240
|
+
this.flushTimer = null;
|
|
241
|
+
}
|
|
242
|
+
while (this.queue.length > 0) {
|
|
243
|
+
const batch = this.extractBatch();
|
|
244
|
+
if (batch.length === 0) {
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
await this.sendBatch(batch);
|
|
248
|
+
}
|
|
249
|
+
this.scheduleFlush();
|
|
250
|
+
}
|
|
251
|
+
extractBatch() {
|
|
252
|
+
const batch = [];
|
|
253
|
+
let batchSize = 0;
|
|
254
|
+
while (this.queue.length > 0 && batch.length < MAX_BATCH_SIZE) {
|
|
255
|
+
const event = this.queue[0];
|
|
256
|
+
const eventSize = this.estimateEventSize(event);
|
|
257
|
+
if (batchSize + eventSize > MAX_BATCH_SIZE_BYTES) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
batch.push(this.queue.shift());
|
|
261
|
+
batchSize += eventSize;
|
|
262
|
+
}
|
|
263
|
+
return batch;
|
|
264
|
+
}
|
|
265
|
+
async sendBatch(batch) {
|
|
266
|
+
if (batch.length === 0) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.config.logger.debug(`Sending batch of ${batch.length} events`);
|
|
270
|
+
const result = await this.doSend(batch);
|
|
271
|
+
if (result === null) {
|
|
272
|
+
const error = new Error(`Failed to send batch of ${batch.length} events after retries`);
|
|
273
|
+
this.invokeOnError(error, batch);
|
|
274
|
+
} else if (result.failed > 0 && result.errors) {
|
|
275
|
+
this.config.logger.warn(
|
|
276
|
+
`Batch partially failed. Accepted: ${result.accepted}, Failed: ${result.failed}`,
|
|
277
|
+
result.errors
|
|
278
|
+
);
|
|
279
|
+
this.invokeOnSuccess(result);
|
|
280
|
+
} else {
|
|
281
|
+
this.config.logger.debug(`Batch sent successfully. Accepted: ${result.accepted}`);
|
|
282
|
+
this.invokeOnSuccess(result);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async sendSync(events) {
|
|
286
|
+
this.config.logger.debug(`Sending ${events.length} events synchronously`);
|
|
287
|
+
const result = await this.doSend(events);
|
|
288
|
+
if (result === null) {
|
|
289
|
+
throw new SendError(`Failed to send ${events.length} events after retries`, events);
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
async doSend(batch) {
|
|
294
|
+
const request = { batch };
|
|
295
|
+
const requestUrl = `${this.config.endpoint}/v1/batch`;
|
|
296
|
+
const body = JSON.stringify(request);
|
|
297
|
+
const useKeepalive = body.length <= KEEPALIVE_MAX_BODY_BYTES;
|
|
298
|
+
let attempt = 0;
|
|
299
|
+
let delay = this.config.retryInitialDelay;
|
|
300
|
+
while (attempt < this.config.retryMaxAttempts) {
|
|
301
|
+
try {
|
|
302
|
+
const response = await fetch(requestUrl, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers: {
|
|
305
|
+
"Content-Type": "application/json",
|
|
306
|
+
Authorization: `Bearer ${this.config.writeKey}`
|
|
307
|
+
},
|
|
308
|
+
body,
|
|
309
|
+
keepalive: useKeepalive
|
|
310
|
+
});
|
|
311
|
+
const data = await response.json();
|
|
312
|
+
if (response.ok) {
|
|
313
|
+
return data;
|
|
109
314
|
}
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
315
|
+
if (response.status === 400 || response.status === 401) {
|
|
316
|
+
this.config.logger.error(`Permanent error (${response.status}):`, data);
|
|
317
|
+
return null;
|
|
113
318
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
319
|
+
if (response.status === 429 || response.status === 503) {
|
|
320
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
321
|
+
if (retryAfter) {
|
|
322
|
+
delay = parseInt(retryAfter, 10) * 1e3;
|
|
323
|
+
}
|
|
324
|
+
attempt++;
|
|
325
|
+
if (attempt < this.config.retryMaxAttempts) {
|
|
326
|
+
this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
|
|
327
|
+
await this.sleep(delay);
|
|
328
|
+
delay = Math.min(delay * 2, 16e3);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
122
331
|
}
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
|
|
332
|
+
attempt++;
|
|
333
|
+
if (attempt < this.config.retryMaxAttempts) {
|
|
334
|
+
this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
|
|
335
|
+
await this.sleep(delay);
|
|
336
|
+
delay = Math.min(delay * 2, 16e3);
|
|
126
337
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
338
|
+
} catch (error) {
|
|
339
|
+
attempt++;
|
|
340
|
+
if (attempt < this.config.retryMaxAttempts) {
|
|
341
|
+
this.config.logger.debug(`Retrying after ${delay}ms (attempt ${attempt})`);
|
|
342
|
+
await this.sleep(delay);
|
|
343
|
+
delay = Math.min(delay * 2, 16e3);
|
|
344
|
+
} else {
|
|
345
|
+
this.config.logger.error("Failed to send batch after retries:", error);
|
|
131
346
|
}
|
|
347
|
+
}
|
|
132
348
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// Process batches
|
|
143
|
-
while (this.queue.length > 0) {
|
|
144
|
-
const batch = this.extractBatch();
|
|
145
|
-
if (batch.length === 0) {
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
await this.sendBatch(batch);
|
|
149
|
-
}
|
|
150
|
-
// Schedule next flush
|
|
151
|
-
this.scheduleFlush();
|
|
152
|
-
}
|
|
153
|
-
extractBatch() {
|
|
154
|
-
const batch = [];
|
|
155
|
-
let batchSize = 0;
|
|
156
|
-
while (this.queue.length > 0 && batch.length < MAX_BATCH_SIZE) {
|
|
157
|
-
const event = this.queue[0];
|
|
158
|
-
const eventSize = this.estimateEventSize(event);
|
|
159
|
-
// Check if adding this event would exceed batch size limit
|
|
160
|
-
if (batchSize + eventSize > MAX_BATCH_SIZE_BYTES) {
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
batch.push(this.queue.shift());
|
|
164
|
-
batchSize += eventSize;
|
|
165
|
-
}
|
|
166
|
-
return batch;
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
invokeOnError(error, batch) {
|
|
352
|
+
if (this.config.onError) {
|
|
353
|
+
try {
|
|
354
|
+
this.config.onError(error, batch);
|
|
355
|
+
} catch (e) {
|
|
356
|
+
this.config.logger.error("Error in onError callback:", e);
|
|
357
|
+
}
|
|
167
358
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
while (attempt < this.config.retryMaxAttempts) {
|
|
177
|
-
try {
|
|
178
|
-
const response = await fetch(url, {
|
|
179
|
-
method: "POST",
|
|
180
|
-
headers: {
|
|
181
|
-
"Content-Type": "application/json",
|
|
182
|
-
Authorization: `Bearer ${this.config.writeKey}`,
|
|
183
|
-
},
|
|
184
|
-
body: JSON.stringify(request),
|
|
185
|
-
});
|
|
186
|
-
const data = await response.json();
|
|
187
|
-
if (response.ok) {
|
|
188
|
-
// Success - check for partial failures
|
|
189
|
-
if (data.failed > 0 && data.errors) {
|
|
190
|
-
console.warn(`Klime: Batch partially failed. Accepted: ${data.accepted}, Failed: ${data.failed}`, data.errors);
|
|
191
|
-
}
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
// Handle error responses
|
|
195
|
-
if (response.status === 400 || response.status === 401) {
|
|
196
|
-
// Permanent errors - don't retry
|
|
197
|
-
console.error(`Klime: Permanent error (${response.status}):`, data);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
// Transient errors - retry with backoff
|
|
201
|
-
if (response.status === 429 || response.status === 503) {
|
|
202
|
-
const retryAfter = response.headers.get("Retry-After");
|
|
203
|
-
if (retryAfter) {
|
|
204
|
-
delay = parseInt(retryAfter, 10) * 1000;
|
|
205
|
-
}
|
|
206
|
-
attempt++;
|
|
207
|
-
if (attempt < this.config.retryMaxAttempts) {
|
|
208
|
-
await this.sleep(delay);
|
|
209
|
-
delay = Math.min(delay * 2, 16000); // Cap at 16s
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// Other errors - retry
|
|
214
|
-
attempt++;
|
|
215
|
-
if (attempt < this.config.retryMaxAttempts) {
|
|
216
|
-
await this.sleep(delay);
|
|
217
|
-
delay = Math.min(delay * 2, 16000);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
catch (error) {
|
|
221
|
-
// Network errors - retry
|
|
222
|
-
attempt++;
|
|
223
|
-
if (attempt < this.config.retryMaxAttempts) {
|
|
224
|
-
await this.sleep(delay);
|
|
225
|
-
delay = Math.min(delay * 2, 16000);
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
console.error("Klime: Failed to send batch after retries:", error);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
359
|
+
}
|
|
360
|
+
invokeOnSuccess(response) {
|
|
361
|
+
if (this.config.onSuccess) {
|
|
362
|
+
try {
|
|
363
|
+
this.config.onSuccess(response);
|
|
364
|
+
} catch (e) {
|
|
365
|
+
this.config.logger.error("Error in onSuccess callback:", e);
|
|
366
|
+
}
|
|
232
367
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
this.flushTimer = setTimeout(() => {
|
|
238
|
-
this.flush();
|
|
239
|
-
}, this.config.flushInterval);
|
|
368
|
+
}
|
|
369
|
+
scheduleFlush() {
|
|
370
|
+
if (this.isShutdown || this.flushTimer) {
|
|
371
|
+
return;
|
|
240
372
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
249
|
-
return v.toString(16);
|
|
250
|
-
});
|
|
373
|
+
this.flushTimer = setTimeout(() => {
|
|
374
|
+
this.flush();
|
|
375
|
+
}, this.config.flushInterval);
|
|
376
|
+
}
|
|
377
|
+
generateUUID() {
|
|
378
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
379
|
+
return crypto.randomUUID();
|
|
251
380
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch (e) {
|
|
278
|
-
// Ignore timezone errors
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return context;
|
|
381
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
382
|
+
const r = Math.random() * 16 | 0;
|
|
383
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
384
|
+
return v.toString(16);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
generateTimestamp() {
|
|
388
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
389
|
+
}
|
|
390
|
+
getContext() {
|
|
391
|
+
const context = {
|
|
392
|
+
library: {
|
|
393
|
+
name: "js-sdk",
|
|
394
|
+
version: SDK_VERSION
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
if (typeof navigator !== "undefined") {
|
|
398
|
+
if (navigator.userAgent) {
|
|
399
|
+
context.userAgent = navigator.userAgent;
|
|
400
|
+
}
|
|
401
|
+
if (navigator.language) {
|
|
402
|
+
context.locale = navigator.language;
|
|
403
|
+
}
|
|
282
404
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
catch {
|
|
289
|
-
// Fallback: rough estimate based on structure
|
|
290
|
-
return 500; // Conservative estimate
|
|
405
|
+
if (typeof Intl !== "undefined" && Intl.DateTimeFormat) {
|
|
406
|
+
try {
|
|
407
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
408
|
+
if (timezone) {
|
|
409
|
+
context.timezone = timezone;
|
|
291
410
|
}
|
|
411
|
+
} catch (e) {
|
|
412
|
+
}
|
|
292
413
|
}
|
|
293
|
-
|
|
294
|
-
|
|
414
|
+
return context;
|
|
415
|
+
}
|
|
416
|
+
estimateEventSize(event) {
|
|
417
|
+
try {
|
|
418
|
+
return JSON.stringify(event).length;
|
|
419
|
+
} catch {
|
|
420
|
+
return 500;
|
|
295
421
|
}
|
|
296
|
-
}
|
|
422
|
+
}
|
|
423
|
+
sleep(ms) {
|
|
424
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
export {
|
|
428
|
+
KlimeClient,
|
|
429
|
+
SendError
|
|
430
|
+
};
|
|
431
|
+
//# sourceMappingURL=index.js.map
|