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
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "node-avo-inspector",
3
+ "version": "1.1.1",
4
+ "description": "Avo Inspector for Node.js",
5
+ "main": "dist/index.js",
6
+ "repository": "https://github.com/avohq/node-avo-inspector",
7
+ "author": "Avo (https://www.avo.app)",
8
+ "license": "MIT",
9
+ "private": false,
10
+ "bugs": {
11
+ "url": "https://github.com/avohq/node-avo-inspector/issues"
12
+ },
13
+ "homepage": "https://github.com/avohq/node-avo-inspector#readme",
14
+ "scripts": {
15
+ "build": "tsc --outDir dist",
16
+ "test": "jest",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "jest": {
20
+ "transform": {
21
+ "^.+\\.[j]sx?$": "babel-jest",
22
+ "^.+\\.[t]sx?$": "ts-jest"
23
+ },
24
+ "testPathIgnorePatterns": [
25
+ "constants",
26
+ "examples",
27
+ "node_modules",
28
+ "dist",
29
+ "dist-native"
30
+ ],
31
+ "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
32
+ "moduleFileExtensions": [
33
+ "ts",
34
+ "tsx",
35
+ "js"
36
+ ]
37
+ },
38
+ "devDependencies": {
39
+ "@types/jest": "^29.5.12",
40
+ "@types/node": "^22.3.0",
41
+ "jest": "^29.7.0",
42
+ "ts-jest": "^29.2.4",
43
+ "typescript": "*"
44
+ },
45
+ "dependencies": {
46
+ "safe-regex2": "^5.0.0"
47
+ }
48
+ }
@@ -0,0 +1,11 @@
1
+ type t;
2
+
3
+ type env = [
4
+ | `Dev
5
+ | `Staging
6
+ | `Prod
7
+ ];
8
+
9
+ let make: (~apiKey: string, ~env: env, ~version: string, , ~appName: string, unit) => t;
10
+
11
+ let trackSchemaFromEvent: t => string => Js.Json.t => Js.Promise.t(unit);
@@ -0,0 +1,27 @@
1
+ type t;
2
+
3
+ [@bs.deriving jsConverter]
4
+ type env = [
5
+ | [@bs.as "dev"] `Dev
6
+ | [@bs.as "staging"] `Staging
7
+ | [@bs.as "prod"] `Prod
8
+ ];
9
+
10
+ [@bs.deriving abstract]
11
+ type options = {
12
+ apiKey: string,
13
+ env: string,
14
+ version: string,
15
+ appName: string,
16
+ };
17
+
18
+ [@bs.new] [@bs.module "node-avo-inspector"]
19
+ external make: options => t = "AvoInspector";
20
+
21
+ let make = (~apiKey, ~env, ~version, ~appName, ()) => {
22
+ make(options(~apiKey, ~env=envToJs(env), ~version, ~appName));
23
+ };
24
+
25
+ [@bs.send]
26
+ external trackSchemaFromEvent: (t, string, Js.Json.t) => Js.Promise.t(unit) =
27
+ "trackSchemaFromEvent";
@@ -0,0 +1,146 @@
1
+ import { deepEquals } from "./utils";
2
+
3
+ export class AvoDeduplicator {
4
+ avoFunctionsEvents: { [time: number]: string } = {};
5
+ manualEvents: { [time: number]: string } = {};
6
+ private msToConsiderOld = 500;
7
+
8
+ avoFunctionsEventsParams: {
9
+ [eventName: string]: { [propName: string]: any };
10
+ } = {};
11
+ manualEventsParams: { [eventName: string]: { [propName: string]: any } } = {};
12
+
13
+ shouldRegisterEvent(
14
+ eventName: string,
15
+ params: { [propName: string]: any },
16
+ fromAvoFunction: boolean
17
+ ): boolean {
18
+ this.clearOldEvents();
19
+
20
+ if (fromAvoFunction) {
21
+ this.avoFunctionsEvents[Date.now()] = eventName;
22
+ this.avoFunctionsEventsParams[eventName] = params;
23
+ } else {
24
+ this.manualEvents[Date.now()] = eventName;
25
+ this.manualEventsParams[eventName] = params;
26
+ }
27
+
28
+ let checkInAvoFunctions = !fromAvoFunction;
29
+
30
+ return !this.hasSameEventAs(eventName, params, checkInAvoFunctions);
31
+ }
32
+
33
+ private hasSameEventAs(
34
+ eventName: string,
35
+ params: { [propName: string]: any },
36
+ checkInAvoFunctions: boolean
37
+ ): boolean {
38
+ let result = false;
39
+
40
+ if (checkInAvoFunctions) {
41
+ if (
42
+ this.lookForEventIn(eventName, params, this.avoFunctionsEventsParams)
43
+ ) {
44
+ result = true;
45
+ }
46
+ } else {
47
+ if (this.lookForEventIn(eventName, params, this.manualEventsParams)) {
48
+ result = true;
49
+ }
50
+ }
51
+
52
+ if (result) {
53
+ delete this.avoFunctionsEventsParams[eventName];
54
+ delete this.manualEventsParams[eventName];
55
+ }
56
+
57
+ return result;
58
+ }
59
+
60
+ private lookForEventIn(
61
+ eventName: string,
62
+ params: { [propName: string]: any },
63
+ eventsStorage: { [eventName: string]: { [propName: string]: any } }
64
+ ): boolean {
65
+ for (const otherEventName in eventsStorage) {
66
+ if (eventsStorage.hasOwnProperty(otherEventName)) {
67
+ if (otherEventName === eventName) {
68
+ const otherParams = eventsStorage[eventName];
69
+ if (otherParams && deepEquals(params, otherParams)) {
70
+ return true;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ return false;
76
+ }
77
+
78
+ hasSeenEventParams(
79
+ params: { [propName: string]: any },
80
+ checkInAvoFunctions: boolean
81
+ ) {
82
+ let result = false;
83
+
84
+ if (checkInAvoFunctions) {
85
+ if (this.lookForEventParamsIn(params, this.avoFunctionsEventsParams)) {
86
+ result = true;
87
+ }
88
+ } else {
89
+ if (this.lookForEventParamsIn(params, this.manualEventsParams)) {
90
+ result = true;
91
+ }
92
+ }
93
+
94
+ return result;
95
+ }
96
+
97
+ private lookForEventParamsIn(
98
+ params: { [propName: string]: any },
99
+ eventsStorage: { [eventName: string]: { [propName: string]: any } }
100
+ ): boolean {
101
+ for (const otherEventName in eventsStorage) {
102
+ if (eventsStorage.hasOwnProperty(otherEventName)) {
103
+ const otherParams = eventsStorage[otherEventName];
104
+ if (otherParams && deepEquals(params, otherParams)) {
105
+ return true;
106
+ }
107
+ }
108
+ }
109
+ return false;
110
+ }
111
+
112
+ private clearOldEvents() {
113
+ const now = Date.now();
114
+
115
+ for (const time in this.avoFunctionsEvents) {
116
+ if (this.avoFunctionsEvents.hasOwnProperty(time)) {
117
+ const timestamp = Number(time) || 0;
118
+ if (now - timestamp > this.msToConsiderOld) {
119
+ const eventName = this.avoFunctionsEvents[time];
120
+ delete this.avoFunctionsEvents[time];
121
+ delete this.avoFunctionsEventsParams[eventName];
122
+ }
123
+ }
124
+ }
125
+
126
+ for (const time in this.manualEvents) {
127
+ if (this.manualEvents.hasOwnProperty(time)) {
128
+ const timestamp = Number(time) || 0;
129
+ if (now - timestamp > this.msToConsiderOld) {
130
+ const eventName = this.manualEvents[time];
131
+ delete this.manualEvents[time];
132
+ delete this.manualEventsParams[eventName];
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ // used in tests
139
+ private _clearEvents() {
140
+ this.avoFunctionsEvents = {};
141
+ this.manualEvents = {};
142
+
143
+ this.avoFunctionsEventsParams = {};
144
+ this.manualEventsParams = {};
145
+ }
146
+ }
@@ -0,0 +1,90 @@
1
+ import * as crypto from "crypto";
2
+
3
+ export class AvoEncryption {
4
+ /**
5
+ * Determines whether encryption should be applied.
6
+ *
7
+ * Truth table:
8
+ * dev + key => true
9
+ * staging + key => true
10
+ * prod + key => false
11
+ * dev + null => false
12
+ * dev + "" => false
13
+ */
14
+ static shouldEncrypt(
15
+ env: string,
16
+ publicEncryptionKey: string | undefined | null
17
+ ): boolean {
18
+ if (!publicEncryptionKey || publicEncryptionKey.length === 0) {
19
+ return false;
20
+ }
21
+ if (env === "prod") {
22
+ return false;
23
+ }
24
+ return true;
25
+ }
26
+
27
+ /**
28
+ * Returns true if the propertyType represents a list type (e.g. "list(string)").
29
+ */
30
+ static isListType(propertyType: string): boolean {
31
+ return propertyType.startsWith("list(");
32
+ }
33
+
34
+ /**
35
+ * Encrypts a string value using ECIES with prime256v1.
36
+ *
37
+ * Wire format: [0x00][65-byte ephemeral pubkey][16-byte IV][16-byte auth tag][ciphertext]
38
+ * Output is base64-encoded.
39
+ *
40
+ * Returns null on failure, logging a warning.
41
+ */
42
+ static encryptValue(
43
+ value: string,
44
+ recipientPubKeyHex: string
45
+ ): string | null {
46
+ try {
47
+ const recipientPubKey = Buffer.from(recipientPubKeyHex, "hex");
48
+
49
+ // Generate ephemeral key pair
50
+ const ecdh = crypto.createECDH("prime256v1");
51
+ ecdh.generateKeys();
52
+ const ephemeralPubKey = ecdh.getPublicKey(); // 65 bytes, uncompressed
53
+
54
+ // Compute shared secret (raw 32-byte X-coordinate)
55
+ const sharedSecret = ecdh.computeSecret(recipientPubKey);
56
+
57
+ // Derive AES key via SHA-256
58
+ const aesKey = crypto.createHash("sha256").update(sharedSecret).digest();
59
+
60
+ // Generate random 16-byte IV
61
+ const iv = crypto.randomBytes(16);
62
+
63
+ // Encrypt with AES-256-GCM
64
+ const cipher = crypto.createCipheriv("aes-256-gcm", aesKey, iv, {
65
+ authTagLength: 16,
66
+ });
67
+ const encrypted = Buffer.concat([
68
+ cipher.update(value, "utf8"),
69
+ cipher.final(),
70
+ ]);
71
+ const authTag = cipher.getAuthTag(); // 16 bytes
72
+
73
+ // Assemble wire format: [version][ephemeralPubKey][IV][authTag][ciphertext]
74
+ const wire = Buffer.concat([
75
+ Buffer.from([0x00]), // version byte
76
+ ephemeralPubKey, // 65 bytes
77
+ iv, // 16 bytes
78
+ authTag, // 16 bytes
79
+ encrypted, // variable length
80
+ ]);
81
+
82
+ return wire.toString("base64");
83
+ } catch (e) {
84
+ console.warn(
85
+ `[Avo Inspector] Warning: encryption failed for value, omitting property. ${e}`
86
+ );
87
+ return null;
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,9 @@
1
+ export class AvoGuid {
2
+ static newGuid(): string {
3
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
4
+ var r = (Math.random() * 16) | 0,
5
+ v = c == "x" ? r : (r & 0x3) | 0x8;
6
+ return v.toString(16);
7
+ });
8
+ }
9
+ }