@atsignal/js-core 0.1.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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/index.cjs +1995 -0
- package/dist/index.d.cts +393 -0
- package/dist/index.d.ts +393 -0
- package/dist/index.js +1965 -0
- package/package.json +39 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1995 @@
|
|
|
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
|
+
HookRegistry: () => HookRegistry,
|
|
24
|
+
InMemoryStorage: () => InMemoryStorage,
|
|
25
|
+
SignalClient: () => SignalClient,
|
|
26
|
+
createFetchTransport: () => createFetchTransport
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/types.ts
|
|
31
|
+
var EVENT_TIME_KEY = "@time";
|
|
32
|
+
var EVENT_INSERT_ID_KEY = "@insertid";
|
|
33
|
+
var EVENT_DEVICE_ID_KEY = "@deviceid";
|
|
34
|
+
var EVENT_USER_ID_KEY = "@userid";
|
|
35
|
+
var EVENT_TYPE_KEY = "@event";
|
|
36
|
+
var EVENT_VERSION_KEY = "@version";
|
|
37
|
+
|
|
38
|
+
// src/storage.ts
|
|
39
|
+
var InMemoryStorage = class {
|
|
40
|
+
constructor() {
|
|
41
|
+
this._map = /* @__PURE__ */ new Map();
|
|
42
|
+
}
|
|
43
|
+
get(key) {
|
|
44
|
+
if (!this._map.has(key)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return this._map.get(key);
|
|
48
|
+
}
|
|
49
|
+
set(key, value) {
|
|
50
|
+
this._map.set(key, value);
|
|
51
|
+
}
|
|
52
|
+
remove(key) {
|
|
53
|
+
this._map.delete(key);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function isRecord(value) {
|
|
57
|
+
return Boolean(value) && typeof value === "object";
|
|
58
|
+
}
|
|
59
|
+
function toResolvedTrackOptions(value) {
|
|
60
|
+
if (!isRecord(value)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const flush = value.flush;
|
|
64
|
+
const endpoint = value.endpoint;
|
|
65
|
+
const retries = value.retries;
|
|
66
|
+
const timeoutMs = value.timeoutMs;
|
|
67
|
+
if (flush !== "scheduled" && flush !== "immediate" || typeof endpoint !== "string") {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (typeof retries !== "number" || !Number.isFinite(retries) || retries < 0) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const normalizedRetries = Math.floor(retries);
|
|
74
|
+
if (!Number.isFinite(normalizedRetries)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const options = {
|
|
78
|
+
flush,
|
|
79
|
+
endpoint,
|
|
80
|
+
retries: normalizedRetries
|
|
81
|
+
};
|
|
82
|
+
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
|
|
83
|
+
options.timeoutMs = timeoutMs;
|
|
84
|
+
}
|
|
85
|
+
return options;
|
|
86
|
+
}
|
|
87
|
+
function toPersistedQueueEntry(value) {
|
|
88
|
+
if (!isRecord(value)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const event = value.event;
|
|
92
|
+
const options = value.options;
|
|
93
|
+
const attempts = value.attempts;
|
|
94
|
+
const id = value.id;
|
|
95
|
+
const resolvedOptions = toResolvedTrackOptions(options);
|
|
96
|
+
if (!isRecord(event) || !resolvedOptions || typeof attempts !== "number") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const normalizedId = typeof id === "string" && id.length > 0 ? id : typeof event[EVENT_INSERT_ID_KEY] === "string" && event[EVENT_INSERT_ID_KEY].length > 0 ? event[EVENT_INSERT_ID_KEY] : null;
|
|
100
|
+
if (!normalizedId) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
id: normalizedId,
|
|
105
|
+
deviceId: typeof value.deviceId === "string" ? value.deviceId : "",
|
|
106
|
+
event,
|
|
107
|
+
options: resolvedOptions,
|
|
108
|
+
attempts
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function normalizePersistedQueueEntries(value) {
|
|
112
|
+
if (!Array.isArray(value)) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
return value.map((entry) => toPersistedQueueEntry(entry)).filter((entry) => Boolean(entry));
|
|
116
|
+
}
|
|
117
|
+
var SnapshotQueueStorage = class {
|
|
118
|
+
constructor(storage, storageKey) {
|
|
119
|
+
this._storage = storage;
|
|
120
|
+
this._storageKey = storageKey;
|
|
121
|
+
}
|
|
122
|
+
async load(limit) {
|
|
123
|
+
const normalized = await this._readAll();
|
|
124
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
return normalized.slice(-Math.floor(limit));
|
|
128
|
+
}
|
|
129
|
+
async upsert(entries) {
|
|
130
|
+
if (entries.length === 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const stored = await this._readAll();
|
|
134
|
+
const storedIndexById = /* @__PURE__ */ new Map();
|
|
135
|
+
for (const [index, entry] of stored.entries()) {
|
|
136
|
+
storedIndexById.set(entry.id, index);
|
|
137
|
+
}
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const existingIndex = storedIndexById.get(entry.id);
|
|
140
|
+
if (existingIndex !== void 0) {
|
|
141
|
+
stored[existingIndex] = entry;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
storedIndexById.set(entry.id, stored.length);
|
|
145
|
+
stored.push(entry);
|
|
146
|
+
}
|
|
147
|
+
await this._storage.set(this._storageKey, stored);
|
|
148
|
+
}
|
|
149
|
+
async remove(ids) {
|
|
150
|
+
if (ids.length === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const idSet = new Set(ids);
|
|
154
|
+
const stored = await this._readAll();
|
|
155
|
+
let changed = false;
|
|
156
|
+
const filtered = [];
|
|
157
|
+
for (const entry of stored) {
|
|
158
|
+
if (idSet.has(entry.id)) {
|
|
159
|
+
changed = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
filtered.push(entry);
|
|
163
|
+
}
|
|
164
|
+
if (!changed) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
await this._storage.set(this._storageKey, filtered);
|
|
168
|
+
}
|
|
169
|
+
async trim(maxEntries) {
|
|
170
|
+
if (!Number.isFinite(maxEntries) || maxEntries < 0) {
|
|
171
|
+
await this.clear();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const max = Math.floor(maxEntries);
|
|
175
|
+
const stored = await this._readAll();
|
|
176
|
+
if (stored.length <= max) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
await this._storage.set(this._storageKey, stored.slice(-max));
|
|
180
|
+
}
|
|
181
|
+
async clear() {
|
|
182
|
+
await this._storage.remove(this._storageKey);
|
|
183
|
+
}
|
|
184
|
+
async _readAll() {
|
|
185
|
+
const stored = await this._storage.get(this._storageKey);
|
|
186
|
+
return normalizePersistedQueueEntries(stored);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/timers.ts
|
|
191
|
+
function resolveTimerApi() {
|
|
192
|
+
const maybeSetTimeout = globalThis.setTimeout;
|
|
193
|
+
const maybeClearTimeout = globalThis.clearTimeout;
|
|
194
|
+
if (typeof maybeSetTimeout !== "function" || typeof maybeClearTimeout !== "function") {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
setTimeout: maybeSetTimeout,
|
|
199
|
+
clearTimeout: maybeClearTimeout
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function scheduleTask(callback, delayMs) {
|
|
203
|
+
const timers = resolveTimerApi();
|
|
204
|
+
if (!timers) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const handle = timers.setTimeout(callback, delayMs);
|
|
208
|
+
return {
|
|
209
|
+
cancel: () => {
|
|
210
|
+
timers.clearTimeout(handle);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function createTimeoutController(timeoutMs, createTimeoutValue) {
|
|
215
|
+
if (typeof timeoutMs !== "number" || timeoutMs <= 0) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const timers = resolveTimerApi();
|
|
219
|
+
if (!timers) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
let handle;
|
|
223
|
+
const timeout = new Promise((resolve) => {
|
|
224
|
+
handle = timers.setTimeout(() => resolve(createTimeoutValue()), timeoutMs);
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
timeout,
|
|
228
|
+
clear() {
|
|
229
|
+
timers.clearTimeout(handle);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/transport.ts
|
|
235
|
+
function resolveGlobalFetch() {
|
|
236
|
+
const candidate = globalThis.fetch;
|
|
237
|
+
if (typeof candidate !== "function") {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return candidate;
|
|
241
|
+
}
|
|
242
|
+
function encodeRequestBody(request) {
|
|
243
|
+
const parts = [
|
|
244
|
+
`${encodeURIComponent("apikey")}=${encodeURIComponent(request.apiKey)}`,
|
|
245
|
+
`${encodeURIComponent("txtime")}=${encodeURIComponent(String(request.txTime))}`
|
|
246
|
+
];
|
|
247
|
+
const locationFromIpEnabled = request.locationFromIp;
|
|
248
|
+
if (!locationFromIpEnabled) {
|
|
249
|
+
parts.push(`${encodeURIComponent("ip")}=${encodeURIComponent("false")}`);
|
|
250
|
+
}
|
|
251
|
+
for (const group of request.eventGroups) {
|
|
252
|
+
const entries = group.events.map((event, index) => ({ event, index }));
|
|
253
|
+
entries.sort((left, right) => {
|
|
254
|
+
var _a, _b;
|
|
255
|
+
const timeDelta = Number((_a = left.event[EVENT_TIME_KEY]) != null ? _a : 0) - Number((_b = right.event[EVENT_TIME_KEY]) != null ? _b : 0);
|
|
256
|
+
return timeDelta !== 0 ? timeDelta : left.index - right.index;
|
|
257
|
+
});
|
|
258
|
+
parts.push(
|
|
259
|
+
`${encodeURIComponent(`@${group.deviceId}`)}=${encodeURIComponent(
|
|
260
|
+
entries.map(({ event }) => JSON.stringify(event)).join("\n")
|
|
261
|
+
)}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return parts.join("&");
|
|
265
|
+
}
|
|
266
|
+
var FetchTransport = class {
|
|
267
|
+
constructor(options = {}) {
|
|
268
|
+
var _a, _b, _c;
|
|
269
|
+
const fetchImpl = (_a = options.fetch) != null ? _a : resolveGlobalFetch();
|
|
270
|
+
if (!fetchImpl) {
|
|
271
|
+
throw new Error("No fetch implementation found for FetchTransport");
|
|
272
|
+
}
|
|
273
|
+
this._fetch = fetchImpl;
|
|
274
|
+
this._keepalive = (_b = options.keepalive) != null ? _b : false;
|
|
275
|
+
this._headers = (_c = options.headers) != null ? _c : {};
|
|
276
|
+
}
|
|
277
|
+
async send(request) {
|
|
278
|
+
const timer = createTimeoutController(request.timeoutMs, () => ({
|
|
279
|
+
ok: false,
|
|
280
|
+
retryable: true,
|
|
281
|
+
status: 0,
|
|
282
|
+
error: new Error("Request timed out")
|
|
283
|
+
}));
|
|
284
|
+
const requestPromise = this._fetch(request.endpoint, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
headers: {
|
|
287
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
288
|
+
...this._headers
|
|
289
|
+
},
|
|
290
|
+
body: encodeRequestBody(request),
|
|
291
|
+
keepalive: this._keepalive
|
|
292
|
+
}).then((response) => {
|
|
293
|
+
const status = response.status;
|
|
294
|
+
if (response.ok) {
|
|
295
|
+
return { ok: true, status };
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
status,
|
|
300
|
+
retryable: status >= 500 || status === 429
|
|
301
|
+
};
|
|
302
|
+
}).catch((error) => {
|
|
303
|
+
return {
|
|
304
|
+
ok: false,
|
|
305
|
+
status: 0,
|
|
306
|
+
retryable: true,
|
|
307
|
+
error
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
if (!timer) {
|
|
311
|
+
return requestPromise;
|
|
312
|
+
}
|
|
313
|
+
const result = await Promise.race([requestPromise, timer.timeout]);
|
|
314
|
+
timer.clear();
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
function createFetchTransport(options = {}) {
|
|
319
|
+
return new FetchTransport(options);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/client-config.ts
|
|
323
|
+
var DEFAULT_ENDPOINT = "https://api.example.com/track";
|
|
324
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
|
|
325
|
+
var DEFAULT_MAX_BATCH_SIZE = 20;
|
|
326
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e3;
|
|
327
|
+
var DEFAULT_PART_ID = "";
|
|
328
|
+
var DEFAULT_RETRY_BASE_MS = 500;
|
|
329
|
+
var DEFAULT_RETRY_MAX_MS = 3e4;
|
|
330
|
+
var DEFAULT_RETRY_ATTEMPTS = 5;
|
|
331
|
+
var DEFAULT_RETRY_JITTER = 0.2;
|
|
332
|
+
var MISSING_QUEUE_STORAGE_ERROR = "Signal client requires storage.queue when persistQueue is true; set persistQueue to false for in-memory queueing.";
|
|
333
|
+
var EMPTY_BLOCKED_PROPERTIES = Object.freeze([]);
|
|
334
|
+
var NON_BLOCKABLE_EVENT_PROPERTIES = /* @__PURE__ */ new Set([
|
|
335
|
+
EVENT_TYPE_KEY,
|
|
336
|
+
EVENT_TIME_KEY,
|
|
337
|
+
EVENT_INSERT_ID_KEY,
|
|
338
|
+
EVENT_USER_ID_KEY,
|
|
339
|
+
EVENT_VERSION_KEY
|
|
340
|
+
]);
|
|
341
|
+
var IDENTITY_STORAGE_KEY = "signal.identity.v1";
|
|
342
|
+
var QUEUE_STORAGE_KEY = "signal.queue.v1";
|
|
343
|
+
var NOOP_QUEUE_STORAGE = {
|
|
344
|
+
load: () => [],
|
|
345
|
+
upsert: () => {
|
|
346
|
+
},
|
|
347
|
+
remove: () => {
|
|
348
|
+
},
|
|
349
|
+
trim: () => {
|
|
350
|
+
},
|
|
351
|
+
clear: () => {
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
function toInteger(value, fallback) {
|
|
355
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
356
|
+
return fallback;
|
|
357
|
+
}
|
|
358
|
+
const rounded = Math.floor(value);
|
|
359
|
+
return rounded > 0 ? rounded : fallback;
|
|
360
|
+
}
|
|
361
|
+
function resolvePartId(partId) {
|
|
362
|
+
if (typeof partId !== "string") {
|
|
363
|
+
return DEFAULT_PART_ID;
|
|
364
|
+
}
|
|
365
|
+
return partId;
|
|
366
|
+
}
|
|
367
|
+
function resolveVersion(version) {
|
|
368
|
+
if (typeof version !== "string") {
|
|
369
|
+
return void 0;
|
|
370
|
+
}
|
|
371
|
+
return version;
|
|
372
|
+
}
|
|
373
|
+
function resolveBlockedProperties(blockedProperties) {
|
|
374
|
+
if (!Array.isArray(blockedProperties) || blockedProperties.length === 0) {
|
|
375
|
+
return EMPTY_BLOCKED_PROPERTIES;
|
|
376
|
+
}
|
|
377
|
+
const normalized = [];
|
|
378
|
+
const seen = /* @__PURE__ */ new Set();
|
|
379
|
+
for (const property of blockedProperties) {
|
|
380
|
+
if (typeof property !== "string" || property.length === 0 || seen.has(property) || NON_BLOCKABLE_EVENT_PROPERTIES.has(property)) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
seen.add(property);
|
|
384
|
+
normalized.push(property);
|
|
385
|
+
}
|
|
386
|
+
if (normalized.length === 0) {
|
|
387
|
+
return EMPTY_BLOCKED_PROPERTIES;
|
|
388
|
+
}
|
|
389
|
+
return Object.freeze(normalized);
|
|
390
|
+
}
|
|
391
|
+
function resolveLocationFromIp(locationFromIp) {
|
|
392
|
+
return locationFromIp !== false;
|
|
393
|
+
}
|
|
394
|
+
function resolveOptOut(optOut) {
|
|
395
|
+
return optOut === true;
|
|
396
|
+
}
|
|
397
|
+
function resolveInitialUserId(userId) {
|
|
398
|
+
if (typeof userId === "string" || userId === null) {
|
|
399
|
+
return userId;
|
|
400
|
+
}
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
function resolveInitialDeviceId(deviceId) {
|
|
404
|
+
if (typeof deviceId !== "string" || deviceId.length === 0) {
|
|
405
|
+
return void 0;
|
|
406
|
+
}
|
|
407
|
+
return deviceId;
|
|
408
|
+
}
|
|
409
|
+
function createIdentityStorage(storage) {
|
|
410
|
+
return storage != null ? storage : new InMemoryStorage();
|
|
411
|
+
}
|
|
412
|
+
function isQueueStorage(storage) {
|
|
413
|
+
if (!storage || typeof storage !== "object") {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
const queueStorage = storage;
|
|
417
|
+
return typeof queueStorage.load === "function" && typeof queueStorage.upsert === "function" && typeof queueStorage.remove === "function" && typeof queueStorage.trim === "function" && typeof queueStorage.clear === "function";
|
|
418
|
+
}
|
|
419
|
+
function createQueueStorage(storage) {
|
|
420
|
+
if (storage == null) {
|
|
421
|
+
throw new Error(MISSING_QUEUE_STORAGE_ERROR);
|
|
422
|
+
}
|
|
423
|
+
if (isQueueStorage(storage)) {
|
|
424
|
+
return storage;
|
|
425
|
+
}
|
|
426
|
+
return new SnapshotQueueStorage(storage, QUEUE_STORAGE_KEY);
|
|
427
|
+
}
|
|
428
|
+
function normalizeConfig(apiKey, config) {
|
|
429
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
430
|
+
if (!apiKey) {
|
|
431
|
+
throw new Error("Signal client requires apiKey");
|
|
432
|
+
}
|
|
433
|
+
const persistQueue = (_a = config.persistQueue) != null ? _a : true;
|
|
434
|
+
const initialIdentityOverrides = {
|
|
435
|
+
userId: resolveInitialUserId(config.userId),
|
|
436
|
+
deviceId: resolveInitialDeviceId(config.deviceId)
|
|
437
|
+
};
|
|
438
|
+
return {
|
|
439
|
+
apiKey,
|
|
440
|
+
resolvedConfig: {
|
|
441
|
+
endpoint: (_b = config.endpoint) != null ? _b : DEFAULT_ENDPOINT,
|
|
442
|
+
flushIntervalMs: toInteger(config.flushIntervalMs, DEFAULT_FLUSH_INTERVAL_MS),
|
|
443
|
+
maxBatchSize: toInteger(config.maxBatchSize, DEFAULT_MAX_BATCH_SIZE),
|
|
444
|
+
maxQueueSize: toInteger(config.maxQueueSize, DEFAULT_MAX_QUEUE_SIZE),
|
|
445
|
+
retry: {
|
|
446
|
+
baseMs: toInteger((_c = config.retry) == null ? void 0 : _c.baseMs, DEFAULT_RETRY_BASE_MS),
|
|
447
|
+
maxMs: toInteger((_d = config.retry) == null ? void 0 : _d.maxMs, DEFAULT_RETRY_MAX_MS),
|
|
448
|
+
maxAttempts: toInteger((_e = config.retry) == null ? void 0 : _e.maxAttempts, DEFAULT_RETRY_ATTEMPTS),
|
|
449
|
+
jitter: typeof ((_f = config.retry) == null ? void 0 : _f.jitter) === "number" ? config.retry.jitter : DEFAULT_RETRY_JITTER
|
|
450
|
+
},
|
|
451
|
+
persistQueue,
|
|
452
|
+
locationFromIp: resolveLocationFromIp(config.locationFromIp),
|
|
453
|
+
optOut: resolveOptOut(config.optOut),
|
|
454
|
+
partId: resolvePartId(config.partId),
|
|
455
|
+
version: resolveVersion(config.version),
|
|
456
|
+
blockedProperties: resolveBlockedProperties(config.blockedProperties)
|
|
457
|
+
},
|
|
458
|
+
storage: {
|
|
459
|
+
identity: createIdentityStorage((_g = config.storage) == null ? void 0 : _g.identity),
|
|
460
|
+
queue: persistQueue ? createQueueStorage((_h = config.storage) == null ? void 0 : _h.queue) : NOOP_QUEUE_STORAGE
|
|
461
|
+
},
|
|
462
|
+
transport: (_i = config.transport) != null ? _i : createFetchTransport(),
|
|
463
|
+
plugins: (_j = config.plugins) != null ? _j : [],
|
|
464
|
+
initialIdentityOverrides
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/client-mutation.ts
|
|
469
|
+
function createDeferred() {
|
|
470
|
+
let resolvePromise = () => {
|
|
471
|
+
};
|
|
472
|
+
let rejectPromise = () => {
|
|
473
|
+
};
|
|
474
|
+
const deferred = {
|
|
475
|
+
promise: new Promise((resolve, reject) => {
|
|
476
|
+
resolvePromise = resolve;
|
|
477
|
+
rejectPromise = reject;
|
|
478
|
+
}),
|
|
479
|
+
settled: false,
|
|
480
|
+
resolve(value) {
|
|
481
|
+
if (deferred.settled) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
deferred.settled = true;
|
|
485
|
+
resolvePromise(value);
|
|
486
|
+
},
|
|
487
|
+
reject(error) {
|
|
488
|
+
if (deferred.settled) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
deferred.settled = true;
|
|
492
|
+
rejectPromise(error);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
return deferred;
|
|
496
|
+
}
|
|
497
|
+
function createTrackFamilyMutationTask(request) {
|
|
498
|
+
return {
|
|
499
|
+
kind: "track-family",
|
|
500
|
+
requests: [request]
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function collectMergedMutationTask(queue) {
|
|
504
|
+
const first = queue.shift();
|
|
505
|
+
if (!first) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
let merged = first;
|
|
509
|
+
while (queue.length > 0) {
|
|
510
|
+
const next = queue[0];
|
|
511
|
+
if (!next) {
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
const candidate = mergeMutationTask(merged, next);
|
|
515
|
+
if (!candidate) {
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
merged = candidate;
|
|
519
|
+
queue.shift();
|
|
520
|
+
}
|
|
521
|
+
return merged;
|
|
522
|
+
}
|
|
523
|
+
function mergeMutationTask(left, right) {
|
|
524
|
+
if (left.kind !== "track-family" || right.kind !== "track-family") {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
kind: "track-family",
|
|
529
|
+
requests: [...left.requests, ...right.requests]
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/client-queue.ts
|
|
534
|
+
function haveCompatibleRequestOptions(left, right) {
|
|
535
|
+
return left.options.endpoint === right.options.endpoint && left.options.timeoutMs === right.options.timeoutMs;
|
|
536
|
+
}
|
|
537
|
+
function isTrackProperties(value) {
|
|
538
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
539
|
+
}
|
|
540
|
+
function createQueueEntryId() {
|
|
541
|
+
const random = Math.random().toString(16).slice(2, 10);
|
|
542
|
+
const now = Date.now().toString(16);
|
|
543
|
+
return `q_${now}_${random}`;
|
|
544
|
+
}
|
|
545
|
+
function isQueueEntry(value) {
|
|
546
|
+
var _a, _b, _c, _d;
|
|
547
|
+
if (!value || typeof value !== "object") {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
const entry = value;
|
|
551
|
+
return typeof entry.id === "string" && typeof entry.deviceId === "string" && Boolean(entry.event) && Boolean(entry.options) && typeof entry.attempts === "number" && typeof ((_a = entry.event) == null ? void 0 : _a[EVENT_TYPE_KEY]) === "string" && typeof ((_b = entry.event) == null ? void 0 : _b[EVENT_INSERT_ID_KEY]) === "string" && typeof ((_c = entry.event) == null ? void 0 : _c[EVENT_TIME_KEY]) === "number" && typeof ((_d = entry.options) == null ? void 0 : _d.endpoint) === "string";
|
|
552
|
+
}
|
|
553
|
+
function cloneIdentity(identity) {
|
|
554
|
+
return {
|
|
555
|
+
userId: identity.userId,
|
|
556
|
+
deviceId: identity.deviceId
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function collectCompatibleBatch(queue, maxEntries) {
|
|
560
|
+
const first = queue[0];
|
|
561
|
+
if (!first) {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
const batch = [first];
|
|
565
|
+
const limit = Math.max(1, Math.floor(maxEntries));
|
|
566
|
+
for (let index = 1; index < queue.length && batch.length < limit; index += 1) {
|
|
567
|
+
const entry = queue[index];
|
|
568
|
+
if (!entry || !haveCompatibleRequestOptions(first, entry)) {
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
batch.push(entry);
|
|
572
|
+
}
|
|
573
|
+
return batch;
|
|
574
|
+
}
|
|
575
|
+
function createTransportGroups(entries) {
|
|
576
|
+
const groupedEntries = /* @__PURE__ */ new Map();
|
|
577
|
+
for (const entry of entries) {
|
|
578
|
+
const existing = groupedEntries.get(entry.deviceId);
|
|
579
|
+
if (existing) {
|
|
580
|
+
existing.push(entry.event);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
groupedEntries.set(entry.deviceId, [entry.event]);
|
|
584
|
+
}
|
|
585
|
+
return [...groupedEntries.entries()].map(([deviceId, events]) => ({
|
|
586
|
+
deviceId,
|
|
587
|
+
events
|
|
588
|
+
}));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/hooks.ts
|
|
592
|
+
function registerListener(listeners, listener) {
|
|
593
|
+
listeners.add(listener);
|
|
594
|
+
return () => {
|
|
595
|
+
listeners.delete(listener);
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
var HookRegistry = class {
|
|
599
|
+
constructor() {
|
|
600
|
+
this._onInit = /* @__PURE__ */ new Set();
|
|
601
|
+
this._onEventCreated = /* @__PURE__ */ new Set();
|
|
602
|
+
this._onBeforeEnqueue = /* @__PURE__ */ new Set();
|
|
603
|
+
this._onEventQueued = /* @__PURE__ */ new Set();
|
|
604
|
+
this._onBeforeFlush = /* @__PURE__ */ new Set();
|
|
605
|
+
this._onAfterFlush = /* @__PURE__ */ new Set();
|
|
606
|
+
this._onBeforeSend = /* @__PURE__ */ new Set();
|
|
607
|
+
this._onAfterSend = /* @__PURE__ */ new Set();
|
|
608
|
+
this._onIdentityChanged = /* @__PURE__ */ new Set();
|
|
609
|
+
this._onOptOutChanged = /* @__PURE__ */ new Set();
|
|
610
|
+
this._onError = /* @__PURE__ */ new Set();
|
|
611
|
+
}
|
|
612
|
+
onInit(listener) {
|
|
613
|
+
return registerListener(this._onInit, listener);
|
|
614
|
+
}
|
|
615
|
+
onEventCreated(listener) {
|
|
616
|
+
return registerListener(this._onEventCreated, listener);
|
|
617
|
+
}
|
|
618
|
+
onBeforeEnqueue(listener) {
|
|
619
|
+
return registerListener(this._onBeforeEnqueue, listener);
|
|
620
|
+
}
|
|
621
|
+
onEventQueued(listener) {
|
|
622
|
+
return registerListener(this._onEventQueued, listener);
|
|
623
|
+
}
|
|
624
|
+
onBeforeFlush(listener) {
|
|
625
|
+
return registerListener(this._onBeforeFlush, listener);
|
|
626
|
+
}
|
|
627
|
+
onAfterFlush(listener) {
|
|
628
|
+
return registerListener(this._onAfterFlush, listener);
|
|
629
|
+
}
|
|
630
|
+
onBeforeSend(listener) {
|
|
631
|
+
return registerListener(this._onBeforeSend, listener);
|
|
632
|
+
}
|
|
633
|
+
onAfterSend(listener) {
|
|
634
|
+
return registerListener(this._onAfterSend, listener);
|
|
635
|
+
}
|
|
636
|
+
onIdentityChanged(listener) {
|
|
637
|
+
return registerListener(this._onIdentityChanged, listener);
|
|
638
|
+
}
|
|
639
|
+
onOptOutChanged(listener) {
|
|
640
|
+
return registerListener(this._onOptOutChanged, listener);
|
|
641
|
+
}
|
|
642
|
+
onError(listener) {
|
|
643
|
+
this._onError.add(listener);
|
|
644
|
+
return () => {
|
|
645
|
+
this._onError.delete(listener);
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async emitInit(context) {
|
|
649
|
+
await this._emitAsync(this._onInit, context);
|
|
650
|
+
}
|
|
651
|
+
async emitEventCreated(context) {
|
|
652
|
+
await this._emitAsync(this._onEventCreated, context);
|
|
653
|
+
}
|
|
654
|
+
async emitBeforeEnqueue(context) {
|
|
655
|
+
await this._emitAsync(this._onBeforeEnqueue, context);
|
|
656
|
+
}
|
|
657
|
+
async emitEventQueued(context) {
|
|
658
|
+
await this._emitAsync(this._onEventQueued, context);
|
|
659
|
+
}
|
|
660
|
+
async emitBeforeFlush(context) {
|
|
661
|
+
await this._emitAsync(this._onBeforeFlush, context);
|
|
662
|
+
}
|
|
663
|
+
async emitAfterFlush(context) {
|
|
664
|
+
await this._emitAsync(this._onAfterFlush, context);
|
|
665
|
+
}
|
|
666
|
+
async emitBeforeSend(context) {
|
|
667
|
+
await this._emitAsync(this._onBeforeSend, context);
|
|
668
|
+
}
|
|
669
|
+
async emitAfterSend(context) {
|
|
670
|
+
await this._emitAsync(this._onAfterSend, context);
|
|
671
|
+
}
|
|
672
|
+
async emitIdentityChanged(context) {
|
|
673
|
+
await this._emitAsync(this._onIdentityChanged, context);
|
|
674
|
+
}
|
|
675
|
+
async emitOptOutChanged(context) {
|
|
676
|
+
await this._emitAsync(this._onOptOutChanged, context);
|
|
677
|
+
}
|
|
678
|
+
emitError(context) {
|
|
679
|
+
for (const listener of this._onError) {
|
|
680
|
+
try {
|
|
681
|
+
listener(context);
|
|
682
|
+
} catch (e) {
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async _emitAsync(listeners, context) {
|
|
687
|
+
for (const listener of listeners) {
|
|
688
|
+
await listener(context);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/insert-id.ts
|
|
694
|
+
var COUNTER_MASK = 65535;
|
|
695
|
+
var InsertIdGenerator = class {
|
|
696
|
+
constructor(partId = "") {
|
|
697
|
+
this._counter = 0;
|
|
698
|
+
this._partId = partId;
|
|
699
|
+
}
|
|
700
|
+
next() {
|
|
701
|
+
const counter = this._counter;
|
|
702
|
+
this._counter = counter + 1 & COUNTER_MASK;
|
|
703
|
+
return `${this._partId}${counter.toString(16)}`;
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// src/plugin-runtime.ts
|
|
708
|
+
var PluginRuntime = class {
|
|
709
|
+
constructor(options) {
|
|
710
|
+
this._plugins = [];
|
|
711
|
+
this._setupPluginNames = /* @__PURE__ */ new Set();
|
|
712
|
+
this._pendingPluginSetups = /* @__PURE__ */ new Map();
|
|
713
|
+
this._pluginUnsubscribes = /* @__PURE__ */ new Map();
|
|
714
|
+
var _a;
|
|
715
|
+
this._config = options.config;
|
|
716
|
+
this._hooks = options.hooks;
|
|
717
|
+
this._emitError = options.emitError;
|
|
718
|
+
for (const plugin of (_a = options.plugins) != null ? _a : []) {
|
|
719
|
+
this.register(plugin);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
register(plugin) {
|
|
723
|
+
const duplicate = this._plugins.find((existing) => existing.name === plugin.name);
|
|
724
|
+
if (duplicate) {
|
|
725
|
+
throw new Error(`Plugin already registered: ${plugin.name}`);
|
|
726
|
+
}
|
|
727
|
+
this._plugins.push(plugin);
|
|
728
|
+
this._pluginUnsubscribes.set(plugin.name, /* @__PURE__ */ new Set());
|
|
729
|
+
}
|
|
730
|
+
list() {
|
|
731
|
+
return [...this._plugins];
|
|
732
|
+
}
|
|
733
|
+
async setup(plugin) {
|
|
734
|
+
if (!this._plugins.includes(plugin)) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (this._setupPluginNames.has(plugin.name)) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const pendingSetup = this._pendingPluginSetups.get(plugin.name);
|
|
741
|
+
if (pendingSetup) {
|
|
742
|
+
await pendingSetup;
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const setup = plugin.setup;
|
|
746
|
+
if (!setup) {
|
|
747
|
+
this._setupPluginNames.add(plugin.name);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const setupPromise = (async () => {
|
|
751
|
+
try {
|
|
752
|
+
await setup(this._createSetupContext(plugin.name));
|
|
753
|
+
if (!this._plugins.includes(plugin)) {
|
|
754
|
+
if (!plugin.teardown) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
await plugin.teardown();
|
|
759
|
+
} catch (error) {
|
|
760
|
+
this._emitError({ stage: "plugin:teardown", pluginName: plugin.name, error });
|
|
761
|
+
}
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
this._setupPluginNames.add(plugin.name);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
this._emitError({ stage: "plugin:setup", pluginName: plugin.name, error });
|
|
767
|
+
} finally {
|
|
768
|
+
this._pendingPluginSetups.delete(plugin.name);
|
|
769
|
+
}
|
|
770
|
+
})();
|
|
771
|
+
this._pendingPluginSetups.set(plugin.name, setupPromise);
|
|
772
|
+
await setupPromise;
|
|
773
|
+
}
|
|
774
|
+
async setupAll() {
|
|
775
|
+
for (const plugin of this.list()) {
|
|
776
|
+
await this.setup(plugin);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async remove(pluginName) {
|
|
780
|
+
const index = this._plugins.findIndex((plugin2) => plugin2.name === pluginName);
|
|
781
|
+
if (index < 0) {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
const [plugin] = this._plugins.splice(index, 1);
|
|
785
|
+
if (!plugin) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
this._detachPluginListeners(plugin.name);
|
|
789
|
+
const pendingSetup = this._pendingPluginSetups.get(plugin.name);
|
|
790
|
+
if (pendingSetup) {
|
|
791
|
+
await pendingSetup;
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
const wasSetup = this._setupPluginNames.delete(plugin.name);
|
|
795
|
+
if (!plugin.teardown || !wasSetup) {
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
await plugin.teardown();
|
|
800
|
+
} catch (error) {
|
|
801
|
+
this._emitError({ stage: "plugin:teardown", pluginName: plugin.name, error });
|
|
802
|
+
}
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
async flushAll() {
|
|
806
|
+
for (const plugin of this.list()) {
|
|
807
|
+
if (!plugin.flush) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
try {
|
|
811
|
+
await plugin.flush();
|
|
812
|
+
} catch (error) {
|
|
813
|
+
this._emitError({ stage: "plugin:flush", pluginName: plugin.name, error });
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
async teardownAll() {
|
|
818
|
+
for (const plugin of this.list()) {
|
|
819
|
+
if (!plugin.teardown) {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
await plugin.teardown();
|
|
824
|
+
} catch (error) {
|
|
825
|
+
this._emitError({ stage: "plugin:teardown", pluginName: plugin.name, error });
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
_createSetupContext(pluginName) {
|
|
830
|
+
return {
|
|
831
|
+
hooks: this._createScopedHooks(pluginName),
|
|
832
|
+
config: this._config
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
_createScopedHooks(pluginName) {
|
|
836
|
+
return {
|
|
837
|
+
onInit: (listener) => this._registerHook(pluginName, this._hooks.onInit.bind(this._hooks), listener),
|
|
838
|
+
onEventCreated: (listener) => this._registerHook(pluginName, this._hooks.onEventCreated.bind(this._hooks), listener),
|
|
839
|
+
onBeforeEnqueue: (listener) => this._registerHook(pluginName, this._hooks.onBeforeEnqueue.bind(this._hooks), listener),
|
|
840
|
+
onEventQueued: (listener) => this._registerHook(pluginName, this._hooks.onEventQueued.bind(this._hooks), listener),
|
|
841
|
+
onBeforeFlush: (listener) => this._registerHook(pluginName, this._hooks.onBeforeFlush.bind(this._hooks), listener),
|
|
842
|
+
onAfterFlush: (listener) => this._registerHook(pluginName, this._hooks.onAfterFlush.bind(this._hooks), listener),
|
|
843
|
+
onBeforeSend: (listener) => this._registerHook(pluginName, this._hooks.onBeforeSend.bind(this._hooks), listener),
|
|
844
|
+
onAfterSend: (listener) => this._registerHook(pluginName, this._hooks.onAfterSend.bind(this._hooks), listener),
|
|
845
|
+
onIdentityChanged: (listener) => this._registerHook(pluginName, this._hooks.onIdentityChanged.bind(this._hooks), listener),
|
|
846
|
+
onOptOutChanged: (listener) => this._registerHook(pluginName, this._hooks.onOptOutChanged.bind(this._hooks), listener),
|
|
847
|
+
onError: (listener) => this._registerErrorHook(pluginName, this._hooks.onError.bind(this._hooks), listener)
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
_registerHook(pluginName, register, listener) {
|
|
851
|
+
if (!this._plugins.some((plugin) => plugin.name === pluginName)) {
|
|
852
|
+
return () => {
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
const unsubscribe = register(listener);
|
|
856
|
+
return this._trackUnsubscribe(pluginName, unsubscribe);
|
|
857
|
+
}
|
|
858
|
+
_registerErrorHook(pluginName, register, listener) {
|
|
859
|
+
if (!this._plugins.some((plugin) => plugin.name === pluginName)) {
|
|
860
|
+
return () => {
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
const unsubscribe = register(listener);
|
|
864
|
+
return this._trackUnsubscribe(pluginName, unsubscribe);
|
|
865
|
+
}
|
|
866
|
+
_trackUnsubscribe(pluginName, unsubscribe) {
|
|
867
|
+
const pluginUnsubscribes = this._pluginUnsubscribes.get(pluginName);
|
|
868
|
+
if (!pluginUnsubscribes) {
|
|
869
|
+
unsubscribe();
|
|
870
|
+
return () => {
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
let active = true;
|
|
874
|
+
const trackedUnsubscribe = () => {
|
|
875
|
+
if (!active) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
active = false;
|
|
879
|
+
pluginUnsubscribes.delete(trackedUnsubscribe);
|
|
880
|
+
unsubscribe();
|
|
881
|
+
};
|
|
882
|
+
pluginUnsubscribes.add(trackedUnsubscribe);
|
|
883
|
+
if (!this._plugins.some((plugin) => plugin.name === pluginName)) {
|
|
884
|
+
trackedUnsubscribe();
|
|
885
|
+
}
|
|
886
|
+
return trackedUnsubscribe;
|
|
887
|
+
}
|
|
888
|
+
_detachPluginListeners(pluginName) {
|
|
889
|
+
const pluginUnsubscribes = this._pluginUnsubscribes.get(pluginName);
|
|
890
|
+
if (!pluginUnsubscribes) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
this._pluginUnsubscribes.delete(pluginName);
|
|
894
|
+
for (const unsubscribe of [...pluginUnsubscribes]) {
|
|
895
|
+
unsubscribe();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
// src/uuid.ts
|
|
901
|
+
function getCrypto() {
|
|
902
|
+
const maybeCrypto = globalThis.crypto;
|
|
903
|
+
if (!maybeCrypto || typeof maybeCrypto !== "object") {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
return maybeCrypto;
|
|
907
|
+
}
|
|
908
|
+
function byteToHex(byte) {
|
|
909
|
+
return byte.toString(16).padStart(2, "0");
|
|
910
|
+
}
|
|
911
|
+
function getByte(bytes, index) {
|
|
912
|
+
var _a;
|
|
913
|
+
return (_a = bytes[index]) != null ? _a : 0;
|
|
914
|
+
}
|
|
915
|
+
function formatUuidV4(bytes) {
|
|
916
|
+
bytes[6] = getByte(bytes, 6) & 15 | 64;
|
|
917
|
+
bytes[8] = getByte(bytes, 8) & 63 | 128;
|
|
918
|
+
return [
|
|
919
|
+
byteToHex(getByte(bytes, 0)) + byteToHex(getByte(bytes, 1)) + byteToHex(getByte(bytes, 2)) + byteToHex(getByte(bytes, 3)),
|
|
920
|
+
byteToHex(getByte(bytes, 4)) + byteToHex(getByte(bytes, 5)),
|
|
921
|
+
byteToHex(getByte(bytes, 6)) + byteToHex(getByte(bytes, 7)),
|
|
922
|
+
byteToHex(getByte(bytes, 8)) + byteToHex(getByte(bytes, 9)),
|
|
923
|
+
byteToHex(getByte(bytes, 10)) + byteToHex(getByte(bytes, 11)) + byteToHex(getByte(bytes, 12)) + byteToHex(getByte(bytes, 13)) + byteToHex(getByte(bytes, 14)) + byteToHex(getByte(bytes, 15))
|
|
924
|
+
].join("-");
|
|
925
|
+
}
|
|
926
|
+
function createMathRandomBytes() {
|
|
927
|
+
const bytes = new Uint8Array(16);
|
|
928
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
929
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
930
|
+
}
|
|
931
|
+
return bytes;
|
|
932
|
+
}
|
|
933
|
+
function createUuidV4() {
|
|
934
|
+
const crypto = getCrypto();
|
|
935
|
+
if (typeof (crypto == null ? void 0 : crypto.randomUUID) === "function") {
|
|
936
|
+
return crypto.randomUUID();
|
|
937
|
+
}
|
|
938
|
+
if (typeof (crypto == null ? void 0 : crypto.getRandomValues) === "function") {
|
|
939
|
+
return formatUuidV4(crypto.getRandomValues(new Uint8Array(16)));
|
|
940
|
+
}
|
|
941
|
+
return formatUuidV4(createMathRandomBytes());
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// src/client.ts
|
|
945
|
+
var SignalClient = class {
|
|
946
|
+
constructor() {
|
|
947
|
+
this._apiKey = "";
|
|
948
|
+
this._hooks = new HookRegistry();
|
|
949
|
+
this._blockedProperties = null;
|
|
950
|
+
this._identity = {
|
|
951
|
+
userId: null,
|
|
952
|
+
deviceId: ""
|
|
953
|
+
};
|
|
954
|
+
this._optOut = false;
|
|
955
|
+
this._queue = [];
|
|
956
|
+
this._ready = null;
|
|
957
|
+
this._state = "uninitialized";
|
|
958
|
+
this._initError = null;
|
|
959
|
+
this._queuedMutations = [];
|
|
960
|
+
this._drainingQueuedMutations = false;
|
|
961
|
+
this._flushTask = null;
|
|
962
|
+
this._mutationQueue = [];
|
|
963
|
+
this._mutationDrainPromise = null;
|
|
964
|
+
this._automaticFlushPending = false;
|
|
965
|
+
this._activeAutomaticFlushReason = null;
|
|
966
|
+
this._acceptEvents = true;
|
|
967
|
+
this._shutdownPromise = null;
|
|
968
|
+
}
|
|
969
|
+
hooks() {
|
|
970
|
+
return this._hooks;
|
|
971
|
+
}
|
|
972
|
+
async init(apiKey, options = {}) {
|
|
973
|
+
if (this._state === "initializing" || this._state === "ready") {
|
|
974
|
+
throw new Error("Signal client init() can only be called once per instance");
|
|
975
|
+
}
|
|
976
|
+
if (this._state === "shutdown") {
|
|
977
|
+
throw new Error("Signal client has been shut down; create a new client instance instead");
|
|
978
|
+
}
|
|
979
|
+
if (this._state === "failed") {
|
|
980
|
+
throw new Error(
|
|
981
|
+
"Signal client init() previously failed; create a new client instance to retry"
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
this._state = "initializing";
|
|
985
|
+
try {
|
|
986
|
+
const normalized = normalizeConfig(apiKey, this.resolveInitConfig(options));
|
|
987
|
+
this._applyNormalizedConfig(normalized);
|
|
988
|
+
this._ready = this._initialize(normalized.initialIdentityOverrides);
|
|
989
|
+
await this._ready;
|
|
990
|
+
await this._drainQueuedMutations();
|
|
991
|
+
if (!this._isShutdown()) {
|
|
992
|
+
this._state = "ready";
|
|
993
|
+
}
|
|
994
|
+
return this;
|
|
995
|
+
} catch (error) {
|
|
996
|
+
this._state = "failed";
|
|
997
|
+
this._initError = error;
|
|
998
|
+
this._rejectQueuedMutations(error);
|
|
999
|
+
this._emitError({ stage: "init", error });
|
|
1000
|
+
throw error;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
resolveInitConfig(options) {
|
|
1004
|
+
return options;
|
|
1005
|
+
}
|
|
1006
|
+
async add(plugin) {
|
|
1007
|
+
return this._queueOrRunMutation("mutation", async () => {
|
|
1008
|
+
this._pluginRuntime.register(plugin);
|
|
1009
|
+
const completion = createDeferred();
|
|
1010
|
+
const task = {
|
|
1011
|
+
kind: "plugin-add",
|
|
1012
|
+
plugin,
|
|
1013
|
+
completion
|
|
1014
|
+
};
|
|
1015
|
+
this._enqueueMutation(task);
|
|
1016
|
+
await completion.promise;
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
async remove(pluginName) {
|
|
1020
|
+
return this._queueOrRunMutationWithResult(async () => {
|
|
1021
|
+
const completion = createDeferred();
|
|
1022
|
+
const task = {
|
|
1023
|
+
kind: "plugin-remove",
|
|
1024
|
+
pluginName,
|
|
1025
|
+
completion
|
|
1026
|
+
};
|
|
1027
|
+
this._enqueueMutation(task);
|
|
1028
|
+
return completion.promise;
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
listPlugins() {
|
|
1032
|
+
return this._requireReadyRuntime().list();
|
|
1033
|
+
}
|
|
1034
|
+
async loggedIn(userId) {
|
|
1035
|
+
return this._queueOrRunMutation("mutation", async () => {
|
|
1036
|
+
const completion = createDeferred();
|
|
1037
|
+
const task = {
|
|
1038
|
+
kind: "identity",
|
|
1039
|
+
action: "logged-in",
|
|
1040
|
+
userId,
|
|
1041
|
+
completion
|
|
1042
|
+
};
|
|
1043
|
+
this._enqueueMutation(task);
|
|
1044
|
+
await completion.promise;
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
async loggedOut() {
|
|
1048
|
+
return this._queueOrRunMutation("mutation", async () => {
|
|
1049
|
+
const completion = createDeferred();
|
|
1050
|
+
const task = {
|
|
1051
|
+
kind: "identity",
|
|
1052
|
+
action: "logged-out",
|
|
1053
|
+
completion
|
|
1054
|
+
};
|
|
1055
|
+
this._enqueueMutation(task);
|
|
1056
|
+
await completion.promise;
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
async reset(deviceId) {
|
|
1060
|
+
return this._queueOrRunMutation("mutation", async () => {
|
|
1061
|
+
const completion = createDeferred();
|
|
1062
|
+
const task = {
|
|
1063
|
+
kind: "identity",
|
|
1064
|
+
action: "reset",
|
|
1065
|
+
deviceId,
|
|
1066
|
+
completion
|
|
1067
|
+
};
|
|
1068
|
+
this._enqueueMutation(task);
|
|
1069
|
+
await completion.promise;
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
userId() {
|
|
1073
|
+
this._requireReadyRuntime();
|
|
1074
|
+
return this._identity.userId;
|
|
1075
|
+
}
|
|
1076
|
+
optOut() {
|
|
1077
|
+
this._requireReadyRuntime();
|
|
1078
|
+
return this._optOut;
|
|
1079
|
+
}
|
|
1080
|
+
async setOptOut(optOut) {
|
|
1081
|
+
return this._queueOrRunMutation("mutation", async () => {
|
|
1082
|
+
const completion = createDeferred();
|
|
1083
|
+
const task = {
|
|
1084
|
+
kind: "opt-out",
|
|
1085
|
+
optOut,
|
|
1086
|
+
completion
|
|
1087
|
+
};
|
|
1088
|
+
this._enqueueMutation(task);
|
|
1089
|
+
await completion.promise;
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
async track(event, properties = {}, options = {}) {
|
|
1093
|
+
return this._queueOrRunMutation("mutation", async () => {
|
|
1094
|
+
if (!this._acceptEvents) {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
const completion = createDeferred();
|
|
1098
|
+
const request = {
|
|
1099
|
+
event,
|
|
1100
|
+
properties,
|
|
1101
|
+
options,
|
|
1102
|
+
waitForImmediateFlush: options.flush === "immediate",
|
|
1103
|
+
completion
|
|
1104
|
+
};
|
|
1105
|
+
this._enqueueMutation(createTrackFamilyMutationTask(request));
|
|
1106
|
+
await completion.promise;
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
async trackNow(event, properties = {}, options = {}) {
|
|
1110
|
+
await this.track(event, properties, { ...options, flush: "immediate" });
|
|
1111
|
+
}
|
|
1112
|
+
async flush(reason = "manual") {
|
|
1113
|
+
return this._queueOrRunMutation("mutation", async () => {
|
|
1114
|
+
const completion = createDeferred();
|
|
1115
|
+
const task = {
|
|
1116
|
+
kind: "flush",
|
|
1117
|
+
reason,
|
|
1118
|
+
automatic: false,
|
|
1119
|
+
completion
|
|
1120
|
+
};
|
|
1121
|
+
this._enqueueMutation(task);
|
|
1122
|
+
await completion.promise;
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
async shutdown() {
|
|
1126
|
+
if (this._state === "shutdown") {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
return this._queueOrRunMutation("shutdown", async () => {
|
|
1130
|
+
if (this._shutdownPromise) {
|
|
1131
|
+
await this._shutdownPromise;
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
this._acceptEvents = false;
|
|
1135
|
+
this._cancelScheduledFlush();
|
|
1136
|
+
const completion = createDeferred();
|
|
1137
|
+
const task = {
|
|
1138
|
+
kind: "shutdown",
|
|
1139
|
+
completion
|
|
1140
|
+
};
|
|
1141
|
+
this._shutdownPromise = completion.promise;
|
|
1142
|
+
this._enqueueMutation(task);
|
|
1143
|
+
await this._shutdownPromise;
|
|
1144
|
+
this._state = "shutdown";
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
config() {
|
|
1148
|
+
this._requireReadyRuntime();
|
|
1149
|
+
return this._config;
|
|
1150
|
+
}
|
|
1151
|
+
_applyNormalizedConfig(normalized) {
|
|
1152
|
+
this._apiKey = normalized.apiKey;
|
|
1153
|
+
this._config = normalized.resolvedConfig;
|
|
1154
|
+
this._storage = normalized.storage;
|
|
1155
|
+
this._transport = normalized.transport;
|
|
1156
|
+
this._insertIdGenerator = new InsertIdGenerator(this._config.partId);
|
|
1157
|
+
this._blockedProperties = this._config.blockedProperties.length > 0 ? new Set(this._config.blockedProperties) : null;
|
|
1158
|
+
this._pluginRuntime = new PluginRuntime({
|
|
1159
|
+
config: this._config,
|
|
1160
|
+
hooks: this._hooks,
|
|
1161
|
+
emitError: this._emitError.bind(this),
|
|
1162
|
+
plugins: normalized.plugins
|
|
1163
|
+
});
|
|
1164
|
+
this._optOut = this._config.optOut;
|
|
1165
|
+
this._identity = {
|
|
1166
|
+
userId: null,
|
|
1167
|
+
deviceId: createUuidV4()
|
|
1168
|
+
};
|
|
1169
|
+
this._queue = [];
|
|
1170
|
+
this._flushTask = null;
|
|
1171
|
+
this._mutationQueue = [];
|
|
1172
|
+
this._mutationDrainPromise = null;
|
|
1173
|
+
this._automaticFlushPending = false;
|
|
1174
|
+
this._activeAutomaticFlushReason = null;
|
|
1175
|
+
this._acceptEvents = true;
|
|
1176
|
+
this._shutdownPromise = null;
|
|
1177
|
+
}
|
|
1178
|
+
_requireReadyRuntime() {
|
|
1179
|
+
if (this._state !== "ready") {
|
|
1180
|
+
throw new Error("Signal client is not initialized; call init(apiKey, options) first");
|
|
1181
|
+
}
|
|
1182
|
+
return this._pluginRuntime;
|
|
1183
|
+
}
|
|
1184
|
+
async _queueOrRunMutation(kind, run) {
|
|
1185
|
+
var _a;
|
|
1186
|
+
if (this._state === "shutdown") {
|
|
1187
|
+
throw this._shutdownError();
|
|
1188
|
+
}
|
|
1189
|
+
if (this._state === "failed") {
|
|
1190
|
+
throw (_a = this._initError) != null ? _a : new Error("Signal client initialization failed");
|
|
1191
|
+
}
|
|
1192
|
+
if (this._state === "uninitialized" || this._state === "initializing") {
|
|
1193
|
+
const deferred = createDeferred();
|
|
1194
|
+
this._queuedMutations.push({
|
|
1195
|
+
kind,
|
|
1196
|
+
deferred,
|
|
1197
|
+
run
|
|
1198
|
+
});
|
|
1199
|
+
return deferred.promise;
|
|
1200
|
+
}
|
|
1201
|
+
return run();
|
|
1202
|
+
}
|
|
1203
|
+
async _queueOrRunMutationWithResult(run) {
|
|
1204
|
+
var _a;
|
|
1205
|
+
if (this._state === "shutdown") {
|
|
1206
|
+
throw this._shutdownError();
|
|
1207
|
+
}
|
|
1208
|
+
if (this._state === "failed") {
|
|
1209
|
+
throw (_a = this._initError) != null ? _a : new Error("Signal client initialization failed");
|
|
1210
|
+
}
|
|
1211
|
+
if (this._state === "uninitialized" || this._state === "initializing") {
|
|
1212
|
+
const deferred = createDeferred();
|
|
1213
|
+
this._queuedMutations.push({
|
|
1214
|
+
kind: "mutation",
|
|
1215
|
+
deferred,
|
|
1216
|
+
run
|
|
1217
|
+
});
|
|
1218
|
+
return deferred.promise;
|
|
1219
|
+
}
|
|
1220
|
+
return run();
|
|
1221
|
+
}
|
|
1222
|
+
async _drainQueuedMutations() {
|
|
1223
|
+
if (this._drainingQueuedMutations) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
this._drainingQueuedMutations = true;
|
|
1227
|
+
try {
|
|
1228
|
+
while (this._queuedMutations.length > 0) {
|
|
1229
|
+
const mutation = this._queuedMutations.shift();
|
|
1230
|
+
if (!mutation) {
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
const value = await mutation.run();
|
|
1235
|
+
mutation.deferred.resolve(value);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
mutation.deferred.reject(error);
|
|
1238
|
+
}
|
|
1239
|
+
if (mutation.kind === "shutdown") {
|
|
1240
|
+
this._rejectQueuedMutations(this._shutdownError());
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
} finally {
|
|
1245
|
+
this._drainingQueuedMutations = false;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
_rejectQueuedMutations(error) {
|
|
1249
|
+
var _a;
|
|
1250
|
+
while (this._queuedMutations.length > 0) {
|
|
1251
|
+
(_a = this._queuedMutations.shift()) == null ? void 0 : _a.deferred.reject(error);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
_shutdownError() {
|
|
1255
|
+
return new Error("Signal client has been shut down; create a new client instance instead");
|
|
1256
|
+
}
|
|
1257
|
+
_isShutdown() {
|
|
1258
|
+
return this._state === "shutdown";
|
|
1259
|
+
}
|
|
1260
|
+
_enqueueMutation(task) {
|
|
1261
|
+
this._mutationQueue.push(task);
|
|
1262
|
+
this._ensureMutationDrain();
|
|
1263
|
+
}
|
|
1264
|
+
_ensureMutationDrain() {
|
|
1265
|
+
if (this._mutationDrainPromise) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
this._mutationDrainPromise = this._drainMutations().finally(() => {
|
|
1269
|
+
this._mutationDrainPromise = null;
|
|
1270
|
+
if (this._mutationQueue.length > 0) {
|
|
1271
|
+
this._ensureMutationDrain();
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
async _drainMutations() {
|
|
1276
|
+
while (this._mutationQueue.length > 0) {
|
|
1277
|
+
const headTask = this._mutationQueue[0];
|
|
1278
|
+
if (!headTask) {
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
let task = null;
|
|
1282
|
+
try {
|
|
1283
|
+
await this._awaitMutationPrerequisites(headTask);
|
|
1284
|
+
task = collectMergedMutationTask(this._mutationQueue);
|
|
1285
|
+
if (!task) {
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
await this._runMutationTask(task);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
if (task) {
|
|
1291
|
+
this._rejectMutationTask(task, error);
|
|
1292
|
+
} else {
|
|
1293
|
+
const failedHead = this._mutationQueue.shift();
|
|
1294
|
+
if (failedHead) {
|
|
1295
|
+
this._rejectMutationTask(failedHead, error);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
this._emitError({ stage: "mutation", error });
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
_rejectMutationTask(task, error) {
|
|
1303
|
+
switch (task.kind) {
|
|
1304
|
+
case "plugin-add":
|
|
1305
|
+
case "identity":
|
|
1306
|
+
case "opt-out":
|
|
1307
|
+
case "flush":
|
|
1308
|
+
case "shutdown":
|
|
1309
|
+
task.completion.reject(error);
|
|
1310
|
+
return;
|
|
1311
|
+
case "plugin-remove":
|
|
1312
|
+
task.completion.reject(error);
|
|
1313
|
+
return;
|
|
1314
|
+
case "track-family":
|
|
1315
|
+
for (const request of task.requests) {
|
|
1316
|
+
request.completion.reject(error);
|
|
1317
|
+
}
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
async _awaitMutationPrerequisites(task) {
|
|
1322
|
+
switch (task.kind) {
|
|
1323
|
+
case "plugin-add":
|
|
1324
|
+
case "plugin-remove":
|
|
1325
|
+
return;
|
|
1326
|
+
default:
|
|
1327
|
+
await this._ready;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
async _runMutationTask(task) {
|
|
1331
|
+
switch (task.kind) {
|
|
1332
|
+
case "plugin-add":
|
|
1333
|
+
await this._pluginRuntime.setup(task.plugin);
|
|
1334
|
+
task.completion.resolve(void 0);
|
|
1335
|
+
return;
|
|
1336
|
+
case "plugin-remove": {
|
|
1337
|
+
const removed = await this._pluginRuntime.remove(task.pluginName);
|
|
1338
|
+
task.completion.resolve(removed);
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
case "identity":
|
|
1342
|
+
await this._runIdentityMutationTask(task);
|
|
1343
|
+
task.completion.resolve(void 0);
|
|
1344
|
+
return;
|
|
1345
|
+
case "opt-out":
|
|
1346
|
+
await this._runOptOutMutationTask(task);
|
|
1347
|
+
task.completion.resolve(void 0);
|
|
1348
|
+
return;
|
|
1349
|
+
case "track-family":
|
|
1350
|
+
await this._runTrackFamilyMutationTask(task);
|
|
1351
|
+
return;
|
|
1352
|
+
case "flush":
|
|
1353
|
+
await this._runFlushMutationTask(task);
|
|
1354
|
+
return;
|
|
1355
|
+
case "shutdown":
|
|
1356
|
+
await this._runShutdownMutationTask();
|
|
1357
|
+
task.completion.resolve(void 0);
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
async _runIdentityMutationTask(task) {
|
|
1362
|
+
var _a;
|
|
1363
|
+
const previousIdentity = cloneIdentity(this._identity);
|
|
1364
|
+
switch (task.action) {
|
|
1365
|
+
case "logged-in":
|
|
1366
|
+
this._identity.userId = (_a = task.userId) != null ? _a : null;
|
|
1367
|
+
break;
|
|
1368
|
+
case "logged-out":
|
|
1369
|
+
this._identity.userId = null;
|
|
1370
|
+
break;
|
|
1371
|
+
case "reset":
|
|
1372
|
+
this._identity.userId = null;
|
|
1373
|
+
this._identity.deviceId = typeof task.deviceId === "string" && task.deviceId.length > 0 ? task.deviceId : createUuidV4();
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
await this._persistIdentity();
|
|
1377
|
+
await this._emitIdentityChanged(previousIdentity, task.action);
|
|
1378
|
+
}
|
|
1379
|
+
async _runOptOutMutationTask(task) {
|
|
1380
|
+
const previousOptOut = this._optOut;
|
|
1381
|
+
if (previousOptOut === task.optOut) {
|
|
1382
|
+
task.completion.resolve(void 0);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
this._optOut = task.optOut;
|
|
1386
|
+
this._config.optOut = task.optOut;
|
|
1387
|
+
await this._persistIdentity();
|
|
1388
|
+
if (task.optOut) {
|
|
1389
|
+
this._cancelScheduledFlush();
|
|
1390
|
+
this._queue = [];
|
|
1391
|
+
await this._clearQueueStorage();
|
|
1392
|
+
}
|
|
1393
|
+
await this._hooks.emitOptOutChanged({
|
|
1394
|
+
previousOptOut,
|
|
1395
|
+
optOut: task.optOut
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
async _runTrackFamilyMutationTask(task) {
|
|
1399
|
+
if (!this._acceptEvents || this._optOut) {
|
|
1400
|
+
for (const request of task.requests) {
|
|
1401
|
+
request.completion.resolve(void 0);
|
|
1402
|
+
}
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const queuedResults = [];
|
|
1406
|
+
const droppedIds = [];
|
|
1407
|
+
let shouldFlushImmediately = false;
|
|
1408
|
+
for (const request of task.requests) {
|
|
1409
|
+
const entry = await this._createQueueEntry(
|
|
1410
|
+
request.event,
|
|
1411
|
+
request.properties,
|
|
1412
|
+
request.options
|
|
1413
|
+
);
|
|
1414
|
+
if (!entry) {
|
|
1415
|
+
request.completion.resolve(void 0);
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
const dropped = this._enqueue(entry);
|
|
1419
|
+
queuedResults.push({
|
|
1420
|
+
entry,
|
|
1421
|
+
queuedEvents: this._queue.length,
|
|
1422
|
+
request
|
|
1423
|
+
});
|
|
1424
|
+
if (dropped) {
|
|
1425
|
+
droppedIds.push(dropped.id);
|
|
1426
|
+
}
|
|
1427
|
+
shouldFlushImmediately || (shouldFlushImmediately = entry.options.flush === "immediate");
|
|
1428
|
+
}
|
|
1429
|
+
await this._upsertQueueEntries(queuedResults.map(({ entry }) => entry));
|
|
1430
|
+
if (droppedIds.length > 0) {
|
|
1431
|
+
await this._removeQueueEntries(droppedIds);
|
|
1432
|
+
}
|
|
1433
|
+
for (const { entry, queuedEvents, request } of queuedResults) {
|
|
1434
|
+
await this._hooks.emitEventQueued({
|
|
1435
|
+
event: entry.event,
|
|
1436
|
+
options: entry.options,
|
|
1437
|
+
queuedEvents
|
|
1438
|
+
});
|
|
1439
|
+
if (!request.waitForImmediateFlush) {
|
|
1440
|
+
request.completion.resolve(void 0);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
if (queuedResults.length === 0) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (shouldFlushImmediately) {
|
|
1447
|
+
this._cancelScheduledFlush();
|
|
1448
|
+
await this._performFlush("immediate");
|
|
1449
|
+
for (const { request } of queuedResults) {
|
|
1450
|
+
if (request.waitForImmediateFlush) {
|
|
1451
|
+
request.completion.resolve(void 0);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
this._scheduleAutomaticFlush(this._config.flushIntervalMs, "scheduled");
|
|
1457
|
+
}
|
|
1458
|
+
async _runFlushMutationTask(task) {
|
|
1459
|
+
if (!task.automatic) {
|
|
1460
|
+
this._cancelScheduledFlush();
|
|
1461
|
+
} else {
|
|
1462
|
+
this._activeAutomaticFlushReason = task.reason;
|
|
1463
|
+
}
|
|
1464
|
+
try {
|
|
1465
|
+
await this._performFlush(task.reason);
|
|
1466
|
+
task.completion.resolve(void 0);
|
|
1467
|
+
} finally {
|
|
1468
|
+
if (task.automatic) {
|
|
1469
|
+
this._activeAutomaticFlushReason = null;
|
|
1470
|
+
this._automaticFlushPending = false;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
async _runShutdownMutationTask() {
|
|
1475
|
+
await this._performFlush("shutdown");
|
|
1476
|
+
await this._pluginRuntime.flushAll();
|
|
1477
|
+
await this._pluginRuntime.teardownAll();
|
|
1478
|
+
if (!this._transport.shutdown) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
try {
|
|
1482
|
+
await this._transport.shutdown();
|
|
1483
|
+
} catch (error) {
|
|
1484
|
+
this._emitError({ stage: "transport:shutdown", error });
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
async _initialize(initialIdentityOverrides) {
|
|
1488
|
+
await this._hydrateIdentity();
|
|
1489
|
+
await this._applyInitialIdentityOverrides(initialIdentityOverrides);
|
|
1490
|
+
if (this._optOut) {
|
|
1491
|
+
await this._clearQueueStorage();
|
|
1492
|
+
} else {
|
|
1493
|
+
await this._hydrateQueue();
|
|
1494
|
+
}
|
|
1495
|
+
await this._pluginRuntime.setupAll();
|
|
1496
|
+
await this._hooks.emitInit({
|
|
1497
|
+
config: this._config,
|
|
1498
|
+
identity: cloneIdentity(this._identity),
|
|
1499
|
+
optOut: this._optOut
|
|
1500
|
+
});
|
|
1501
|
+
if (this._queue.length > 0) {
|
|
1502
|
+
this._scheduleAutomaticFlush(this._config.flushIntervalMs, "scheduled");
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
async _hydrateIdentity() {
|
|
1506
|
+
try {
|
|
1507
|
+
const stored = await this._storage.identity.get(IDENTITY_STORAGE_KEY);
|
|
1508
|
+
if (!stored || typeof stored.deviceId !== "string" || typeof stored.optOut !== "boolean") {
|
|
1509
|
+
await this._persistIdentity();
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
this._identity.userId = typeof stored.userId === "string" ? stored.userId : null;
|
|
1513
|
+
this._identity.deviceId = stored.deviceId;
|
|
1514
|
+
this._optOut = stored.optOut;
|
|
1515
|
+
this._config.optOut = stored.optOut;
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
this._emitError({ stage: "storage:hydrate-identity", error });
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
async _applyInitialIdentityOverrides(initialIdentityOverrides) {
|
|
1521
|
+
let changed = false;
|
|
1522
|
+
if (initialIdentityOverrides.userId !== void 0 && this._identity.userId !== initialIdentityOverrides.userId) {
|
|
1523
|
+
this._identity.userId = initialIdentityOverrides.userId;
|
|
1524
|
+
changed = true;
|
|
1525
|
+
}
|
|
1526
|
+
if (typeof initialIdentityOverrides.deviceId === "string" && this._identity.deviceId !== initialIdentityOverrides.deviceId) {
|
|
1527
|
+
this._identity.deviceId = initialIdentityOverrides.deviceId;
|
|
1528
|
+
changed = true;
|
|
1529
|
+
}
|
|
1530
|
+
if (!changed) {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
await this._persistIdentity();
|
|
1534
|
+
}
|
|
1535
|
+
async _hydrateQueue() {
|
|
1536
|
+
if (!this._config.persistQueue) {
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
try {
|
|
1540
|
+
const stored = await this._storage.queue.load(this._config.maxQueueSize);
|
|
1541
|
+
const hydrated = stored.filter(isQueueEntry).slice(-this._config.maxQueueSize);
|
|
1542
|
+
this._queue.push(...hydrated);
|
|
1543
|
+
await this._trimQueueStorage();
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
this._emitError({ stage: "storage:hydrate-queue", error });
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
async _createQueueEntry(eventType, properties, options) {
|
|
1549
|
+
const resolvedOptions = this._resolveTrackOptions(options);
|
|
1550
|
+
const now = Date.now();
|
|
1551
|
+
if (typeof eventType !== "string" || eventType.length === 0) {
|
|
1552
|
+
this._emitError({
|
|
1553
|
+
stage: "event:validation",
|
|
1554
|
+
error: new Error("track() requires a non-empty event name")
|
|
1555
|
+
});
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
const normalizedProperties = isTrackProperties(properties) ? properties : {};
|
|
1559
|
+
const {
|
|
1560
|
+
[EVENT_TYPE_KEY]: _ignoredEventType,
|
|
1561
|
+
[EVENT_TIME_KEY]: _ignoredTime,
|
|
1562
|
+
[EVENT_USER_ID_KEY]: _ignoredUserId,
|
|
1563
|
+
[EVENT_DEVICE_ID_KEY]: _ignoredDeviceId,
|
|
1564
|
+
[EVENT_INSERT_ID_KEY]: _ignoredInsertId,
|
|
1565
|
+
[EVENT_VERSION_KEY]: _ignoredVersion,
|
|
1566
|
+
...rest
|
|
1567
|
+
} = normalizedProperties;
|
|
1568
|
+
const event = {
|
|
1569
|
+
...rest,
|
|
1570
|
+
[EVENT_TYPE_KEY]: eventType,
|
|
1571
|
+
[EVENT_TIME_KEY]: now,
|
|
1572
|
+
[EVENT_USER_ID_KEY]: this._identity.userId,
|
|
1573
|
+
[EVENT_INSERT_ID_KEY]: this._insertIdGenerator.next(),
|
|
1574
|
+
...this._config.version === void 0 ? {} : {
|
|
1575
|
+
[EVENT_VERSION_KEY]: this._config.version
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
let dropped = false;
|
|
1579
|
+
const drop = () => {
|
|
1580
|
+
dropped = true;
|
|
1581
|
+
};
|
|
1582
|
+
await this._hooks.emitEventCreated({ event, options: resolvedOptions, drop });
|
|
1583
|
+
if (dropped) {
|
|
1584
|
+
return null;
|
|
1585
|
+
}
|
|
1586
|
+
await this._hooks.emitBeforeEnqueue({ event, options: resolvedOptions, drop });
|
|
1587
|
+
if (dropped) {
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
if (typeof event[EVENT_INSERT_ID_KEY] !== "string" || event[EVENT_INSERT_ID_KEY].length === 0) {
|
|
1591
|
+
this._emitError({
|
|
1592
|
+
stage: "event:validation",
|
|
1593
|
+
error: new Error("track() requires event.@insertid")
|
|
1594
|
+
});
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
id: createQueueEntryId(),
|
|
1599
|
+
deviceId: this._identity.deviceId,
|
|
1600
|
+
event,
|
|
1601
|
+
options: resolvedOptions,
|
|
1602
|
+
attempts: 0
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
_resolveTrackOptions(options) {
|
|
1606
|
+
var _a;
|
|
1607
|
+
return {
|
|
1608
|
+
flush: options.flush === "immediate" ? "immediate" : "scheduled",
|
|
1609
|
+
endpoint: (_a = options.endpoint) != null ? _a : this._config.endpoint,
|
|
1610
|
+
timeoutMs: options.timeoutMs,
|
|
1611
|
+
retries: typeof options.retries === "number" && options.retries >= 0 ? Math.floor(options.retries) : this._config.retry.maxAttempts
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
_enqueue(entry) {
|
|
1615
|
+
var _a;
|
|
1616
|
+
let dropped = null;
|
|
1617
|
+
if (this._queue.length >= this._config.maxQueueSize) {
|
|
1618
|
+
dropped = (_a = this._queue.shift()) != null ? _a : null;
|
|
1619
|
+
}
|
|
1620
|
+
this._queue.push(entry);
|
|
1621
|
+
return dropped;
|
|
1622
|
+
}
|
|
1623
|
+
_scheduleAutomaticFlush(delayMs, reason) {
|
|
1624
|
+
if (!this._acceptEvents) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
if (reason === "scheduled" && this._automaticFlushPending) {
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
if (reason === "retry") {
|
|
1631
|
+
this._cancelScheduledFlush();
|
|
1632
|
+
}
|
|
1633
|
+
const enqueueAutomaticFlush = () => {
|
|
1634
|
+
this._flushTask = null;
|
|
1635
|
+
if (!this._acceptEvents) {
|
|
1636
|
+
if (!this._activeAutomaticFlushReason) {
|
|
1637
|
+
this._automaticFlushPending = false;
|
|
1638
|
+
}
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
const completion = createDeferred();
|
|
1642
|
+
const task2 = {
|
|
1643
|
+
kind: "flush",
|
|
1644
|
+
reason,
|
|
1645
|
+
automatic: true,
|
|
1646
|
+
completion
|
|
1647
|
+
};
|
|
1648
|
+
this._enqueueMutation(task2);
|
|
1649
|
+
};
|
|
1650
|
+
this._automaticFlushPending = true;
|
|
1651
|
+
if (delayMs <= 0) {
|
|
1652
|
+
enqueueAutomaticFlush();
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
const task = scheduleTask(() => {
|
|
1656
|
+
enqueueAutomaticFlush();
|
|
1657
|
+
}, delayMs);
|
|
1658
|
+
if (!task) {
|
|
1659
|
+
enqueueAutomaticFlush();
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
this._flushTask = task;
|
|
1663
|
+
}
|
|
1664
|
+
async _performFlush(reason) {
|
|
1665
|
+
await this._hooks.emitBeforeFlush({
|
|
1666
|
+
reason,
|
|
1667
|
+
queuedEvents: this._queue.length
|
|
1668
|
+
});
|
|
1669
|
+
let attempted = 0;
|
|
1670
|
+
let sent = 0;
|
|
1671
|
+
let dropped = 0;
|
|
1672
|
+
let queueMutated = false;
|
|
1673
|
+
let scheduledRetry = false;
|
|
1674
|
+
const removedIds = [];
|
|
1675
|
+
const upsertedEntries = [];
|
|
1676
|
+
const maxEventsForRun = reason === "scheduled" || reason === "retry" ? this._config.maxBatchSize : Number.POSITIVE_INFINITY;
|
|
1677
|
+
const stopAfterFirstRequest = reason === "scheduled" || reason === "retry";
|
|
1678
|
+
while (this._queue.length > 0 && attempted < maxEventsForRun) {
|
|
1679
|
+
const batch = collectCompatibleBatch(this._queue, maxEventsForRun - attempted);
|
|
1680
|
+
if (batch.length === 0) {
|
|
1681
|
+
break;
|
|
1682
|
+
}
|
|
1683
|
+
attempted += batch.length;
|
|
1684
|
+
const { droppedBeforeSendEntries, transportEntries } = await this._prepareBatchForTransport(
|
|
1685
|
+
reason,
|
|
1686
|
+
batch
|
|
1687
|
+
);
|
|
1688
|
+
if (droppedBeforeSendEntries.length > 0) {
|
|
1689
|
+
queueMutated = true;
|
|
1690
|
+
dropped += droppedBeforeSendEntries.length;
|
|
1691
|
+
removedIds.push(
|
|
1692
|
+
...await this._removeDroppedBeforeSendEntries(
|
|
1693
|
+
reason,
|
|
1694
|
+
batch,
|
|
1695
|
+
transportEntries,
|
|
1696
|
+
droppedBeforeSendEntries
|
|
1697
|
+
)
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
if (transportEntries.length === 0) {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
const txTime = Date.now();
|
|
1704
|
+
const request = this._createTransportRequest(transportEntries, txTime);
|
|
1705
|
+
const result = await this._transport.send(request);
|
|
1706
|
+
if (result.ok) {
|
|
1707
|
+
queueMutated = true;
|
|
1708
|
+
sent += transportEntries.length;
|
|
1709
|
+
removedIds.push(
|
|
1710
|
+
...await this._handleSuccessfulTransportBatch(reason, transportEntries, txTime, result)
|
|
1711
|
+
);
|
|
1712
|
+
if (stopAfterFirstRequest) {
|
|
1713
|
+
break;
|
|
1714
|
+
}
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
const failedTransportBatch = await this._handleFailedTransportBatch(
|
|
1718
|
+
reason,
|
|
1719
|
+
transportEntries,
|
|
1720
|
+
txTime,
|
|
1721
|
+
result
|
|
1722
|
+
);
|
|
1723
|
+
queueMutated = true;
|
|
1724
|
+
dropped += failedTransportBatch.dropped;
|
|
1725
|
+
removedIds.push(...failedTransportBatch.removedIds);
|
|
1726
|
+
upsertedEntries.push(...failedTransportBatch.upsertedEntries);
|
|
1727
|
+
scheduledRetry || (scheduledRetry = failedTransportBatch.scheduledRetry);
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
if (queueMutated) {
|
|
1731
|
+
await this._upsertQueueEntries(upsertedEntries);
|
|
1732
|
+
await this._removeQueueEntries(removedIds);
|
|
1733
|
+
}
|
|
1734
|
+
const pendingAutomaticFlush = this._automaticFlushPending && this._activeAutomaticFlushReason === null;
|
|
1735
|
+
if (this._acceptEvents && !scheduledRetry && this._queue.length > 0 && !this._flushTask && !pendingAutomaticFlush) {
|
|
1736
|
+
this._scheduleAutomaticFlush(this._config.flushIntervalMs, "scheduled");
|
|
1737
|
+
}
|
|
1738
|
+
await this._hooks.emitAfterFlush({
|
|
1739
|
+
reason,
|
|
1740
|
+
attempted,
|
|
1741
|
+
sent,
|
|
1742
|
+
dropped,
|
|
1743
|
+
queuedEvents: this._queue.length
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
_computeBackoff(attempt) {
|
|
1747
|
+
const exponential = this._config.retry.baseMs * 2 ** Math.max(0, attempt - 1);
|
|
1748
|
+
const clamped = Math.min(this._config.retry.maxMs, exponential);
|
|
1749
|
+
if (this._config.retry.jitter <= 0) {
|
|
1750
|
+
return clamped;
|
|
1751
|
+
}
|
|
1752
|
+
const jitterWindow = clamped * this._config.retry.jitter;
|
|
1753
|
+
const randomOffset = (Math.random() * 2 - 1) * jitterWindow;
|
|
1754
|
+
return Math.max(1, Math.floor(clamped + randomOffset));
|
|
1755
|
+
}
|
|
1756
|
+
async _persistIdentity() {
|
|
1757
|
+
try {
|
|
1758
|
+
await this._storage.identity.set(IDENTITY_STORAGE_KEY, {
|
|
1759
|
+
userId: this._identity.userId,
|
|
1760
|
+
deviceId: this._identity.deviceId,
|
|
1761
|
+
optOut: this._optOut
|
|
1762
|
+
});
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
this._emitError({ stage: "storage:persist-identity", error });
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
async _emitIdentityChanged(previousIdentity, reason) {
|
|
1768
|
+
await this._hooks.emitIdentityChanged({
|
|
1769
|
+
reason,
|
|
1770
|
+
previousIdentity,
|
|
1771
|
+
identity: cloneIdentity(this._identity)
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
_cancelScheduledFlush() {
|
|
1775
|
+
if (!this._flushTask) {
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
this._flushTask.cancel();
|
|
1779
|
+
this._flushTask = null;
|
|
1780
|
+
if (!this._activeAutomaticFlushReason) {
|
|
1781
|
+
this._automaticFlushPending = false;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
async _clearQueueStorage() {
|
|
1785
|
+
if (!this._config.persistQueue) {
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
try {
|
|
1789
|
+
await this._storage.queue.clear();
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
this._emitError({ stage: "storage:clear-queue", error });
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
async _upsertQueueEntries(entries) {
|
|
1795
|
+
if (!this._config.persistQueue) {
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (entries.length === 0) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1802
|
+
for (const entry of entries) {
|
|
1803
|
+
byId.set(entry.id, entry);
|
|
1804
|
+
}
|
|
1805
|
+
try {
|
|
1806
|
+
await this._storage.queue.upsert([...byId.values()]);
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
this._emitError({ stage: "storage:upsert-queue", error });
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
async _removeQueueEntries(ids) {
|
|
1812
|
+
if (!this._config.persistQueue) {
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
if (ids.length === 0) {
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
const uniqueIds = [...new Set(ids)];
|
|
1819
|
+
try {
|
|
1820
|
+
await this._storage.queue.remove(uniqueIds);
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
this._emitError({ stage: "storage:remove-queue", error });
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
async _trimQueueStorage() {
|
|
1826
|
+
if (!this._config.persistQueue) {
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
try {
|
|
1830
|
+
await this._storage.queue.trim(this._config.maxQueueSize);
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
this._emitError({ stage: "storage:trim-queue", error });
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
_emitError(context) {
|
|
1836
|
+
this._hooks.emitError(context);
|
|
1837
|
+
}
|
|
1838
|
+
async _prepareBatchForTransport(reason, batch) {
|
|
1839
|
+
const droppedBeforeSendEntries = [];
|
|
1840
|
+
const transportEntries = [];
|
|
1841
|
+
for (const entry of batch) {
|
|
1842
|
+
const attempt = entry.attempts + 1;
|
|
1843
|
+
let droppedBeforeSend = false;
|
|
1844
|
+
const drop = () => {
|
|
1845
|
+
droppedBeforeSend = true;
|
|
1846
|
+
};
|
|
1847
|
+
await this._hooks.emitBeforeSend({
|
|
1848
|
+
reason,
|
|
1849
|
+
event: entry.event,
|
|
1850
|
+
options: entry.options,
|
|
1851
|
+
attempt,
|
|
1852
|
+
drop
|
|
1853
|
+
});
|
|
1854
|
+
if (droppedBeforeSend) {
|
|
1855
|
+
droppedBeforeSendEntries.push({ attempt, entry });
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
this._stripBlockedProperties(entry.event);
|
|
1859
|
+
transportEntries.push({ attempt, entry });
|
|
1860
|
+
}
|
|
1861
|
+
return {
|
|
1862
|
+
droppedBeforeSendEntries,
|
|
1863
|
+
transportEntries
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
async _removeDroppedBeforeSendEntries(reason, batch, transportEntries, droppedBeforeSendEntries) {
|
|
1867
|
+
const removedIds = droppedBeforeSendEntries.map(({ entry }) => entry.id);
|
|
1868
|
+
this._queue.splice(0, batch.length, ...transportEntries.map(({ entry }) => entry));
|
|
1869
|
+
for (const { attempt, entry } of droppedBeforeSendEntries) {
|
|
1870
|
+
await this._hooks.emitAfterSend({
|
|
1871
|
+
reason,
|
|
1872
|
+
event: entry.event,
|
|
1873
|
+
options: entry.options,
|
|
1874
|
+
attempt,
|
|
1875
|
+
outcome: "dropped",
|
|
1876
|
+
queuedEvents: this._queue.length
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
return removedIds;
|
|
1880
|
+
}
|
|
1881
|
+
_createTransportRequest(transportEntries, txTime) {
|
|
1882
|
+
const [firstTransportEntry] = transportEntries;
|
|
1883
|
+
if (!firstTransportEntry) {
|
|
1884
|
+
throw new Error("Transport request requires at least one queue entry");
|
|
1885
|
+
}
|
|
1886
|
+
const requestEntries = transportEntries.map(({ entry }) => entry);
|
|
1887
|
+
const locationFromIpEnabled = this._config.locationFromIp;
|
|
1888
|
+
return {
|
|
1889
|
+
apiKey: this._apiKey,
|
|
1890
|
+
endpoint: firstTransportEntry.entry.options.endpoint,
|
|
1891
|
+
// Keep both request views derived from the same queue batch.
|
|
1892
|
+
events: requestEntries.map(({ event }) => event),
|
|
1893
|
+
eventGroups: createTransportGroups(requestEntries),
|
|
1894
|
+
locationFromIp: locationFromIpEnabled,
|
|
1895
|
+
txTime,
|
|
1896
|
+
timeoutMs: firstTransportEntry.entry.options.timeoutMs
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
async _handleSuccessfulTransportBatch(reason, transportEntries, txTime, result) {
|
|
1900
|
+
const removedIds = transportEntries.map(({ entry }) => entry.id);
|
|
1901
|
+
this._queue.splice(0, transportEntries.length);
|
|
1902
|
+
for (const { attempt, entry } of transportEntries) {
|
|
1903
|
+
await this._hooks.emitAfterSend({
|
|
1904
|
+
reason,
|
|
1905
|
+
event: entry.event,
|
|
1906
|
+
options: entry.options,
|
|
1907
|
+
attempt,
|
|
1908
|
+
outcome: "sent",
|
|
1909
|
+
txTime,
|
|
1910
|
+
result,
|
|
1911
|
+
queuedEvents: this._queue.length
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
return removedIds;
|
|
1915
|
+
}
|
|
1916
|
+
async _handleFailedTransportBatch(reason, transportEntries, txTime, result) {
|
|
1917
|
+
var _a, _b, _c;
|
|
1918
|
+
const status = (_a = result.status) != null ? _a : 0;
|
|
1919
|
+
const retryable = (_b = result.retryable) != null ? _b : status >= 500 || status === 429 || status === 0;
|
|
1920
|
+
this._emitTransportFailure(transportEntries, result, status);
|
|
1921
|
+
const retryEntries = [];
|
|
1922
|
+
const droppedAfterFailureEntries = [];
|
|
1923
|
+
for (const transportEntry of transportEntries) {
|
|
1924
|
+
if (retryable && transportEntry.entry.attempts < transportEntry.entry.options.retries) {
|
|
1925
|
+
transportEntry.entry.attempts += 1;
|
|
1926
|
+
retryEntries.push(transportEntry);
|
|
1927
|
+
continue;
|
|
1928
|
+
}
|
|
1929
|
+
droppedAfterFailureEntries.push(transportEntry);
|
|
1930
|
+
}
|
|
1931
|
+
this._queue.splice(0, transportEntries.length, ...retryEntries.map(({ entry }) => entry));
|
|
1932
|
+
let scheduledRetry = false;
|
|
1933
|
+
if (retryEntries.length > 0 && this._acceptEvents) {
|
|
1934
|
+
const [firstRetryEntry] = retryEntries;
|
|
1935
|
+
const delay = this._computeBackoff((_c = firstRetryEntry == null ? void 0 : firstRetryEntry.entry.attempts) != null ? _c : 1);
|
|
1936
|
+
this._scheduleAutomaticFlush(delay, "retry");
|
|
1937
|
+
scheduledRetry = true;
|
|
1938
|
+
}
|
|
1939
|
+
for (const { attempt, entry } of retryEntries) {
|
|
1940
|
+
await this._hooks.emitAfterSend({
|
|
1941
|
+
reason,
|
|
1942
|
+
event: entry.event,
|
|
1943
|
+
options: entry.options,
|
|
1944
|
+
attempt,
|
|
1945
|
+
outcome: "retry",
|
|
1946
|
+
txTime,
|
|
1947
|
+
result,
|
|
1948
|
+
queuedEvents: this._queue.length
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
for (const { attempt, entry } of droppedAfterFailureEntries) {
|
|
1952
|
+
await this._hooks.emitAfterSend({
|
|
1953
|
+
reason,
|
|
1954
|
+
event: entry.event,
|
|
1955
|
+
options: entry.options,
|
|
1956
|
+
attempt,
|
|
1957
|
+
outcome: "dropped",
|
|
1958
|
+
result,
|
|
1959
|
+
queuedEvents: this._queue.length
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
return {
|
|
1963
|
+
dropped: droppedAfterFailureEntries.length,
|
|
1964
|
+
removedIds: droppedAfterFailureEntries.map(({ entry }) => entry.id),
|
|
1965
|
+
scheduledRetry,
|
|
1966
|
+
upsertedEntries: retryEntries.map(({ entry }) => entry)
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
_emitTransportFailure(transportEntries, result, status) {
|
|
1970
|
+
var _a;
|
|
1971
|
+
const failedInsertIds = transportEntries.map(({ entry }) => {
|
|
1972
|
+
var _a2;
|
|
1973
|
+
return String((_a2 = entry.event[EVENT_INSERT_ID_KEY]) != null ? _a2 : "unknown");
|
|
1974
|
+
}).join(", ");
|
|
1975
|
+
this._emitError({
|
|
1976
|
+
stage: "transport:send",
|
|
1977
|
+
error: (_a = result.error) != null ? _a : new Error(`Transport failed with status ${status} for events ${failedInsertIds}`)
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
_stripBlockedProperties(properties) {
|
|
1981
|
+
if (!this._blockedProperties) {
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
for (const property of this._blockedProperties) {
|
|
1985
|
+
delete properties[property];
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1990
|
+
0 && (module.exports = {
|
|
1991
|
+
HookRegistry,
|
|
1992
|
+
InMemoryStorage,
|
|
1993
|
+
SignalClient,
|
|
1994
|
+
createFetchTransport
|
|
1995
|
+
});
|