@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/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
+ });