node-avo-inspector 1.2.0 → 2.0.0-beta.2

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 (132) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +270 -4
  3. package/dist/AvoBatchQueue.d.ts +73 -0
  4. package/dist/AvoBatchQueue.js +239 -0
  5. package/dist/AvoDeduplicator.d.ts +15 -6
  6. package/dist/AvoDeduplicator.js +46 -24
  7. package/dist/AvoInspector.d.ts +136 -23
  8. package/dist/AvoInspector.js +777 -109
  9. package/dist/AvoInspectorVersion.d.ts +3 -0
  10. package/dist/AvoInspectorVersion.js +10 -0
  11. package/dist/AvoLog.d.ts +52 -0
  12. package/dist/AvoLog.js +246 -0
  13. package/dist/AvoNetworkCallsHandler.d.ts +55 -8
  14. package/dist/AvoNetworkCallsHandler.js +266 -92
  15. package/dist/AvoSchemaParser.d.ts +6 -0
  16. package/dist/AvoSchemaParser.js +138 -33
  17. package/dist/AvoStreamId.d.ts +2 -1
  18. package/dist/AvoStreamId.js +21 -2
  19. package/dist/eventSpec/AvoEventSpecCache.d.ts +28 -5
  20. package/dist/eventSpec/AvoEventSpecCache.js +66 -11
  21. package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +50 -18
  22. package/dist/eventSpec/AvoEventSpecFetcher.d.ts +29 -6
  23. package/dist/eventSpec/AvoEventSpecFetcher.js +279 -69
  24. package/dist/eventSpec/EventValidator.d.ts +16 -10
  25. package/dist/eventSpec/EventValidator.js +351 -38
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +4 -1
  28. package/dist/utils.d.ts +29 -1
  29. package/dist/utils.js +86 -10
  30. package/package.json +18 -1
  31. package/.claude/scheduled_tasks.lock +0 -1
  32. package/.claude/settings.local.json +0 -11
  33. package/.claude/worktrees/release-1.1.1/LICENSE +0 -21
  34. package/.claude/worktrees/release-1.1.1/README.md +0 -69
  35. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/index.js +0 -30
  36. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/package.json +0 -15
  37. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/yarn.lock +0 -6
  38. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/dist/index.js +0 -34
  39. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/index.ts +0 -30
  40. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/package.json +0 -18
  41. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/tsconfig.json +0 -15
  42. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/yarn.lock +0 -11
  43. package/.claude/worktrees/release-1.1.1/package-lock.json +0 -4039
  44. package/.claude/worktrees/release-1.1.1/package.json +0 -48
  45. package/.claude/worktrees/release-1.1.1/reason/AvoInspector.rei +0 -11
  46. package/.claude/worktrees/release-1.1.1/reason/NodeAvoInspector.re +0 -27
  47. package/.claude/worktrees/release-1.1.1/src/AvoDeduplicator.ts +0 -146
  48. package/.claude/worktrees/release-1.1.1/src/AvoEncryption.ts +0 -90
  49. package/.claude/worktrees/release-1.1.1/src/AvoGuid.ts +0 -9
  50. package/.claude/worktrees/release-1.1.1/src/AvoInspector.ts +0 -383
  51. package/.claude/worktrees/release-1.1.1/src/AvoInspectorEnv.ts +0 -8
  52. package/.claude/worktrees/release-1.1.1/src/AvoNetworkCallsHandler.ts +0 -267
  53. package/.claude/worktrees/release-1.1.1/src/AvoSchemaParser.ts +0 -115
  54. package/.claude/worktrees/release-1.1.1/src/AvoStreamId.ts +0 -16
  55. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEncryption.ts +0 -515
  56. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecCache.ts +0 -242
  57. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecFetcher.ts +0 -241
  58. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoInspector.ts +0 -360
  59. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoStreamId.ts +0 -72
  60. package/.claude/worktrees/release-1.1.1/src/__tests__/TestDeduplicator.ts +0 -191
  61. package/.claude/worktrees/release-1.1.1/src/__tests__/TestEventValidator.ts +0 -231
  62. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandler.ts +0 -179
  63. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandlerPublic.ts +0 -208
  64. package/.claude/worktrees/release-1.1.1/src/__tests__/TestParsing.ts +0 -153
  65. package/.claude/worktrees/release-1.1.1/src/__tests__/constants.ts +0 -63
  66. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecCache.ts +0 -109
  67. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetchTypes.ts +0 -47
  68. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetcher.ts +0 -106
  69. package/.claude/worktrees/release-1.1.1/src/eventSpec/EventValidator.ts +0 -74
  70. package/.claude/worktrees/release-1.1.1/src/index.ts +0 -2
  71. package/.claude/worktrees/release-1.1.1/src/utils.ts +0 -49
  72. package/.claude/worktrees/release-1.1.1/tsconfig.json +0 -13
  73. package/.claude/worktrees/release-1.1.1/yarn.lock +0 -2310
  74. package/.github/workflows/claude.yml +0 -55
  75. package/dist/__tests__/TestAvoEncryption.d.ts +0 -1
  76. package/dist/__tests__/TestAvoEncryption.js +0 -288
  77. package/dist/__tests__/TestAvoEventSpecCache.d.ts +0 -1
  78. package/dist/__tests__/TestAvoEventSpecCache.js +0 -194
  79. package/dist/__tests__/TestAvoEventSpecFetcher.d.ts +0 -1
  80. package/dist/__tests__/TestAvoEventSpecFetcher.js +0 -219
  81. package/dist/__tests__/TestAvoInspector.d.ts +0 -1
  82. package/dist/__tests__/TestAvoInspector.js +0 -291
  83. package/dist/__tests__/TestAvoStreamId.d.ts +0 -1
  84. package/dist/__tests__/TestAvoStreamId.js +0 -55
  85. package/dist/__tests__/TestDeduplicator.d.ts +0 -1
  86. package/dist/__tests__/TestDeduplicator.js +0 -102
  87. package/dist/__tests__/TestEventValidator.d.ts +0 -1
  88. package/dist/__tests__/TestEventValidator.js +0 -196
  89. package/dist/__tests__/TestNetworkCallsHandler.d.ts +0 -1
  90. package/dist/__tests__/TestNetworkCallsHandler.js +0 -86
  91. package/dist/__tests__/TestNetworkCallsHandlerPublic.d.ts +0 -1
  92. package/dist/__tests__/TestNetworkCallsHandlerPublic.js +0 -160
  93. package/dist/__tests__/TestParsing.d.ts +0 -1
  94. package/dist/__tests__/TestParsing.js +0 -128
  95. package/dist/__tests__/constants.d.ts +0 -42
  96. package/dist/__tests__/constants.js +0 -53
  97. package/examples/node-inspector-js/index.js +0 -30
  98. package/examples/node-inspector-js/package.json +0 -15
  99. package/examples/node-inspector-js/yarn.lock +0 -6
  100. package/examples/node-inspector-ts/dist/index.js +0 -34
  101. package/examples/node-inspector-ts/index.ts +0 -30
  102. package/examples/node-inspector-ts/package.json +0 -18
  103. package/examples/node-inspector-ts/tsconfig.json +0 -15
  104. package/examples/node-inspector-ts/yarn.lock +0 -11
  105. package/reason/AvoInspector.rei +0 -11
  106. package/reason/NodeAvoInspector.re +0 -27
  107. package/src/AvoDeduplicator.ts +0 -150
  108. package/src/AvoEncryption.ts +0 -90
  109. package/src/AvoGuid.ts +0 -9
  110. package/src/AvoInspector.ts +0 -498
  111. package/src/AvoInspectorEnv.ts +0 -8
  112. package/src/AvoNetworkCallsHandler.ts +0 -340
  113. package/src/AvoSchemaParser.ts +0 -115
  114. package/src/AvoStreamId.ts +0 -16
  115. package/src/__tests__/TestAvoEncryption.ts +0 -515
  116. package/src/__tests__/TestAvoEventSpecCache.ts +0 -242
  117. package/src/__tests__/TestAvoEventSpecFetcher.ts +0 -279
  118. package/src/__tests__/TestAvoInspector.ts +0 -376
  119. package/src/__tests__/TestAvoStreamId.ts +0 -72
  120. package/src/__tests__/TestDeduplicator.ts +0 -191
  121. package/src/__tests__/TestEventValidator.ts +0 -231
  122. package/src/__tests__/TestNetworkCallsHandler.ts +0 -181
  123. package/src/__tests__/TestNetworkCallsHandlerPublic.ts +0 -208
  124. package/src/__tests__/TestParsing.ts +0 -153
  125. package/src/__tests__/constants.ts +0 -63
  126. package/src/eventSpec/AvoEventSpecCache.ts +0 -119
  127. package/src/eventSpec/AvoEventSpecFetchTypes.ts +0 -47
  128. package/src/eventSpec/AvoEventSpecFetcher.ts +0 -186
  129. package/src/eventSpec/EventValidator.ts +0 -74
  130. package/src/index.ts +0 -2
  131. package/src/utils.ts +0 -49
  132. package/tsconfig.json +0 -13
