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.
Files changed (93) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/.claude/settings.local.json +11 -0
  3. package/.claude/worktrees/release-1.1.1/LICENSE +21 -0
  4. package/.claude/worktrees/release-1.1.1/README.md +69 -0
  5. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/index.js +30 -0
  6. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/package.json +15 -0
  7. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/yarn.lock +6 -0
  8. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/dist/index.js +34 -0
  9. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/index.ts +30 -0
  10. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/package.json +18 -0
  11. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/tsconfig.json +15 -0
  12. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/yarn.lock +11 -0
  13. package/.claude/worktrees/release-1.1.1/package-lock.json +4039 -0
  14. package/.claude/worktrees/release-1.1.1/package.json +48 -0
  15. package/.claude/worktrees/release-1.1.1/reason/AvoInspector.rei +11 -0
  16. package/.claude/worktrees/release-1.1.1/reason/NodeAvoInspector.re +27 -0
  17. package/.claude/worktrees/release-1.1.1/src/AvoDeduplicator.ts +146 -0
  18. package/.claude/worktrees/release-1.1.1/src/AvoEncryption.ts +90 -0
  19. package/.claude/worktrees/release-1.1.1/src/AvoGuid.ts +9 -0
  20. package/.claude/worktrees/release-1.1.1/src/AvoInspector.ts +383 -0
  21. package/.claude/worktrees/release-1.1.1/src/AvoInspectorEnv.ts +8 -0
  22. package/.claude/worktrees/release-1.1.1/src/AvoNetworkCallsHandler.ts +267 -0
  23. package/.claude/worktrees/release-1.1.1/src/AvoSchemaParser.ts +115 -0
  24. package/.claude/worktrees/release-1.1.1/src/AvoStreamId.ts +16 -0
  25. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEncryption.ts +515 -0
  26. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecCache.ts +242 -0
  27. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecFetcher.ts +241 -0
  28. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoInspector.ts +360 -0
  29. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoStreamId.ts +72 -0
  30. package/.claude/worktrees/release-1.1.1/src/__tests__/TestDeduplicator.ts +191 -0
  31. package/.claude/worktrees/release-1.1.1/src/__tests__/TestEventValidator.ts +231 -0
  32. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandler.ts +179 -0
  33. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandlerPublic.ts +208 -0
  34. package/.claude/worktrees/release-1.1.1/src/__tests__/TestParsing.ts +153 -0
  35. package/.claude/worktrees/release-1.1.1/src/__tests__/constants.ts +63 -0
  36. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecCache.ts +109 -0
  37. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
  38. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetcher.ts +106 -0
  39. package/.claude/worktrees/release-1.1.1/src/eventSpec/EventValidator.ts +74 -0
  40. package/.claude/worktrees/release-1.1.1/src/index.ts +2 -0
  41. package/.claude/worktrees/release-1.1.1/src/utils.ts +49 -0
  42. package/.claude/worktrees/release-1.1.1/tsconfig.json +13 -0
  43. package/.claude/worktrees/release-1.1.1/yarn.lock +2310 -0
  44. package/.github/workflows/claude.yml +55 -0
  45. package/dist/AvoDeduplicator.d.ts +4 -3
  46. package/dist/AvoDeduplicator.js +25 -24
  47. package/dist/AvoEncryption.d.ts +26 -0
  48. package/dist/AvoEncryption.js +77 -0
  49. package/dist/AvoInspector.d.ts +34 -3
  50. package/dist/AvoInspector.js +182 -36
  51. package/dist/AvoNetworkCallsHandler.d.ts +39 -13
  52. package/dist/AvoNetworkCallsHandler.js +127 -33
  53. package/dist/AvoStreamId.d.ts +5 -0
  54. package/dist/AvoStreamId.js +15 -0
  55. package/dist/__tests__/TestAvoEncryption.d.ts +1 -0
  56. package/dist/__tests__/TestAvoEncryption.js +288 -0
  57. package/dist/__tests__/TestAvoEventSpecCache.d.ts +1 -0
  58. package/dist/__tests__/TestAvoEventSpecCache.js +194 -0
  59. package/dist/__tests__/TestAvoEventSpecFetcher.d.ts +1 -0
  60. package/dist/__tests__/TestAvoEventSpecFetcher.js +219 -0
  61. package/dist/__tests__/TestAvoInspector.js +76 -2
  62. package/dist/__tests__/TestAvoStreamId.d.ts +1 -0
  63. package/dist/__tests__/TestAvoStreamId.js +55 -0
  64. package/dist/__tests__/TestEventValidator.d.ts +1 -0
  65. package/dist/__tests__/TestEventValidator.js +196 -0
  66. package/dist/__tests__/TestNetworkCallsHandler.js +37 -10
  67. package/dist/__tests__/TestNetworkCallsHandlerPublic.js +74 -41
  68. package/dist/eventSpec/AvoEventSpecCache.d.ts +19 -0
  69. package/dist/eventSpec/AvoEventSpecCache.js +100 -0
  70. package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +41 -0
  71. package/dist/eventSpec/AvoEventSpecFetchTypes.js +5 -0
  72. package/dist/eventSpec/AvoEventSpecFetcher.d.ts +25 -0
  73. package/dist/eventSpec/AvoEventSpecFetcher.js +159 -0
  74. package/dist/eventSpec/EventValidator.d.ts +15 -0
  75. package/dist/eventSpec/EventValidator.js +53 -0
  76. package/package.json +4 -2
  77. package/src/AvoDeduplicator.ts +31 -27
  78. package/src/AvoEncryption.ts +90 -0
  79. package/src/AvoInspector.ts +275 -49
  80. package/src/AvoNetworkCallsHandler.ts +205 -45
  81. package/src/AvoStreamId.ts +16 -0
  82. package/src/__tests__/TestAvoEncryption.ts +515 -0
  83. package/src/__tests__/TestAvoEventSpecCache.ts +242 -0
  84. package/src/__tests__/TestAvoEventSpecFetcher.ts +279 -0
  85. package/src/__tests__/TestAvoInspector.ts +114 -2
  86. package/src/__tests__/TestAvoStreamId.ts +72 -0
  87. package/src/__tests__/TestEventValidator.ts +231 -0
  88. package/src/__tests__/TestNetworkCallsHandler.ts +82 -15
  89. package/src/__tests__/TestNetworkCallsHandlerPublic.ts +110 -44
  90. package/src/eventSpec/AvoEventSpecCache.ts +119 -0
  91. package/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
  92. package/src/eventSpec/AvoEventSpecFetcher.ts +186 -0
  93. package/src/eventSpec/EventValidator.ts +74 -0
