@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/react-native.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/react-native.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/react-native.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/react-native.js
CHANGED
package/package.json
CHANGED