@analyticscli/sdk 0.1.0-preview.7 → 0.1.0-preview.9
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/browser.cjs +190 -8
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/{chunk-O4FIH647.js → chunk-FRXYHOBX.js} +190 -8
- package/dist/index.cjs +190 -8
- package/dist/index.d.cts +76 -1
- package/dist/index.d.ts +76 -1
- package/dist/index.js +1 -1
- package/dist/react-native.cjs +190 -8
- package/dist/react-native.d.cts +1 -1
- package/dist/react-native.d.ts +1 -1
- package/dist/react-native.js +1 -1
- package/package.json +1 -1
package/dist/browser.cjs
CHANGED
|
@@ -587,6 +587,49 @@ var sanitizeSurveyResponseInput = (input) => {
|
|
|
587
587
|
|
|
588
588
|
// src/analytics-client.ts
|
|
589
589
|
var DEFAULT_CONSENT_STORAGE_KEY = "analyticscli:consent:v1";
|
|
590
|
+
var AUTH_FAILURE_FLUSH_PAUSE_MS = 6e4;
|
|
591
|
+
var resolveDefaultOsNameFromPlatform = (platform) => {
|
|
592
|
+
if (!platform) {
|
|
593
|
+
return void 0;
|
|
594
|
+
}
|
|
595
|
+
if (platform === "ios") {
|
|
596
|
+
return "iOS";
|
|
597
|
+
}
|
|
598
|
+
if (platform === "android") {
|
|
599
|
+
return "Android";
|
|
600
|
+
}
|
|
601
|
+
if (platform === "web") {
|
|
602
|
+
return "Web";
|
|
603
|
+
}
|
|
604
|
+
if (platform === "mac") {
|
|
605
|
+
return "macOS";
|
|
606
|
+
}
|
|
607
|
+
if (platform === "windows") {
|
|
608
|
+
return "Windows";
|
|
609
|
+
}
|
|
610
|
+
return void 0;
|
|
611
|
+
};
|
|
612
|
+
var IngestSendError = class extends Error {
|
|
613
|
+
retryable;
|
|
614
|
+
attempts;
|
|
615
|
+
status;
|
|
616
|
+
errorCode;
|
|
617
|
+
serverMessage;
|
|
618
|
+
requestId;
|
|
619
|
+
constructor(input) {
|
|
620
|
+
super(input.message);
|
|
621
|
+
this.name = "IngestSendError";
|
|
622
|
+
this.retryable = input.retryable;
|
|
623
|
+
this.attempts = input.attempts;
|
|
624
|
+
this.status = input.status;
|
|
625
|
+
this.errorCode = input.errorCode;
|
|
626
|
+
this.serverMessage = input.serverMessage;
|
|
627
|
+
this.requestId = input.requestId;
|
|
628
|
+
if (input.cause !== void 0) {
|
|
629
|
+
this.cause = input.cause;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
590
633
|
var AnalyticsClient = class {
|
|
591
634
|
apiKey;
|
|
592
635
|
hasIngestConfig;
|
|
@@ -595,6 +638,7 @@ var AnalyticsClient = class {
|
|
|
595
638
|
flushIntervalMs;
|
|
596
639
|
maxRetries;
|
|
597
640
|
debug;
|
|
641
|
+
onIngestError;
|
|
598
642
|
platform;
|
|
599
643
|
projectSurface;
|
|
600
644
|
appVersion;
|
|
@@ -627,6 +671,7 @@ var AnalyticsClient = class {
|
|
|
627
671
|
deferredEventsBeforeHydration = [];
|
|
628
672
|
onboardingStepViewStateSessionId = null;
|
|
629
673
|
onboardingStepViewsSeen = /* @__PURE__ */ new Set();
|
|
674
|
+
flushPausedUntilMs = 0;
|
|
630
675
|
constructor(options) {
|
|
631
676
|
const normalizedOptions = this.normalizeOptions(options);
|
|
632
677
|
this.apiKey = this.readRequiredStringOption(normalizedOptions.apiKey);
|
|
@@ -639,11 +684,17 @@ var AnalyticsClient = class {
|
|
|
639
684
|
this.flushIntervalMs = normalizedOptions.flushIntervalMs ?? 5e3;
|
|
640
685
|
this.maxRetries = normalizedOptions.maxRetries ?? 4;
|
|
641
686
|
this.debug = normalizedOptions.debug ?? false;
|
|
687
|
+
this.onIngestError = typeof normalizedOptions.onIngestError === "function" ? normalizedOptions.onIngestError : null;
|
|
642
688
|
this.platform = this.normalizePlatformOption(normalizedOptions.platform) ?? detectDefaultPlatform();
|
|
643
689
|
this.projectSurface = this.normalizeProjectSurfaceOption(normalizedOptions.projectSurface);
|
|
644
690
|
this.appVersion = this.readRequiredStringOption(normalizedOptions.appVersion) || detectDefaultAppVersion();
|
|
645
691
|
this.identityTrackingMode = this.resolveIdentityTrackingModeOption(normalizedOptions);
|
|
646
|
-
|
|
692
|
+
const initialContext = { ...normalizedOptions.context ?? {} };
|
|
693
|
+
const hasExplicitOsName = this.readRequiredStringOption(initialContext.osName).length > 0;
|
|
694
|
+
this.context = {
|
|
695
|
+
...initialContext,
|
|
696
|
+
osName: hasExplicitOsName ? initialContext.osName : resolveDefaultOsNameFromPlatform(this.platform) ?? initialContext.osName
|
|
697
|
+
};
|
|
647
698
|
this.runtimeEnv = detectRuntimeEnv();
|
|
648
699
|
this.persistConsentState = normalizedOptions.persistConsentState ?? false;
|
|
649
700
|
this.consentStorageKey = this.readRequiredStringOption(normalizedOptions.consentStorageKey) || DEFAULT_CONSENT_STORAGE_KEY;
|
|
@@ -676,6 +727,7 @@ var AnalyticsClient = class {
|
|
|
676
727
|
this.writePersistedConsent(this.configuredStorage, this.fullTrackingConsentGranted);
|
|
677
728
|
}
|
|
678
729
|
this.hydrationPromise = this.hydrateIdentityFromStorage();
|
|
730
|
+
this.enqueueInitialSessionStart();
|
|
679
731
|
this.startAutoFlush();
|
|
680
732
|
}
|
|
681
733
|
/**
|
|
@@ -733,6 +785,32 @@ var AnalyticsClient = class {
|
|
|
733
785
|
...context
|
|
734
786
|
};
|
|
735
787
|
}
|
|
788
|
+
enqueueInitialSessionStart() {
|
|
789
|
+
if (!this.consentGranted) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (this.shouldDeferEventsUntilHydrated()) {
|
|
793
|
+
this.deferEventUntilHydrated(() => {
|
|
794
|
+
this.enqueueInitialSessionStart();
|
|
795
|
+
});
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const sessionId = this.getSessionId();
|
|
799
|
+
this.enqueue({
|
|
800
|
+
eventId: randomId(),
|
|
801
|
+
eventName: "session_start",
|
|
802
|
+
ts: nowIso(),
|
|
803
|
+
sessionId,
|
|
804
|
+
anonId: this.anonId,
|
|
805
|
+
userId: this.getEventUserId(),
|
|
806
|
+
properties: this.withRuntimeMetadata({ source: "sdk_mount" }, sessionId),
|
|
807
|
+
platform: this.platform,
|
|
808
|
+
projectSurface: this.projectSurface,
|
|
809
|
+
appVersion: this.appVersion,
|
|
810
|
+
...this.withEventContext(),
|
|
811
|
+
type: "track"
|
|
812
|
+
});
|
|
813
|
+
}
|
|
736
814
|
/**
|
|
737
815
|
* Associates following events with a known user id.
|
|
738
816
|
* Anonymous history remains linked by anonId/sessionId.
|
|
@@ -1102,6 +1180,9 @@ var AnalyticsClient = class {
|
|
|
1102
1180
|
if (this.queue.length === 0 || this.isFlushing || !this.consentGranted) {
|
|
1103
1181
|
return;
|
|
1104
1182
|
}
|
|
1183
|
+
if (Date.now() < this.flushPausedUntilMs) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1105
1186
|
this.isFlushing = true;
|
|
1106
1187
|
const batch = this.queue.splice(0, this.batchSize);
|
|
1107
1188
|
const payload = {
|
|
@@ -1116,9 +1197,20 @@ var AnalyticsClient = class {
|
|
|
1116
1197
|
}
|
|
1117
1198
|
try {
|
|
1118
1199
|
await this.sendWithRetry(payload);
|
|
1200
|
+
this.flushPausedUntilMs = 0;
|
|
1119
1201
|
} catch (error) {
|
|
1120
|
-
this.log("Send failed permanently, requeueing batch", error);
|
|
1121
1202
|
this.queue = [...batch, ...this.queue];
|
|
1203
|
+
const ingestError = this.toIngestSendError(error);
|
|
1204
|
+
const diagnostics = this.createIngestDiagnostics(ingestError, batch.length, this.queue.length);
|
|
1205
|
+
if (ingestError.status === 401 || ingestError.status === 403) {
|
|
1206
|
+
this.flushPausedUntilMs = Date.now() + AUTH_FAILURE_FLUSH_PAUSE_MS;
|
|
1207
|
+
this.log("Pausing ingest flush after auth failure", {
|
|
1208
|
+
status: ingestError.status,
|
|
1209
|
+
retryAfterMs: AUTH_FAILURE_FLUSH_PAUSE_MS
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
this.log("Send failed permanently, requeueing batch", diagnostics);
|
|
1213
|
+
this.reportIngestError(diagnostics);
|
|
1122
1214
|
} finally {
|
|
1123
1215
|
this.isFlushing = false;
|
|
1124
1216
|
}
|
|
@@ -1147,9 +1239,8 @@ var AnalyticsClient = class {
|
|
|
1147
1239
|
});
|
|
1148
1240
|
}
|
|
1149
1241
|
async sendWithRetry(payload) {
|
|
1150
|
-
let attempt = 0;
|
|
1151
1242
|
let delay = 250;
|
|
1152
|
-
|
|
1243
|
+
for (let attempt = 1; attempt <= this.maxRetries + 1; attempt += 1) {
|
|
1153
1244
|
try {
|
|
1154
1245
|
const response = await fetch(`${this.endpoint}/v1/collect`, {
|
|
1155
1246
|
method: "POST",
|
|
@@ -1161,19 +1252,110 @@ var AnalyticsClient = class {
|
|
|
1161
1252
|
keepalive: true
|
|
1162
1253
|
});
|
|
1163
1254
|
if (!response.ok) {
|
|
1164
|
-
throw
|
|
1255
|
+
throw await this.createHttpIngestSendError(response, attempt);
|
|
1165
1256
|
}
|
|
1166
1257
|
return;
|
|
1167
1258
|
} catch (error) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1259
|
+
const normalized = this.toIngestSendError(error, attempt);
|
|
1260
|
+
const finalAttempt = attempt >= this.maxRetries + 1;
|
|
1261
|
+
this.log("Ingest attempt failed", {
|
|
1262
|
+
attempt: normalized.attempts,
|
|
1263
|
+
maxRetries: this.maxRetries,
|
|
1264
|
+
retryable: normalized.retryable,
|
|
1265
|
+
status: normalized.status,
|
|
1266
|
+
errorCode: normalized.errorCode,
|
|
1267
|
+
requestId: normalized.requestId,
|
|
1268
|
+
nextRetryInMs: !finalAttempt && normalized.retryable ? delay : null
|
|
1269
|
+
});
|
|
1270
|
+
if (finalAttempt || !normalized.retryable) {
|
|
1271
|
+
throw normalized;
|
|
1171
1272
|
}
|
|
1172
1273
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1173
1274
|
delay *= 2;
|
|
1174
1275
|
}
|
|
1175
1276
|
}
|
|
1176
1277
|
}
|
|
1278
|
+
async createHttpIngestSendError(response, attempts) {
|
|
1279
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("cf-ray") ?? void 0;
|
|
1280
|
+
let errorCode;
|
|
1281
|
+
let serverMessage;
|
|
1282
|
+
try {
|
|
1283
|
+
const parsed = await response.json();
|
|
1284
|
+
const errorBody = parsed && typeof parsed === "object" && parsed.error && typeof parsed.error === "object" ? parsed.error : void 0;
|
|
1285
|
+
if (typeof errorBody?.code === "string") {
|
|
1286
|
+
errorCode = errorBody.code;
|
|
1287
|
+
}
|
|
1288
|
+
if (typeof errorBody?.message === "string") {
|
|
1289
|
+
serverMessage = errorBody.message;
|
|
1290
|
+
}
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
const retryable = this.shouldRetryHttpStatus(response.status);
|
|
1294
|
+
const statusSuffix = errorCode ? ` ${errorCode}` : "";
|
|
1295
|
+
const message = `ingest status=${response.status}${statusSuffix}`;
|
|
1296
|
+
return new IngestSendError({
|
|
1297
|
+
message,
|
|
1298
|
+
retryable,
|
|
1299
|
+
attempts,
|
|
1300
|
+
status: response.status,
|
|
1301
|
+
errorCode,
|
|
1302
|
+
serverMessage,
|
|
1303
|
+
requestId
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
shouldRetryHttpStatus(status) {
|
|
1307
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
1308
|
+
}
|
|
1309
|
+
toIngestSendError(error, attempts) {
|
|
1310
|
+
if (error instanceof IngestSendError) {
|
|
1311
|
+
const resolvedAttempts = attempts ?? error.attempts;
|
|
1312
|
+
return new IngestSendError({
|
|
1313
|
+
message: error.message,
|
|
1314
|
+
retryable: error.retryable,
|
|
1315
|
+
attempts: resolvedAttempts,
|
|
1316
|
+
status: error.status,
|
|
1317
|
+
errorCode: error.errorCode,
|
|
1318
|
+
serverMessage: error.serverMessage,
|
|
1319
|
+
requestId: error.requestId,
|
|
1320
|
+
cause: error.cause
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
const fallbackMessage = error instanceof Error ? error.message : "ingest request failed";
|
|
1324
|
+
return new IngestSendError({
|
|
1325
|
+
message: fallbackMessage,
|
|
1326
|
+
retryable: true,
|
|
1327
|
+
attempts: attempts ?? 1,
|
|
1328
|
+
cause: error
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
createIngestDiagnostics(error, batchSize, queueSize) {
|
|
1332
|
+
return {
|
|
1333
|
+
name: "AnalyticsIngestError",
|
|
1334
|
+
message: error.message,
|
|
1335
|
+
endpoint: this.endpoint,
|
|
1336
|
+
path: "/v1/collect",
|
|
1337
|
+
status: error.status,
|
|
1338
|
+
errorCode: error.errorCode,
|
|
1339
|
+
serverMessage: error.serverMessage,
|
|
1340
|
+
requestId: error.requestId,
|
|
1341
|
+
retryable: error.retryable,
|
|
1342
|
+
attempts: error.attempts,
|
|
1343
|
+
maxRetries: this.maxRetries,
|
|
1344
|
+
batchSize,
|
|
1345
|
+
queueSize,
|
|
1346
|
+
timestamp: nowIso()
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
reportIngestError(error) {
|
|
1350
|
+
if (!this.onIngestError) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
try {
|
|
1354
|
+
this.onIngestError(error);
|
|
1355
|
+
} catch (callbackError) {
|
|
1356
|
+
this.log("onIngestError callback threw", callbackError);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1177
1359
|
parsePersistedConsent(raw) {
|
|
1178
1360
|
if (raw === "granted") {
|
|
1179
1361
|
return true;
|
package/dist/browser.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { AnalyticsClient, AnalyticsClientOptions, AnalyticsConsentState, AnalyticsContext, AnalyticsContextConsentControls, AnalyticsContextUserControls, AnalyticsStorageAdapter, CreateAnalyticsContextOptions, EventContext, EventProperties, IdentityTrackingMode, InitInput, InitOptions, ONBOARDING_EVENTS, ONBOARDING_PROGRESS_EVENT_ORDER, ONBOARDING_SCREEN_EVENT_PREFIXES, ONBOARDING_SURVEY_EVENTS, OnboardingEventName, OnboardingEventProperties, OnboardingStepTracker, OnboardingSurveyAnswerType, OnboardingSurveyEventName, OnboardingSurveyResponseInput, OnboardingTracker, OnboardingTrackerDefaults, OnboardingTrackerSurveyInput, PAYWALL_ANCHOR_EVENT_CANDIDATES, PAYWALL_EVENTS, PAYWALL_JOURNEY_EVENT_ORDER, PAYWALL_SKIP_EVENT_CANDIDATES, PURCHASE_EVENTS, PURCHASE_SUCCESS_EVENT_CANDIDATES, PaywallEventName, PaywallEventProperties, PaywallJourneyEventName, PaywallTracker, PaywallTrackerDefaults, PaywallTrackerProperties, PurchaseEventName, SetConsentOptions, createAnalyticsContext, init, initAsync, initConsentFirst, initConsentFirstAsync } from './index.cjs';
|
|
1
|
+
export { AnalyticsClient, AnalyticsClientOptions, AnalyticsConsentState, AnalyticsContext, AnalyticsContextConsentControls, AnalyticsContextUserControls, AnalyticsIngestError, AnalyticsIngestErrorHandler, AnalyticsStorageAdapter, CreateAnalyticsContextOptions, EventContext, EventProperties, IdentityTrackingMode, InitInput, InitOptions, ONBOARDING_EVENTS, ONBOARDING_PROGRESS_EVENT_ORDER, ONBOARDING_SCREEN_EVENT_PREFIXES, ONBOARDING_SURVEY_EVENTS, OnboardingEventName, OnboardingEventProperties, OnboardingStepTracker, OnboardingSurveyAnswerType, OnboardingSurveyEventName, OnboardingSurveyResponseInput, OnboardingTracker, OnboardingTrackerDefaults, OnboardingTrackerSurveyInput, PAYWALL_ANCHOR_EVENT_CANDIDATES, PAYWALL_EVENTS, PAYWALL_JOURNEY_EVENT_ORDER, PAYWALL_SKIP_EVENT_CANDIDATES, PURCHASE_EVENTS, PURCHASE_SUCCESS_EVENT_CANDIDATES, PaywallEventName, PaywallEventProperties, PaywallJourneyEventName, PaywallTracker, PaywallTrackerDefaults, PaywallTrackerProperties, PurchaseEventName, SetConsentOptions, createAnalyticsContext, init, initAsync, initConsentFirst, initConsentFirstAsync } from './index.cjs';
|
package/dist/browser.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { AnalyticsClient, AnalyticsClientOptions, AnalyticsConsentState, AnalyticsContext, AnalyticsContextConsentControls, AnalyticsContextUserControls, AnalyticsStorageAdapter, CreateAnalyticsContextOptions, EventContext, EventProperties, IdentityTrackingMode, InitInput, InitOptions, ONBOARDING_EVENTS, ONBOARDING_PROGRESS_EVENT_ORDER, ONBOARDING_SCREEN_EVENT_PREFIXES, ONBOARDING_SURVEY_EVENTS, OnboardingEventName, OnboardingEventProperties, OnboardingStepTracker, OnboardingSurveyAnswerType, OnboardingSurveyEventName, OnboardingSurveyResponseInput, OnboardingTracker, OnboardingTrackerDefaults, OnboardingTrackerSurveyInput, PAYWALL_ANCHOR_EVENT_CANDIDATES, PAYWALL_EVENTS, PAYWALL_JOURNEY_EVENT_ORDER, PAYWALL_SKIP_EVENT_CANDIDATES, PURCHASE_EVENTS, PURCHASE_SUCCESS_EVENT_CANDIDATES, PaywallEventName, PaywallEventProperties, PaywallJourneyEventName, PaywallTracker, PaywallTrackerDefaults, PaywallTrackerProperties, PurchaseEventName, SetConsentOptions, createAnalyticsContext, init, initAsync, initConsentFirst, initConsentFirstAsync } from './index.js';
|
|
1
|
+
export { AnalyticsClient, AnalyticsClientOptions, AnalyticsConsentState, AnalyticsContext, AnalyticsContextConsentControls, AnalyticsContextUserControls, AnalyticsIngestError, AnalyticsIngestErrorHandler, AnalyticsStorageAdapter, CreateAnalyticsContextOptions, EventContext, EventProperties, IdentityTrackingMode, InitInput, InitOptions, ONBOARDING_EVENTS, ONBOARDING_PROGRESS_EVENT_ORDER, ONBOARDING_SCREEN_EVENT_PREFIXES, ONBOARDING_SURVEY_EVENTS, OnboardingEventName, OnboardingEventProperties, OnboardingStepTracker, OnboardingSurveyAnswerType, OnboardingSurveyEventName, OnboardingSurveyResponseInput, OnboardingTracker, OnboardingTrackerDefaults, OnboardingTrackerSurveyInput, PAYWALL_ANCHOR_EVENT_CANDIDATES, PAYWALL_EVENTS, PAYWALL_JOURNEY_EVENT_ORDER, PAYWALL_SKIP_EVENT_CANDIDATES, PURCHASE_EVENTS, PURCHASE_SUCCESS_EVENT_CANDIDATES, PaywallEventName, PaywallEventProperties, PaywallJourneyEventName, PaywallTracker, PaywallTrackerDefaults, PaywallTrackerProperties, PurchaseEventName, SetConsentOptions, createAnalyticsContext, init, initAsync, initConsentFirst, initConsentFirstAsync } from './index.js';
|
package/dist/browser.js
CHANGED
|
@@ -546,6 +546,49 @@ var sanitizeSurveyResponseInput = (input) => {
|
|
|
546
546
|
|
|
547
547
|
// src/analytics-client.ts
|
|
548
548
|
var DEFAULT_CONSENT_STORAGE_KEY = "analyticscli:consent:v1";
|
|
549
|
+
var AUTH_FAILURE_FLUSH_PAUSE_MS = 6e4;
|
|
550
|
+
var resolveDefaultOsNameFromPlatform = (platform) => {
|
|
551
|
+
if (!platform) {
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
if (platform === "ios") {
|
|
555
|
+
return "iOS";
|
|
556
|
+
}
|
|
557
|
+
if (platform === "android") {
|
|
558
|
+
return "Android";
|
|
559
|
+
}
|
|
560
|
+
if (platform === "web") {
|
|
561
|
+
return "Web";
|
|
562
|
+
}
|
|
563
|
+
if (platform === "mac") {
|
|
564
|
+
return "macOS";
|
|
565
|
+
}
|
|
566
|
+
if (platform === "windows") {
|
|
567
|
+
return "Windows";
|
|
568
|
+
}
|
|
569
|
+
return void 0;
|
|
570
|
+
};
|
|
571
|
+
var IngestSendError = class extends Error {
|
|
572
|
+
retryable;
|
|
573
|
+
attempts;
|
|
574
|
+
status;
|
|
575
|
+
errorCode;
|
|
576
|
+
serverMessage;
|
|
577
|
+
requestId;
|
|
578
|
+
constructor(input) {
|
|
579
|
+
super(input.message);
|
|
580
|
+
this.name = "IngestSendError";
|
|
581
|
+
this.retryable = input.retryable;
|
|
582
|
+
this.attempts = input.attempts;
|
|
583
|
+
this.status = input.status;
|
|
584
|
+
this.errorCode = input.errorCode;
|
|
585
|
+
this.serverMessage = input.serverMessage;
|
|
586
|
+
this.requestId = input.requestId;
|
|
587
|
+
if (input.cause !== void 0) {
|
|
588
|
+
this.cause = input.cause;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
};
|
|
549
592
|
var AnalyticsClient = class {
|
|
550
593
|
apiKey;
|
|
551
594
|
hasIngestConfig;
|
|
@@ -554,6 +597,7 @@ var AnalyticsClient = class {
|
|
|
554
597
|
flushIntervalMs;
|
|
555
598
|
maxRetries;
|
|
556
599
|
debug;
|
|
600
|
+
onIngestError;
|
|
557
601
|
platform;
|
|
558
602
|
projectSurface;
|
|
559
603
|
appVersion;
|
|
@@ -586,6 +630,7 @@ var AnalyticsClient = class {
|
|
|
586
630
|
deferredEventsBeforeHydration = [];
|
|
587
631
|
onboardingStepViewStateSessionId = null;
|
|
588
632
|
onboardingStepViewsSeen = /* @__PURE__ */ new Set();
|
|
633
|
+
flushPausedUntilMs = 0;
|
|
589
634
|
constructor(options) {
|
|
590
635
|
const normalizedOptions = this.normalizeOptions(options);
|
|
591
636
|
this.apiKey = this.readRequiredStringOption(normalizedOptions.apiKey);
|
|
@@ -598,11 +643,17 @@ var AnalyticsClient = class {
|
|
|
598
643
|
this.flushIntervalMs = normalizedOptions.flushIntervalMs ?? 5e3;
|
|
599
644
|
this.maxRetries = normalizedOptions.maxRetries ?? 4;
|
|
600
645
|
this.debug = normalizedOptions.debug ?? false;
|
|
646
|
+
this.onIngestError = typeof normalizedOptions.onIngestError === "function" ? normalizedOptions.onIngestError : null;
|
|
601
647
|
this.platform = this.normalizePlatformOption(normalizedOptions.platform) ?? detectDefaultPlatform();
|
|
602
648
|
this.projectSurface = this.normalizeProjectSurfaceOption(normalizedOptions.projectSurface);
|
|
603
649
|
this.appVersion = this.readRequiredStringOption(normalizedOptions.appVersion) || detectDefaultAppVersion();
|
|
604
650
|
this.identityTrackingMode = this.resolveIdentityTrackingModeOption(normalizedOptions);
|
|
605
|
-
|
|
651
|
+
const initialContext = { ...normalizedOptions.context ?? {} };
|
|
652
|
+
const hasExplicitOsName = this.readRequiredStringOption(initialContext.osName).length > 0;
|
|
653
|
+
this.context = {
|
|
654
|
+
...initialContext,
|
|
655
|
+
osName: hasExplicitOsName ? initialContext.osName : resolveDefaultOsNameFromPlatform(this.platform) ?? initialContext.osName
|
|
656
|
+
};
|
|
606
657
|
this.runtimeEnv = detectRuntimeEnv();
|
|
607
658
|
this.persistConsentState = normalizedOptions.persistConsentState ?? false;
|
|
608
659
|
this.consentStorageKey = this.readRequiredStringOption(normalizedOptions.consentStorageKey) || DEFAULT_CONSENT_STORAGE_KEY;
|
|
@@ -635,6 +686,7 @@ var AnalyticsClient = class {
|
|
|
635
686
|
this.writePersistedConsent(this.configuredStorage, this.fullTrackingConsentGranted);
|
|
636
687
|
}
|
|
637
688
|
this.hydrationPromise = this.hydrateIdentityFromStorage();
|
|
689
|
+
this.enqueueInitialSessionStart();
|
|
638
690
|
this.startAutoFlush();
|
|
639
691
|
}
|
|
640
692
|
/**
|
|
@@ -692,6 +744,32 @@ var AnalyticsClient = class {
|
|
|
692
744
|
...context
|
|
693
745
|
};
|
|
694
746
|
}
|
|
747
|
+
enqueueInitialSessionStart() {
|
|
748
|
+
if (!this.consentGranted) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (this.shouldDeferEventsUntilHydrated()) {
|
|
752
|
+
this.deferEventUntilHydrated(() => {
|
|
753
|
+
this.enqueueInitialSessionStart();
|
|
754
|
+
});
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const sessionId = this.getSessionId();
|
|
758
|
+
this.enqueue({
|
|
759
|
+
eventId: randomId(),
|
|
760
|
+
eventName: "session_start",
|
|
761
|
+
ts: nowIso(),
|
|
762
|
+
sessionId,
|
|
763
|
+
anonId: this.anonId,
|
|
764
|
+
userId: this.getEventUserId(),
|
|
765
|
+
properties: this.withRuntimeMetadata({ source: "sdk_mount" }, sessionId),
|
|
766
|
+
platform: this.platform,
|
|
767
|
+
projectSurface: this.projectSurface,
|
|
768
|
+
appVersion: this.appVersion,
|
|
769
|
+
...this.withEventContext(),
|
|
770
|
+
type: "track"
|
|
771
|
+
});
|
|
772
|
+
}
|
|
695
773
|
/**
|
|
696
774
|
* Associates following events with a known user id.
|
|
697
775
|
* Anonymous history remains linked by anonId/sessionId.
|
|
@@ -1061,6 +1139,9 @@ var AnalyticsClient = class {
|
|
|
1061
1139
|
if (this.queue.length === 0 || this.isFlushing || !this.consentGranted) {
|
|
1062
1140
|
return;
|
|
1063
1141
|
}
|
|
1142
|
+
if (Date.now() < this.flushPausedUntilMs) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1064
1145
|
this.isFlushing = true;
|
|
1065
1146
|
const batch = this.queue.splice(0, this.batchSize);
|
|
1066
1147
|
const payload = {
|
|
@@ -1075,9 +1156,20 @@ var AnalyticsClient = class {
|
|
|
1075
1156
|
}
|
|
1076
1157
|
try {
|
|
1077
1158
|
await this.sendWithRetry(payload);
|
|
1159
|
+
this.flushPausedUntilMs = 0;
|
|
1078
1160
|
} catch (error) {
|
|
1079
|
-
this.log("Send failed permanently, requeueing batch", error);
|
|
1080
1161
|
this.queue = [...batch, ...this.queue];
|
|
1162
|
+
const ingestError = this.toIngestSendError(error);
|
|
1163
|
+
const diagnostics = this.createIngestDiagnostics(ingestError, batch.length, this.queue.length);
|
|
1164
|
+
if (ingestError.status === 401 || ingestError.status === 403) {
|
|
1165
|
+
this.flushPausedUntilMs = Date.now() + AUTH_FAILURE_FLUSH_PAUSE_MS;
|
|
1166
|
+
this.log("Pausing ingest flush after auth failure", {
|
|
1167
|
+
status: ingestError.status,
|
|
1168
|
+
retryAfterMs: AUTH_FAILURE_FLUSH_PAUSE_MS
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
this.log("Send failed permanently, requeueing batch", diagnostics);
|
|
1172
|
+
this.reportIngestError(diagnostics);
|
|
1081
1173
|
} finally {
|
|
1082
1174
|
this.isFlushing = false;
|
|
1083
1175
|
}
|
|
@@ -1106,9 +1198,8 @@ var AnalyticsClient = class {
|
|
|
1106
1198
|
});
|
|
1107
1199
|
}
|
|
1108
1200
|
async sendWithRetry(payload) {
|
|
1109
|
-
let attempt = 0;
|
|
1110
1201
|
let delay = 250;
|
|
1111
|
-
|
|
1202
|
+
for (let attempt = 1; attempt <= this.maxRetries + 1; attempt += 1) {
|
|
1112
1203
|
try {
|
|
1113
1204
|
const response = await fetch(`${this.endpoint}/v1/collect`, {
|
|
1114
1205
|
method: "POST",
|
|
@@ -1120,19 +1211,110 @@ var AnalyticsClient = class {
|
|
|
1120
1211
|
keepalive: true
|
|
1121
1212
|
});
|
|
1122
1213
|
if (!response.ok) {
|
|
1123
|
-
throw
|
|
1214
|
+
throw await this.createHttpIngestSendError(response, attempt);
|
|
1124
1215
|
}
|
|
1125
1216
|
return;
|
|
1126
1217
|
} catch (error) {
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1218
|
+
const normalized = this.toIngestSendError(error, attempt);
|
|
1219
|
+
const finalAttempt = attempt >= this.maxRetries + 1;
|
|
1220
|
+
this.log("Ingest attempt failed", {
|
|
1221
|
+
attempt: normalized.attempts,
|
|
1222
|
+
maxRetries: this.maxRetries,
|
|
1223
|
+
retryable: normalized.retryable,
|
|
1224
|
+
status: normalized.status,
|
|
1225
|
+
errorCode: normalized.errorCode,
|
|
1226
|
+
requestId: normalized.requestId,
|
|
1227
|
+
nextRetryInMs: !finalAttempt && normalized.retryable ? delay : null
|
|
1228
|
+
});
|
|
1229
|
+
if (finalAttempt || !normalized.retryable) {
|
|
1230
|
+
throw normalized;
|
|
1130
1231
|
}
|
|
1131
1232
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1132
1233
|
delay *= 2;
|
|
1133
1234
|
}
|
|
1134
1235
|
}
|
|
1135
1236
|
}
|
|
1237
|
+
async createHttpIngestSendError(response, attempts) {
|
|
1238
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("cf-ray") ?? void 0;
|
|
1239
|
+
let errorCode;
|
|
1240
|
+
let serverMessage;
|
|
1241
|
+
try {
|
|
1242
|
+
const parsed = await response.json();
|
|
1243
|
+
const errorBody = parsed && typeof parsed === "object" && parsed.error && typeof parsed.error === "object" ? parsed.error : void 0;
|
|
1244
|
+
if (typeof errorBody?.code === "string") {
|
|
1245
|
+
errorCode = errorBody.code;
|
|
1246
|
+
}
|
|
1247
|
+
if (typeof errorBody?.message === "string") {
|
|
1248
|
+
serverMessage = errorBody.message;
|
|
1249
|
+
}
|
|
1250
|
+
} catch {
|
|
1251
|
+
}
|
|
1252
|
+
const retryable = this.shouldRetryHttpStatus(response.status);
|
|
1253
|
+
const statusSuffix = errorCode ? ` ${errorCode}` : "";
|
|
1254
|
+
const message = `ingest status=${response.status}${statusSuffix}`;
|
|
1255
|
+
return new IngestSendError({
|
|
1256
|
+
message,
|
|
1257
|
+
retryable,
|
|
1258
|
+
attempts,
|
|
1259
|
+
status: response.status,
|
|
1260
|
+
errorCode,
|
|
1261
|
+
serverMessage,
|
|
1262
|
+
requestId
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
shouldRetryHttpStatus(status) {
|
|
1266
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
1267
|
+
}
|
|
1268
|
+
toIngestSendError(error, attempts) {
|
|
1269
|
+
if (error instanceof IngestSendError) {
|
|
1270
|
+
const resolvedAttempts = attempts ?? error.attempts;
|
|
1271
|
+
return new IngestSendError({
|
|
1272
|
+
message: error.message,
|
|
1273
|
+
retryable: error.retryable,
|
|
1274
|
+
attempts: resolvedAttempts,
|
|
1275
|
+
status: error.status,
|
|
1276
|
+
errorCode: error.errorCode,
|
|
1277
|
+
serverMessage: error.serverMessage,
|
|
1278
|
+
requestId: error.requestId,
|
|
1279
|
+
cause: error.cause
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
const fallbackMessage = error instanceof Error ? error.message : "ingest request failed";
|
|
1283
|
+
return new IngestSendError({
|
|
1284
|
+
message: fallbackMessage,
|
|
1285
|
+
retryable: true,
|
|
1286
|
+
attempts: attempts ?? 1,
|
|
1287
|
+
cause: error
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
createIngestDiagnostics(error, batchSize, queueSize) {
|
|
1291
|
+
return {
|
|
1292
|
+
name: "AnalyticsIngestError",
|
|
1293
|
+
message: error.message,
|
|
1294
|
+
endpoint: this.endpoint,
|
|
1295
|
+
path: "/v1/collect",
|
|
1296
|
+
status: error.status,
|
|
1297
|
+
errorCode: error.errorCode,
|
|
1298
|
+
serverMessage: error.serverMessage,
|
|
1299
|
+
requestId: error.requestId,
|
|
1300
|
+
retryable: error.retryable,
|
|
1301
|
+
attempts: error.attempts,
|
|
1302
|
+
maxRetries: this.maxRetries,
|
|
1303
|
+
batchSize,
|
|
1304
|
+
queueSize,
|
|
1305
|
+
timestamp: nowIso()
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
reportIngestError(error) {
|
|
1309
|
+
if (!this.onIngestError) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
try {
|
|
1313
|
+
this.onIngestError(error);
|
|
1314
|
+
} catch (callbackError) {
|
|
1315
|
+
this.log("onIngestError callback threw", callbackError);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1136
1318
|
parsePersistedConsent(raw) {
|
|
1137
1319
|
if (raw === "granted") {
|
|
1138
1320
|
return true;
|