@@ -0,0 +1,383 @@
1
+ import { AvoInspectorEnv, AvoInspectorEnvValueType } from "./AvoInspectorEnv";
2
+ import { AvoSchemaParser } from "./AvoSchemaParser";
3
+ import { AvoNetworkCallsHandler } from "./AvoNetworkCallsHandler";
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";
10
+
11
+ import { isValueEmpty } from "./utils";
12
+
13
+ const libVersion = require("../package.json").version;
14
+
15
+ export class AvoInspector {
16
+ environment: AvoInspectorEnvValueType;
17
+ avoNetworkCallsHandler: AvoNetworkCallsHandler;
18
+ avoDeduplicator: AvoDeduplicator;
19
+ apiKey: string;
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
+
27
+ private static _shouldLog = false;
28
+ static get shouldLog() {
29
+ return this._shouldLog;
30
+ }
31
+ static set shouldLog(enable) {
32
+ this._shouldLog = enable;
33
+ }
34
+
35
+ constructor(options: {
36
+ apiKey: string;
37
+ env: AvoInspectorEnvValueType;
38
+ version: string;
39
+ appName?: string;
40
+ publicEncryptionKey?: string;
41
+ }) {
42
+ // the constructor does aggressive null/undefined checking because same code paths will be accessible from JS
43
+ if (isValueEmpty(options.env)) {
44
+ this.environment = AvoInspectorEnv.Dev;
45
+ console.warn(
46
+ "[Avo Inspector] No environment provided. Defaulting to dev."
47
+ );
48
+ } else if (Object.values(AvoInspectorEnv).indexOf(options.env) === -1) {
49
+ this.environment = AvoInspectorEnv.Dev;
50
+ console.warn(
51
+ "[Avo Inspector] Unsupported environment provided. Defaulting to dev. Supported environments - Dev, Staging, Prod."
52
+ );
53
+ } else {
54
+ this.environment = options.env;
55
+ }
56
+
57
+ if (isValueEmpty(options.apiKey)) {
58
+ throw new Error(
59
+ "[Avo Inspector] No API key provided. Inspector can't operate without API key."
60
+ );
61
+ } else {
62
+ this.apiKey = options.apiKey;
63
+ }
64
+
65
+ if (isValueEmpty(options.version)) {
66
+ throw new Error(
67
+ "[Avo Inspector] No version provided. Many features of Inspector rely on versioning. Please provide comparable string version, i.e. integer or semantic."
68
+ );
69
+ } else {
70
+ this.version = options.version;
71
+ }
72
+
73
+ this.publicEncryptionKey = options.publicEncryptionKey;
74
+
75
+ if (
76
+ this.publicEncryptionKey &&
77
+ this.environment !== AvoInspectorEnv.Prod
78
+ ) {
79
+ const hexPattern = /^[0-9a-fA-F]+$/;
80
+ if (!hexPattern.test(this.publicEncryptionKey) || this.publicEncryptionKey.length !== 130) {
81
+ console.warn(
82
+ "[Avo Inspector] Warning: publicEncryptionKey does not look like a valid uncompressed P-256 public key (expected 130 hex characters). Encryption may fail."
83
+ );
84
+ }
85
+ }
86
+
87
+ if (this.environment === AvoInspectorEnv.Dev) {
88
+ AvoInspector._shouldLog = true;
89
+ } else {
90
+ AvoInspector._shouldLog = false;
91
+ }
92
+
93
+ this.avoNetworkCallsHandler = new AvoNetworkCallsHandler(
94
+ this.apiKey,
95
+ this.environment.toString(),
96
+ options.appName || "",
97
+ this.version,
98
+ libVersion,
99
+ this.publicEncryptionKey
100
+ );
101
+ this.avoDeduplicator = new AvoDeduplicator();
102
+
103
+ // Initialize event spec validation for dev/staging only
104
+ if (this.environment !== AvoInspectorEnv.Prod) {
105
+ this.eventSpecFetcher = new AvoEventSpecFetcher(this.apiKey);
106
+ this.eventSpecCache = new AvoEventSpecCache();
107
+ this.eventValidator = new EventValidator();
108
+ }
109
+ }
110
+
111
+ trackSchemaFromEvent(
112
+ eventName: string,
113
+ eventProperties: { [propName: string]: any },
114
+ streamId?: string
115
+ ): Promise<
116
+ Array<{
117
+ propertyName: string;
118
+ propertyType: string;
119
+ children?: any;
120
+ }>
121
+ > {
122
+ try {
123
+ const avoStreamId = new AvoStreamId(streamId);
124
+ const anonymousId = avoStreamId.streamId;
125
+
126
+ if (
127
+ this.avoDeduplicator.shouldRegisterEvent(
128
+ eventName,
129
+ eventProperties,
130
+ false
131
+ )
132
+ ) {
133
+ if (AvoInspector.shouldLog) {
134
+ console.log(
135
+ "Avo Inspector: supplied event " +
136
+ eventName +
137
+ " with params " +
138
+ JSON.stringify(eventProperties)
139
+ );
140
+ }
141
+ let eventSchema = this.extractSchema(eventProperties, false);
142
+
143
+ // Fire-and-forget: validate against event spec (dev/staging only)
144
+ this.fetchAndValidateAsync(eventName, eventSchema, anonymousId, eventProperties);
145
+
146
+ return this.trackSchemaInternal(
147
+ eventName,
148
+ eventSchema,
149
+ null,
150
+ null,
151
+ anonymousId,
152
+ eventProperties
153
+ ).then(() => {
154
+ return eventSchema;
155
+ });
156
+ } else {
157
+ if (AvoInspector.shouldLog) {
158
+ console.log("Avo Inspector: Deduplicated event: " + eventName);
159
+ }
160
+ return Promise.resolve([]);
161
+ }
162
+ } catch (e) {
163
+ console.error(
164
+ "Avo Inspector: something went wrong. Please report to support@avo.app.",
165
+ e
166
+ );
167
+ return Promise.reject(
168
+ "Avo Inspector: something went wrong. Please report to support@avo.app."
169
+ );
170
+ }
171
+ }
172
+
173
+ _avoFunctionTrackSchemaFromEvent(
174
+ eventName: string,
175
+ eventProperties: { [propName: string]: any },
176
+ eventId: string,
177
+ eventHash: string,
178
+ streamId?: string
179
+ ): Promise<
180
+ Array<{
181
+ propertyName: string;
182
+ propertyType: string;
183
+ children?: any;
184
+ }>
185
+ > {
186
+ try {
187
+ if (
188
+ this.avoDeduplicator.shouldRegisterEvent(
189
+ eventName,
190
+ eventProperties,
191
+ true
192
+ )
193
+ ) {
194
+ if (AvoInspector.shouldLog) {
195
+ console.log(
196
+ "Avo Inspector: supplied event " +
197
+ eventName +
198
+ " with params " +
199
+ JSON.stringify(eventProperties)
200
+ );
201
+ }
202
+ let eventSchema = this.extractSchema(eventProperties, false);
203
+ const avoStreamId = new AvoStreamId(streamId);
204
+
205
+ // Fire-and-forget: validate against event spec (dev/staging only)
206
+ this.fetchAndValidateAsync(eventName, eventSchema, avoStreamId.streamId, eventProperties);
207
+
208
+ return this.trackSchemaInternal(
209
+ eventName,
210
+ eventSchema,
211
+ eventId,
212
+ eventHash,
213
+ avoStreamId.streamId,
214
+ eventProperties
215
+ ).then(() => {
216
+ return eventSchema;
217
+ });
218
+ } else {
219
+ if (AvoInspector.shouldLog) {
220
+ console.log("Avo Inspector: Deduplicated event: " + eventName);
221
+ }
222
+ return Promise.resolve([]);
223
+ }
224
+ } catch (e) {
225
+ console.error(
226
+ "Avo Inspector: something went wrong. Please report to support@avo.app.",
227
+ e
228
+ );
229
+ return Promise.reject(
230
+ "Avo Inspector: something went wrong. Please report to support@avo.app."
231
+ );
232
+ }
233
+ }
234
+
235
+ private async trackSchemaInternal(
236
+ eventName: string,
237
+ eventSchema: Array<{
238
+ propertyName: string;
239
+ propertyType: string;
240
+ children?: any;
241
+ }>,
242
+ eventId: string | null,
243
+ eventHash: string | null,
244
+ anonymousId: string,
245
+ rawEventProperties?: { [propName: string]: any }
246
+ ): Promise<void> {
247
+ try {
248
+ await this.avoNetworkCallsHandler.callInspectorWithBatchBody([
249
+ this.avoNetworkCallsHandler.bodyForEventSchemaCall(
250
+ anonymousId,
251
+ eventName,
252
+ eventSchema,
253
+ eventId,
254
+ eventHash,
255
+ rawEventProperties
256
+ )]);
257
+ if (AvoInspector.shouldLog) {
258
+ console.log("Avo Inspector: schema sent successfully.");
259
+ }
260
+ } catch (err) {
261
+ console.error("Avo Inspector: schema sending failed: " + err + ".");
262
+ }
263
+ }
264
+
265
+ enableLogging(enable: boolean) {
266
+ AvoInspector._shouldLog = enable;
267
+ }
268
+
269
+ extractSchema(
270
+ eventProperties: {
271
+ [propName: string]: any;
272
+ },
273
+ shouldLogIfEnabled = true
274
+ ): Array<{
275
+ propertyName: string;
276
+ propertyType: string;
277
+ children?: any;
278
+ }> {
279
+ try {
280
+ if (this.avoDeduplicator.hasSeenEventParams(eventProperties, true)) {
281
+ if (shouldLogIfEnabled && AvoInspector.shouldLog) {
282
+ console.warn(
283
+ "Avo Inspector: WARNING! You are trying to extract schema shape that was just reported by your Codegen. " +
284
+ "This is an indicator of duplicate inspector reporting. " +
285
+ "Please reach out to support@avo.app for advice if you are not sure how to handle this."
286
+ );
287
+ }
288
+ }
289
+
290
+ if (AvoInspector.shouldLog) {
291
+ console.log(
292
+ "Avo Inspector: extracting schema from " +
293
+ JSON.stringify(eventProperties)
294
+ );
295
+ }
296
+
297
+ return AvoSchemaParser.extractSchema(eventProperties);
298
+ } catch (e) {
299
+ console.error(
300
+ "Avo Inspector: something went wrong. Please report to support@avo.app.",
301
+ e
302
+ );
303
+ return [];
304
+ }
305
+ }
306
+
307
+ private fetchAndValidateAsync(
308
+ eventName: string,
309
+ eventSchema: Array<{
310
+ propertyName: string;
311
+ propertyType: string;
312
+ children?: any;
313
+ }>,
314
+ anonymousId: string,
315
+ rawEventProperties?: { [propName: string]: any }
316
+ ): void {
317
+ if (!this.eventSpecFetcher || !this.eventSpecCache || !this.eventValidator) {
318
+ return;
319
+ }
320
+
321
+ const cacheKey = AvoEventSpecCache.makeKey(this.apiKey, anonymousId, eventName);
322
+ const fetcher = this.eventSpecFetcher;
323
+ const cache = this.eventSpecCache;
324
+ const validator = this.eventValidator;
325
+ const networkHandler = this.avoNetworkCallsHandler;
326
+
327
+ const validateAndSend = (specResponse: EventSpecResponse) => {
328
+ if (specResponse.eventSpec === null) {
329
+ return;
330
+ }
331
+
332
+ const eventProperties = eventSchema.map((prop) => ({
333
+ propertyName: prop.propertyName,
334
+ propertyType: prop.propertyType,
335
+ ...(rawEventProperties && rawEventProperties[prop.propertyName] !== undefined
336
+ ? { propertyValue: String(rawEventProperties[prop.propertyName]) }
337
+ : {}),
338
+ }));
339
+
340
+ const results = validator.validate(
341
+ specResponse.eventSpec,
342
+ eventProperties,
343
+ eventName
344
+ );
345
+
346
+ networkHandler.callInspectorWithBatchBody([
347
+ networkHandler.bodyForValidatedEventSchemaCall(
348
+ anonymousId,
349
+ eventName,
350
+ specResponse.metadata,
351
+ results
352
+ ),
353
+ ]).catch(() => {
354
+ // Fire-and-forget: validation send failures are non-critical
355
+ });
356
+ };
357
+
358
+ const cached = cache.get(cacheKey);
359
+ if (cached !== undefined) {
360
+ validateAndSend(cached);
361
+ return;
362
+ }
363
+
364
+ fetcher.fetch(eventName, anonymousId, (result) => {
365
+ if (result !== null) {
366
+ cache.set(cacheKey, result);
367
+ validateAndSend(result);
368
+ }
369
+ });
370
+ }
371
+
372
+ destroy(): void {
373
+ if (this.eventSpecFetcher) {
374
+ this.eventSpecFetcher.destroy();
375
+ this.eventSpecFetcher = null;
376
+ }
377
+ if (this.eventSpecCache) {
378
+ this.eventSpecCache.flush();
379
+ this.eventSpecCache = null;
380
+ }
381
+ this.eventValidator = null;
382
+ }
383
+ }
@@ -0,0 +1,8 @@
1
+ export const AvoInspectorEnv = {
2
+ Prod: "prod",
3
+ Dev: "dev",
4
+ Staging: "staging",
5
+ } as const;
6
+
7
+ export type AvoInspectorEnvType = typeof AvoInspectorEnv;
8
+ export type AvoInspectorEnvValueType = AvoInspectorEnvType[keyof AvoInspectorEnvType];
@@ -0,0 +1,267 @@
1
+ import { AvoGuid } from "./AvoGuid";
2
+ import { AvoInspector } from "./AvoInspector";
3
+ import { AvoEncryption } from "./AvoEncryption";
4
+ import { request } from "https";
5
+ import { EventSpecMetadata, PropertyValidationResult } from "./eventSpec/AvoEventSpecFetchTypes";
6
+
7
+ export interface BaseBody {
8
+ apiKey: string;
9
+ appName: string;
10
+ appVersion: string;
11
+ libVersion: string;
12
+ env: string;
13
+ libPlatform: "node";
14
+ messageId: string;
15
+ anonymousId: string;
16
+ createdAt: string;
17
+ samplingRate: number;
18
+ publicEncryptionKey?: string;
19
+ }
20
+
21
+ export interface EventPropertyEncrypted {
22
+ propertyName: string;
23
+ propertyType: string;
24
+ encryptedPropertyValue: string;
25
+ children?: any;
26
+ }
27
+
28
+ export interface EventPropertyPlain {
29
+ propertyName: string;
30
+ propertyType: string;
31
+ children?: any;
32
+ }
33
+
34
+ export type EventProperty = EventPropertyEncrypted | EventPropertyPlain;
35
+
36
+ export interface EventSchemaBody extends BaseBody {
37
+ type: "event";
38
+ eventName: string;
39
+ eventProperties: Array<EventProperty>;
40
+ avoFunction: boolean;
41
+ eventId: string | null;
42
+ eventHash: string | null;
43
+ }
44
+
45
+ export interface ValidatedEventSchemaBody extends BaseBody {
46
+ type: "validatedEvent";
47
+ eventName: string;
48
+ eventSpecMetadata: EventSpecMetadata;
49
+ propertyResults: PropertyValidationResult[];
50
+ }
51
+
52
+ export type InspectorBody = EventSchemaBody | ValidatedEventSchemaBody;
53
+
54
+ export class AvoNetworkCallsHandler {
55
+ private apiKey: string;
56
+ private envName: string;
57
+ private appName: string;
58
+ private appVersion: string;
59
+ private libVersion: string;
60
+ private samplingRate: number = 1.0;
61
+ private publicEncryptionKey?: string;
62
+
63
+ private static trackingEndpoint = "/inspector/v1/track";
64
+
65
+ constructor(
66
+ apiKey: string,
67
+ envName: string,
68
+ appName: string,
69
+ appVersion: string,
70
+ libVersion: string,
71
+ publicEncryptionKey?: string
72
+ ) {
73
+ this.apiKey = apiKey;
74
+ this.envName = envName;
75
+ this.appName = appName;
76
+ this.appVersion = appVersion;
77
+ this.libVersion = libVersion;
78
+ this.publicEncryptionKey = publicEncryptionKey;
79
+ }
80
+
81
+ callInspectorWithBatchBody(
82
+ inEvents: Array<InspectorBody>
83
+ ): Promise<void> {
84
+ const events = inEvents.filter((x) => x != null);
85
+
86
+ if (events.length === 0) {
87
+ return Promise.resolve();
88
+ }
89
+
90
+ if (Math.random() > this.samplingRate) {
91
+ if (AvoInspector.shouldLog) {
92
+ console.log(
93
+ "Avo Inspector: last event schema dropped due to sampling rate."
94
+ );
95
+ }
96
+ return Promise.resolve();
97
+ }
98
+
99
+ if (AvoInspector.shouldLog) {
100
+ events.forEach(function (event) {
101
+ if (event.type === "event") {
102
+ let schemaEvent: EventSchemaBody = event;
103
+ console.log(
104
+ "Avo Inspector: sending event " +
105
+ schemaEvent.eventName +
106
+ " with schema " +
107
+ JSON.stringify(schemaEvent.eventProperties)
108
+ );
109
+ } else if (event.type === "validatedEvent") {
110
+ let validatedEvent: ValidatedEventSchemaBody = event;
111
+ console.log(
112
+ "Avo Inspector: sending validated event " +
113
+ validatedEvent.eventName +
114
+ " with " +
115
+ validatedEvent.propertyResults.length +
116
+ " property results"
117
+ );
118
+ }
119
+ });
120
+ }
121
+
122
+ return new Promise((resolve, reject) => {
123
+ const data = JSON.stringify(events);
124
+ const options = {
125
+ hostname: "api.avo.app",
126
+ port: 443,
127
+ path: AvoNetworkCallsHandler.trackingEndpoint,
128
+ method: "POST",
129
+ headers: {
130
+ "Content-Type": "text/plain",
131
+ "Content-Length": Buffer.byteLength(data),
132
+ },
133
+ };
134
+ const req = request(options, (res: any) => {
135
+ const chunks: any = [];
136
+ res.on("data", (data: any) => chunks.push(data));
137
+ res.on("end", () => {
138
+ try {
139
+ const data = JSON.parse(Buffer.concat(chunks).toString());
140
+ this.samplingRate = data.samplingRate;
141
+ } catch (e) {}
142
+ resolve();
143
+ });
144
+ });
145
+ req.write(data);
146
+ req.setTimeout(10_000);
147
+ req.on("error", () => {
148
+ reject("Request failed");
149
+ });
150
+ req.on("timeout", () => {
151
+ req.destroy();
152
+ reject("Request timed out");
153
+ });
154
+ req.end();
155
+ });
156
+ }
157
+
158
+ bodyForEventSchemaCall(
159
+ anonymousId: string,
160
+ eventName: string,
161
+ eventProperties: Array<{
162
+ propertyName: string;
163
+ propertyType: string;
164
+ children?: any;
165
+ }>,
166
+ eventId: string | null,
167
+ eventHash: string | null,
168
+ rawEventProperties?: { [propName: string]: any }
169
+ ): EventSchemaBody {
170
+ let eventSchemaBody = this.createBaseCallBody(anonymousId) as EventSchemaBody;
171
+ eventSchemaBody.type = "event";
172
+ eventSchemaBody.eventName = eventName;
173
+
174
+ if (AvoEncryption.shouldEncrypt(this.envName, this.publicEncryptionKey) && rawEventProperties) {
175
+ eventSchemaBody.eventProperties = this.encryptProperties(eventProperties, rawEventProperties);
176
+ } else {
177
+ eventSchemaBody.eventProperties = eventProperties;
178
+ }
179
+
180
+ if (eventId != null) {
181
+ eventSchemaBody.avoFunction = true;
182
+ eventSchemaBody.eventId = eventId;
183
+ eventSchemaBody.eventHash = eventHash;
184
+ } else {
185
+ eventSchemaBody.avoFunction = false;
186
+ eventSchemaBody.eventId = null;
187
+ eventSchemaBody.eventHash = null;
188
+ }
189
+
190
+ return eventSchemaBody;
191
+ }
192
+
193
+ bodyForValidatedEventSchemaCall(
194
+ anonymousId: string,
195
+ eventName: string,
196
+ eventSpecMetadata: EventSpecMetadata,
197
+ propertyResults: PropertyValidationResult[]
198
+ ): ValidatedEventSchemaBody {
199
+ let body = this.createBaseCallBody(anonymousId) as ValidatedEventSchemaBody;
200
+ body.type = "validatedEvent";
201
+ body.eventName = eventName;
202
+ body.eventSpecMetadata = eventSpecMetadata;
203
+ body.propertyResults = propertyResults;
204
+ return body;
205
+ }
206
+
207
+ private encryptProperties(
208
+ properties: Array<{
209
+ propertyName: string;
210
+ propertyType: string;
211
+ children?: any;
212
+ }>,
213
+ rawEventProperties: { [propName: string]: any }
214
+ ): Array<EventProperty> {
215
+ const result: Array<EventProperty> = [];
216
+
217
+ for (const prop of properties) {
218
+ // List-type properties: omit entirely
219
+ if (AvoEncryption.isListType(prop.propertyType)) {
220
+ continue;
221
+ }
222
+
223
+ const rawValue = rawEventProperties[prop.propertyName];
224
+ const jsonValue = JSON.stringify(rawValue) ?? "null";
225
+
226
+ const encrypted = AvoEncryption.encryptValue(
227
+ jsonValue,
228
+ this.publicEncryptionKey!
229
+ );
230
+
231
+ if (encrypted === null) {
232
+ // Encryption failure: omit the property (warning already logged by encryptValue)
233
+ continue;
234
+ }
235
+
236
+ result.push({
237
+ propertyName: prop.propertyName,
238
+ propertyType: prop.propertyType,
239
+ encryptedPropertyValue: encrypted,
240
+ ...(prop.children !== undefined ? { children: prop.children } : {}),
241
+ });
242
+ }
243
+
244
+ return result;
245
+ }
246
+
247
+ private createBaseCallBody(anonymousId: string): BaseBody {
248
+ const body: BaseBody = {
249
+ apiKey: this.apiKey,
250
+ appName: this.appName,
251
+ appVersion: this.appVersion,
252
+ libVersion: this.libVersion,
253
+ env: this.envName,
254
+ libPlatform: "node",
255
+ messageId: AvoGuid.newGuid(),
256
+ anonymousId: anonymousId,
257
+ createdAt: new Date().toISOString(),
258
+ samplingRate: this.samplingRate,
259
+ };
260
+
261
+ if (this.publicEncryptionKey && this.publicEncryptionKey.length > 0) {
262
+ body.publicEncryptionKey = this.publicEncryptionKey;
263
+ }
264
+
265
+ return body;
266
+ }
267
+ }