node-avo-inspector 1.1.1 → 1.2.0

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
@@ -1,6 +1,8 @@
1
1
  import { AvoGuid } from "./AvoGuid";
2
2
  import { AvoInspector } from "./AvoInspector";
3
+ import { AvoEncryption } from "./AvoEncryption";
3
4
  import { request } from "https";
5
+ import { EventSpecMetadata, PropertyValidationResult } from "./eventSpec/AvoEventSpecFetchTypes";
4
6
 
5
7
  export interface BaseBody {
6
8
  apiKey: string;
@@ -11,28 +13,46 @@ export interface BaseBody {
11
13
  libPlatform: "node";
12
14
  messageId: string;
13
15
  trackingId: string;
14
- createdAt: string;
15
16
  sessionId: string;
17
+ anonymousId: string;
18
+ createdAt: string;
16
19
  samplingRate: number;
20
+ publicEncryptionKey?: string;
17
21
  }
18
22
 
19
- export interface SessionStartedBody extends BaseBody {
20
- type: "sessionStarted";
23
+ export interface EventPropertyEncrypted {
24
+ propertyName: string;
25
+ propertyType: string;
26
+ encryptedPropertyValue: string;
27
+ children?: any;
21
28
  }
22
29
 
30
+ export interface EventPropertyPlain {
31
+ propertyName: string;
32
+ propertyType: string;
33
+ children?: any;
34
+ }
35
+
36
+ export interface EventPropertyValidation {
37
+ failedEventIds?: string[];
38
+ passedEventIds?: string[];
39
+ }
40
+
41
+ export type EventProperty = (EventPropertyEncrypted | EventPropertyPlain) & EventPropertyValidation;
42
+
23
43
  export interface EventSchemaBody extends BaseBody {
24
44
  type: "event";
25
45
  eventName: string;
26
- eventProperties: Array<{
27
- propertyName: string;
28
- propertyType: string;
29
- children?: any;
30
- }>;
46
+ eventProperties: Array<EventProperty>;
31
47
  avoFunction: boolean;
32
48
  eventId: string | null;
33
49
  eventHash: string | null;
50
+ streamId?: string;
51
+ eventSpecMetadata?: EventSpecMetadata;
34
52
  }
35
53
 
54
+ export type InspectorBody = EventSchemaBody;
55
+
36
56
  export class AvoNetworkCallsHandler {
37
57
  private apiKey: string;
38
58
  private envName: string;
@@ -40,6 +60,7 @@ export class AvoNetworkCallsHandler {
40
60
  private appVersion: string;
41
61
  private libVersion: string;
42
62
  private samplingRate: number = 1.0;
63
+ private publicEncryptionKey?: string;
43
64
 
44
65
  private static trackingEndpoint = "/inspector/v1/track";
45
66
 
@@ -48,17 +69,19 @@ export class AvoNetworkCallsHandler {
48
69
  envName: string,
49
70
  appName: string,
50
71
  appVersion: string,
51
- libVersion: string
72
+ libVersion: string,
73
+ publicEncryptionKey?: string
52
74
  ) {
53
75
  this.apiKey = apiKey;
54
76
  this.envName = envName;
55
77
  this.appName = appName;
56
78
  this.appVersion = appVersion;
57
79
  this.libVersion = libVersion;
80
+ this.publicEncryptionKey = publicEncryptionKey;
58
81
  }
59
82
 
60
83
  callInspectorWithBatchBody(
61
- inEvents: Array<SessionStartedBody | EventSchemaBody>
84
+ inEvents: Array<InspectorBody>
62
85
  ): Promise<void> {
63
86
  const events = inEvents.filter((x) => x != null);
64
87
 
@@ -77,64 +100,83 @@ export class AvoNetworkCallsHandler {
77
100
 
78
101
  if (AvoInspector.shouldLog) {
79
102
  events.forEach(function (event) {
80
- if (event.type === "sessionStarted") {
81
- console.log("Avo Inspector: sending session started event.");
82
- } else if (event.type === "event") {
83
- let schemaEvent: EventSchemaBody = event;
84
- console.log(
85
- "Avo Inspector: sending event " +
86
- schemaEvent.eventName +
87
- " with schema " +
88
- JSON.stringify(schemaEvent.eventProperties)
89
- );
90
- }
103
+ const eventProps = event.eventProperties
104
+ .map(p => '\t"' + p.propertyName + '": "' + p.propertyType + '"')
105
+ .join(";\n");
106
+ const validated = event.eventSpecMetadata ? " (validated)" : "";
107
+ console.log(
108
+ "Avo Inspector: Sending event " +
109
+ event.eventName + validated +
110
+ " with schema {\n" + eventProps + "\n}"
111
+ );
91
112
  });
92
113
  }
93
114
 
94
- return new Promise(function (resolve, reject) {
115
+ return new Promise((resolve, reject) => {
95
116
  const data = JSON.stringify(events);
96
- var options = {
117
+ const options = {
97
118
  hostname: "api.avo.app",
98
119
  port: 443,
99
120
  path: AvoNetworkCallsHandler.trackingEndpoint,
100
121
  method: "POST",
101
122
  headers: {
102
- "Content-Type": "text/plain",
123
+ "Accept": "application/json",
124
+ "Content-Type": "application/json",
103
125
  "Content-Length": Buffer.byteLength(data),
104
126
  },
105
127
  };
106
- var req = request(options, (res: any) => {
128
+
129
+ if (AvoInspector.shouldLog) {
130
+ console.log("Avo Inspector: [network] POST https://" + options.hostname + options.path);
131
+ console.log("Avo Inspector: [network] Request body (" + Buffer.byteLength(data) + " bytes)");
132
+ }
133
+
134
+ const req = request(options, (res: any) => {
135
+ if (AvoInspector.shouldLog) {
136
+ console.log("Avo Inspector: [network] Response status: " + res.statusCode + " " + res.statusMessage);
137
+ }
107
138
  const chunks: any = [];
108
139
  res.on("data", (data: any) => chunks.push(data));
109
140
  res.on("end", () => {
110
- try {
111
- // @ts-ignore
112
- const data = JSON.parse(Buffer.concat(chunks).toString());
113
- // @ts-ignore
114
- this.samplingRate = data.samplingRate;
115
- } catch (e) {}
141
+ if (res.statusCode === 200) {
142
+ try {
143
+ const responseBody = Buffer.concat(chunks).toString();
144
+ const data = JSON.parse(responseBody);
145
+ if (typeof data.samplingRate === "number" && data.samplingRate >= 0 && data.samplingRate <= 1) {
146
+ this.samplingRate = data.samplingRate;
147
+ }
148
+ } catch (e) {
149
+ if (AvoInspector.shouldLog) {
150
+ console.warn("Avo Inspector: [network] Failed to parse response JSON: " + e);
151
+ }
152
+ }
153
+ } else if (AvoInspector.shouldLog) {
154
+ console.warn("Avo Inspector: [network] Non-200 response: " + res.statusCode);
155
+ }
116
156
  resolve();
117
157
  });
118
158
  });
119
159
  req.write(data);
120
- req.on("error", () => {
160
+ req.setTimeout(10_000);
161
+ req.on("error", (err: any) => {
162
+ if (AvoInspector.shouldLog) {
163
+ console.error("Avo Inspector: [network] Request error: " + err);
164
+ }
121
165
  reject("Request failed");
122
166
  });
123
167
  req.on("timeout", () => {
168
+ if (AvoInspector.shouldLog) {
169
+ console.error("Avo Inspector: [network] Request timed out after 10s");
170
+ }
171
+ req.destroy();
124
172
  reject("Request timed out");
125
173
  });
126
174
  req.end();
127
175
  });
128
176
  }
129
177
 
130
- bodyForSessionStartedCall(sessionId: string): SessionStartedBody {
131
- let sessionBody = this.createBaseCallBody(sessionId) as SessionStartedBody;
132
- sessionBody.type = "sessionStarted";
133
- return sessionBody;
134
- }
135
-
136
178
  bodyForEventSchemaCall(
137
- sessionId: string,
179
+ anonymousId: string,
138
180
  eventName: string,
139
181
  eventProperties: Array<{
140
182
  propertyName: string;
@@ -142,12 +184,18 @@ export class AvoNetworkCallsHandler {
142
184
  children?: any;
143
185
  }>,
144
186
  eventId: string | null,
145
- eventHash: string | null
187
+ eventHash: string | null,
188
+ rawEventProperties?: { [propName: string]: any }
146
189
  ): EventSchemaBody {
147
- let eventSchemaBody = this.createBaseCallBody(sessionId) as EventSchemaBody;
190
+ let eventSchemaBody = this.createBaseCallBody(anonymousId) as EventSchemaBody;
148
191
  eventSchemaBody.type = "event";
149
192
  eventSchemaBody.eventName = eventName;
150
- eventSchemaBody.eventProperties = eventProperties;
193
+
194
+ if (AvoEncryption.shouldEncrypt(this.envName, this.publicEncryptionKey) && rawEventProperties) {
195
+ eventSchemaBody.eventProperties = this.encryptProperties(eventProperties, rawEventProperties);
196
+ } else {
197
+ eventSchemaBody.eventProperties = eventProperties;
198
+ }
151
199
 
152
200
  if (eventId != null) {
153
201
  eventSchemaBody.avoFunction = true;
@@ -162,8 +210,113 @@ export class AvoNetworkCallsHandler {
162
210
  return eventSchemaBody;
163
211
  }
164
212
 
165
- private createBaseCallBody(sessionId: string): BaseBody {
166
- return {
213
+ buildEventProperties(
214
+ eventProperties: Array<{
215
+ propertyName: string;
216
+ propertyType: string;
217
+ children?: any;
218
+ }>,
219
+ rawEventProperties?: { [propName: string]: any }
220
+ ): Array<EventProperty> {
221
+ if (AvoEncryption.shouldEncrypt(this.envName, this.publicEncryptionKey) && rawEventProperties) {
222
+ return this.encryptProperties(eventProperties, rawEventProperties);
223
+ }
224
+ return eventProperties;
225
+ }
226
+
227
+ bodyForValidatedEventSchemaCall(
228
+ anonymousId: string,
229
+ eventName: string,
230
+ eventProperties: Array<EventProperty>,
231
+ eventId: string | null,
232
+ eventHash: string | null,
233
+ eventSpecMetadata: EventSpecMetadata,
234
+ propertyResults: PropertyValidationResult[]
235
+ ): EventSchemaBody {
236
+ // Build a map of validation results by property name
237
+ const validationMap = new Map<string, PropertyValidationResult>();
238
+ for (const result of propertyResults) {
239
+ validationMap.set(result.propertyName, result);
240
+ }
241
+
242
+ // Merge validation results into eventProperties (matching Android)
243
+ const mergedProperties: Array<EventProperty> = eventProperties.map((prop) => {
244
+ const validation = validationMap.get(prop.propertyName);
245
+ if (validation) {
246
+ const merged: EventProperty = { ...prop };
247
+ if (validation.failedEventIds.length > 0) {
248
+ merged.failedEventIds = validation.failedEventIds;
249
+ }
250
+ if (validation.passedEventIds.length > 0) {
251
+ merged.passedEventIds = validation.passedEventIds;
252
+ }
253
+ return merged;
254
+ }
255
+ return prop;
256
+ });
257
+
258
+ let body = this.createBaseCallBody(anonymousId) as EventSchemaBody;
259
+ body.type = "event";
260
+ body.eventName = eventName;
261
+ body.eventProperties = mergedProperties;
262
+ body.streamId = anonymousId;
263
+ body.eventSpecMetadata = eventSpecMetadata;
264
+
265
+ if (eventId != null) {
266
+ body.avoFunction = true;
267
+ body.eventId = eventId;
268
+ body.eventHash = eventHash;
269
+ } else {
270
+ body.avoFunction = false;
271
+ body.eventId = null;
272
+ body.eventHash = null;
273
+ }
274
+
275
+ return body;
276
+ }
277
+
278
+ private encryptProperties(
279
+ properties: Array<{
280
+ propertyName: string;
281
+ propertyType: string;
282
+ children?: any;
283
+ }>,
284
+ rawEventProperties: { [propName: string]: any }
285
+ ): Array<EventProperty> {
286
+ const result: Array<EventProperty> = [];
287
+
288
+ for (const prop of properties) {
289
+ // List-type properties: omit entirely
290
+ if (AvoEncryption.isListType(prop.propertyType)) {
291
+ continue;
292
+ }
293
+
294
+ const rawValue = rawEventProperties[prop.propertyName];
295
+ const jsonValue = JSON.stringify(rawValue) ?? "null";
296
+
297
+ const encrypted = AvoEncryption.encryptValue(
298
+ jsonValue,
299
+ this.publicEncryptionKey!
300
+ );
301
+
302
+ if (encrypted === null) {
303
+ // Encryption failure: omit the property (warning already logged by encryptValue)
304
+ continue;
305
+ }
306
+
307
+ result.push({
308
+ propertyName: prop.propertyName,
309
+ propertyType: prop.propertyType,
310
+ encryptedPropertyValue: encrypted,
311
+ ...(prop.children !== undefined ? { children: prop.children } : {}),
312
+ });
313
+ }
314
+
315
+ return result;
316
+ }
317
+
318
+ private createBaseCallBody(anonymousId: string): BaseBody {
319
+ const body: BaseBody = {
167
320
  apiKey: this.apiKey,
168
321
  appName: this.appName,
169
322
  appVersion: this.appVersion,
@@ -172,9 +325,16 @@ export class AvoNetworkCallsHandler {
172
325
  libPlatform: "node",
173
326
  messageId: AvoGuid.newGuid(),
174
327
  trackingId: "",
328
+ sessionId: "",
329
+ anonymousId: anonymousId,
175
330
  createdAt: new Date().toISOString(),
176
- sessionId: sessionId,
177
331
  samplingRate: this.samplingRate,
178
332
  };
333
+
334
+ if (this.publicEncryptionKey && this.publicEncryptionKey.length > 0) {
335
+ body.publicEncryptionKey = this.publicEncryptionKey;
336
+ }
337
+
338
+ return body;
179
339
  }
180
340
  }
@@ -0,0 +1,16 @@
1
+ export class AvoStreamId {
2
+ private _streamId: string;
3
+
4
+ constructor(streamId?: string) {
5
+ this._streamId = streamId ?? "";
6
+ if (this._streamId.includes(":")) {
7
+ console.warn(
8
+ "[Avo Inspector] Warning: streamId contains ':' which is not supported"
9
+ );
10
+ }
11
+ }
12
+
13
+ get streamId(): string {
14
+ return this._streamId;
15
+ }
16
+ }