avo-inspector 3.2.0 → 3.2.3
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/README.md +10 -0
- package/dist/AvoBatcher.d.ts +9 -3
- package/dist/AvoInspector.d.ts +4 -0
- package/dist/AvoNetworkCallsHandler.d.ts +80 -5
- package/dist/__tests__/UnboundPublicMethods_test.d.ts +1 -0
- package/dist/__tests__/constants.d.ts +2 -1
- package/dist/__tests__/helpers/internalGateway.d.ts +23 -0
- package/dist/eventSpec/AvoEventSpecCache.d.ts +7 -4
- package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +12 -1
- package/dist/eventSpec/AvoEventSpecFetcher.d.ts +6 -1
- package/dist/index.js +1 -1
- package/dist/lite/AvoBatcherLite.d.ts +9 -3
- package/dist/lite/AvoBatcherLite.js +46 -7
- package/dist/lite/AvoInspectorLite.d.ts +2 -0
- package/dist/lite/AvoInspectorLite.js +95 -15
- package/dist/lite/AvoNetworkCallsHandlerLite.d.ts +79 -5
- package/dist/lite/AvoNetworkCallsHandlerLite.js +300 -20
- package/package.json +10 -1
- package/dist/AvoBatcher.js +0 -87
- package/dist/AvoDeduplicator.js +0 -142
- package/dist/AvoEncryption.js +0 -176
- package/dist/AvoInspector.js +0 -435
- package/dist/AvoNetworkCallsHandler.js +0 -229
- package/dist/AvoSchemaParser.js +0 -95
- package/dist/AvoStreamId.js +0 -82
- package/dist/__mocks__/xhr.js +0 -14
- package/dist/__mocks__/xhrError.js +0 -14
- package/dist/__tests__/AvoEncryption.js +0 -272
- package/dist/__tests__/AvoEncryptionIntegration.js +0 -191
- package/dist/__tests__/AvoEventSpecFetcher.js +0 -377
- package/dist/__tests__/AvoInspector.js +0 -335
- package/dist/__tests__/Batcher.js +0 -214
- package/dist/__tests__/BatcherPublic.js +0 -139
- package/dist/__tests__/Deduplicator.js +0 -190
- package/dist/__tests__/EventSpecCache.js +0 -143
- package/dist/__tests__/EventValidator.js +0 -543
- package/dist/__tests__/InstallationId.js +0 -60
- package/dist/__tests__/NetworkCallsHandler.js +0 -222
- package/dist/__tests__/NetworkCallsHandlerModelA.js +0 -206
- package/dist/__tests__/Parsing.js +0 -115
- package/dist/__tests__/Sessions.d.ts +0 -1
- package/dist/__tests__/Sessions.js +0 -10
- package/dist/__tests__/Storage.d.ts +0 -1
- package/dist/__tests__/Storage.js +0 -173
- package/dist/__tests__/StreamId.d.ts +0 -1
- package/dist/__tests__/StreamId.js +0 -153
- package/dist/__tests__/constants.js +0 -48
- package/dist/esm/AvoBatcher.js +0 -75
- package/dist/esm/AvoDeduplicator.js +0 -137
- package/dist/esm/AvoEncryption.js +0 -277
- package/dist/esm/AvoGuid.js +0 -9
- package/dist/esm/AvoInspector.js +0 -470
- package/dist/esm/AvoInspectorEnv.js +0 -5
- package/dist/esm/AvoNetworkCallsHandler.js +0 -181
- package/dist/esm/AvoSchemaParser.js +0 -128
- package/dist/esm/AvoStorage.js +0 -176
- package/dist/esm/AvoStreamId.js +0 -140
- package/dist/esm/eventSpec/AvoEventSpecCache.js +0 -165
- package/dist/esm/eventSpec/AvoEventSpecFetchTypes.js +0 -6
- package/dist/esm/eventSpec/AvoEventSpecFetcher.js +0 -203
- package/dist/esm/eventSpec/EventValidator.js +0 -687
- package/dist/esm/eventSpec/index.js +0 -4
- package/dist/esm/index.js +0 -3
- package/dist/esm/utils.js +0 -38
- package/dist/esm/version.js +0 -2
- package/dist/eventSpec/AvoEventSpecCache.js +0 -181
- package/dist/eventSpec/AvoEventSpecFetcher.js +0 -290
- package/dist/eventSpec/EventValidator.js +0 -846
- package/dist/eventSpec/index.js +0 -20
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -5
- /package/dist/__tests__/{AvoEncryption.d.ts → ApiKeyHeaderInjection_test.d.ts} +0 -0
- /package/dist/__tests__/{AvoEncryptionIntegration.d.ts → BrowserBootstrap_test.d.ts} +0 -0
- /package/dist/__tests__/{AvoEventSpecFetcher.d.ts → EventSpecGatewayCoordinates_test.d.ts} +0 -0
- /package/dist/__tests__/{AvoInspector.d.ts → InFlightQueueGaps_test.d.ts} +0 -0
- /package/dist/__tests__/{Batcher.d.ts → InFlightQueue_test.d.ts} +0 -0
- /package/dist/__tests__/{BatcherPublic.d.ts → NetworkCallsHandlerLite_test.d.ts} +0 -0
- /package/dist/__tests__/{Deduplicator.d.ts → PublicSurface_test.d.ts} +0 -0
- /package/dist/__tests__/{EventSpecCache.d.ts → TrackOptionsIntegration_test.d.ts} +0 -0
- /package/dist/__tests__/{EventValidator.d.ts → TrackOptionsTransport_test.d.ts} +0 -0
- /package/dist/__tests__/{InstallationId.d.ts → TrackingHeaders_test.d.ts} +0 -0
- /package/dist/__tests__/{NetworkCallsHandler.d.ts → V1WireBaseline_test.d.ts} +0 -0
- /package/dist/__tests__/{NetworkCallsHandlerModelA.d.ts → V2QueuedAttribution_test.d.ts} +0 -0
- /package/dist/__tests__/{Parsing.d.ts → V2StopSending_test.d.ts} +0 -0
package/dist/esm/AvoInspector.js
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import { AvoInspectorEnv } from "./AvoInspectorEnv";
|
|
2
|
-
import { AvoSchemaParser } from "./AvoSchemaParser";
|
|
3
|
-
import { AvoBatcher } from "./AvoBatcher";
|
|
4
|
-
import { AvoNetworkCallsHandler } from "./AvoNetworkCallsHandler";
|
|
5
|
-
import { AvoStorage } from "./AvoStorage";
|
|
6
|
-
import { AvoDeduplicator } from "./AvoDeduplicator";
|
|
7
|
-
import { AvoStreamId } from "./AvoStreamId";
|
|
8
|
-
import { isValueEmpty } from "./utils";
|
|
9
|
-
import { LIB_VERSION } from "./version";
|
|
10
|
-
const libVersion = LIB_VERSION;
|
|
11
|
-
export class AvoInspector {
|
|
12
|
-
static get batchSize() {
|
|
13
|
-
return this._batchSize;
|
|
14
|
-
}
|
|
15
|
-
static set batchSize(newSize) {
|
|
16
|
-
if (newSize < 1) {
|
|
17
|
-
this._batchSize = 1;
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
this._batchSize = newSize;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
static get batchFlushSeconds() {
|
|
24
|
-
return this._batchFlushSeconds;
|
|
25
|
-
}
|
|
26
|
-
static get shouldLog() {
|
|
27
|
-
return this._shouldLog;
|
|
28
|
-
}
|
|
29
|
-
static set shouldLog(enable) {
|
|
30
|
-
this._shouldLog = enable;
|
|
31
|
-
}
|
|
32
|
-
static get networkTimeout() {
|
|
33
|
-
return this._networkTimeout;
|
|
34
|
-
}
|
|
35
|
-
static set networkTimeout(timeout) {
|
|
36
|
-
this._networkTimeout = timeout;
|
|
37
|
-
}
|
|
38
|
-
constructor(options) {
|
|
39
|
-
this.currentBranchId = null;
|
|
40
|
-
// the constructor does aggressive null/undefined checking because same code paths will be accessible from JS
|
|
41
|
-
if (isValueEmpty(options.env)) {
|
|
42
|
-
this.environment = AvoInspectorEnv.Dev;
|
|
43
|
-
console.warn("[Avo Inspector] No environment provided. Defaulting to dev.");
|
|
44
|
-
}
|
|
45
|
-
else if (!Object.values(AvoInspectorEnv).includes(options.env)) {
|
|
46
|
-
this.environment = AvoInspectorEnv.Dev;
|
|
47
|
-
console.warn("[Avo Inspector] Unsupported environment provided. Defaulting to dev. Supported environments - Dev, Staging, Prod.");
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
this.environment = options.env;
|
|
51
|
-
}
|
|
52
|
-
if (isValueEmpty(options.apiKey)) {
|
|
53
|
-
throw new Error("[Avo Inspector] No API key provided. Inspector can't operate without API key.");
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
this.apiKey = options.apiKey;
|
|
57
|
-
}
|
|
58
|
-
if (isValueEmpty(options.version)) {
|
|
59
|
-
throw new Error("[Avo Inspector] No version provided. Many features of Inspector rely on versioning. Please provide comparable string version, i.e. integer or semantic.");
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
this.version = options.version;
|
|
63
|
-
}
|
|
64
|
-
if (this.environment === AvoInspectorEnv.Dev) {
|
|
65
|
-
AvoInspector._batchSize = 1;
|
|
66
|
-
AvoInspector._shouldLog = true;
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
AvoInspector._batchSize = 30;
|
|
70
|
-
AvoInspector._batchFlushSeconds = 30;
|
|
71
|
-
AvoInspector._shouldLog = false;
|
|
72
|
-
}
|
|
73
|
-
AvoInspector.avoStorage = new AvoStorage(AvoInspector._shouldLog, options.suffix != null ? options.suffix : "");
|
|
74
|
-
this.publicEncryptionKey = options.publicEncryptionKey;
|
|
75
|
-
this.avoNetworkCallsHandler = new AvoNetworkCallsHandler(this.apiKey, this.environment.toString(), options.appName || "", this.version, libVersion, this.publicEncryptionKey);
|
|
76
|
-
this.avoBatcher = new AvoBatcher(this.avoNetworkCallsHandler);
|
|
77
|
-
this.avoDeduplicator = new AvoDeduplicator();
|
|
78
|
-
this.streamId = AvoStreamId.streamId;
|
|
79
|
-
// Enable event spec fetching if streamId is present (note: "unknown" is truthy and will trigger initEventSpecModules; see streamId edge case in spec)
|
|
80
|
-
if (this.streamId && this.environment !== AvoInspectorEnv.Prod) {
|
|
81
|
-
this._eventSpecReady = this.initEventSpecModules();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
async initEventSpecModules() {
|
|
85
|
-
// ORDERING CONSTRAINT: All instance field assignments (this.eventSpecCache,
|
|
86
|
-
// this.eventSpecFetcher, this._validateEvent) MUST occur inside the try block,
|
|
87
|
-
// before the finally block clears this._eventSpecReady. The finally block sets
|
|
88
|
-
// this._eventSpecReady = undefined to signal completion. Concurrent callers that
|
|
89
|
-
// await this._eventSpecReady (via ensureEventSpecReady) will resume after the
|
|
90
|
-
// finally block executes. If any field assignment were moved to or after the
|
|
91
|
-
// finally block, those concurrent callers would find fields unpopulated, creating
|
|
92
|
-
// a race condition where they incorrectly proceed as if no event spec is available.
|
|
93
|
-
try {
|
|
94
|
-
const [{ EventSpecCache }, { AvoEventSpecFetcher }, { validateEvent }] = await Promise.all([
|
|
95
|
-
import("./eventSpec/AvoEventSpecCache"),
|
|
96
|
-
import("./eventSpec/AvoEventSpecFetcher"),
|
|
97
|
-
import("./eventSpec/EventValidator")
|
|
98
|
-
]);
|
|
99
|
-
this.eventSpecCache = new EventSpecCache(AvoInspector._shouldLog);
|
|
100
|
-
this.eventSpecFetcher = new AvoEventSpecFetcher(AvoInspector._networkTimeout, AvoInspector._shouldLog, this.environment);
|
|
101
|
-
this._validateEvent = validateEvent;
|
|
102
|
-
if (AvoInspector._shouldLog) {
|
|
103
|
-
console.log("[Avo Inspector] Event spec fetching and validation enabled");
|
|
104
|
-
if (this.publicEncryptionKey) {
|
|
105
|
-
console.log("[Avo Inspector] Property value encryption enabled");
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
if (AvoInspector._shouldLog) {
|
|
111
|
-
console.error("[Avo Inspector] Failed to load event spec modules:", error);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
this._eventSpecReady = undefined;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async ensureEventSpecReady() {
|
|
119
|
-
if (this.environment === AvoInspectorEnv.Prod)
|
|
120
|
-
return null;
|
|
121
|
-
if (this._eventSpecReady)
|
|
122
|
-
await this._eventSpecReady;
|
|
123
|
-
if (!this.eventSpecCache || !this.eventSpecFetcher || !this.streamId || !this._validateEvent)
|
|
124
|
-
return null;
|
|
125
|
-
return {
|
|
126
|
-
cache: this.eventSpecCache,
|
|
127
|
-
fetcher: this.eventSpecFetcher,
|
|
128
|
-
validate: this._validateEvent,
|
|
129
|
-
streamId: this.streamId,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
async trackSchemaFromEvent(eventName, eventProperties) {
|
|
133
|
-
try {
|
|
134
|
-
if (this.avoDeduplicator.shouldRegisterEvent(eventName, eventProperties, false)) {
|
|
135
|
-
if (AvoInspector.shouldLog) {
|
|
136
|
-
console.log("Avo Inspector: supplied event " +
|
|
137
|
-
eventName +
|
|
138
|
-
" with params " +
|
|
139
|
-
JSON.stringify(eventProperties));
|
|
140
|
-
}
|
|
141
|
-
const eventSchema = await this.extractSchema(eventProperties, false);
|
|
142
|
-
// Fetch and validate event spec (sync blocking)
|
|
143
|
-
const validationResult = await this.fetchAndValidateEvent(eventName, eventProperties);
|
|
144
|
-
if (validationResult) {
|
|
145
|
-
// Spec available: merge validation results into schema and send immediately
|
|
146
|
-
const schemaWithValidation = this.mergeValidationResults(eventSchema, validationResult);
|
|
147
|
-
this.sendEventWithValidation(eventName, schemaWithValidation, null, null, validationResult);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
// No spec: fall back to batched flow
|
|
151
|
-
this.trackSchemaInternal(eventName, eventSchema, null, null);
|
|
152
|
-
}
|
|
153
|
-
return eventSchema;
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
if (AvoInspector.shouldLog) {
|
|
157
|
-
console.log("Avo Inspector: Deduplicated event: " + eventName);
|
|
158
|
-
}
|
|
159
|
-
return [];
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch (e) {
|
|
163
|
-
console.error("Avo Inspector: something went wrong. Please report to support@avo.app.", e);
|
|
164
|
-
return [];
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async _avoFunctionTrackSchemaFromEvent(eventName, eventProperties, eventId, eventHash) {
|
|
168
|
-
try {
|
|
169
|
-
if (this.avoDeduplicator.shouldRegisterEvent(eventName, eventProperties, true)) {
|
|
170
|
-
if (AvoInspector.shouldLog) {
|
|
171
|
-
console.log("Avo Inspector: supplied event " +
|
|
172
|
-
eventName +
|
|
173
|
-
" with params " +
|
|
174
|
-
JSON.stringify(eventProperties));
|
|
175
|
-
}
|
|
176
|
-
const eventSchema = await this.extractSchema(eventProperties, false);
|
|
177
|
-
// Fetch and validate event spec (sync blocking)
|
|
178
|
-
const validationResult = await this.fetchAndValidateEvent(eventName, eventProperties);
|
|
179
|
-
if (validationResult) {
|
|
180
|
-
// Spec available: merge validation results into schema and send immediately
|
|
181
|
-
const schemaWithValidation = this.mergeValidationResults(eventSchema, validationResult);
|
|
182
|
-
this.sendEventWithValidation(eventName, schemaWithValidation, eventId, eventHash, validationResult);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
// No spec: fall back to batched flow
|
|
186
|
-
this.trackSchemaInternal(eventName, eventSchema, eventId, eventHash);
|
|
187
|
-
}
|
|
188
|
-
return eventSchema;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
if (AvoInspector.shouldLog) {
|
|
192
|
-
console.log("Avo Inspector: Deduplicated event: " + eventName);
|
|
193
|
-
}
|
|
194
|
-
return [];
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (e) {
|
|
198
|
-
console.error("Avo Inspector: something went wrong. Please report to support@avo.app.", e);
|
|
199
|
-
return [];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
async trackSchema(eventName, eventSchema) {
|
|
203
|
-
try {
|
|
204
|
-
if (await this.avoDeduplicator.shouldRegisterSchemaFromManually(eventName, eventSchema)) {
|
|
205
|
-
if (AvoInspector.shouldLog) {
|
|
206
|
-
console.log("Avo Inspector: supplied event " +
|
|
207
|
-
eventName +
|
|
208
|
-
" with schema " +
|
|
209
|
-
JSON.stringify(eventSchema));
|
|
210
|
-
}
|
|
211
|
-
// For trackSchema we don't have raw properties, so we can't validate
|
|
212
|
-
// Just fetch/cache spec for future use and use batched flow
|
|
213
|
-
await this.fetchEventSpecIfNeeded(eventName);
|
|
214
|
-
this.trackSchemaInternal(eventName, eventSchema, null, null);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
if (AvoInspector.shouldLog) {
|
|
218
|
-
console.log("Avo Inspector: Deduplicated event: " + eventName);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
catch (e) {
|
|
223
|
-
console.error("Avo Inspector: something went wrong. Please report to support@avo.app.", e);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
trackSchemaInternal(eventName, eventSchema, eventId, eventHash) {
|
|
227
|
-
try {
|
|
228
|
-
this.avoBatcher.handleTrackSchema(eventName, eventSchema, eventId, eventHash);
|
|
229
|
-
}
|
|
230
|
-
catch (e) {
|
|
231
|
-
console.error("Avo Inspector: something went wrong. Please report to support@avo.app.", e);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
enableLogging(enable) {
|
|
235
|
-
AvoInspector._shouldLog = enable;
|
|
236
|
-
}
|
|
237
|
-
async extractSchema(eventProperties, shouldLogIfEnabled = true) {
|
|
238
|
-
try {
|
|
239
|
-
if (this.avoDeduplicator.hasSeenEventParams(eventProperties, true)) {
|
|
240
|
-
if (shouldLogIfEnabled && AvoInspector.shouldLog) {
|
|
241
|
-
console.warn("Avo Inspector: WARNING! You are trying to extract schema shape that was just reported by your Avo Codegen. " +
|
|
242
|
-
"This is an indicator of duplicate inspector reporting. " +
|
|
243
|
-
"Please reach out to support@avo.app for advice if you are not sure how to handle this.");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
if (AvoInspector.shouldLog) {
|
|
247
|
-
console.log("Avo Inspector: extracting schema from " +
|
|
248
|
-
JSON.stringify(eventProperties));
|
|
249
|
-
}
|
|
250
|
-
return await AvoSchemaParser.extractSchema(eventProperties, this.publicEncryptionKey, this.environment);
|
|
251
|
-
}
|
|
252
|
-
catch (e) {
|
|
253
|
-
console.error("Avo Inspector: something went wrong in extractSchema. Please report to support@avo.app.", e);
|
|
254
|
-
return [];
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
setBatchSize(newBatchSize) {
|
|
258
|
-
AvoInspector._batchSize = newBatchSize;
|
|
259
|
-
}
|
|
260
|
-
setBatchFlushSeconds(newBatchFlushSeconds) {
|
|
261
|
-
AvoInspector._batchFlushSeconds = newBatchFlushSeconds;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Handles branch change detection and cache storage for a fetched event spec.
|
|
265
|
-
* This logic is shared between fetchEventSpecIfNeeded and fetchAndValidateEvent.
|
|
266
|
-
*/
|
|
267
|
-
handleBranchChangeAndCache(specResponse, eventName) {
|
|
268
|
-
// Check for branch change
|
|
269
|
-
const newBranchId = specResponse.metadata.branchId;
|
|
270
|
-
if (this.currentBranchId !== null && this.currentBranchId !== newBranchId) {
|
|
271
|
-
if (AvoInspector.shouldLog) {
|
|
272
|
-
console.log(`[Avo Inspector] Branch changed from ${this.currentBranchId} to ${newBranchId}. Flushing cache.`);
|
|
273
|
-
}
|
|
274
|
-
this.eventSpecCache?.clear();
|
|
275
|
-
}
|
|
276
|
-
this.currentBranchId = newBranchId;
|
|
277
|
-
// Store in cache
|
|
278
|
-
if (this.eventSpecCache && this.streamId) {
|
|
279
|
-
this.eventSpecCache.set(this.apiKey, this.streamId, eventName, specResponse);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Fetches the event spec if spec fetching is enabled.
|
|
284
|
-
* Used by trackSchema when we don't have raw properties to validate.
|
|
285
|
-
*
|
|
286
|
-
* Note: EventSpec fetching only happens in dev/staging environments.
|
|
287
|
-
*/
|
|
288
|
-
async fetchEventSpecIfNeeded(eventName) {
|
|
289
|
-
const spec = await this.ensureEventSpecReady();
|
|
290
|
-
if (!spec)
|
|
291
|
-
return;
|
|
292
|
-
try {
|
|
293
|
-
// Check cache first (includes cached empty responses)
|
|
294
|
-
if (spec.cache.contains(this.apiKey, spec.streamId, eventName)) {
|
|
295
|
-
const cached = spec.cache.get(this.apiKey, spec.streamId, eventName);
|
|
296
|
-
if (!cached) {
|
|
297
|
-
// Cached empty response — no spec exists for this event
|
|
298
|
-
if (AvoInspector.shouldLog) {
|
|
299
|
-
console.log(`[Avo Inspector] Cache hit (empty) for event: ${eventName}. Sending without validation.`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
// Cache miss - fetch from API (blocking)
|
|
305
|
-
const specResponse = await spec.fetcher.fetch({
|
|
306
|
-
apiKey: this.apiKey,
|
|
307
|
-
streamId: spec.streamId,
|
|
308
|
-
eventName
|
|
309
|
-
});
|
|
310
|
-
if (specResponse) {
|
|
311
|
-
this.handleBranchChangeAndCache(specResponse, eventName);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
// Cache the empty response so we don't re-fetch
|
|
315
|
-
spec.cache.set(this.apiKey, spec.streamId, eventName, null);
|
|
316
|
-
if (AvoInspector.shouldLog) {
|
|
317
|
-
console.log(`[Avo Inspector] Event spec fetch returned null for event: ${eventName}. Cached empty response.`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
// Graceful degradation - log but don't fail
|
|
323
|
-
if (AvoInspector.shouldLog) {
|
|
324
|
-
console.error(`[Avo Inspector] Error fetching event spec for ${eventName}:`, error);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Fetches event spec and validates the event against it.
|
|
330
|
-
* Returns ValidationResult if spec is available, null otherwise.
|
|
331
|
-
*
|
|
332
|
-
* Note: EventSpec fetching and validation only happens in dev/staging environments.
|
|
333
|
-
*/
|
|
334
|
-
async fetchAndValidateEvent(eventName, eventProperties) {
|
|
335
|
-
const spec = await this.ensureEventSpecReady();
|
|
336
|
-
if (!spec)
|
|
337
|
-
return null;
|
|
338
|
-
try {
|
|
339
|
-
// Check cache first (includes cached empty responses)
|
|
340
|
-
if (spec.cache.contains(this.apiKey, spec.streamId, eventName)) {
|
|
341
|
-
const cached = spec.cache.get(this.apiKey, spec.streamId, eventName);
|
|
342
|
-
if (cached) {
|
|
343
|
-
if (AvoInspector.shouldLog) {
|
|
344
|
-
console.log(`[Avo Inspector] Cache hit for event: ${eventName}`);
|
|
345
|
-
}
|
|
346
|
-
return spec.validate(eventProperties, cached);
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
// Cached empty response — no spec exists for this event
|
|
350
|
-
if (AvoInspector.shouldLog) {
|
|
351
|
-
console.log(`[Avo Inspector] Cache hit (empty) for event: ${eventName}. Sending without validation.`);
|
|
352
|
-
}
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// Cache miss - fetch from API (blocking)
|
|
357
|
-
const specResponse = await spec.fetcher.fetch({
|
|
358
|
-
apiKey: this.apiKey,
|
|
359
|
-
streamId: spec.streamId,
|
|
360
|
-
eventName
|
|
361
|
-
});
|
|
362
|
-
if (specResponse) {
|
|
363
|
-
this.handleBranchChangeAndCache(specResponse, eventName);
|
|
364
|
-
return spec.validate(eventProperties, specResponse);
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
// Cache the empty response so we don't re-fetch
|
|
368
|
-
spec.cache.set(this.apiKey, spec.streamId, eventName, null);
|
|
369
|
-
if (AvoInspector.shouldLog) {
|
|
370
|
-
console.log(`[Avo Inspector] Event spec fetch returned null for event: ${eventName}. Cached empty response.`);
|
|
371
|
-
}
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
// Graceful degradation - log but don't fail
|
|
377
|
-
if (AvoInspector.shouldLog) {
|
|
378
|
-
console.error(`[Avo Inspector] Error validating event ${eventName}:`, error);
|
|
379
|
-
}
|
|
380
|
-
return null;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Merges validation results into the event schema.
|
|
385
|
-
* Adds failedEventIds or passedEventIds to each property based on validation.
|
|
386
|
-
* Recursively merges validation results for nested children.
|
|
387
|
-
*/
|
|
388
|
-
mergeValidationResults(eventSchema, validationResult) {
|
|
389
|
-
return eventSchema.map((prop) => {
|
|
390
|
-
const propValidation = validationResult.propertyResults[prop.propertyName];
|
|
391
|
-
return this.mergePropertyValidation(prop, propValidation);
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Merges validation result into a single property, recursively handling children.
|
|
396
|
-
*/
|
|
397
|
-
mergePropertyValidation(prop, propValidation) {
|
|
398
|
-
const result = {
|
|
399
|
-
propertyName: prop.propertyName,
|
|
400
|
-
propertyType: prop.propertyType
|
|
401
|
-
};
|
|
402
|
-
if (prop.encryptedPropertyValue) {
|
|
403
|
-
result.encryptedPropertyValue = prop.encryptedPropertyValue;
|
|
404
|
-
}
|
|
405
|
-
// Recursively merge validation results into children
|
|
406
|
-
if (prop.children && Array.isArray(prop.children)) {
|
|
407
|
-
result.children = prop.children.map((child) => {
|
|
408
|
-
// Children can be strings (for array types) or objects (for nested properties)
|
|
409
|
-
if (typeof child === 'string') {
|
|
410
|
-
return child;
|
|
411
|
-
}
|
|
412
|
-
if (child && typeof child === 'object' && child.propertyName) {
|
|
413
|
-
// Get nested validation result for this child
|
|
414
|
-
const childValidation = propValidation?.children?.[child.propertyName];
|
|
415
|
-
return this.mergePropertyValidation(child, childValidation);
|
|
416
|
-
}
|
|
417
|
-
return child;
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
// Add validation result for this property
|
|
421
|
-
if (propValidation) {
|
|
422
|
-
if (propValidation.failedEventIds) {
|
|
423
|
-
result.failedEventIds = propValidation.failedEventIds;
|
|
424
|
-
}
|
|
425
|
-
if (propValidation.passedEventIds) {
|
|
426
|
-
result.passedEventIds = propValidation.passedEventIds;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
return result;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Sends an event immediately with validation data (bypasses batching).
|
|
433
|
-
* Logs validation info if shouldLog is true.
|
|
434
|
-
*/
|
|
435
|
-
sendEventWithValidation(eventName, eventSchema, eventId, eventHash, validationResult) {
|
|
436
|
-
// Log validation info if shouldLog is enabled
|
|
437
|
-
if (AvoInspector.shouldLog) {
|
|
438
|
-
const hasFailures = eventSchema.some((p) => p.failedEventIds && p.failedEventIds.length > 0);
|
|
439
|
-
if (hasFailures) {
|
|
440
|
-
console.log(`[Avo Inspector] Validation failures for event "${eventName}":`, eventSchema
|
|
441
|
-
.filter((p) => p.failedEventIds && p.failedEventIds.length > 0)
|
|
442
|
-
.map((p) => ({
|
|
443
|
-
property: p.propertyName,
|
|
444
|
-
failedEventIds: p.failedEventIds
|
|
445
|
-
})));
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
// Create the event body with validation data
|
|
449
|
-
const eventBody = this.avoNetworkCallsHandler.bodyForEventSchemaCall(eventName, eventSchema, eventId, eventHash, validationResult.metadata ?? undefined, validationResult.metadata?.branchId);
|
|
450
|
-
// Send immediately (bypass batching)
|
|
451
|
-
this.avoNetworkCallsHandler.callInspectorImmediately(eventBody, (error) => {
|
|
452
|
-
if (error) {
|
|
453
|
-
if (AvoInspector.shouldLog) {
|
|
454
|
-
console.error(`[Avo Inspector] Failed to send event "${eventName}" with validation:`, error);
|
|
455
|
-
}
|
|
456
|
-
// Fallback: add to batch on failure (without validation data)
|
|
457
|
-
this.avoBatcher.handleTrackSchema(eventName, eventSchema, eventId, eventHash);
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
if (AvoInspector.shouldLog) {
|
|
461
|
-
console.log(`[Avo Inspector] Event "${eventName}" sent successfully with validation`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
AvoInspector._batchSize = 30;
|
|
468
|
-
AvoInspector._batchFlushSeconds = 30;
|
|
469
|
-
AvoInspector._shouldLog = false;
|
|
470
|
-
AvoInspector._networkTimeout = 2000;
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import AvoGuid from "./AvoGuid";
|
|
2
|
-
import { AvoInspector } from "./AvoInspector";
|
|
3
|
-
import { AvoStreamId } from "./AvoStreamId";
|
|
4
|
-
export class AvoNetworkCallsHandler {
|
|
5
|
-
constructor(apiKey, envName, appName, appVersion, libVersion, publicEncryptionKey) {
|
|
6
|
-
this.samplingRate = 1.0;
|
|
7
|
-
this.sending = false;
|
|
8
|
-
this.apiKey = apiKey;
|
|
9
|
-
this.envName = envName;
|
|
10
|
-
this.appName = appName;
|
|
11
|
-
this.appVersion = appVersion;
|
|
12
|
-
this.libVersion = libVersion;
|
|
13
|
-
this.publicEncryptionKey = publicEncryptionKey;
|
|
14
|
-
}
|
|
15
|
-
callInspectorWithBatchBody(inEvents, onCompleted) {
|
|
16
|
-
if (this.sending) {
|
|
17
|
-
onCompleted(new Error("Batch sending cancelled because another batch sending is in progress. Your events will be sent with next batch."));
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
const events = inEvents.filter((x) => x != null);
|
|
21
|
-
this.fixStreamIds(events);
|
|
22
|
-
if (events.length === 0) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (this.shouldDropBySampling()) {
|
|
26
|
-
if (AvoInspector.shouldLog) {
|
|
27
|
-
console.log("Avo Inspector: last event schema dropped due to sampling rate.");
|
|
28
|
-
}
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (AvoInspector.shouldLog) {
|
|
32
|
-
console.log("Avo Inspector: events", events);
|
|
33
|
-
events.forEach((event) => {
|
|
34
|
-
if (event.type === "sessionStarted") {
|
|
35
|
-
console.log("Avo Inspector: sending session started event.");
|
|
36
|
-
}
|
|
37
|
-
else if (event.type === "event") {
|
|
38
|
-
console.log("Avo Inspector: sending event " +
|
|
39
|
-
event.eventName +
|
|
40
|
-
" with schema " +
|
|
41
|
-
JSON.stringify(event.eventProperties));
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
this.sending = true;
|
|
46
|
-
this.callInspectorApi(events, (error) => {
|
|
47
|
-
this.sending = false;
|
|
48
|
-
onCompleted(error);
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
fixStreamIds(events) {
|
|
52
|
-
let knownStreamId = null;
|
|
53
|
-
events.forEach(function (event) {
|
|
54
|
-
if (event.streamId !== null &&
|
|
55
|
-
event.streamId !== undefined &&
|
|
56
|
-
event.streamId !== "unknown") {
|
|
57
|
-
knownStreamId = event.streamId;
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
events.forEach(function (event) {
|
|
61
|
-
if (event.streamId === "unknown") {
|
|
62
|
-
if (knownStreamId != null) {
|
|
63
|
-
event.streamId = knownStreamId;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
event.streamId = AvoStreamId.streamId;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
bodyForSessionStartedCall() {
|
|
72
|
-
const sessionBody = this.createBaseCallBody();
|
|
73
|
-
sessionBody.type = "sessionStarted";
|
|
74
|
-
return sessionBody;
|
|
75
|
-
}
|
|
76
|
-
bodyForEventSchemaCall(eventName, eventProperties, eventId, eventHash, eventSpecMetadata, validatedBranchId) {
|
|
77
|
-
const eventSchemaBody = this.createBaseCallBody();
|
|
78
|
-
eventSchemaBody.type = "event";
|
|
79
|
-
eventSchemaBody.eventName = eventName;
|
|
80
|
-
eventSchemaBody.eventProperties = eventProperties;
|
|
81
|
-
if (eventId != null) {
|
|
82
|
-
eventSchemaBody.avoFunction = true;
|
|
83
|
-
eventSchemaBody.eventId = eventId;
|
|
84
|
-
eventSchemaBody.eventHash = eventHash;
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
eventSchemaBody.avoFunction = false;
|
|
88
|
-
eventSchemaBody.eventId = null;
|
|
89
|
-
eventSchemaBody.eventHash = null;
|
|
90
|
-
}
|
|
91
|
-
// Set metadata on base body if provided
|
|
92
|
-
if (eventSpecMetadata) {
|
|
93
|
-
eventSchemaBody.eventSpecMetadata = eventSpecMetadata;
|
|
94
|
-
}
|
|
95
|
-
// Set validated branch ID if value validation was performed
|
|
96
|
-
if (validatedBranchId) {
|
|
97
|
-
eventSchemaBody.validatedBranchId = validatedBranchId;
|
|
98
|
-
}
|
|
99
|
-
return eventSchemaBody;
|
|
100
|
-
}
|
|
101
|
-
createBaseCallBody() {
|
|
102
|
-
const body = {
|
|
103
|
-
apiKey: this.apiKey,
|
|
104
|
-
appName: this.appName,
|
|
105
|
-
appVersion: this.appVersion,
|
|
106
|
-
libVersion: this.libVersion,
|
|
107
|
-
env: this.envName,
|
|
108
|
-
libPlatform: "web",
|
|
109
|
-
messageId: AvoGuid.newGuid(),
|
|
110
|
-
trackingId: "",
|
|
111
|
-
createdAt: new Date().toISOString(),
|
|
112
|
-
sessionId: "",
|
|
113
|
-
streamId: AvoStreamId.streamId,
|
|
114
|
-
samplingRate: this.samplingRate
|
|
115
|
-
};
|
|
116
|
-
if (this.publicEncryptionKey) {
|
|
117
|
-
body.publicEncryptionKey = this.publicEncryptionKey;
|
|
118
|
-
}
|
|
119
|
-
return body;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Calls Inspector API immediately with a single event (bypasses batching).
|
|
123
|
-
* Used when event spec validation is available.
|
|
124
|
-
* Note: Does not drop due to sampling - validated events are always sent.
|
|
125
|
-
*/
|
|
126
|
-
callInspectorImmediately(eventBody, onCompleted) {
|
|
127
|
-
// Fix stream ID if needed
|
|
128
|
-
if (eventBody.streamId === "unknown") {
|
|
129
|
-
eventBody.streamId = AvoStreamId.streamId;
|
|
130
|
-
}
|
|
131
|
-
if (AvoInspector.shouldLog) {
|
|
132
|
-
console.log("Avo Inspector: calling inspector immediately (with validation)", eventBody.eventName);
|
|
133
|
-
console.log("Avo Inspector: event body", eventBody);
|
|
134
|
-
}
|
|
135
|
-
this.callInspectorApi([eventBody], onCompleted);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Check if event should be dropped based on sampling rate.
|
|
139
|
-
*/
|
|
140
|
-
shouldDropBySampling() {
|
|
141
|
-
return Math.random() > this.samplingRate;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Core Inspector API call logic shared by batch and immediate calls.
|
|
145
|
-
*/
|
|
146
|
-
callInspectorApi(events, onCompleted) {
|
|
147
|
-
const xmlhttp = new XMLHttpRequest();
|
|
148
|
-
xmlhttp.open("POST", AvoNetworkCallsHandler.trackingEndpoint, true);
|
|
149
|
-
xmlhttp.setRequestHeader("Content-Type", "text/plain");
|
|
150
|
-
xmlhttp.timeout = AvoInspector.networkTimeout;
|
|
151
|
-
xmlhttp.send(JSON.stringify(events));
|
|
152
|
-
xmlhttp.onload = () => {
|
|
153
|
-
if (xmlhttp.status !== 200) {
|
|
154
|
-
onCompleted(new Error(`Error ${xmlhttp.status}: ${xmlhttp.statusText}`));
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
let response;
|
|
158
|
-
try {
|
|
159
|
-
response = JSON.parse(xmlhttp.response);
|
|
160
|
-
}
|
|
161
|
-
catch (e) {
|
|
162
|
-
onCompleted(new Error(`Failed to parse response: ${e instanceof Error ? e.message : String(e)}`));
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (response != null &&
|
|
166
|
-
typeof response.samplingRate === "number" &&
|
|
167
|
-
!isNaN(response.samplingRate)) {
|
|
168
|
-
this.samplingRate = response.samplingRate;
|
|
169
|
-
}
|
|
170
|
-
onCompleted(null);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
xmlhttp.onerror = () => {
|
|
174
|
-
onCompleted(new Error("Request failed"));
|
|
175
|
-
};
|
|
176
|
-
xmlhttp.ontimeout = () => {
|
|
177
|
-
onCompleted(new Error("Request timed out"));
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
AvoNetworkCallsHandler.trackingEndpoint = "https://api.avo.app/inspector/v1/track";
|