node-avo-inspector 1.1.1 → 1.2.0-alpha.1
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/.claude/scheduled_tasks.lock +1 -0
- package/.claude/settings.local.json +11 -0
- package/.claude/worktrees/release-1.1.1/LICENSE +21 -0
- package/.claude/worktrees/release-1.1.1/README.md +69 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/index.js +30 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/package.json +15 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/yarn.lock +6 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/dist/index.js +34 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/index.ts +30 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/package.json +18 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/tsconfig.json +15 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/yarn.lock +11 -0
- package/.claude/worktrees/release-1.1.1/package-lock.json +4039 -0
- package/.claude/worktrees/release-1.1.1/package.json +48 -0
- package/.claude/worktrees/release-1.1.1/reason/AvoInspector.rei +11 -0
- package/.claude/worktrees/release-1.1.1/reason/NodeAvoInspector.re +27 -0
- package/.claude/worktrees/release-1.1.1/src/AvoDeduplicator.ts +146 -0
- package/.claude/worktrees/release-1.1.1/src/AvoEncryption.ts +90 -0
- package/.claude/worktrees/release-1.1.1/src/AvoGuid.ts +9 -0
- package/.claude/worktrees/release-1.1.1/src/AvoInspector.ts +383 -0
- package/.claude/worktrees/release-1.1.1/src/AvoInspectorEnv.ts +8 -0
- package/.claude/worktrees/release-1.1.1/src/AvoNetworkCallsHandler.ts +267 -0
- package/.claude/worktrees/release-1.1.1/src/AvoSchemaParser.ts +115 -0
- package/.claude/worktrees/release-1.1.1/src/AvoStreamId.ts +16 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEncryption.ts +515 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecCache.ts +242 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecFetcher.ts +241 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoInspector.ts +360 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoStreamId.ts +72 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestDeduplicator.ts +191 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestEventValidator.ts +231 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandler.ts +179 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandlerPublic.ts +208 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestParsing.ts +153 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/constants.ts +63 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecCache.ts +109 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetcher.ts +106 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/EventValidator.ts +74 -0
- package/.claude/worktrees/release-1.1.1/src/index.ts +2 -0
- package/.claude/worktrees/release-1.1.1/src/utils.ts +49 -0
- package/.claude/worktrees/release-1.1.1/tsconfig.json +13 -0
- package/.claude/worktrees/release-1.1.1/yarn.lock +2310 -0
- package/.github/workflows/claude.yml +55 -0
- package/dist/AvoDeduplicator.d.ts +4 -3
- package/dist/AvoDeduplicator.js +25 -24
- package/dist/AvoEncryption.d.ts +26 -0
- package/dist/AvoEncryption.js +77 -0
- package/dist/AvoInspector.d.ts +34 -3
- package/dist/AvoInspector.js +182 -36
- package/dist/AvoNetworkCallsHandler.d.ts +39 -13
- package/dist/AvoNetworkCallsHandler.js +127 -33
- package/dist/AvoStreamId.d.ts +5 -0
- package/dist/AvoStreamId.js +15 -0
- package/dist/__tests__/TestAvoEncryption.d.ts +1 -0
- package/dist/__tests__/TestAvoEncryption.js +288 -0
- package/dist/__tests__/TestAvoEventSpecCache.d.ts +1 -0
- package/dist/__tests__/TestAvoEventSpecCache.js +194 -0
- package/dist/__tests__/TestAvoEventSpecFetcher.d.ts +1 -0
- package/dist/__tests__/TestAvoEventSpecFetcher.js +219 -0
- package/dist/__tests__/TestAvoInspector.js +76 -2
- package/dist/__tests__/TestAvoStreamId.d.ts +1 -0
- package/dist/__tests__/TestAvoStreamId.js +55 -0
- package/dist/__tests__/TestEventValidator.d.ts +1 -0
- package/dist/__tests__/TestEventValidator.js +196 -0
- package/dist/__tests__/TestNetworkCallsHandler.js +37 -10
- package/dist/__tests__/TestNetworkCallsHandlerPublic.js +74 -41
- package/dist/eventSpec/AvoEventSpecCache.d.ts +19 -0
- package/dist/eventSpec/AvoEventSpecCache.js +100 -0
- package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +41 -0
- package/dist/eventSpec/AvoEventSpecFetchTypes.js +5 -0
- package/dist/eventSpec/AvoEventSpecFetcher.d.ts +25 -0
- package/dist/eventSpec/AvoEventSpecFetcher.js +159 -0
- package/dist/eventSpec/EventValidator.d.ts +15 -0
- package/dist/eventSpec/EventValidator.js +53 -0
- package/package.json +4 -2
- package/src/AvoDeduplicator.ts +31 -27
- package/src/AvoEncryption.ts +90 -0
- package/src/AvoInspector.ts +275 -49
- package/src/AvoNetworkCallsHandler.ts +205 -45
- package/src/AvoStreamId.ts +16 -0
- package/src/__tests__/TestAvoEncryption.ts +515 -0
- package/src/__tests__/TestAvoEventSpecCache.ts +242 -0
- package/src/__tests__/TestAvoEventSpecFetcher.ts +279 -0
- package/src/__tests__/TestAvoInspector.ts +114 -2
- package/src/__tests__/TestAvoStreamId.ts +72 -0
- package/src/__tests__/TestEventValidator.ts +231 -0
- package/src/__tests__/TestNetworkCallsHandler.ts +82 -15
- package/src/__tests__/TestNetworkCallsHandlerPublic.ts +110 -44
- package/src/eventSpec/AvoEventSpecCache.ts +119 -0
- package/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
- package/src/eventSpec/AvoEventSpecFetcher.ts +186 -0
- package/src/eventSpec/EventValidator.ts +74 -0
package/src/AvoInspector.ts
CHANGED
|
@@ -2,9 +2,13 @@ import { AvoInspectorEnv, AvoInspectorEnvValueType } from "./AvoInspectorEnv";
|
|
|
2
2
|
import { AvoSchemaParser } from "./AvoSchemaParser";
|
|
3
3
|
import { AvoNetworkCallsHandler } from "./AvoNetworkCallsHandler";
|
|
4
4
|
import { AvoDeduplicator } from "./AvoDeduplicator";
|
|
5
|
+
import { AvoStreamId } from "./AvoStreamId";
|
|
6
|
+
import { AvoEventSpecFetcher } from "./eventSpec/AvoEventSpecFetcher";
|
|
7
|
+
import { AvoEventSpecCache } from "./eventSpec/AvoEventSpecCache";
|
|
8
|
+
import { EventValidator } from "./eventSpec/EventValidator";
|
|
9
|
+
import { EventSpecResponse } from "./eventSpec/AvoEventSpecFetchTypes";
|
|
5
10
|
|
|
6
11
|
import { isValueEmpty } from "./utils";
|
|
7
|
-
import { AvoGuid } from "./AvoGuid";
|
|
8
12
|
|
|
9
13
|
const libVersion = require("../package.json").version;
|
|
10
14
|
|
|
@@ -14,6 +18,33 @@ export class AvoInspector {
|
|
|
14
18
|
avoDeduplicator: AvoDeduplicator;
|
|
15
19
|
apiKey: string;
|
|
16
20
|
version: string;
|
|
21
|
+
private publicEncryptionKey?: string;
|
|
22
|
+
|
|
23
|
+
private eventSpecFetcher: AvoEventSpecFetcher | null = null;
|
|
24
|
+
private eventSpecCache: AvoEventSpecCache | null = null;
|
|
25
|
+
private eventValidator: EventValidator | null = null;
|
|
26
|
+
private generatedAnonymousId: string = "";
|
|
27
|
+
|
|
28
|
+
// Keep the Node process alive while there are pending inspector operations.
|
|
29
|
+
// Without this, short-lived processes (e.g. CLIs) can exit before async
|
|
30
|
+
// sends complete, since the caller typically doesn't await the inspector promise.
|
|
31
|
+
private pendingCount = 0;
|
|
32
|
+
private keepAliveTimer: ReturnType<typeof setInterval> | null = null;
|
|
33
|
+
|
|
34
|
+
private trackPending(promise: Promise<any>): void {
|
|
35
|
+
this.pendingCount++;
|
|
36
|
+
if (this.keepAliveTimer === null) {
|
|
37
|
+
this.keepAliveTimer = setInterval(() => {}, 60_000);
|
|
38
|
+
}
|
|
39
|
+
const done = () => {
|
|
40
|
+
this.pendingCount--;
|
|
41
|
+
if (this.pendingCount === 0 && this.keepAliveTimer !== null) {
|
|
42
|
+
clearInterval(this.keepAliveTimer);
|
|
43
|
+
this.keepAliveTimer = null;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
promise.then(done, done);
|
|
47
|
+
}
|
|
17
48
|
|
|
18
49
|
private static _shouldLog = false;
|
|
19
50
|
static get shouldLog() {
|
|
@@ -28,6 +59,7 @@ export class AvoInspector {
|
|
|
28
59
|
env: AvoInspectorEnvValueType;
|
|
29
60
|
version: string;
|
|
30
61
|
appName?: string;
|
|
62
|
+
publicEncryptionKey?: string;
|
|
31
63
|
}) {
|
|
32
64
|
// the constructor does aggressive null/undefined checking because same code paths will be accessible from JS
|
|
33
65
|
if (isValueEmpty(options.env)) {
|
|
@@ -60,6 +92,23 @@ export class AvoInspector {
|
|
|
60
92
|
this.version = options.version;
|
|
61
93
|
}
|
|
62
94
|
|
|
95
|
+
this.publicEncryptionKey = options.publicEncryptionKey;
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
this.publicEncryptionKey &&
|
|
99
|
+
this.environment !== AvoInspectorEnv.Prod
|
|
100
|
+
) {
|
|
101
|
+
const hexPattern = /^[0-9a-fA-F]+$/;
|
|
102
|
+
const len = this.publicEncryptionKey.length;
|
|
103
|
+
// Accept both compressed (66 hex chars, prefix 02/03) and uncompressed (130 hex chars, prefix 04) P-256 keys
|
|
104
|
+
const isValidLength = len === 66 || len === 130;
|
|
105
|
+
if (!hexPattern.test(this.publicEncryptionKey) || !isValidLength) {
|
|
106
|
+
console.warn(
|
|
107
|
+
"[Avo Inspector] Warning: publicEncryptionKey does not look like a valid P-256 public key (expected 66 or 130 hex characters). Encryption may fail."
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
63
112
|
if (this.environment === AvoInspectorEnv.Dev) {
|
|
64
113
|
AvoInspector._shouldLog = true;
|
|
65
114
|
} else {
|
|
@@ -71,14 +120,23 @@ export class AvoInspector {
|
|
|
71
120
|
this.environment.toString(),
|
|
72
121
|
options.appName || "",
|
|
73
122
|
this.version,
|
|
74
|
-
libVersion
|
|
123
|
+
libVersion,
|
|
124
|
+
this.publicEncryptionKey
|
|
75
125
|
);
|
|
76
126
|
this.avoDeduplicator = new AvoDeduplicator();
|
|
127
|
+
|
|
128
|
+
// Initialize event spec validation for dev/staging only
|
|
129
|
+
if (this.environment !== AvoInspectorEnv.Prod) {
|
|
130
|
+
this.eventSpecFetcher = new AvoEventSpecFetcher(this.apiKey);
|
|
131
|
+
this.eventSpecCache = new AvoEventSpecCache();
|
|
132
|
+
this.eventValidator = new EventValidator();
|
|
133
|
+
}
|
|
77
134
|
}
|
|
78
135
|
|
|
79
136
|
trackSchemaFromEvent(
|
|
80
137
|
eventName: string,
|
|
81
|
-
eventProperties: { [propName: string]: any }
|
|
138
|
+
eventProperties: { [propName: string]: any },
|
|
139
|
+
streamId?: string
|
|
82
140
|
): Promise<
|
|
83
141
|
Array<{
|
|
84
142
|
propertyName: string;
|
|
@@ -87,33 +145,40 @@ export class AvoInspector {
|
|
|
87
145
|
}>
|
|
88
146
|
> {
|
|
89
147
|
try {
|
|
148
|
+
const avoStreamId = new AvoStreamId(streamId);
|
|
149
|
+
const anonymousId = avoStreamId.streamId || this.generatedAnonymousId;
|
|
150
|
+
|
|
90
151
|
if (
|
|
91
152
|
this.avoDeduplicator.shouldRegisterEvent(
|
|
92
153
|
eventName,
|
|
93
154
|
eventProperties,
|
|
94
|
-
false
|
|
155
|
+
false,
|
|
156
|
+
anonymousId
|
|
95
157
|
)
|
|
96
158
|
) {
|
|
97
159
|
if (AvoInspector.shouldLog) {
|
|
98
160
|
console.log(
|
|
99
|
-
"Avo Inspector:
|
|
161
|
+
"Avo Inspector: Supplied event " +
|
|
100
162
|
eventName +
|
|
101
|
-
" with params " +
|
|
163
|
+
" with params \n" +
|
|
102
164
|
JSON.stringify(eventProperties)
|
|
103
165
|
);
|
|
104
166
|
}
|
|
105
167
|
let eventSchema = this.extractSchema(eventProperties, false);
|
|
106
|
-
|
|
168
|
+
|
|
169
|
+
return this.sendEventWithOptionalValidation(
|
|
107
170
|
eventName,
|
|
108
171
|
eventSchema,
|
|
109
172
|
null,
|
|
110
|
-
null
|
|
173
|
+
null,
|
|
174
|
+
anonymousId,
|
|
175
|
+
eventProperties
|
|
111
176
|
).then(() => {
|
|
112
177
|
return eventSchema;
|
|
113
178
|
});
|
|
114
179
|
} else {
|
|
115
180
|
if (AvoInspector.shouldLog) {
|
|
116
|
-
console.log("Avo Inspector: Deduplicated event
|
|
181
|
+
console.log("Avo Inspector: Deduplicated event " + eventName);
|
|
117
182
|
}
|
|
118
183
|
return Promise.resolve([]);
|
|
119
184
|
}
|
|
@@ -128,11 +193,12 @@ export class AvoInspector {
|
|
|
128
193
|
}
|
|
129
194
|
}
|
|
130
195
|
|
|
131
|
-
|
|
196
|
+
_avoFunctionTrackSchemaFromEvent(
|
|
132
197
|
eventName: string,
|
|
133
198
|
eventProperties: { [propName: string]: any },
|
|
134
199
|
eventId: string,
|
|
135
|
-
eventHash: string
|
|
200
|
+
eventHash: string,
|
|
201
|
+
streamId?: string
|
|
136
202
|
): Promise<
|
|
137
203
|
Array<{
|
|
138
204
|
propertyName: string;
|
|
@@ -141,33 +207,39 @@ export class AvoInspector {
|
|
|
141
207
|
}>
|
|
142
208
|
> {
|
|
143
209
|
try {
|
|
210
|
+
const anonymousId = (streamId && streamId.length > 0) ? streamId : this.generatedAnonymousId;
|
|
211
|
+
|
|
144
212
|
if (
|
|
145
213
|
this.avoDeduplicator.shouldRegisterEvent(
|
|
146
214
|
eventName,
|
|
147
215
|
eventProperties,
|
|
148
|
-
true
|
|
216
|
+
true,
|
|
217
|
+
anonymousId
|
|
149
218
|
)
|
|
150
219
|
) {
|
|
151
220
|
if (AvoInspector.shouldLog) {
|
|
152
221
|
console.log(
|
|
153
|
-
"Avo Inspector:
|
|
222
|
+
"Avo Inspector: Supplied event " +
|
|
154
223
|
eventName +
|
|
155
|
-
" with params " +
|
|
224
|
+
" with params \n" +
|
|
156
225
|
JSON.stringify(eventProperties)
|
|
157
226
|
);
|
|
158
227
|
}
|
|
159
228
|
let eventSchema = this.extractSchema(eventProperties, false);
|
|
160
|
-
|
|
229
|
+
|
|
230
|
+
return this.sendEventWithOptionalValidation(
|
|
161
231
|
eventName,
|
|
162
232
|
eventSchema,
|
|
163
233
|
eventId,
|
|
164
|
-
eventHash
|
|
234
|
+
eventHash,
|
|
235
|
+
anonymousId,
|
|
236
|
+
eventProperties
|
|
165
237
|
).then(() => {
|
|
166
238
|
return eventSchema;
|
|
167
239
|
});
|
|
168
240
|
} else {
|
|
169
241
|
if (AvoInspector.shouldLog) {
|
|
170
|
-
console.log("Avo Inspector: Deduplicated event
|
|
242
|
+
console.log("Avo Inspector: Deduplicated event " + eventName);
|
|
171
243
|
}
|
|
172
244
|
return Promise.resolve([]);
|
|
173
245
|
}
|
|
@@ -182,7 +254,16 @@ export class AvoInspector {
|
|
|
182
254
|
}
|
|
183
255
|
}
|
|
184
256
|
|
|
185
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Try to fetch event spec and validate, then send a single event call.
|
|
259
|
+
* If validation succeeds, sends a validated event (with results merged in).
|
|
260
|
+
* If unavailable, sends a plain event.
|
|
261
|
+
*
|
|
262
|
+
* Uses trackPending() to keep the Node process alive until the work completes,
|
|
263
|
+
* so short-lived processes (CLIs) don't exit before the send finishes — even
|
|
264
|
+
* when the caller doesn't await the returned promise.
|
|
265
|
+
*/
|
|
266
|
+
private sendEventWithOptionalValidation(
|
|
186
267
|
eventName: string,
|
|
187
268
|
eventSchema: Array<{
|
|
188
269
|
propertyName: string;
|
|
@@ -190,41 +271,66 @@ export class AvoInspector {
|
|
|
190
271
|
children?: any;
|
|
191
272
|
}>,
|
|
192
273
|
eventId: string | null,
|
|
193
|
-
eventHash: string | null
|
|
274
|
+
eventHash: string | null,
|
|
275
|
+
anonymousId: string,
|
|
276
|
+
rawEventProperties?: { [propName: string]: any }
|
|
194
277
|
): Promise<void> {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
278
|
+
const work = this.doSendEventWithOptionalValidation(
|
|
279
|
+
eventName, eventSchema, eventId, eventHash, anonymousId, rawEventProperties
|
|
280
|
+
);
|
|
281
|
+
this.trackPending(work);
|
|
282
|
+
return work;
|
|
283
|
+
}
|
|
201
284
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
285
|
+
private async doSendEventWithOptionalValidation(
|
|
286
|
+
eventName: string,
|
|
287
|
+
eventSchema: Array<{
|
|
288
|
+
propertyName: string;
|
|
289
|
+
propertyType: string;
|
|
290
|
+
children?: any;
|
|
291
|
+
}>,
|
|
292
|
+
eventId: string | null,
|
|
293
|
+
eventHash: string | null,
|
|
294
|
+
anonymousId: string,
|
|
295
|
+
rawEventProperties?: { [propName: string]: any }
|
|
296
|
+
): Promise<void> {
|
|
297
|
+
const validationResult = await this.fetchAndValidate(eventName, eventSchema, anonymousId, rawEventProperties, eventId);
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
let body;
|
|
301
|
+
if (validationResult) {
|
|
218
302
|
if (AvoInspector.shouldLog) {
|
|
219
|
-
console.log("Avo Inspector:
|
|
303
|
+
console.log("Avo Inspector: Sending validated event " + eventName);
|
|
220
304
|
}
|
|
305
|
+
const eventProps = this.avoNetworkCallsHandler.buildEventProperties(eventSchema, rawEventProperties);
|
|
306
|
+
body = this.avoNetworkCallsHandler.bodyForValidatedEventSchemaCall(
|
|
307
|
+
anonymousId,
|
|
308
|
+
eventName,
|
|
309
|
+
eventProps,
|
|
310
|
+
eventId,
|
|
311
|
+
eventHash,
|
|
312
|
+
validationResult.metadata,
|
|
313
|
+
validationResult.propertyResults
|
|
314
|
+
);
|
|
315
|
+
} else {
|
|
316
|
+
body = this.avoNetworkCallsHandler.bodyForEventSchemaCall(
|
|
317
|
+
anonymousId,
|
|
318
|
+
eventName,
|
|
319
|
+
eventSchema,
|
|
320
|
+
eventId,
|
|
321
|
+
eventHash,
|
|
322
|
+
rawEventProperties
|
|
323
|
+
);
|
|
221
324
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
325
|
+
|
|
326
|
+
await this.avoNetworkCallsHandler.callInspectorWithBatchBody([body]);
|
|
327
|
+
|
|
328
|
+
if (AvoInspector.shouldLog) {
|
|
329
|
+
const schemaString = eventSchema.map(p => '\t"' + p.propertyName + '": "' + p.propertyType + '"').join(";\n");
|
|
330
|
+
console.log("Avo Inspector: Saved event " + eventName + " with schema {\n" + schemaString + "\n}");
|
|
331
|
+
}
|
|
332
|
+
} catch (err) {
|
|
333
|
+
console.error("Avo Inspector: schema sending failed: " + err + ".");
|
|
228
334
|
}
|
|
229
335
|
}
|
|
230
336
|
|
|
@@ -260,7 +366,14 @@ export class AvoInspector {
|
|
|
260
366
|
);
|
|
261
367
|
}
|
|
262
368
|
|
|
263
|
-
|
|
369
|
+
const schema = AvoSchemaParser.extractSchema(eventProperties);
|
|
370
|
+
|
|
371
|
+
if (AvoInspector.shouldLog) {
|
|
372
|
+
const schemaString = schema.map(p => '\t"' + p.propertyName + '": "' + p.propertyType + '"').join(";\n");
|
|
373
|
+
console.log("Avo Inspector: Parsed schema {\n" + schemaString + "\n}");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return schema;
|
|
264
377
|
} catch (e) {
|
|
265
378
|
console.error(
|
|
266
379
|
"Avo Inspector: something went wrong. Please report to support@avo.app.",
|
|
@@ -269,4 +382,117 @@ export class AvoInspector {
|
|
|
269
382
|
return [];
|
|
270
383
|
}
|
|
271
384
|
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Fetch event spec and validate. Returns validation results + metadata if
|
|
388
|
+
* validation succeeds, or null if unavailable (prod, no spec, fetch error).
|
|
389
|
+
*/
|
|
390
|
+
private async fetchAndValidate(
|
|
391
|
+
eventName: string,
|
|
392
|
+
eventSchema: Array<{
|
|
393
|
+
propertyName: string;
|
|
394
|
+
propertyType: string;
|
|
395
|
+
children?: any;
|
|
396
|
+
}>,
|
|
397
|
+
anonymousId: string,
|
|
398
|
+
rawEventProperties?: { [propName: string]: any },
|
|
399
|
+
eventId?: string | null
|
|
400
|
+
): Promise<{ metadata: import("./eventSpec/AvoEventSpecFetchTypes").EventSpecMetadata; propertyResults: import("./eventSpec/AvoEventSpecFetchTypes").PropertyValidationResult[] } | null> {
|
|
401
|
+
if (!this.eventSpecFetcher || !this.eventSpecCache || !this.eventValidator) {
|
|
402
|
+
if (AvoInspector.shouldLog) {
|
|
403
|
+
console.log("Avo Inspector: Skipping event spec validation for event: " + eventName
|
|
404
|
+
+ " (fetcher=" + (this.eventSpecFetcher != null)
|
|
405
|
+
+ ", cache=" + (this.eventSpecCache != null)
|
|
406
|
+
+ ", env=" + this.environment + ")");
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const cacheKey = AvoEventSpecCache.makeKey(this.apiKey, anonymousId, eventName);
|
|
412
|
+
const cache = this.eventSpecCache;
|
|
413
|
+
const validator = this.eventValidator;
|
|
414
|
+
|
|
415
|
+
const doValidate = (specResponse: EventSpecResponse) => {
|
|
416
|
+
if (specResponse.eventSpec === null) {
|
|
417
|
+
if (AvoInspector.shouldLog) {
|
|
418
|
+
console.log("Avo Inspector: Event spec fetch returned null for event: " + eventName + ". Sending without validation.");
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const eventProperties = eventSchema.map((prop) => ({
|
|
424
|
+
propertyName: prop.propertyName,
|
|
425
|
+
propertyType: prop.propertyType,
|
|
426
|
+
...(rawEventProperties && rawEventProperties[prop.propertyName] !== undefined
|
|
427
|
+
? { propertyValue: String(rawEventProperties[prop.propertyName]) }
|
|
428
|
+
: {}),
|
|
429
|
+
}));
|
|
430
|
+
|
|
431
|
+
if (AvoInspector.shouldLog) {
|
|
432
|
+
console.log("Avo Inspector: Validating event: " + eventName
|
|
433
|
+
+ " with " + eventProperties.length + " properties"
|
|
434
|
+
+ " against " + specResponse.eventSpec.properties.length + " spec properties");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const validationId = eventId || eventName;
|
|
438
|
+
const results = validator.validate(
|
|
439
|
+
specResponse.eventSpec,
|
|
440
|
+
eventProperties,
|
|
441
|
+
validationId
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
if (AvoInspector.shouldLog) {
|
|
445
|
+
console.log("Avo Inspector: Validation complete for event: " + eventName
|
|
446
|
+
+ " with " + results.length + " property results");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return { metadata: specResponse.metadata, propertyResults: results };
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Check cache first
|
|
453
|
+
const cached = cache.get(cacheKey);
|
|
454
|
+
if (cached !== undefined) {
|
|
455
|
+
if (AvoInspector.shouldLog) {
|
|
456
|
+
console.log("Avo Inspector: Event spec cache hit for event: " + eventName);
|
|
457
|
+
}
|
|
458
|
+
return doValidate(cached);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Cache miss — fetch spec (process stays alive via trackPending)
|
|
462
|
+
if (AvoInspector.shouldLog) {
|
|
463
|
+
console.log("Avo Inspector: Event spec cache miss for event: " + eventName + ". Fetching before sending.");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const fetcher = this.eventSpecFetcher;
|
|
467
|
+
return new Promise((resolve) => {
|
|
468
|
+
fetcher.fetch(eventName, anonymousId, (result) => {
|
|
469
|
+
if (result !== null) {
|
|
470
|
+
cache.set(cacheKey, result);
|
|
471
|
+
resolve(doValidate(result));
|
|
472
|
+
} else {
|
|
473
|
+
if (AvoInspector.shouldLog) {
|
|
474
|
+
console.log("Avo Inspector: Event spec fetch returned null for event: " + eventName + ". Cached empty response. Sending without validation.");
|
|
475
|
+
}
|
|
476
|
+
resolve(null);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
destroy(): void {
|
|
483
|
+
if (this.eventSpecFetcher) {
|
|
484
|
+
this.eventSpecFetcher.destroy();
|
|
485
|
+
this.eventSpecFetcher = null;
|
|
486
|
+
}
|
|
487
|
+
if (this.eventSpecCache) {
|
|
488
|
+
this.eventSpecCache.flush();
|
|
489
|
+
this.eventSpecCache = null;
|
|
490
|
+
}
|
|
491
|
+
this.eventValidator = null;
|
|
492
|
+
if (this.keepAliveTimer !== null) {
|
|
493
|
+
clearInterval(this.keepAliveTimer);
|
|
494
|
+
this.keepAliveTimer = null;
|
|
495
|
+
}
|
|
496
|
+
this.pendingCount = 0;
|
|
497
|
+
}
|
|
272
498
|
}
|