@@ -2,52 +2,365 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EventValidator = void 0;
4
4
  const safe_regex2_1 = require("safe-regex2");
5
+ const AvoInspector_1 = require("../AvoInspector");
6
+ // Validation covers a property (depth 0) and the properties of an object or of each object in
7
+ // a list (depth 1); deeper objects are not checked, as in the web and Android SDKs.
8
+ const MAX_CHILD_DEPTH = 2;
9
+ // Compiled patterns and parsed allowed-value lists, keyed by their wire string. Both come from
10
+ // the tracking plan, so they stay few; the bound only guards a long-lived process against a
11
+ // plan that keeps changing. null marks a pattern that was skipped (unsafe or invalid).
12
+ const MAX_CACHED_CONSTRAINTS = 1000;
13
+ // Validation runs synchronously on the track path, so a value past these bounds is not
14
+ // checked: a list with more items, or a string longer than this against a regex (safe-regex2
15
+ // rejects exponential patterns, not quadratic ones).
16
+ const MAX_VALIDATED_LIST_ITEMS = 1000;
17
+ const MAX_REGEX_INPUT_LENGTH = 10000;
18
+ const regexCache = new Map();
19
+ const allowedValuesCache = new Map();
20
+ /**
21
+ * Checks event property values against the value constraints of an event spec: pinned values,
22
+ * allowed values, regex patterns and min/max ranges, on a property and on the properties of an
23
+ * object (or of each object in a list).
24
+ *
25
+ * Each constraint applies to the tracking-plan event IDs (base event and variants) the spec
26
+ * lists for it, so a value can pass for one variant and fail for another. Types and required
27
+ * properties are not checked here; Avo compares the reported schema with the plan for those.
28
+ */
5
29
  class EventValidator {
6
30
  /**
7
- * Validates event properties against the spec.
8
- * Returns per-property validation results with bandwidth optimization:
9
- * passedEventIds is returned only when strictly smaller than failedEventIds.
31
+ * Returns one result per property that failed for at least one event ID, or whose object
32
+ * properties did. Properties the spec has no constraints for are left out.
10
33
  */
11
- validate(spec, eventProperties, eventId) {
34
+ validate(spec, eventSchema, rawEventProperties) {
35
+ const constraintsByName = collectConstraintsByName(spec.events);
12
36
  const results = [];
13
- const eventPropMap = new Map();
14
- for (const prop of eventProperties) {
15
- eventPropMap.set(prop.propertyName, prop);
16
- }
17
- for (const specProp of spec.properties) {
18
- const actualProp = eventPropMap.get(specProp.propertyName);
19
- let passed = false;
20
- if (actualProp) {
21
- // Type check
22
- passed = actualProp.propertyType === specProp.propertyType;
23
- // Regex check (only if type passed, regex is defined, and value is available)
24
- if (passed && specProp.regex && actualProp.propertyValue !== undefined) {
25
- if (!(0, safe_regex2_1.default)(specProp.regex)) {
26
- console.warn(`[Avo Inspector] Warning: Unsafe regex pattern skipped for property "${specProp.propertyName}": ${specProp.regex}`);
27
- }
28
- else {
29
- try {
30
- const re = new RegExp(specProp.regex);
31
- passed = re.test(actualProp.propertyValue);
32
- }
33
- catch (e) {
34
- console.warn(`[Avo Inspector] Warning: Invalid regex pattern for property "${specProp.propertyName}": ${specProp.regex}`);
35
- }
36
- }
37
- }
37
+ for (const { propertyName } of eventSchema) {
38
+ const constraints = constraintsByName.get(propertyName);
39
+ if (constraints === undefined) {
40
+ continue;
38
41
  }
39
- const result = {
40
- propertyName: specProp.propertyName,
41
- failedEventIds: passed ? [] : [eventId],
42
- passedEventIds: passed ? [eventId] : [],
43
- };
44
- // Bandwidth optimization: return passedEventIds only when strictly smaller than failedEventIds
45
- if (result.passedEventIds.length >= result.failedEventIds.length) {
46
- result.passedEventIds = [];
42
+ const value = rawEventProperties ? ownValue(rawEventProperties, propertyName) : undefined;
43
+ const outcome = validateProperty(value, constraints, 0);
44
+ if (outcome !== null) {
45
+ results.push(Object.assign({ propertyName }, outcome));
47
46
  }
48
- results.push(result);
49
47
  }
50
48
  return results;
51
49
  }
52
50
  }
53
51
  exports.EventValidator = EventValidator;
52
+ // =============================================================================
53
+ // Constraints across events
54
+ // =============================================================================
55
+ /**
56
+ * One lookup of constraints per property name across every event in the spec. Name mapping
57
+ * can match several events; each constraint keeps its own event IDs, so merging is a union.
58
+ */
59
+ function collectConstraintsByName(events) {
60
+ const result = new Map();
61
+ for (const event of events) {
62
+ for (const name of Object.keys(event.props)) {
63
+ const constraints = event.props[name];
64
+ const existing = result.get(name);
65
+ result.set(name, existing === undefined ? constraints : mergeConstraints(existing, constraints));
66
+ }
67
+ }
68
+ return result;
69
+ }
70
+ function mergeConstraints(a, b) {
71
+ const merged = {
72
+ type: a.type,
73
+ // A missing value fails only where every matching event requires the property.
74
+ required: a.required && b.required,
75
+ };
76
+ if (a.isList || b.isList) {
77
+ merged.isList = true;
78
+ }
79
+ const pinnedValues = mergeIdMappings(a.pinnedValues, b.pinnedValues);
80
+ if (pinnedValues) {
81
+ merged.pinnedValues = pinnedValues;
82
+ }
83
+ const allowedValues = mergeIdMappings(a.allowedValues, b.allowedValues);
84
+ if (allowedValues) {
85
+ merged.allowedValues = allowedValues;
86
+ }
87
+ const regexPatterns = mergeIdMappings(a.regexPatterns, b.regexPatterns);
88
+ if (regexPatterns) {
89
+ merged.regexPatterns = regexPatterns;
90
+ }
91
+ const minMaxRanges = mergeIdMappings(a.minMaxRanges, b.minMaxRanges);
92
+ if (minMaxRanges) {
93
+ merged.minMaxRanges = minMaxRanges;
94
+ }
95
+ if (a.children || b.children) {
96
+ const children = Object.create(null);
97
+ for (const source of [a.children, b.children]) {
98
+ if (!source) {
99
+ continue;
100
+ }
101
+ for (const name of Object.keys(source)) {
102
+ const existing = children[name];
103
+ children[name] = existing === undefined ? source[name] : mergeConstraints(existing, source[name]);
104
+ }
105
+ }
106
+ merged.children = children;
107
+ }
108
+ return merged;
109
+ }
110
+ function mergeIdMappings(a, b) {
111
+ if (!a || !b) {
112
+ return a !== null && a !== void 0 ? a : b;
113
+ }
114
+ const merged = Object.create(null);
115
+ for (const source of [a, b]) {
116
+ for (const key of Object.keys(source)) {
117
+ const ids = merged[key];
118
+ merged[key] = ids === undefined ? source[key] : Array.from(new Set([...ids, ...source[key]]));
119
+ }
120
+ }
121
+ return merged;
122
+ }
123
+ // =============================================================================
124
+ // Validation
125
+ // =============================================================================
126
+ function validateProperty(value, constraints, depth) {
127
+ if (depth >= MAX_CHILD_DEPTH) {
128
+ return null;
129
+ }
130
+ if (constraints.isList) {
131
+ return validateList(value, constraints, depth);
132
+ }
133
+ if (constraints.children) {
134
+ return validateObject(value, constraints.children, depth);
135
+ }
136
+ // An optional property sent as null or undefined has no value to check.
137
+ if ((value === null || value === undefined) && !constraints.required) {
138
+ return null;
139
+ }
140
+ return outcomeOf(checkValue(value, constraints, new Set()));
141
+ }
142
+ /**
143
+ * A list of objects is checked property by property across every item: an event ID fails a
144
+ * child property when any item's value fails it. A list of primitives is checked item by item.
145
+ * A value that is not an array is not checked (its type is reported in the schema).
146
+ */
147
+ function validateList(value, constraints, depth) {
148
+ if (!Array.isArray(value)) {
149
+ return null;
150
+ }
151
+ if (value.length > MAX_VALIDATED_LIST_ITEMS) {
152
+ if (AvoInspector_1.AvoInspector.shouldLog) {
153
+ console.warn(`[Avo Inspector] Warning: List of ${value.length} items not validated (limit ${MAX_VALIDATED_LIST_ITEMS})`);
154
+ }
155
+ return null;
156
+ }
157
+ if (constraints.children) {
158
+ const children = Object.create(null);
159
+ let failed = false;
160
+ for (const name of Object.keys(constraints.children)) {
161
+ const failedIds = new Set();
162
+ for (const item of value) {
163
+ const outcome = validateProperty(childValue(item, name), constraints.children[name], depth + 1);
164
+ if (outcome !== null) {
165
+ outcome.failedEventIds.forEach((id) => failedIds.add(id));
166
+ }
167
+ }
168
+ const outcome = outcomeOf(failedIds);
169
+ if (outcome !== null) {
170
+ children[name] = outcome;
171
+ failed = true;
172
+ }
173
+ }
174
+ return failed ? { failedEventIds: [], children } : null;
175
+ }
176
+ const failedIds = new Set();
177
+ for (const item of value) {
178
+ checkValue(item, constraints, failedIds);
179
+ }
180
+ return outcomeOf(failedIds);
181
+ }
182
+ function validateObject(value, childConstraints, depth) {
183
+ const children = Object.create(null);
184
+ let failed = false;
185
+ for (const name of Object.keys(childConstraints)) {
186
+ const outcome = validateProperty(childValue(value, name), childConstraints[name], depth + 1);
187
+ if (outcome !== null) {
188
+ children[name] = outcome;
189
+ failed = true;
190
+ }
191
+ }
192
+ return failed ? { failedEventIds: [], children } : null;
193
+ }
194
+ function outcomeOf(failedIds) {
195
+ return failedIds.size > 0 ? { failedEventIds: Array.from(failedIds) } : null;
196
+ }
197
+ // A property of a plain object (null-prototype included); anything else has none.
198
+ function childValue(container, name) {
199
+ if (container === null || typeof container !== "object" || Array.isArray(container)) {
200
+ return undefined;
201
+ }
202
+ return ownValue(container, name);
203
+ }
204
+ // Own properties only, so "constructor" or "toString" never reads a prototype method.
205
+ function ownValue(container, name) {
206
+ return Object.prototype.hasOwnProperty.call(container, name) ? container[name] : undefined;
207
+ }
208
+ /** Adds to failedIds the event IDs whose constraints the value breaks. */
209
+ function checkValue(value, constraints, failedIds) {
210
+ if (constraints.pinnedValues || constraints.allowedValues) {
211
+ const actual = comparableString(value);
212
+ if (constraints.pinnedValues) {
213
+ checkPinnedValues(actual, constraints.pinnedValues, failedIds);
214
+ }
215
+ if (constraints.allowedValues) {
216
+ checkAllowedValues(actual, constraints.allowedValues, failedIds);
217
+ }
218
+ }
219
+ if (constraints.regexPatterns) {
220
+ checkRegexPatterns(value, constraints.regexPatterns, failedIds);
221
+ }
222
+ if (constraints.minMaxRanges) {
223
+ checkMinMaxRanges(value, constraints.minMaxRanges, failedIds);
224
+ }
225
+ return failedIds;
226
+ }
227
+ function addAll(ids, failedIds) {
228
+ for (const id of ids) {
229
+ failedIds.add(id);
230
+ }
231
+ }
232
+ /**
233
+ * The string a value is compared as against pinned and allowed values: String() for a
234
+ * primitive, JSON for an object or list. A value with neither (a cyclic object, a throwing
235
+ * toJSON or toString) falls back to its tag, e.g. "[object Object]".
236
+ */
237
+ function comparableString(value) {
238
+ if (value !== null && typeof value === "object") {
239
+ try {
240
+ const json = JSON.stringify(value);
241
+ if (json !== undefined) {
242
+ return json;
243
+ }
244
+ }
245
+ catch (e) {
246
+ // Falls through to String().
247
+ }
248
+ }
249
+ try {
250
+ return String(value);
251
+ }
252
+ catch (e) {
253
+ return Object.prototype.toString.call(value);
254
+ }
255
+ }
256
+ function checkPinnedValues(actual, pinnedValues, failedIds) {
257
+ for (const pinned of Object.keys(pinnedValues)) {
258
+ if (actual !== pinned) {
259
+ addAll(pinnedValues[pinned], failedIds);
260
+ }
261
+ }
262
+ }
263
+ function checkAllowedValues(actual, allowedValues, failedIds) {
264
+ for (const json of Object.keys(allowedValues)) {
265
+ const allowed = parseAllowedValues(json);
266
+ // A list that is not a JSON array of strings is skipped: nothing fails it.
267
+ if (allowed !== null && !allowed.has(actual)) {
268
+ addAll(allowedValues[json], failedIds);
269
+ }
270
+ }
271
+ }
272
+ function checkRegexPatterns(value, regexPatterns, failedIds) {
273
+ if (typeof value === "string" && value.length > MAX_REGEX_INPUT_LENGTH) {
274
+ if (AvoInspector_1.AvoInspector.shouldLog) {
275
+ console.warn(`[Avo Inspector] Warning: String of ${value.length} characters not checked against regex patterns (limit ${MAX_REGEX_INPUT_LENGTH})`);
276
+ }
277
+ return;
278
+ }
279
+ for (const pattern of Object.keys(regexPatterns)) {
280
+ // Only a string can match; an unsafe or invalid pattern is skipped (nothing fails it).
281
+ if (typeof value !== "string") {
282
+ addAll(regexPatterns[pattern], failedIds);
283
+ continue;
284
+ }
285
+ const regex = compileRegex(pattern);
286
+ if (regex !== null && !regex.test(value)) {
287
+ addAll(regexPatterns[pattern], failedIds);
288
+ }
289
+ }
290
+ }
291
+ /**
292
+ * "min,max" with either bound possibly empty: "0," has no maximum, ",100" no minimum. Only a
293
+ * number (or bigint) can be in range; NaN never is. A range that does not parse is skipped.
294
+ */
295
+ function checkMinMaxRanges(value, minMaxRanges, failedIds) {
296
+ const actual = typeof value === "number" ? value : typeof value === "bigint" ? Number(value) : NaN;
297
+ for (const range of Object.keys(minMaxRanges)) {
298
+ const bounds = parseRange(range);
299
+ if (bounds === null) {
300
+ if (AvoInspector_1.AvoInspector.shouldLog) {
301
+ console.warn(`[Avo Inspector] Warning: Invalid min/max range skipped: ${range}`);
302
+ }
303
+ continue;
304
+ }
305
+ if (Number.isNaN(actual) || actual < bounds.min || actual > bounds.max) {
306
+ addAll(minMaxRanges[range], failedIds);
307
+ }
308
+ }
309
+ }
310
+ function parseRange(range) {
311
+ const parts = range.split(",");
312
+ if (parts.length !== 2) {
313
+ return null;
314
+ }
315
+ const [minPart, maxPart] = parts.map((part) => part.trim());
316
+ const min = minPart === "" ? -Infinity : Number(minPart);
317
+ const max = maxPart === "" ? Infinity : Number(maxPart);
318
+ return Number.isNaN(min) || Number.isNaN(max) ? null : { min, max };
319
+ }
320
+ function parseAllowedValues(json) {
321
+ const cached = allowedValuesCache.get(json);
322
+ if (cached !== undefined) {
323
+ return cached;
324
+ }
325
+ let allowed = null;
326
+ try {
327
+ const parsed = JSON.parse(json);
328
+ if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
329
+ allowed = new Set(parsed);
330
+ }
331
+ }
332
+ catch (e) {
333
+ // Left null: skipped below.
334
+ }
335
+ if (allowed === null && AvoInspector_1.AvoInspector.shouldLog) {
336
+ console.warn(`[Avo Inspector] Warning: Invalid allowed values skipped: ${json}`);
337
+ }
338
+ cacheConstraint(allowedValuesCache, json, allowed);
339
+ return allowed;
340
+ }
341
+ function compileRegex(pattern) {
342
+ const cached = regexCache.get(pattern);
343
+ if (cached !== undefined) {
344
+ return cached;
345
+ }
346
+ let regex = null;
347
+ if (!(0, safe_regex2_1.default)(pattern)) {
348
+ console.warn(`[Avo Inspector] Warning: Unsafe regex pattern skipped: ${pattern}`);
349
+ }
350
+ else {
351
+ try {
352
+ regex = new RegExp(pattern);
353
+ }
354
+ catch (e) {
355
+ console.warn(`[Avo Inspector] Warning: Invalid regex pattern skipped: ${pattern}`);
356
+ }
357
+ }
358
+ cacheConstraint(regexCache, pattern, regex);
359
+ return regex;
360
+ }
361
+ function cacheConstraint(cache, key, value) {
362
+ if (cache.size >= MAX_CACHED_CONSTRAINTS) {
363
+ cache.clear();
364
+ }
365
+ cache.set(key, value);
366
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export { AvoInspector } from "./AvoInspector";
2
+ export type { InspectorEvent } from "./AvoInspector";
2
3
  export { AvoInspectorEnv } from "./AvoInspectorEnv";
4
+ export { VERSION, SPEC_VERSION } from "./AvoInspectorVersion";
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AvoInspectorEnv = exports.AvoInspector = void 0;
3
+ exports.SPEC_VERSION = exports.VERSION = exports.AvoInspectorEnv = exports.AvoInspector = void 0;
4
4
  var AvoInspector_1 = require("./AvoInspector");
5
5
  Object.defineProperty(exports, "AvoInspector", { enumerable: true, get: function () { return AvoInspector_1.AvoInspector; } });
6
6
  var AvoInspectorEnv_1 = require("./AvoInspectorEnv");
7
7
  Object.defineProperty(exports, "AvoInspectorEnv", { enumerable: true, get: function () { return AvoInspectorEnv_1.AvoInspectorEnv; } });
8
+ var AvoInspectorVersion_1 = require("./AvoInspectorVersion");
9
+ Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return AvoInspectorVersion_1.VERSION; } });
10
+ Object.defineProperty(exports, "SPEC_VERSION", { enumerable: true, get: function () { return AvoInspectorVersion_1.SPEC_VERSION; } });
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,31 @@
1
+ /** True for null, undefined, or a string that is empty after trimming. */
1
2
  declare const isValueEmpty: (value: string | null | undefined) => boolean;
3
+ /**
4
+ * Structural equality, within the schema extraction limits: objects and lists nested more
5
+ * than 10 levels deep, past the 10,000th compared in one call, or past 10,000 compared
6
+ * properties, are treated as not equal (unless they are the same reference), so a huge
7
+ * payload cannot overflow the stack or block. Binary data is compared by its bytes, never
8
+ * enumerated.
9
+ */
2
10
  declare function deepEquals(x: any, y: any): boolean;
3
- export { isValueEmpty, deepEquals };
11
+ /** A schema as printed in debug logs: `{`, one `\t"name": "type"` per property (`;`-separated), `}`. */
12
+ declare const formatSchema: (schema: Array<{
13
+ propertyName: string;
14
+ propertyType: string;
15
+ }>) => string;
16
+ /** Trims a gateway option value; anything that is not a non-blank string is absent. */
17
+ declare const normalizeOption: (value: unknown) => string | undefined;
18
+ /**
19
+ * No control character (Unicode Cc: C0, DEL, C1) may reach a header value; CR, LF and NUL
20
+ * would split or end the header field. Tab is the one exception: it is valid in a value.
21
+ */
22
+ declare const hasHeaderControlChar: (value: string) => boolean;
23
+ /** Node sends header values as Latin-1 and throws synchronously on anything above U+00FF. */
24
+ declare const hasNonLatin1Char: (value: string) => boolean;
25
+ /**
26
+ * Milliseconds from a monotonic clock (process.hrtime): only differences are meaningful, and
27
+ * they never jump with wall-clock changes. Shared by the deduplicator, AvoLog and the exit
28
+ * drain deadline.
29
+ */
30
+ declare const monotonicNowMs: () => number;
31
+ export { monotonicNowMs, isValueEmpty, deepEquals, formatSchema, normalizeOption, hasHeaderControlChar, hasNonLatin1Char };
package/dist/utils.js CHANGED
@@ -1,26 +1,73 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isValueEmpty = void 0;
3
+ exports.hasNonLatin1Char = exports.hasHeaderControlChar = exports.normalizeOption = exports.formatSchema = exports.isValueEmpty = exports.monotonicNowMs = void 0;
4
4
  exports.deepEquals = deepEquals;
5
+ /** True for null, undefined, or a string that is empty after trimming. */
5
6
  const isValueEmpty = (value) => {
6
7
  return value === null || value === undefined || value.trim().length == 0;
7
8
  };
8
9
  exports.isValueEmpty = isValueEmpty;
10
+ // The schema extraction limits: deepEquals expands no deeper and no more than this.
11
+ const DEEP_EQUALS_MAX_DEPTH = 10;
12
+ const DEEP_EQUALS_MAX_EXPANSIONS = 10000;
13
+ const DEEP_EQUALS_MAX_PROPERTIES = 10000;
14
+ // Binary data (an ArrayBuffer view, ArrayBuffer or SharedArrayBuffer) as bytes, or null.
15
+ const binaryBytes = (value) => {
16
+ if (ArrayBuffer.isView(value)) {
17
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
18
+ }
19
+ const tag = Object.prototype.toString.call(value);
20
+ return tag === "[object ArrayBuffer]" || tag === "[object SharedArrayBuffer]" ? new Uint8Array(value) : null;
21
+ };
22
+ const sameBytes = (a, b) => a.byteLength === b.byteLength && Buffer.compare(a, b) === 0;
23
+ /**
24
+ * Structural equality, within the schema extraction limits: objects and lists nested more
25
+ * than 10 levels deep, past the 10,000th compared in one call, or past 10,000 compared
26
+ * properties, are treated as not equal (unless they are the same reference), so a huge
27
+ * payload cannot overflow the stack or block. Binary data is compared by its bytes, never
28
+ * enumerated.
29
+ */
9
30
  function deepEquals(x, y) {
31
+ return deepEqualsWithin(x, y, 0, new Map(), { expansions: 0, properties: 0 });
32
+ }
33
+ // `comparing` holds the object pairs already compared, so cyclic structures terminate: a
34
+ // pair met again is assumed equal (its other properties are still compared where it was
35
+ // first met).
36
+ function deepEqualsWithin(x, y, depth, comparing, budget) {
10
37
  if (x === y) {
11
38
  return true;
12
39
  }
13
- if (!(x instanceof Object) || !(y instanceof Object)) {
40
+ // typeof, not instanceof Object: a null-prototype object is not an instance of Object.
41
+ if (x === null || y === null || typeof x !== "object" || typeof y !== "object") {
14
42
  return false;
15
43
  }
16
- if (x.constructor !== y.constructor) {
44
+ if (Object.getPrototypeOf(x) !== Object.getPrototypeOf(y)) {
17
45
  return false;
18
46
  }
19
- for (var p in x) {
20
- if (!x.hasOwnProperty(p)) {
21
- continue;
47
+ const xBytes = binaryBytes(x);
48
+ if (xBytes !== null) {
49
+ const yBytes = binaryBytes(y);
50
+ return yBytes !== null && sameBytes(xBytes, yBytes);
51
+ }
52
+ let partners = comparing.get(x);
53
+ if (partners && partners.has(y)) {
54
+ return true;
55
+ }
56
+ if (depth >= DEEP_EQUALS_MAX_DEPTH || budget.expansions >= DEEP_EQUALS_MAX_EXPANSIONS) {
57
+ return false;
58
+ }
59
+ budget.expansions++;
60
+ if (!partners) {
61
+ partners = new Set();
62
+ comparing.set(x, partners);
63
+ }
64
+ partners.add(y);
65
+ // hasOwn, not x.hasOwnProperty: user objects may lack the method or shadow it.
66
+ for (const p of Object.keys(x)) {
67
+ if (++budget.properties > DEEP_EQUALS_MAX_PROPERTIES) {
68
+ return false;
22
69
  }
23
- if (!y.hasOwnProperty(p)) {
70
+ if (!hasOwn(y, p)) {
24
71
  return false;
25
72
  }
26
73
  if (x[p] === y[p]) {
@@ -29,14 +76,43 @@ function deepEquals(x, y) {
29
76
  if (typeof x[p] !== "object") {
30
77
  return false;
31
78
  }
32
- if (!deepEquals(x[p], y[p])) {
79
+ if (!deepEqualsWithin(x[p], y[p], depth + 1, comparing, budget)) {
33
80
  return false;
34
81
  }
35
82
  }
36
- for (p in y) {
37
- if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
83
+ for (const p of Object.keys(y)) {
84
+ if (!hasOwn(x, p)) {
38
85
  return false;
39
86
  }
40
87
  }
41
88
  return true;
42
89
  }
90
+ /** A schema as printed in debug logs: `{`, one `\t"name": "type"` per property (`;`-separated), `}`. */
91
+ const formatSchema = (schema) => "{\n" + schema.map((p) => '\t"' + p.propertyName + '": "' + p.propertyType + '"').join(";\n") + "\n}";
92
+ exports.formatSchema = formatSchema;
93
+ const hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
94
+ /** Trims a gateway option value; anything that is not a non-blank string is absent. */
95
+ const normalizeOption = (value) => {
96
+ if (typeof value !== "string") {
97
+ return undefined;
98
+ }
99
+ const trimmed = value.trim();
100
+ return trimmed.length > 0 ? trimmed : undefined;
101
+ };
102
+ exports.normalizeOption = normalizeOption;
103
+ /**
104
+ * No control character (Unicode Cc: C0, DEL, C1) may reach a header value; CR, LF and NUL
105
+ * would split or end the header field. Tab is the one exception: it is valid in a value.
106
+ */
107
+ const hasHeaderControlChar = (value) => /[\u0000-\u0008\u000A-\u001F\u007F-\u009F]/.test(value);
108
+ exports.hasHeaderControlChar = hasHeaderControlChar;
109
+ /** Node sends header values as Latin-1 and throws synchronously on anything above U+00FF. */
110
+ const hasNonLatin1Char = (value) => /[^\u0000-\u00FF]/.test(value);
111
+ exports.hasNonLatin1Char = hasNonLatin1Char;
112
+ /**
113
+ * Milliseconds from a monotonic clock (process.hrtime): only differences are meaningful, and
114
+ * they never jump with wall-clock changes. Shared by the deduplicator, AvoLog and the exit
115
+ * drain deadline.
116
+ */
117
+ const monotonicNowMs = () => Number(process.hrtime.bigint()) / 1e6;
118
+ exports.monotonicNowMs = monotonicNowMs;
package/package.json CHANGED
@@ -1,8 +1,19 @@
1
1
  {
2
2
  "name": "node-avo-inspector",
3
- "version": "1.2.0",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "Avo Inspector for Node.js",
5
5
  "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "!dist/__tests__",
10
+ "README.md",
11
+ "CHANGELOG.md",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=14"
16
+ },
6
17
  "repository": "https://github.com/avohq/node-avo-inspector",
7
18
  "author": "Avo (https://www.avo.app)",
8
19
  "license": "MIT",
@@ -13,6 +24,7 @@
13
24
  "homepage": "https://github.com/avohq/node-avo-inspector#readme",
14
25
  "scripts": {
15
26
  "build": "tsc --outDir dist",
27
+ "prepack": "rm -rf dist && npm run build",
16
28
  "test": "jest",
17
29
  "typecheck": "tsc --noEmit"
18
30
  },
@@ -29,6 +41,11 @@
29
41
  "dist-native"
30
42
  ],
31
43
  "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
44
+ "globalSetup": "<rootDir>/jest.globalSetup.ts",
45
+ "globalTeardown": "<rootDir>/jest.globalTeardown.ts",
46
+ "setupFilesAfterEnv": [
47
+ "<rootDir>/jest.setup.ts"
48
+ ],
32
49
  "moduleFileExtensions": [
33
50
  "ts",
34
51
  "tsx",
@@ -1 +0,0 @@
1
- {"sessionId":"807772ee-303b-4671-8556-d4549bdcb1c0","pid":8979,"acquiredAt":1774537920892}
@@ -1,11 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(grep -n \"propertyType\\\\|propertyName\\\\|encryptedPropertyValue\" /Users/alexverein/code/avo/android-avo-inspector/avoinspector/src/main/java/app/avo/inspector/*.java)",
5
- "Bash(grep -n \"bodyForEventSchemaCall\\\\|bodyForValidatedEventSchemaCall\\\\|remapProperties\\\\|addEncryptedValues\" /Users/alexverein/code/avo/android-avo-inspector/avoinspector/src/main/java/app/avo/inspector/*.java)",
6
- "Bash(npm ls:*)",
7
- "Bash(find /Users/alexverein/code/avo/avo -name CONTRIBUTING* -o -name DEVELOPMENT* -o -name .env.example)",
8
- "WebSearch"
9
- ]
10
- }
11
- }
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2020 Avo
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.