node-avo-inspector 1.1.0 → 1.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/scheduled_tasks.lock +1 -0
- package/.claude/settings.local.json +11 -0
- package/.claude/worktrees/release-1.1.1/LICENSE +21 -0
- package/.claude/worktrees/release-1.1.1/README.md +69 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/index.js +30 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/package.json +15 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/yarn.lock +6 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/dist/index.js +34 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/index.ts +30 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/package.json +18 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/tsconfig.json +15 -0
- package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/yarn.lock +11 -0
- package/.claude/worktrees/release-1.1.1/package-lock.json +4039 -0
- package/.claude/worktrees/release-1.1.1/package.json +48 -0
- package/.claude/worktrees/release-1.1.1/reason/AvoInspector.rei +11 -0
- package/.claude/worktrees/release-1.1.1/reason/NodeAvoInspector.re +27 -0
- package/.claude/worktrees/release-1.1.1/src/AvoDeduplicator.ts +146 -0
- package/.claude/worktrees/release-1.1.1/src/AvoEncryption.ts +90 -0
- package/.claude/worktrees/release-1.1.1/src/AvoGuid.ts +9 -0
- package/.claude/worktrees/release-1.1.1/src/AvoInspector.ts +383 -0
- package/.claude/worktrees/release-1.1.1/src/AvoInspectorEnv.ts +8 -0
- package/.claude/worktrees/release-1.1.1/src/AvoNetworkCallsHandler.ts +267 -0
- package/.claude/worktrees/release-1.1.1/src/AvoSchemaParser.ts +115 -0
- package/.claude/worktrees/release-1.1.1/src/AvoStreamId.ts +16 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEncryption.ts +515 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecCache.ts +242 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecFetcher.ts +241 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoInspector.ts +360 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoStreamId.ts +72 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestDeduplicator.ts +191 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestEventValidator.ts +231 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandler.ts +179 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandlerPublic.ts +208 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/TestParsing.ts +153 -0
- package/.claude/worktrees/release-1.1.1/src/__tests__/constants.ts +63 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecCache.ts +109 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetcher.ts +106 -0
- package/.claude/worktrees/release-1.1.1/src/eventSpec/EventValidator.ts +74 -0
- package/.claude/worktrees/release-1.1.1/src/index.ts +2 -0
- package/.claude/worktrees/release-1.1.1/src/utils.ts +49 -0
- package/.claude/worktrees/release-1.1.1/tsconfig.json +13 -0
- package/.claude/worktrees/release-1.1.1/yarn.lock +2310 -0
- package/.github/workflows/claude.yml +55 -0
- package/dist/AvoDeduplicator.d.ts +4 -3
- package/dist/AvoDeduplicator.js +25 -24
- package/dist/AvoEncryption.d.ts +26 -0
- package/dist/AvoEncryption.js +77 -0
- package/dist/AvoInspector.d.ts +34 -3
- package/dist/AvoInspector.js +196 -40
- package/dist/AvoNetworkCallsHandler.d.ts +39 -13
- package/dist/AvoNetworkCallsHandler.js +127 -33
- package/dist/AvoStreamId.d.ts +5 -0
- package/dist/AvoStreamId.js +15 -0
- package/dist/__tests__/TestAvoEncryption.d.ts +1 -0
- package/dist/__tests__/TestAvoEncryption.js +288 -0
- package/dist/__tests__/TestAvoEventSpecCache.d.ts +1 -0
- package/dist/__tests__/TestAvoEventSpecCache.js +194 -0
- package/dist/__tests__/TestAvoEventSpecFetcher.d.ts +1 -0
- package/dist/__tests__/TestAvoEventSpecFetcher.js +219 -0
- package/dist/__tests__/TestAvoInspector.js +76 -2
- package/dist/__tests__/TestAvoStreamId.d.ts +1 -0
- package/dist/__tests__/TestAvoStreamId.js +55 -0
- package/dist/__tests__/TestDeduplicator.js +3 -0
- package/dist/__tests__/TestEventValidator.d.ts +1 -0
- package/dist/__tests__/TestEventValidator.js +196 -0
- package/dist/__tests__/TestNetworkCallsHandler.js +37 -10
- package/dist/__tests__/TestNetworkCallsHandlerPublic.js +93 -58
- package/dist/eventSpec/AvoEventSpecCache.d.ts +19 -0
- package/dist/eventSpec/AvoEventSpecCache.js +100 -0
- package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +41 -0
- package/dist/eventSpec/AvoEventSpecFetchTypes.js +5 -0
- package/dist/eventSpec/AvoEventSpecFetcher.d.ts +25 -0
- package/dist/eventSpec/AvoEventSpecFetcher.js +159 -0
- package/dist/eventSpec/EventValidator.d.ts +15 -0
- package/dist/eventSpec/EventValidator.js +53 -0
- package/package.json +4 -2
- package/src/AvoDeduplicator.ts +31 -27
- package/src/AvoEncryption.ts +90 -0
- package/src/AvoInspector.ts +278 -54
- package/src/AvoNetworkCallsHandler.ts +205 -45
- package/src/AvoStreamId.ts +16 -0
- package/src/__tests__/TestAvoEncryption.ts +515 -0
- package/src/__tests__/TestAvoEventSpecCache.ts +242 -0
- package/src/__tests__/TestAvoEventSpecFetcher.ts +279 -0
- package/src/__tests__/TestAvoInspector.ts +114 -2
- package/src/__tests__/TestAvoStreamId.ts +72 -0
- package/src/__tests__/TestDeduplicator.ts +4 -0
- package/src/__tests__/TestEventValidator.ts +231 -0
- package/src/__tests__/TestNetworkCallsHandler.ts +82 -15
- package/src/__tests__/TestNetworkCallsHandlerPublic.ts +134 -63
- package/src/eventSpec/AvoEventSpecCache.ts +119 -0
- package/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
- package/src/eventSpec/AvoEventSpecFetcher.ts +186 -0
- package/src/eventSpec/EventValidator.ts +74 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
pull_request_review:
|
|
9
|
+
types: [submitted]
|
|
10
|
+
issues:
|
|
11
|
+
types: [opened, assigned]
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude:
|
|
15
|
+
if: |
|
|
16
|
+
(
|
|
17
|
+
github.event_name == 'issue_comment' &&
|
|
18
|
+
contains(github.event.comment.body || '', '@claude') &&
|
|
19
|
+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|
|
20
|
+
) ||
|
|
21
|
+
(
|
|
22
|
+
github.event_name == 'pull_request_review_comment' &&
|
|
23
|
+
contains(github.event.comment.body || '', '@claude') &&
|
|
24
|
+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|
|
25
|
+
) ||
|
|
26
|
+
(
|
|
27
|
+
github.event_name == 'pull_request_review' &&
|
|
28
|
+
contains(github.event.review.body || '', '@claude') &&
|
|
29
|
+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)
|
|
30
|
+
) ||
|
|
31
|
+
(
|
|
32
|
+
github.event_name == 'issues' &&
|
|
33
|
+
contains(github.event.issue.body || '', '@claude') &&
|
|
34
|
+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)
|
|
35
|
+
)
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
permissions:
|
|
38
|
+
contents: read
|
|
39
|
+
pull-requests: write
|
|
40
|
+
issues: write
|
|
41
|
+
id-token: write
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout repository
|
|
44
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
45
|
+
with:
|
|
46
|
+
fetch-depth: 1
|
|
47
|
+
|
|
48
|
+
- name: Run Claude Code
|
|
49
|
+
uses: anthropics/claude-code-action@673eb13aa77026be5c507eda12322c1a58b80f0b # v1
|
|
50
|
+
with:
|
|
51
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
52
|
+
plugin_marketplaces: |
|
|
53
|
+
https://github.com/anthropics/claude-code.git
|
|
54
|
+
plugins: |
|
|
55
|
+
code-review@claude-code-plugins
|
|
@@ -7,18 +7,19 @@ export declare class AvoDeduplicator {
|
|
|
7
7
|
};
|
|
8
8
|
private msToConsiderOld;
|
|
9
9
|
avoFunctionsEventsParams: {
|
|
10
|
-
[
|
|
10
|
+
[key: string]: {
|
|
11
11
|
[propName: string]: any;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
14
|
manualEventsParams: {
|
|
15
|
-
[
|
|
15
|
+
[key: string]: {
|
|
16
16
|
[propName: string]: any;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
+
private static dedupKey;
|
|
19
20
|
shouldRegisterEvent(eventName: string, params: {
|
|
20
21
|
[propName: string]: any;
|
|
21
|
-
}, fromAvoFunction: boolean): boolean;
|
|
22
|
+
}, fromAvoFunction: boolean, streamId?: string): boolean;
|
|
22
23
|
private hasSameEventAs;
|
|
23
24
|
private lookForEventIn;
|
|
24
25
|
hasSeenEventParams(params: {
|
package/dist/AvoDeduplicator.js
CHANGED
|
@@ -7,49 +7,50 @@ class AvoDeduplicator {
|
|
|
7
7
|
this.avoFunctionsEvents = {};
|
|
8
8
|
this.manualEvents = {};
|
|
9
9
|
this.msToConsiderOld = 500;
|
|
10
|
+
// Keyed by streamId\0eventName to prevent cross-stream suppression on server
|
|
10
11
|
this.avoFunctionsEventsParams = {};
|
|
11
12
|
this.manualEventsParams = {};
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
static dedupKey(eventName, streamId) {
|
|
15
|
+
return streamId + "\0" + eventName;
|
|
16
|
+
}
|
|
17
|
+
shouldRegisterEvent(eventName, params, fromAvoFunction, streamId = "") {
|
|
14
18
|
this.clearOldEvents();
|
|
19
|
+
const key = AvoDeduplicator.dedupKey(eventName, streamId);
|
|
15
20
|
if (fromAvoFunction) {
|
|
16
|
-
this.avoFunctionsEvents[Date.now()] =
|
|
17
|
-
this.avoFunctionsEventsParams[
|
|
21
|
+
this.avoFunctionsEvents[Date.now()] = key;
|
|
22
|
+
this.avoFunctionsEventsParams[key] = params;
|
|
18
23
|
}
|
|
19
24
|
else {
|
|
20
|
-
this.manualEvents[Date.now()] =
|
|
21
|
-
this.manualEventsParams[
|
|
25
|
+
this.manualEvents[Date.now()] = key;
|
|
26
|
+
this.manualEventsParams[key] = params;
|
|
22
27
|
}
|
|
23
28
|
let checkInAvoFunctions = !fromAvoFunction;
|
|
24
|
-
return !this.hasSameEventAs(
|
|
29
|
+
return !this.hasSameEventAs(key, params, checkInAvoFunctions);
|
|
25
30
|
}
|
|
26
|
-
hasSameEventAs(
|
|
31
|
+
hasSameEventAs(key, params, checkInAvoFunctions) {
|
|
27
32
|
let result = false;
|
|
28
33
|
if (checkInAvoFunctions) {
|
|
29
|
-
if (this.lookForEventIn(
|
|
34
|
+
if (this.lookForEventIn(key, params, this.avoFunctionsEventsParams)) {
|
|
30
35
|
result = true;
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
else {
|
|
34
|
-
if (this.lookForEventIn(
|
|
39
|
+
if (this.lookForEventIn(key, params, this.manualEventsParams)) {
|
|
35
40
|
result = true;
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
43
|
if (result) {
|
|
39
|
-
delete this.avoFunctionsEventsParams[
|
|
40
|
-
delete this.manualEventsParams[
|
|
44
|
+
delete this.avoFunctionsEventsParams[key];
|
|
45
|
+
delete this.manualEventsParams[key];
|
|
41
46
|
}
|
|
42
47
|
return result;
|
|
43
48
|
}
|
|
44
|
-
lookForEventIn(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (otherParams && (0, utils_1.deepEquals)(params, otherParams)) {
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
49
|
+
lookForEventIn(key, params, eventsStorage) {
|
|
50
|
+
if (eventsStorage.hasOwnProperty(key)) {
|
|
51
|
+
const otherParams = eventsStorage[key];
|
|
52
|
+
if (otherParams && (0, utils_1.deepEquals)(params, otherParams)) {
|
|
53
|
+
return true;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
return false;
|
|
@@ -85,9 +86,9 @@ class AvoDeduplicator {
|
|
|
85
86
|
if (this.avoFunctionsEvents.hasOwnProperty(time)) {
|
|
86
87
|
const timestamp = Number(time) || 0;
|
|
87
88
|
if (now - timestamp > this.msToConsiderOld) {
|
|
88
|
-
const
|
|
89
|
+
const key = this.avoFunctionsEvents[time];
|
|
89
90
|
delete this.avoFunctionsEvents[time];
|
|
90
|
-
delete this.avoFunctionsEventsParams[
|
|
91
|
+
delete this.avoFunctionsEventsParams[key];
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
}
|
|
@@ -95,9 +96,9 @@ class AvoDeduplicator {
|
|
|
95
96
|
if (this.manualEvents.hasOwnProperty(time)) {
|
|
96
97
|
const timestamp = Number(time) || 0;
|
|
97
98
|
if (now - timestamp > this.msToConsiderOld) {
|
|
98
|
-
const
|
|
99
|
+
const key = this.manualEvents[time];
|
|
99
100
|
delete this.manualEvents[time];
|
|
100
|
-
delete this.manualEventsParams[
|
|
101
|
+
delete this.manualEventsParams[key];
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class AvoEncryption {
|
|
2
|
+
/**
|
|
3
|
+
* Determines whether encryption should be applied.
|
|
4
|
+
*
|
|
5
|
+
* Truth table:
|
|
6
|
+
* dev + key => true
|
|
7
|
+
* staging + key => true
|
|
8
|
+
* prod + key => false
|
|
9
|
+
* dev + null => false
|
|
10
|
+
* dev + "" => false
|
|
11
|
+
*/
|
|
12
|
+
static shouldEncrypt(env: string, publicEncryptionKey: string | undefined | null): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if the propertyType represents a list type (e.g. "list(string)").
|
|
15
|
+
*/
|
|
16
|
+
static isListType(propertyType: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Encrypts a string value using ECIES with prime256v1.
|
|
19
|
+
*
|
|
20
|
+
* Wire format: [0x00][65-byte ephemeral pubkey][16-byte IV][16-byte auth tag][ciphertext]
|
|
21
|
+
* Output is base64-encoded.
|
|
22
|
+
*
|
|
23
|
+
* Returns null on failure, logging a warning.
|
|
24
|
+
*/
|
|
25
|
+
static encryptValue(value: string, recipientPubKeyHex: string): string | null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AvoEncryption = void 0;
|
|
4
|
+
const crypto = require("crypto");
|
|
5
|
+
class AvoEncryption {
|
|
6
|
+
/**
|
|
7
|
+
* Determines whether encryption should be applied.
|
|
8
|
+
*
|
|
9
|
+
* Truth table:
|
|
10
|
+
* dev + key => true
|
|
11
|
+
* staging + key => true
|
|
12
|
+
* prod + key => false
|
|
13
|
+
* dev + null => false
|
|
14
|
+
* dev + "" => false
|
|
15
|
+
*/
|
|
16
|
+
static shouldEncrypt(env, publicEncryptionKey) {
|
|
17
|
+
if (!publicEncryptionKey || publicEncryptionKey.length === 0) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (env === "prod") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns true if the propertyType represents a list type (e.g. "list(string)").
|
|
27
|
+
*/
|
|
28
|
+
static isListType(propertyType) {
|
|
29
|
+
return propertyType.startsWith("list(");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Encrypts a string value using ECIES with prime256v1.
|
|
33
|
+
*
|
|
34
|
+
* Wire format: [0x00][65-byte ephemeral pubkey][16-byte IV][16-byte auth tag][ciphertext]
|
|
35
|
+
* Output is base64-encoded.
|
|
36
|
+
*
|
|
37
|
+
* Returns null on failure, logging a warning.
|
|
38
|
+
*/
|
|
39
|
+
static encryptValue(value, recipientPubKeyHex) {
|
|
40
|
+
try {
|
|
41
|
+
const recipientPubKey = Buffer.from(recipientPubKeyHex, "hex");
|
|
42
|
+
// Generate ephemeral key pair
|
|
43
|
+
const ecdh = crypto.createECDH("prime256v1");
|
|
44
|
+
ecdh.generateKeys();
|
|
45
|
+
const ephemeralPubKey = ecdh.getPublicKey(); // 65 bytes, uncompressed
|
|
46
|
+
// Compute shared secret (raw 32-byte X-coordinate)
|
|
47
|
+
const sharedSecret = ecdh.computeSecret(recipientPubKey);
|
|
48
|
+
// Derive AES key via SHA-256
|
|
49
|
+
const aesKey = crypto.createHash("sha256").update(sharedSecret).digest();
|
|
50
|
+
// Generate random 16-byte IV
|
|
51
|
+
const iv = crypto.randomBytes(16);
|
|
52
|
+
// Encrypt with AES-256-GCM
|
|
53
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", aesKey, iv, {
|
|
54
|
+
authTagLength: 16,
|
|
55
|
+
});
|
|
56
|
+
const encrypted = Buffer.concat([
|
|
57
|
+
cipher.update(value, "utf8"),
|
|
58
|
+
cipher.final(),
|
|
59
|
+
]);
|
|
60
|
+
const authTag = cipher.getAuthTag(); // 16 bytes
|
|
61
|
+
// Assemble wire format: [version][ephemeralPubKey][IV][authTag][ciphertext]
|
|
62
|
+
const wire = Buffer.concat([
|
|
63
|
+
Buffer.from([0x00]), // version byte
|
|
64
|
+
ephemeralPubKey, // 65 bytes
|
|
65
|
+
iv, // 16 bytes
|
|
66
|
+
authTag, // 16 bytes
|
|
67
|
+
encrypted, // variable length
|
|
68
|
+
]);
|
|
69
|
+
return wire.toString("base64");
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
console.warn(`[Avo Inspector] Warning: encryption failed for value, omitting property. ${e}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.AvoEncryption = AvoEncryption;
|
package/dist/AvoInspector.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export declare class AvoInspector {
|
|
|
7
7
|
avoDeduplicator: AvoDeduplicator;
|
|
8
8
|
apiKey: string;
|
|
9
9
|
version: string;
|
|
10
|
+
private publicEncryptionKey?;
|
|
11
|
+
private eventSpecFetcher;
|
|
12
|
+
private eventSpecCache;
|
|
13
|
+
private eventValidator;
|
|
14
|
+
private generatedAnonymousId;
|
|
15
|
+
private pendingCount;
|
|
16
|
+
private keepAliveTimer;
|
|
17
|
+
private trackPending;
|
|
10
18
|
private static _shouldLog;
|
|
11
19
|
static get shouldLog(): boolean;
|
|
12
20
|
static set shouldLog(enable: boolean);
|
|
@@ -15,16 +23,33 @@ export declare class AvoInspector {
|
|
|
15
23
|
env: AvoInspectorEnvValueType;
|
|
16
24
|
version: string;
|
|
17
25
|
appName?: string;
|
|
26
|
+
publicEncryptionKey?: string;
|
|
18
27
|
});
|
|
19
28
|
trackSchemaFromEvent(eventName: string, eventProperties: {
|
|
20
29
|
[propName: string]: any;
|
|
21
|
-
}): Promise<Array<{
|
|
30
|
+
}, streamId?: string): Promise<Array<{
|
|
22
31
|
propertyName: string;
|
|
23
32
|
propertyType: string;
|
|
24
33
|
children?: any;
|
|
25
34
|
}>>;
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
_avoFunctionTrackSchemaFromEvent(eventName: string, eventProperties: {
|
|
36
|
+
[propName: string]: any;
|
|
37
|
+
}, eventId: string, eventHash: string, streamId?: string): Promise<Array<{
|
|
38
|
+
propertyName: string;
|
|
39
|
+
propertyType: string;
|
|
40
|
+
children?: any;
|
|
41
|
+
}>>;
|
|
42
|
+
/**
|
|
43
|
+
* Try to fetch event spec and validate, then send a single event call.
|
|
44
|
+
* If validation succeeds, sends a validated event (with results merged in).
|
|
45
|
+
* If unavailable, sends a plain event.
|
|
46
|
+
*
|
|
47
|
+
* Uses trackPending() to keep the Node process alive until the work completes,
|
|
48
|
+
* so short-lived processes (CLIs) don't exit before the send finishes — even
|
|
49
|
+
* when the caller doesn't await the returned promise.
|
|
50
|
+
*/
|
|
51
|
+
private sendEventWithOptionalValidation;
|
|
52
|
+
private doSendEventWithOptionalValidation;
|
|
28
53
|
enableLogging(enable: boolean): void;
|
|
29
54
|
extractSchema(eventProperties: {
|
|
30
55
|
[propName: string]: any;
|
|
@@ -33,4 +58,10 @@ export declare class AvoInspector {
|
|
|
33
58
|
propertyType: string;
|
|
34
59
|
children?: any;
|
|
35
60
|
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Fetch event spec and validate. Returns validation results + metadata if
|
|
63
|
+
* validation succeeds, or null if unavailable (prod, no spec, fetch error).
|
|
64
|
+
*/
|
|
65
|
+
private fetchAndValidate;
|
|
66
|
+
destroy(): void;
|
|
36
67
|
}
|
package/dist/AvoInspector.js
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.AvoInspector = void 0;
|
|
4
13
|
const AvoInspectorEnv_1 = require("./AvoInspectorEnv");
|
|
5
14
|
const AvoSchemaParser_1 = require("./AvoSchemaParser");
|
|
6
15
|
const AvoNetworkCallsHandler_1 = require("./AvoNetworkCallsHandler");
|
|
7
16
|
const AvoDeduplicator_1 = require("./AvoDeduplicator");
|
|
17
|
+
const AvoStreamId_1 = require("./AvoStreamId");
|
|
18
|
+
const AvoEventSpecFetcher_1 = require("./eventSpec/AvoEventSpecFetcher");
|
|
19
|
+
const AvoEventSpecCache_1 = require("./eventSpec/AvoEventSpecCache");
|
|
20
|
+
const EventValidator_1 = require("./eventSpec/EventValidator");
|
|
8
21
|
const utils_1 = require("./utils");
|
|
9
|
-
const AvoGuid_1 = require("./AvoGuid");
|
|
10
22
|
const libVersion = require("../package.json").version;
|
|
11
23
|
class AvoInspector {
|
|
24
|
+
trackPending(promise) {
|
|
25
|
+
this.pendingCount++;
|
|
26
|
+
if (this.keepAliveTimer === null) {
|
|
27
|
+
this.keepAliveTimer = setInterval(() => { }, 60000);
|
|
28
|
+
}
|
|
29
|
+
const done = () => {
|
|
30
|
+
this.pendingCount--;
|
|
31
|
+
if (this.pendingCount === 0 && this.keepAliveTimer !== null) {
|
|
32
|
+
clearInterval(this.keepAliveTimer);
|
|
33
|
+
this.keepAliveTimer = null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
promise.then(done, done);
|
|
37
|
+
}
|
|
12
38
|
static get shouldLog() {
|
|
13
39
|
return this._shouldLog;
|
|
14
40
|
}
|
|
@@ -16,6 +42,15 @@ class AvoInspector {
|
|
|
16
42
|
this._shouldLog = enable;
|
|
17
43
|
}
|
|
18
44
|
constructor(options) {
|
|
45
|
+
this.eventSpecFetcher = null;
|
|
46
|
+
this.eventSpecCache = null;
|
|
47
|
+
this.eventValidator = null;
|
|
48
|
+
this.generatedAnonymousId = "";
|
|
49
|
+
// Keep the Node process alive while there are pending inspector operations.
|
|
50
|
+
// Without this, short-lived processes (e.g. CLIs) can exit before async
|
|
51
|
+
// sends complete, since the caller typically doesn't await the inspector promise.
|
|
52
|
+
this.pendingCount = 0;
|
|
53
|
+
this.keepAliveTimer = null;
|
|
19
54
|
// the constructor does aggressive null/undefined checking because same code paths will be accessible from JS
|
|
20
55
|
if ((0, utils_1.isValueEmpty)(options.env)) {
|
|
21
56
|
this.environment = AvoInspectorEnv_1.AvoInspectorEnv.Dev;
|
|
@@ -40,32 +75,51 @@ class AvoInspector {
|
|
|
40
75
|
else {
|
|
41
76
|
this.version = options.version;
|
|
42
77
|
}
|
|
78
|
+
this.publicEncryptionKey = options.publicEncryptionKey;
|
|
79
|
+
if (this.publicEncryptionKey &&
|
|
80
|
+
this.environment !== AvoInspectorEnv_1.AvoInspectorEnv.Prod) {
|
|
81
|
+
const hexPattern = /^[0-9a-fA-F]+$/;
|
|
82
|
+
const len = this.publicEncryptionKey.length;
|
|
83
|
+
// Accept both compressed (66 hex chars, prefix 02/03) and uncompressed (130 hex chars, prefix 04) P-256 keys
|
|
84
|
+
const isValidLength = len === 66 || len === 130;
|
|
85
|
+
if (!hexPattern.test(this.publicEncryptionKey) || !isValidLength) {
|
|
86
|
+
console.warn("[Avo Inspector] Warning: publicEncryptionKey does not look like a valid P-256 public key (expected 66 or 130 hex characters). Encryption may fail.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
43
89
|
if (this.environment === AvoInspectorEnv_1.AvoInspectorEnv.Dev) {
|
|
44
90
|
AvoInspector._shouldLog = true;
|
|
45
91
|
}
|
|
46
92
|
else {
|
|
47
93
|
AvoInspector._shouldLog = false;
|
|
48
94
|
}
|
|
49
|
-
this.avoNetworkCallsHandler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler(this.apiKey, this.environment.toString(), options.appName || "", this.version, libVersion);
|
|
95
|
+
this.avoNetworkCallsHandler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler(this.apiKey, this.environment.toString(), options.appName || "", this.version, libVersion, this.publicEncryptionKey);
|
|
50
96
|
this.avoDeduplicator = new AvoDeduplicator_1.AvoDeduplicator();
|
|
97
|
+
// Initialize event spec validation for dev/staging only
|
|
98
|
+
if (this.environment !== AvoInspectorEnv_1.AvoInspectorEnv.Prod) {
|
|
99
|
+
this.eventSpecFetcher = new AvoEventSpecFetcher_1.AvoEventSpecFetcher(this.apiKey);
|
|
100
|
+
this.eventSpecCache = new AvoEventSpecCache_1.AvoEventSpecCache();
|
|
101
|
+
this.eventValidator = new EventValidator_1.EventValidator();
|
|
102
|
+
}
|
|
51
103
|
}
|
|
52
|
-
trackSchemaFromEvent(eventName, eventProperties) {
|
|
104
|
+
trackSchemaFromEvent(eventName, eventProperties, streamId) {
|
|
53
105
|
try {
|
|
54
|
-
|
|
106
|
+
const avoStreamId = new AvoStreamId_1.AvoStreamId(streamId);
|
|
107
|
+
const anonymousId = avoStreamId.streamId || this.generatedAnonymousId;
|
|
108
|
+
if (this.avoDeduplicator.shouldRegisterEvent(eventName, eventProperties, false, anonymousId)) {
|
|
55
109
|
if (AvoInspector.shouldLog) {
|
|
56
|
-
console.log("Avo Inspector:
|
|
110
|
+
console.log("Avo Inspector: Supplied event " +
|
|
57
111
|
eventName +
|
|
58
|
-
" with params " +
|
|
112
|
+
" with params \n" +
|
|
59
113
|
JSON.stringify(eventProperties));
|
|
60
114
|
}
|
|
61
115
|
let eventSchema = this.extractSchema(eventProperties, false);
|
|
62
|
-
return this.
|
|
116
|
+
return this.sendEventWithOptionalValidation(eventName, eventSchema, null, null, anonymousId, eventProperties).then(() => {
|
|
63
117
|
return eventSchema;
|
|
64
118
|
});
|
|
65
119
|
}
|
|
66
120
|
else {
|
|
67
121
|
if (AvoInspector.shouldLog) {
|
|
68
|
-
console.log("Avo Inspector: Deduplicated event
|
|
122
|
+
console.log("Avo Inspector: Deduplicated event " + eventName);
|
|
69
123
|
}
|
|
70
124
|
return Promise.resolve([]);
|
|
71
125
|
}
|
|
@@ -75,23 +129,24 @@ class AvoInspector {
|
|
|
75
129
|
return Promise.reject("Avo Inspector: something went wrong. Please report to support@avo.app.");
|
|
76
130
|
}
|
|
77
131
|
}
|
|
78
|
-
_avoFunctionTrackSchemaFromEvent(eventName, eventProperties, eventId, eventHash) {
|
|
132
|
+
_avoFunctionTrackSchemaFromEvent(eventName, eventProperties, eventId, eventHash, streamId) {
|
|
79
133
|
try {
|
|
80
|
-
|
|
134
|
+
const anonymousId = (streamId && streamId.length > 0) ? streamId : this.generatedAnonymousId;
|
|
135
|
+
if (this.avoDeduplicator.shouldRegisterEvent(eventName, eventProperties, true, anonymousId)) {
|
|
81
136
|
if (AvoInspector.shouldLog) {
|
|
82
|
-
console.log("Avo Inspector:
|
|
137
|
+
console.log("Avo Inspector: Supplied event " +
|
|
83
138
|
eventName +
|
|
84
|
-
" with params " +
|
|
139
|
+
" with params \n" +
|
|
85
140
|
JSON.stringify(eventProperties));
|
|
86
141
|
}
|
|
87
142
|
let eventSchema = this.extractSchema(eventProperties, false);
|
|
88
|
-
return this.
|
|
143
|
+
return this.sendEventWithOptionalValidation(eventName, eventSchema, eventId, eventHash, anonymousId, eventProperties).then(() => {
|
|
89
144
|
return eventSchema;
|
|
90
145
|
});
|
|
91
146
|
}
|
|
92
147
|
else {
|
|
93
148
|
if (AvoInspector.shouldLog) {
|
|
94
|
-
console.log("Avo Inspector: Deduplicated event
|
|
149
|
+
console.log("Avo Inspector: Deduplicated event " + eventName);
|
|
95
150
|
}
|
|
96
151
|
return Promise.resolve([]);
|
|
97
152
|
}
|
|
@@ -101,35 +156,45 @@ class AvoInspector {
|
|
|
101
156
|
return Promise.reject("Avo Inspector: something went wrong. Please report to support@avo.app.");
|
|
102
157
|
}
|
|
103
158
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Try to fetch event spec and validate, then send a single event call.
|
|
161
|
+
* If validation succeeds, sends a validated event (with results merged in).
|
|
162
|
+
* If unavailable, sends a plain event.
|
|
163
|
+
*
|
|
164
|
+
* Uses trackPending() to keep the Node process alive until the work completes,
|
|
165
|
+
* so short-lived processes (CLIs) don't exit before the send finishes — even
|
|
166
|
+
* when the caller doesn't await the returned promise.
|
|
167
|
+
*/
|
|
168
|
+
sendEventWithOptionalValidation(eventName, eventSchema, eventId, eventHash, anonymousId, rawEventProperties) {
|
|
169
|
+
const work = this.doSendEventWithOptionalValidation(eventName, eventSchema, eventId, eventHash, anonymousId, rawEventProperties);
|
|
170
|
+
this.trackPending(work);
|
|
171
|
+
return work;
|
|
172
|
+
}
|
|
173
|
+
doSendEventWithOptionalValidation(eventName, eventSchema, eventId, eventHash, anonymousId, rawEventProperties) {
|
|
174
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
175
|
+
const validationResult = yield this.fetchAndValidate(eventName, eventSchema, anonymousId, rawEventProperties, eventId);
|
|
176
|
+
try {
|
|
177
|
+
let body;
|
|
178
|
+
if (validationResult) {
|
|
118
179
|
if (AvoInspector.shouldLog) {
|
|
119
|
-
console.log("Avo Inspector:
|
|
180
|
+
console.log("Avo Inspector: Sending validated event " + eventName);
|
|
120
181
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
182
|
+
const eventProps = this.avoNetworkCallsHandler.buildEventProperties(eventSchema, rawEventProperties);
|
|
183
|
+
body = this.avoNetworkCallsHandler.bodyForValidatedEventSchemaCall(anonymousId, eventName, eventProps, eventId, eventHash, validationResult.metadata, validationResult.propertyResults);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
body = this.avoNetworkCallsHandler.bodyForEventSchemaCall(anonymousId, eventName, eventSchema, eventId, eventHash, rawEventProperties);
|
|
187
|
+
}
|
|
188
|
+
yield this.avoNetworkCallsHandler.callInspectorWithBatchBody([body]);
|
|
124
189
|
if (AvoInspector.shouldLog) {
|
|
125
|
-
|
|
190
|
+
const schemaString = eventSchema.map(p => '\t"' + p.propertyName + '": "' + p.propertyType + '"').join(";\n");
|
|
191
|
+
console.log("Avo Inspector: Saved event " + eventName + " with schema {\n" + schemaString + "\n}");
|
|
126
192
|
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
console.error("Avo Inspector: schema sending failed: " + err + ".");
|
|
196
|
+
}
|
|
197
|
+
});
|
|
133
198
|
}
|
|
134
199
|
enableLogging(enable) {
|
|
135
200
|
AvoInspector._shouldLog = enable;
|
|
@@ -147,13 +212,104 @@ class AvoInspector {
|
|
|
147
212
|
console.log("Avo Inspector: extracting schema from " +
|
|
148
213
|
JSON.stringify(eventProperties));
|
|
149
214
|
}
|
|
150
|
-
|
|
215
|
+
const schema = AvoSchemaParser_1.AvoSchemaParser.extractSchema(eventProperties);
|
|
216
|
+
if (AvoInspector.shouldLog) {
|
|
217
|
+
const schemaString = schema.map(p => '\t"' + p.propertyName + '": "' + p.propertyType + '"').join(";\n");
|
|
218
|
+
console.log("Avo Inspector: Parsed schema {\n" + schemaString + "\n}");
|
|
219
|
+
}
|
|
220
|
+
return schema;
|
|
151
221
|
}
|
|
152
222
|
catch (e) {
|
|
153
223
|
console.error("Avo Inspector: something went wrong. Please report to support@avo.app.", e);
|
|
154
224
|
return [];
|
|
155
225
|
}
|
|
156
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Fetch event spec and validate. Returns validation results + metadata if
|
|
229
|
+
* validation succeeds, or null if unavailable (prod, no spec, fetch error).
|
|
230
|
+
*/
|
|
231
|
+
fetchAndValidate(eventName, eventSchema, anonymousId, rawEventProperties, eventId) {
|
|
232
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
233
|
+
if (!this.eventSpecFetcher || !this.eventSpecCache || !this.eventValidator) {
|
|
234
|
+
if (AvoInspector.shouldLog) {
|
|
235
|
+
console.log("Avo Inspector: Skipping event spec validation for event: " + eventName
|
|
236
|
+
+ " (fetcher=" + (this.eventSpecFetcher != null)
|
|
237
|
+
+ ", cache=" + (this.eventSpecCache != null)
|
|
238
|
+
+ ", env=" + this.environment + ")");
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const cacheKey = AvoEventSpecCache_1.AvoEventSpecCache.makeKey(this.apiKey, anonymousId, eventName);
|
|
243
|
+
const cache = this.eventSpecCache;
|
|
244
|
+
const validator = this.eventValidator;
|
|
245
|
+
const doValidate = (specResponse) => {
|
|
246
|
+
if (specResponse.eventSpec === null) {
|
|
247
|
+
if (AvoInspector.shouldLog) {
|
|
248
|
+
console.log("Avo Inspector: Event spec fetch returned null for event: " + eventName + ". Sending without validation.");
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const eventProperties = eventSchema.map((prop) => (Object.assign({ propertyName: prop.propertyName, propertyType: prop.propertyType }, (rawEventProperties && rawEventProperties[prop.propertyName] !== undefined
|
|
253
|
+
? { propertyValue: String(rawEventProperties[prop.propertyName]) }
|
|
254
|
+
: {}))));
|
|
255
|
+
if (AvoInspector.shouldLog) {
|
|
256
|
+
console.log("Avo Inspector: Validating event: " + eventName
|
|
257
|
+
+ " with " + eventProperties.length + " properties"
|
|
258
|
+
+ " against " + specResponse.eventSpec.properties.length + " spec properties");
|
|
259
|
+
}
|
|
260
|
+
const validationId = eventId || eventName;
|
|
261
|
+
const results = validator.validate(specResponse.eventSpec, eventProperties, validationId);
|
|
262
|
+
if (AvoInspector.shouldLog) {
|
|
263
|
+
console.log("Avo Inspector: Validation complete for event: " + eventName
|
|
264
|
+
+ " with " + results.length + " property results");
|
|
265
|
+
}
|
|
266
|
+
return { metadata: specResponse.metadata, propertyResults: results };
|
|
267
|
+
};
|
|
268
|
+
// Check cache first
|
|
269
|
+
const cached = cache.get(cacheKey);
|
|
270
|
+
if (cached !== undefined) {
|
|
271
|
+
if (AvoInspector.shouldLog) {
|
|
272
|
+
console.log("Avo Inspector: Event spec cache hit for event: " + eventName);
|
|
273
|
+
}
|
|
274
|
+
return doValidate(cached);
|
|
275
|
+
}
|
|
276
|
+
// Cache miss — fetch spec (process stays alive via trackPending)
|
|
277
|
+
if (AvoInspector.shouldLog) {
|
|
278
|
+
console.log("Avo Inspector: Event spec cache miss for event: " + eventName + ". Fetching before sending.");
|
|
279
|
+
}
|
|
280
|
+
const fetcher = this.eventSpecFetcher;
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
fetcher.fetch(eventName, anonymousId, (result) => {
|
|
283
|
+
if (result !== null) {
|
|
284
|
+
cache.set(cacheKey, result);
|
|
285
|
+
resolve(doValidate(result));
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
if (AvoInspector.shouldLog) {
|
|
289
|
+
console.log("Avo Inspector: Event spec fetch returned null for event: " + eventName + ". Cached empty response. Sending without validation.");
|
|
290
|
+
}
|
|
291
|
+
resolve(null);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
destroy() {
|
|
298
|
+
if (this.eventSpecFetcher) {
|
|
299
|
+
this.eventSpecFetcher.destroy();
|
|
300
|
+
this.eventSpecFetcher = null;
|
|
301
|
+
}
|
|
302
|
+
if (this.eventSpecCache) {
|
|
303
|
+
this.eventSpecCache.flush();
|
|
304
|
+
this.eventSpecCache = null;
|
|
305
|
+
}
|
|
306
|
+
this.eventValidator = null;
|
|
307
|
+
if (this.keepAliveTimer !== null) {
|
|
308
|
+
clearInterval(this.keepAliveTimer);
|
|
309
|
+
this.keepAliveTimer = null;
|
|
310
|
+
}
|
|
311
|
+
this.pendingCount = 0;
|
|
312
|
+
}
|
|
157
313
|
}
|
|
158
314
|
exports.AvoInspector = AvoInspector;
|
|
159
315
|
AvoInspector._shouldLog = false;
|