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