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.
- 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 +182 -36
- 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__/TestEventValidator.d.ts +1 -0
- package/dist/__tests__/TestEventValidator.js +196 -0
- package/dist/__tests__/TestNetworkCallsHandler.js +37 -10
- package/dist/__tests__/TestNetworkCallsHandlerPublic.js +74 -41
- 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 +275 -49
- 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__/TestEventValidator.ts +231 -0
- package/src/__tests__/TestNetworkCallsHandler.ts +82 -15
- package/src/__tests__/TestNetworkCallsHandlerPublic.ts +110 -44
- 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,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AvoEventSpecFetcher = void 0;
|
|
4
|
+
const https_1 = require("https");
|
|
5
|
+
const AvoInspector_1 = require("../AvoInspector");
|
|
6
|
+
class AvoEventSpecFetcher {
|
|
7
|
+
constructor(apiKey) {
|
|
8
|
+
this.inFlight = new Map();
|
|
9
|
+
this.apiKey = apiKey;
|
|
10
|
+
this.agent = new https_1.Agent({ keepAlive: true });
|
|
11
|
+
}
|
|
12
|
+
fetch(eventName, streamId, callback) {
|
|
13
|
+
const dedupeKey = `${this.apiKey}:${streamId}:${eventName}`;
|
|
14
|
+
// In-flight dedup: if there's already a request in flight for this key,
|
|
15
|
+
// queue the callback
|
|
16
|
+
const existingCallbacks = this.inFlight.get(dedupeKey);
|
|
17
|
+
if (existingCallbacks) {
|
|
18
|
+
existingCallbacks.push(callback);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Register the callback and start the request
|
|
22
|
+
this.inFlight.set(dedupeKey, [callback]);
|
|
23
|
+
const queryParams = new URLSearchParams({
|
|
24
|
+
apiKey: this.apiKey,
|
|
25
|
+
eventName,
|
|
26
|
+
streamId,
|
|
27
|
+
});
|
|
28
|
+
const options = {
|
|
29
|
+
hostname: "api.avo.app",
|
|
30
|
+
port: 443,
|
|
31
|
+
path: `${AvoEventSpecFetcher.specEndpoint}?${queryParams.toString()}`,
|
|
32
|
+
method: "GET",
|
|
33
|
+
agent: this.agent,
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: "application/json",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
39
|
+
console.log("Avo Inspector: [network] GET https://" + options.hostname + AvoEventSpecFetcher.specEndpoint + "?eventName=" + encodeURIComponent(eventName));
|
|
40
|
+
}
|
|
41
|
+
const req = (0, https_1.request)(options, (res) => {
|
|
42
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
43
|
+
console.log("Avo Inspector: [network] Spec response status: " + res.statusCode + " " + res.statusMessage);
|
|
44
|
+
}
|
|
45
|
+
const chunks = [];
|
|
46
|
+
res.on("data", (data) => chunks.push(data));
|
|
47
|
+
res.on("end", () => {
|
|
48
|
+
let result = null;
|
|
49
|
+
if (res.statusCode !== 200) {
|
|
50
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
51
|
+
const body = Buffer.concat(chunks).toString();
|
|
52
|
+
console.warn("Avo Inspector: [network] Spec fetch failed with status " + res.statusCode + ": " + body);
|
|
53
|
+
}
|
|
54
|
+
this.resolveCallbacks(dedupeKey, null);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const body = Buffer.concat(chunks).toString();
|
|
59
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
60
|
+
console.log("Avo Inspector: [network] Spec response body: " + body);
|
|
61
|
+
}
|
|
62
|
+
const wire = JSON.parse(body);
|
|
63
|
+
result = AvoEventSpecFetcher.parseWireResponse(wire, eventName);
|
|
64
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
65
|
+
console.log("Avo Inspector: [network] Parsed spec: " + JSON.stringify(result));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
70
|
+
console.warn("Avo Inspector: [network] Failed to parse spec response: " + e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
this.resolveCallbacks(dedupeKey, result);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
req.on("error", (err) => {
|
|
77
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
78
|
+
console.error("Avo Inspector: [network] Spec fetch error: " + err);
|
|
79
|
+
}
|
|
80
|
+
this.resolveCallbacks(dedupeKey, null);
|
|
81
|
+
});
|
|
82
|
+
req.setTimeout(10000);
|
|
83
|
+
req.on("timeout", () => {
|
|
84
|
+
if (AvoInspector_1.AvoInspector.shouldLog) {
|
|
85
|
+
console.error("Avo Inspector: [network] Spec fetch timed out after 10s");
|
|
86
|
+
}
|
|
87
|
+
req.destroy();
|
|
88
|
+
this.resolveCallbacks(dedupeKey, null);
|
|
89
|
+
});
|
|
90
|
+
req.end();
|
|
91
|
+
}
|
|
92
|
+
resolveCallbacks(key, result) {
|
|
93
|
+
const callbacks = this.inFlight.get(key);
|
|
94
|
+
this.inFlight.delete(key);
|
|
95
|
+
if (callbacks) {
|
|
96
|
+
for (const cb of callbacks) {
|
|
97
|
+
try {
|
|
98
|
+
cb(result);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
// Don't let one callback failure affect others
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parse the wire format from /trackingPlan/eventSpec into our internal types.
|
|
108
|
+
*
|
|
109
|
+
* Wire format:
|
|
110
|
+
* { events: [{ b, id, vids, p: { "PropName": { t: "string", r: true, v: {...}, rx: {...} } } }],
|
|
111
|
+
* metadata: { schemaId, branchId, latestActionId, sourceId } }
|
|
112
|
+
*
|
|
113
|
+
* Internal format:
|
|
114
|
+
* { eventSpec: { eventName, properties: [{ propertyName, propertyType, regex? }] } | null,
|
|
115
|
+
* metadata: { schemaId, branchId, latestActionId, sourceId } }
|
|
116
|
+
*/
|
|
117
|
+
static parseWireResponse(wire, eventName) {
|
|
118
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
119
|
+
const metadata = {
|
|
120
|
+
schemaId: (_b = (_a = wire.metadata) === null || _a === void 0 ? void 0 : _a.schemaId) !== null && _b !== void 0 ? _b : "",
|
|
121
|
+
branchId: (_d = (_c = wire.metadata) === null || _c === void 0 ? void 0 : _c.branchId) !== null && _d !== void 0 ? _d : "",
|
|
122
|
+
latestActionId: (_f = (_e = wire.metadata) === null || _e === void 0 ? void 0 : _e.latestActionId) !== null && _f !== void 0 ? _f : "",
|
|
123
|
+
sourceId: (_h = (_g = wire.metadata) === null || _g === void 0 ? void 0 : _g.sourceId) !== null && _h !== void 0 ? _h : "",
|
|
124
|
+
};
|
|
125
|
+
if (!wire.events || !Array.isArray(wire.events) || wire.events.length === 0) {
|
|
126
|
+
return { eventSpec: null, metadata };
|
|
127
|
+
}
|
|
128
|
+
// Use the first event entry (the endpoint returns specs for the requested event)
|
|
129
|
+
const entry = wire.events[0];
|
|
130
|
+
const properties = [];
|
|
131
|
+
if (entry.p && typeof entry.p === "object") {
|
|
132
|
+
for (const propName of Object.keys(entry.p)) {
|
|
133
|
+
const constraint = entry.p[propName];
|
|
134
|
+
const prop = {
|
|
135
|
+
propertyName: propName,
|
|
136
|
+
propertyType: (_j = constraint === null || constraint === void 0 ? void 0 : constraint.t) !== null && _j !== void 0 ? _j : "unknown",
|
|
137
|
+
};
|
|
138
|
+
// Extract regex pattern if present (rx field maps regex patterns to event IDs)
|
|
139
|
+
if ((constraint === null || constraint === void 0 ? void 0 : constraint.rx) && typeof constraint.rx === "object") {
|
|
140
|
+
const patterns = Object.keys(constraint.rx);
|
|
141
|
+
if (patterns.length > 0) {
|
|
142
|
+
prop.regex = patterns[0];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
properties.push(prop);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const eventSpec = {
|
|
149
|
+
eventName,
|
|
150
|
+
properties,
|
|
151
|
+
};
|
|
152
|
+
return { eventSpec, metadata };
|
|
153
|
+
}
|
|
154
|
+
destroy() {
|
|
155
|
+
this.agent.destroy();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
exports.AvoEventSpecFetcher = AvoEventSpecFetcher;
|
|
159
|
+
AvoEventSpecFetcher.specEndpoint = "/trackingPlan/eventSpec";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EventSpec, PropertyValidationResult } from "./AvoEventSpecFetchTypes";
|
|
2
|
+
interface EventProperty {
|
|
3
|
+
propertyName: string;
|
|
4
|
+
propertyType: string;
|
|
5
|
+
propertyValue?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class EventValidator {
|
|
8
|
+
/**
|
|
9
|
+
* Validates event properties against the spec.
|
|
10
|
+
* Returns per-property validation results with bandwidth optimization:
|
|
11
|
+
* passedEventIds is returned only when strictly smaller than failedEventIds.
|
|
12
|
+
*/
|
|
13
|
+
validate(spec: EventSpec, eventProperties: EventProperty[], eventId: string): PropertyValidationResult[];
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventValidator = void 0;
|
|
4
|
+
const safe_regex2_1 = require("safe-regex2");
|
|
5
|
+
class EventValidator {
|
|
6
|
+
/**
|
|
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.
|
|
10
|
+
*/
|
|
11
|
+
validate(spec, eventProperties, eventId) {
|
|
12
|
+
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
|
+
}
|
|
38
|
+
}
|
|
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 = [];
|
|
47
|
+
}
|
|
48
|
+
results.push(result);
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.EventValidator = EventValidator;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-avo-inspector",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Avo Inspector for Node.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": "https://github.com/avohq/node-avo-inspector",
|
|
@@ -42,5 +42,7 @@
|
|
|
42
42
|
"ts-jest": "^29.2.4",
|
|
43
43
|
"typescript": "*"
|
|
44
44
|
},
|
|
45
|
-
"dependencies": {
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"safe-regex2": "^5.0.0"
|
|
47
|
+
}
|
|
46
48
|
}
|
package/src/AvoDeduplicator.ts
CHANGED
|
@@ -5,33 +5,41 @@ export class AvoDeduplicator {
|
|
|
5
5
|
manualEvents: { [time: number]: string } = {};
|
|
6
6
|
private msToConsiderOld = 500;
|
|
7
7
|
|
|
8
|
+
// Keyed by streamId\0eventName to prevent cross-stream suppression on server
|
|
8
9
|
avoFunctionsEventsParams: {
|
|
9
|
-
[
|
|
10
|
+
[key: string]: { [propName: string]: any };
|
|
10
11
|
} = {};
|
|
11
|
-
manualEventsParams: { [
|
|
12
|
+
manualEventsParams: { [key: string]: { [propName: string]: any } } = {};
|
|
13
|
+
|
|
14
|
+
private static dedupKey(eventName: string, streamId: string): string {
|
|
15
|
+
return streamId + "\0" + eventName;
|
|
16
|
+
}
|
|
12
17
|
|
|
13
18
|
shouldRegisterEvent(
|
|
14
19
|
eventName: string,
|
|
15
20
|
params: { [propName: string]: any },
|
|
16
|
-
fromAvoFunction: boolean
|
|
21
|
+
fromAvoFunction: boolean,
|
|
22
|
+
streamId: string = ""
|
|
17
23
|
): boolean {
|
|
18
24
|
this.clearOldEvents();
|
|
19
25
|
|
|
26
|
+
const key = AvoDeduplicator.dedupKey(eventName, streamId);
|
|
27
|
+
|
|
20
28
|
if (fromAvoFunction) {
|
|
21
|
-
this.avoFunctionsEvents[Date.now()] =
|
|
22
|
-
this.avoFunctionsEventsParams[
|
|
29
|
+
this.avoFunctionsEvents[Date.now()] = key;
|
|
30
|
+
this.avoFunctionsEventsParams[key] = params;
|
|
23
31
|
} else {
|
|
24
|
-
this.manualEvents[Date.now()] =
|
|
25
|
-
this.manualEventsParams[
|
|
32
|
+
this.manualEvents[Date.now()] = key;
|
|
33
|
+
this.manualEventsParams[key] = params;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
let checkInAvoFunctions = !fromAvoFunction;
|
|
29
37
|
|
|
30
|
-
return !this.hasSameEventAs(
|
|
38
|
+
return !this.hasSameEventAs(key, params, checkInAvoFunctions);
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
private hasSameEventAs(
|
|
34
|
-
|
|
42
|
+
key: string,
|
|
35
43
|
params: { [propName: string]: any },
|
|
36
44
|
checkInAvoFunctions: boolean
|
|
37
45
|
): boolean {
|
|
@@ -39,37 +47,33 @@ export class AvoDeduplicator {
|
|
|
39
47
|
|
|
40
48
|
if (checkInAvoFunctions) {
|
|
41
49
|
if (
|
|
42
|
-
this.lookForEventIn(
|
|
50
|
+
this.lookForEventIn(key, params, this.avoFunctionsEventsParams)
|
|
43
51
|
) {
|
|
44
52
|
result = true;
|
|
45
53
|
}
|
|
46
54
|
} else {
|
|
47
|
-
if (this.lookForEventIn(
|
|
55
|
+
if (this.lookForEventIn(key, params, this.manualEventsParams)) {
|
|
48
56
|
result = true;
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
if (result) {
|
|
53
|
-
delete this.avoFunctionsEventsParams[
|
|
54
|
-
delete this.manualEventsParams[
|
|
61
|
+
delete this.avoFunctionsEventsParams[key];
|
|
62
|
+
delete this.manualEventsParams[key];
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
return result;
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
private lookForEventIn(
|
|
61
|
-
|
|
69
|
+
key: string,
|
|
62
70
|
params: { [propName: string]: any },
|
|
63
|
-
eventsStorage: { [
|
|
71
|
+
eventsStorage: { [key: string]: { [propName: string]: any } }
|
|
64
72
|
): boolean {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (otherParams && deepEquals(params, otherParams)) {
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
+
if (eventsStorage.hasOwnProperty(key)) {
|
|
74
|
+
const otherParams = eventsStorage[key];
|
|
75
|
+
if (otherParams && deepEquals(params, otherParams)) {
|
|
76
|
+
return true;
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
return false;
|
|
@@ -116,9 +120,9 @@ export class AvoDeduplicator {
|
|
|
116
120
|
if (this.avoFunctionsEvents.hasOwnProperty(time)) {
|
|
117
121
|
const timestamp = Number(time) || 0;
|
|
118
122
|
if (now - timestamp > this.msToConsiderOld) {
|
|
119
|
-
const
|
|
123
|
+
const key = this.avoFunctionsEvents[time];
|
|
120
124
|
delete this.avoFunctionsEvents[time];
|
|
121
|
-
delete this.avoFunctionsEventsParams[
|
|
125
|
+
delete this.avoFunctionsEventsParams[key];
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
}
|
|
@@ -127,9 +131,9 @@ export class AvoDeduplicator {
|
|
|
127
131
|
if (this.manualEvents.hasOwnProperty(time)) {
|
|
128
132
|
const timestamp = Number(time) || 0;
|
|
129
133
|
if (now - timestamp > this.msToConsiderOld) {
|
|
130
|
-
const
|
|
134
|
+
const key = this.manualEvents[time];
|
|
131
135
|
delete this.manualEvents[time];
|
|
132
|
-
delete this.manualEventsParams[
|
|
136
|
+
delete this.manualEventsParams[key];
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
}
|
|
@@ -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
|
+
}
|