avo-inspector 2.2.0 → 2.2.2-alpha.10
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/settings.local.json +18 -1
- package/CHANGELOG.md +34 -1
- package/README.md +130 -0
- package/bin/avo-inspector.js +149 -0
- package/dist/AvoBatcher.d.ts +4 -11
- package/dist/AvoEncryption.d.ts +33 -0
- package/dist/AvoInspector.d.ts +40 -6
- package/dist/AvoNetworkCallsHandler.d.ts +50 -15
- package/dist/AvoSchemaParser.d.ts +12 -5
- package/dist/AvoStreamId.d.ts +58 -0
- package/dist/eventSpec/AvoEventSpecCache.d.ts +74 -0
- package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +151 -0
- package/dist/eventSpec/AvoEventSpecFetcher.d.ts +60 -0
- package/dist/eventSpec/EventValidator.d.ts +34 -0
- package/dist/eventSpec/cache.d.ts +73 -0
- package/dist/eventSpec/fetcher.d.ts +52 -0
- package/dist/eventSpec/index.d.ts +4 -0
- package/dist/eventSpec/types.d.ts +74 -0
- package/dist/index.js +2 -1
- package/dist/index.js.LICENSE.txt +5 -0
- package/jest.config.js +6 -0
- package/package.json +7 -1
- package/rescript/AvoInspector.res +1 -0
- package/rescript/AvoInspector.resi +1 -1
- package/webpack.browserConfig.js +1 -1
- package/webpack.config.js +1 -1
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(npm test)"
|
|
4
|
+
"Bash(npm test)",
|
|
5
|
+
"Bash(npm test:*)",
|
|
6
|
+
"Bash(npm run build:*)",
|
|
7
|
+
"Bash(yarn typecheck:*)",
|
|
8
|
+
"Bash(yarn test:*)",
|
|
9
|
+
"Bash(yarn build)",
|
|
10
|
+
"WebSearch",
|
|
11
|
+
"Bash(node -e:*)",
|
|
12
|
+
"Bash(git add:*)",
|
|
13
|
+
"mcp__linear__list_issues",
|
|
14
|
+
"Bash(npx ts-node:*)",
|
|
15
|
+
"Bash(git -C /Users/bjorn/code/js-avo-inspector show db2db09 -- src/eventSpec/AvoEventSpecFetchTypes.ts)",
|
|
16
|
+
"Bash(git -C /Users/bjorn/code/js-avo-inspector show db2db09 -- src/eventSpec/AvoEventSpecFetcher.ts)",
|
|
17
|
+
"Bash(git -C /Users/bjorn/code/js-avo-inspector log --all --oneline --since=\"2025-12-03\")",
|
|
18
|
+
"Bash(git -C /Users/bjorn/code/js-avo-inspector branch:*)",
|
|
19
|
+
"Bash(git -C /Users/bjorn/code/js-avo-inspector log --all --oneline -20)",
|
|
20
|
+
"Bash(git -C /Users/bjorn/code/js-avo-inspector show db2db09 --stat)",
|
|
21
|
+
"Bash(git -C /Users/bjorn/code/js-avo-inspector diff --stat)"
|
|
5
22
|
],
|
|
6
23
|
"deny": [],
|
|
7
24
|
"ask": []
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.2.1-alpha] - 2025-11-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **ECC Property Value Encryption**: Optional `publicEncryptionKey` parameter on SDK initialization enables zero-knowledge encryption of property values in dev/staging environments using ECIES (Elliptic Curve Integrated Encryption Scheme). Avo never has access to the private key, ensuring complete data privacy.
|
|
13
|
+
- Uses secp256k1 curve with AES-256-GCM for hybrid encryption
|
|
14
|
+
- Only encrypts in dev/staging environments (production sends schema only)
|
|
15
|
+
- Adds optional `encryptedPropertyValue` field to event schema
|
|
16
|
+
- CLI tool for key generation: `npx avo-inspector generate-keys`
|
|
17
|
+
- Note: Key generation and decryption are not exported from SDK (use CLI tool for keys, decryption happens in Avo's dashboard)
|
|
18
|
+
|
|
19
|
+
- **EventSpec Fetching**: Automatically fetches and caches event specifications from Avo API in dev/staging environments
|
|
20
|
+
- Non-blocking async API calls
|
|
21
|
+
- In-memory caching for performance
|
|
22
|
+
- NOT called in production (prod remains simple and fast)
|
|
23
|
+
- Foundation for Phase 2: event validation
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Production environment now optimized: no property values, no encryption, no EventSpec API calls
|
|
28
|
+
- Dev/Staging environments now support rich debugging with encrypted values and event specs
|
|
29
|
+
|
|
30
|
+
### Dependencies
|
|
31
|
+
|
|
32
|
+
- Added `eciesjs` ^0.4.11 for ECC/ECIES encryption
|
|
33
|
+
|
|
1
34
|
## 2.2.0
|
|
2
35
|
|
|
3
36
|
- Add anonymous ID support to track events without user identification
|
|
@@ -22,4 +55,4 @@ Security update
|
|
|
22
55
|
|
|
23
56
|
## 1.3.0
|
|
24
57
|
|
|
25
|
-
Multiple Avo Inspector instances support, with the optional `suffix` constructor parameter
|
|
58
|
+
Multiple Avo Inspector instances support, with the optional `suffix` constructor parameter
|
package/README.md
CHANGED
|
@@ -98,6 +98,136 @@ let schema = inspector.extractSchema({
|
|
|
98
98
|
|
|
99
99
|
You can experiment with this method to see how more complex schemas look, for example with nested lists and objects.
|
|
100
100
|
|
|
101
|
+
# Property Value Encryption (Dev/Staging Only)
|
|
102
|
+
|
|
103
|
+
Inspector supports encrypting property values in development and staging environments. This allows you to see actual property values in Avo's debugging tools without exposing sensitive data, since only you hold the private decryption key.
|
|
104
|
+
|
|
105
|
+
## Key Features
|
|
106
|
+
|
|
107
|
+
- Uses ECC (Elliptic Curve Cryptography) with secp256k1 for strong security
|
|
108
|
+
- Zero-knowledge architecture: Avo never has access to your private key
|
|
109
|
+
- No message size limitations
|
|
110
|
+
- Only active in dev/staging environments (production is unaffected)
|
|
111
|
+
|
|
112
|
+
## Generating Keys
|
|
113
|
+
|
|
114
|
+
Use the built-in CLI tool to generate a key pair:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx avo-inspector generate-keys
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This will output:
|
|
121
|
+
- **Public key**: Pass this to the SDK to enable encryption
|
|
122
|
+
- **Private key**: Save this externally (you'll use it to decrypt values in Avo's dashboard)
|
|
123
|
+
|
|
124
|
+
## Using Encryption
|
|
125
|
+
|
|
126
|
+
Pass the `publicEncryptionKey` parameter when initializing Inspector:
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
import * as Inspector from "avo-inspector";
|
|
130
|
+
|
|
131
|
+
let inspector = new Inspector.AvoInspector({
|
|
132
|
+
apiKey: "your api key",
|
|
133
|
+
env: Inspector.AvoInspectorEnv.Dev,
|
|
134
|
+
version: "1.0.0",
|
|
135
|
+
publicEncryptionKey: "your-public-key-hex-string" // Enable encryption
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Note:** The public key is not a secret - you can hardcode it, store it in `.env`, or configure it however you prefer. The SDK only uses the public key to encrypt values before sending them to Avo.
|
|
140
|
+
|
|
141
|
+
**Private key:** Save it securely (password manager, secure notes). Never expose it to the SDK. You only need it when viewing encrypted values in Avo's dashboard.
|
|
142
|
+
|
|
143
|
+
When encryption is enabled:
|
|
144
|
+
- Property values are encrypted before being sent to Avo
|
|
145
|
+
- You can decrypt them in Avo's dashboard using your private key
|
|
146
|
+
- Works with all data types: strings, numbers, booleans, objects, arrays, null
|
|
147
|
+
- Handles large payloads (1KB+) without issues
|
|
148
|
+
|
|
149
|
+
# Client-Side Validation (Dev/Staging Only)
|
|
150
|
+
|
|
151
|
+
When initialized with a `publicEncryptionKey` in dev or staging environments, Inspector performs client-side validation of your events against your Avo Tracking Plan.
|
|
152
|
+
|
|
153
|
+
## How It Works
|
|
154
|
+
|
|
155
|
+
1. **Event Spec Fetching**: When you track an event, Inspector fetches the event specification from Avo's backend (results are cached for performance).
|
|
156
|
+
|
|
157
|
+
2. **Event Matching**: Inspector matches your event to the closest event in your tracking plan, considering event names, mapped names, and variant-specific properties.
|
|
158
|
+
|
|
159
|
+
3. **Property Validation**: Your event properties are validated against the spec rules:
|
|
160
|
+
- Required properties are present
|
|
161
|
+
- Property types match (string, int, float, boolean, object, list)
|
|
162
|
+
- Numeric values are within min/max bounds
|
|
163
|
+
- String values match regex patterns
|
|
164
|
+
- Enum values are in the allowed list
|
|
165
|
+
- Pinned values match exactly (variant-specific fixed values)
|
|
166
|
+
- No unexpected properties are sent
|
|
167
|
+
|
|
168
|
+
4. **Immediate Reporting**: Validated events bypass batching and are sent immediately to Inspector, ensuring you get real-time feedback.
|
|
169
|
+
|
|
170
|
+
## Validation Errors
|
|
171
|
+
|
|
172
|
+
If logging is enabled, validation errors are logged to the console:
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
inspector.enableLogging(true);
|
|
176
|
+
|
|
177
|
+
// If "User Signed Up" requires an "email" property:
|
|
178
|
+
inspector.trackSchemaFromEvent("User Signed Up", { name: "John" });
|
|
179
|
+
// Console: [Avo Inspector] Validation errors for event "User Signed Up": [{ code: "RequiredMissing", propertyName: "email" }]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Important: trackSchema Does Not Validate
|
|
183
|
+
|
|
184
|
+
**Note:** Client-side validation only works with `trackSchemaFromEvent`, which has access to actual property values needed for validation.
|
|
185
|
+
|
|
186
|
+
The `trackSchema` method only sends pre-extracted schemas and **does not perform client-side validation** - it goes through the normal batching flow.
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
// ✅ Validates against tracking plan (when publicEncryptionKey is provided)
|
|
190
|
+
inspector.trackSchemaFromEvent("Event Name", { prop: "value" });
|
|
191
|
+
|
|
192
|
+
// ❌ Does NOT validate - only sends schema, uses batching
|
|
193
|
+
inspector.trackSchema("Event Name", [{ propertyName: "prop", propertyType: "string" }]);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Accessing Validation Results
|
|
197
|
+
|
|
198
|
+
`trackSchemaFromEvent` returns a `Promise<EventProperty[]>` that resolves with the validated properties. You can use this in two ways:
|
|
199
|
+
|
|
200
|
+
### Fire-and-Forget (Default)
|
|
201
|
+
|
|
202
|
+
Call without `await` for non-blocking behavior. The event is tracked asynchronously and validation happens in the background:
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// Non-blocking - validation runs in background
|
|
206
|
+
inspector.trackSchemaFromEvent("Event Name", { prop: "value" });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Await for Validation Results
|
|
210
|
+
|
|
211
|
+
If you need to access validation results (e.g., for testing or debugging), you can `await` the call:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
// Blocking - wait for validation to complete
|
|
215
|
+
const validatedProperties = await inspector.trackSchemaFromEvent("Event Name", {
|
|
216
|
+
email: "user@example.com",
|
|
217
|
+
age: 25
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Each property includes validation results:
|
|
221
|
+
// {
|
|
222
|
+
// propertyName: "email",
|
|
223
|
+
// propertyType: "string",
|
|
224
|
+
// failedEventIds: [], // Event IDs where this property failed validation
|
|
225
|
+
// passedEventIds: ["abc123"] // Event IDs where this property passed validation
|
|
226
|
+
// }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Note:** Awaiting will block until the event spec is fetched (if not cached) and validation completes. For production use, fire-and-forget is recommended to avoid impacting your application's performance.
|
|
230
|
+
|
|
101
231
|
# Batching control
|
|
102
232
|
|
|
103
233
|
In order to ensure our SDK doesn't have a large impact on performance or battery life it supports event schemas batching.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Use eciesjs directly for ECC key generation
|
|
4
|
+
const { PrivateKey } = require('eciesjs');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
function generateKeyPair() {
|
|
9
|
+
// Generate a new random private key (32 bytes / 256 bits)
|
|
10
|
+
const privateKey = new PrivateKey();
|
|
11
|
+
|
|
12
|
+
// Derive the public key from the private key
|
|
13
|
+
const publicKey = privateKey.publicKey;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
publicKey: publicKey.toHex(),
|
|
17
|
+
privateKey: privateKey.toHex()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const command = process.argv[2];
|
|
22
|
+
const outputFlag = process.argv[3];
|
|
23
|
+
|
|
24
|
+
function showHelp() {
|
|
25
|
+
console.log(`
|
|
26
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
27
|
+
║ AVO INSPECTOR CLI ║
|
|
28
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
npx avo-inspector <command> [options]
|
|
32
|
+
|
|
33
|
+
Commands:
|
|
34
|
+
generate-keys Generate ECC key pair for property encryption
|
|
35
|
+
help Show this help message
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--output, -o Write keys to files instead of stdout
|
|
39
|
+
Creates: avo-public.key and avo-private.key
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
npx avo-inspector generate-keys # Print keys to console
|
|
43
|
+
npx avo-inspector generate-keys --output # Write keys to files
|
|
44
|
+
npx avo-inspector generate-keys -o # Write keys to files (short form)
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function generateKeys(writeToFiles = false) {
|
|
49
|
+
console.log(`
|
|
50
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
51
|
+
║ Generating ECC Key Pair (secp256k1)... ║
|
|
52
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
53
|
+
`);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { publicKey, privateKey } = generateKeyPair();
|
|
57
|
+
|
|
58
|
+
if (writeToFiles) {
|
|
59
|
+
// Write keys to files instead of printing to console
|
|
60
|
+
const publicKeyPath = path.join(process.cwd(), 'avo-public.key');
|
|
61
|
+
const privateKeyPath = path.join(process.cwd(), 'avo-private.key');
|
|
62
|
+
|
|
63
|
+
// Check if files already exist
|
|
64
|
+
if (fs.existsSync(publicKeyPath) || fs.existsSync(privateKeyPath)) {
|
|
65
|
+
console.error('❌ Error: Key files already exist in current directory.');
|
|
66
|
+
console.error(' Delete avo-public.key and avo-private.key first, or use a different directory.\n');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Write public key
|
|
71
|
+
fs.writeFileSync(publicKeyPath, publicKey + '\n', { mode: 0o644 });
|
|
72
|
+
console.log(`✅ Public key written to: ${publicKeyPath}`);
|
|
73
|
+
|
|
74
|
+
// Write private key with restrictive permissions (owner read/write only)
|
|
75
|
+
fs.writeFileSync(privateKeyPath, privateKey + '\n', { mode: 0o600 });
|
|
76
|
+
console.log(`✅ Private key written to: ${privateKeyPath}`);
|
|
77
|
+
|
|
78
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
79
|
+
console.log('⚠️ IMPORTANT: Add avo-private.key to your .gitignore!\n');
|
|
80
|
+
console.log(' echo "avo-private.key" >> .gitignore\n');
|
|
81
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
82
|
+
console.log('📝 Next Steps:\n');
|
|
83
|
+
console.log('1. Use the PUBLIC KEY in your SDK initialization:\n');
|
|
84
|
+
console.log(' const inspector = new AvoInspector({');
|
|
85
|
+
console.log(' apiKey: "your-api-key",');
|
|
86
|
+
console.log(' env: AvoInspectorEnv.Dev,');
|
|
87
|
+
console.log(' version: "1.0.0",');
|
|
88
|
+
console.log(' publicKey: fs.readFileSync("avo-public.key", "utf8").trim()');
|
|
89
|
+
console.log(' });\n');
|
|
90
|
+
console.log('2. Store avo-private.key securely (password manager, vault, etc.)');
|
|
91
|
+
console.log(' - The SDK never uses the private key');
|
|
92
|
+
console.log(' - You only need it to decrypt values in Avo\'s dashboard\n');
|
|
93
|
+
} else {
|
|
94
|
+
// Print keys to console (original behavior)
|
|
95
|
+
console.log('✅ Key pair generated successfully!\n');
|
|
96
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
97
|
+
console.log('📋 PUBLIC KEY (safe to share with Avo SDK):\n');
|
|
98
|
+
console.log(publicKey);
|
|
99
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
100
|
+
console.log('🔐 PRIVATE KEY (⚠️ KEEP SECURE - DO NOT COMMIT TO GIT):\n');
|
|
101
|
+
console.log(privateKey);
|
|
102
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
103
|
+
console.log('💡 TIP: Use --output flag to write keys to files instead:\n');
|
|
104
|
+
console.log(' npx avo-inspector generate-keys --output\n');
|
|
105
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
106
|
+
console.log('📝 Next Steps:\n');
|
|
107
|
+
console.log('1. Use the PUBLIC KEY in your SDK initialization:\n');
|
|
108
|
+
console.log(' const inspector = new AvoInspector({');
|
|
109
|
+
console.log(' apiKey: "your-api-key",');
|
|
110
|
+
console.log(' env: AvoInspectorEnv.Dev,');
|
|
111
|
+
console.log(' version: "1.0.0",');
|
|
112
|
+
console.log(' publicKey: "<paste public key above>"');
|
|
113
|
+
console.log(' });\n');
|
|
114
|
+
console.log(' 💡 The public key is not a secret - you can hardcode it or use .env\n');
|
|
115
|
+
console.log('2. Save the PRIVATE KEY externally (password manager, secure notes)');
|
|
116
|
+
console.log(' - The SDK never uses the private key');
|
|
117
|
+
console.log(' - You only need it to decrypt values in Avo\'s dashboard\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
121
|
+
console.log('ℹ️ Note: Property encryption only works in dev/staging environments.');
|
|
122
|
+
console.log(' Production data is never encrypted for performance.\n');
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('❌ Error generating keys:', error.message);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Main command router
|
|
130
|
+
switch (command) {
|
|
131
|
+
case 'generate-keys':
|
|
132
|
+
const writeToFiles = outputFlag === '--output' || outputFlag === '-o';
|
|
133
|
+
generateKeys(writeToFiles);
|
|
134
|
+
break;
|
|
135
|
+
case 'help':
|
|
136
|
+
case '--help':
|
|
137
|
+
case '-h':
|
|
138
|
+
showHelp();
|
|
139
|
+
break;
|
|
140
|
+
case undefined:
|
|
141
|
+
console.error('❌ No command specified\n');
|
|
142
|
+
showHelp();
|
|
143
|
+
process.exit(1);
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
console.error(`❌ Unknown command: ${command}\n`);
|
|
147
|
+
showHelp();
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
package/dist/AvoBatcher.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { type AvoNetworkCallsHandler } from "./AvoNetworkCallsHandler";
|
|
1
|
+
import { type AvoNetworkCallsHandler, type EventProperty } from "./AvoNetworkCallsHandler";
|
|
2
|
+
import type { EventSpecMetadata } from "./eventSpec/AvoEventSpecFetchTypes";
|
|
2
3
|
export interface AvoBatcherType {
|
|
3
4
|
handleSessionStarted: () => void;
|
|
4
|
-
handleTrackSchema: (eventName: string, schema:
|
|
5
|
-
propertyName: string;
|
|
6
|
-
propertyType: string;
|
|
7
|
-
children?: any;
|
|
8
|
-
}>, eventId: string | null, eventHash: string | null) => void;
|
|
5
|
+
handleTrackSchema: (eventName: string, schema: EventProperty[], eventId: string | null, eventHash: string | null, eventSpecMetadata?: EventSpecMetadata) => void;
|
|
9
6
|
}
|
|
10
7
|
export declare class AvoBatcher implements AvoBatcherType {
|
|
11
8
|
private events;
|
|
@@ -13,11 +10,7 @@ export declare class AvoBatcher implements AvoBatcherType {
|
|
|
13
10
|
private readonly networkCallsHandler;
|
|
14
11
|
constructor(networkCallsHandler: AvoNetworkCallsHandler);
|
|
15
12
|
handleSessionStarted(): void;
|
|
16
|
-
handleTrackSchema(eventName: string, schema:
|
|
17
|
-
propertyName: string;
|
|
18
|
-
propertyType: string;
|
|
19
|
-
children?: any;
|
|
20
|
-
}>, eventId: string | null, eventHash: string | null): void;
|
|
13
|
+
handleTrackSchema(eventName: string, schema: EventProperty[], eventId: string | null, eventHash: string | null, eventSpecMetadata?: EventSpecMetadata): void;
|
|
21
14
|
private checkIfBatchNeedsToBeSent;
|
|
22
15
|
private saveEvents;
|
|
23
16
|
static get cacheKey(): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a new ECC key pair for encryption/decryption.
|
|
3
|
+
* Uses secp256k1 curve (P-256 family) which provides 128-bit security.
|
|
4
|
+
*
|
|
5
|
+
* @returns An object containing the private and public keys as hex strings
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateKeyPair(): {
|
|
8
|
+
privateKey: string;
|
|
9
|
+
publicKey: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Encrypts a value using ECC public key encryption (ECIES).
|
|
13
|
+
* The encrypted output can only be decrypted by the client using their private key.
|
|
14
|
+
* This ensures that Avo cannot decrypt the values on the backend.
|
|
15
|
+
*
|
|
16
|
+
* ECIES uses hybrid encryption (ECDH + AES-256-GCM) which provides:
|
|
17
|
+
* - No message size limitations
|
|
18
|
+
* - Fast encryption even for large values
|
|
19
|
+
* - Strong authentication via GCM
|
|
20
|
+
*
|
|
21
|
+
* @param value - The value to encrypt (any type - will be JSON stringified)
|
|
22
|
+
* @param publicKey - The ECC public key in hex format provided by the client
|
|
23
|
+
* @returns Base64-encoded encrypted string that can only be decrypted with the private key
|
|
24
|
+
*/
|
|
25
|
+
export declare function encryptValue(value: any, publicKey: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Decrypts a value that was encrypted with encryptValue.
|
|
28
|
+
*
|
|
29
|
+
* @param encryptedValue - The base64-encoded encrypted string
|
|
30
|
+
* @param privateKey - The ECC private key in hex format
|
|
31
|
+
* @returns The original decrypted value (parsed from JSON)
|
|
32
|
+
*/
|
|
33
|
+
export declare function decryptValue(encryptedValue: string, privateKey: string): any;
|
package/dist/AvoInspector.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AvoInspectorEnvValueType } from "./AvoInspectorEnv";
|
|
2
2
|
import { AvoBatcher } from "./AvoBatcher";
|
|
3
|
+
import { type EventProperty } from "./AvoNetworkCallsHandler";
|
|
3
4
|
import { AvoStorage } from "./AvoStorage";
|
|
4
5
|
import { AvoDeduplicator } from "./AvoDeduplicator";
|
|
5
6
|
export declare class AvoInspector {
|
|
@@ -8,6 +9,11 @@ export declare class AvoInspector {
|
|
|
8
9
|
avoDeduplicator: AvoDeduplicator;
|
|
9
10
|
apiKey: string;
|
|
10
11
|
version: string;
|
|
12
|
+
private avoNetworkCallsHandler;
|
|
13
|
+
private publicEncryptionKey?;
|
|
14
|
+
private streamId?;
|
|
15
|
+
private eventSpecCache?;
|
|
16
|
+
private eventSpecFetcher?;
|
|
11
17
|
static avoStorage: AvoStorage;
|
|
12
18
|
private static _batchSize;
|
|
13
19
|
static get batchSize(): number;
|
|
@@ -26,25 +32,53 @@ export declare class AvoInspector {
|
|
|
26
32
|
version: string;
|
|
27
33
|
appName?: string;
|
|
28
34
|
suffix?: string;
|
|
35
|
+
publicEncryptionKey?: string;
|
|
29
36
|
});
|
|
30
|
-
trackSchemaFromEvent(eventName: string, eventProperties: Record<string, any>):
|
|
31
|
-
propertyName: string;
|
|
32
|
-
propertyType: string;
|
|
33
|
-
children?: any;
|
|
34
|
-
}>;
|
|
37
|
+
trackSchemaFromEvent(eventName: string, eventProperties: Record<string, any>): Promise<EventProperty[]>;
|
|
35
38
|
private _avoFunctionTrackSchemaFromEvent;
|
|
36
39
|
trackSchema(eventName: string, eventSchema: Array<{
|
|
37
40
|
propertyName: string;
|
|
38
41
|
propertyType: string;
|
|
42
|
+
encryptedPropertyValue?: string;
|
|
39
43
|
children?: any;
|
|
40
|
-
}>): void
|
|
44
|
+
}>): Promise<void>;
|
|
41
45
|
private trackSchemaInternal;
|
|
42
46
|
enableLogging(enable: boolean): void;
|
|
43
47
|
extractSchema(eventProperties: Record<string, any>, shouldLogIfEnabled?: boolean): Array<{
|
|
44
48
|
propertyName: string;
|
|
45
49
|
propertyType: string;
|
|
50
|
+
encryptedPropertyValue?: string;
|
|
46
51
|
children?: any;
|
|
47
52
|
}>;
|
|
48
53
|
setBatchSize(newBatchSize: number): void;
|
|
49
54
|
setBatchFlushSeconds(newBatchFlushSeconds: number): void;
|
|
55
|
+
/**
|
|
56
|
+
* Fetches the event spec if spec fetching is enabled.
|
|
57
|
+
* Used by trackSchema when we don't have raw properties to validate.
|
|
58
|
+
*
|
|
59
|
+
* Note: EventSpec fetching only happens in dev/staging environments.
|
|
60
|
+
*/
|
|
61
|
+
private fetchEventSpecIfNeeded;
|
|
62
|
+
/**
|
|
63
|
+
* Fetches event spec and validates the event against it.
|
|
64
|
+
* Returns ValidationResult if spec is available, null otherwise.
|
|
65
|
+
*
|
|
66
|
+
* Note: EventSpec fetching and validation only happens in dev/staging environments.
|
|
67
|
+
*/
|
|
68
|
+
private fetchAndValidateEvent;
|
|
69
|
+
/**
|
|
70
|
+
* Merges validation results into the event schema.
|
|
71
|
+
* Adds failedEventIds or passedEventIds to each property based on validation.
|
|
72
|
+
* Recursively merges validation results for nested children.
|
|
73
|
+
*/
|
|
74
|
+
private mergeValidationResults;
|
|
75
|
+
/**
|
|
76
|
+
* Merges validation result into a single property, recursively handling children.
|
|
77
|
+
*/
|
|
78
|
+
private mergePropertyValidation;
|
|
79
|
+
/**
|
|
80
|
+
* Sends an event immediately with validation data (bypasses batching).
|
|
81
|
+
* Logs validation info if shouldLog is true.
|
|
82
|
+
*/
|
|
83
|
+
private sendEventWithValidation;
|
|
50
84
|
}
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
import type { EventSpecMetadata } from "./eventSpec/AvoEventSpecFetchTypes";
|
|
2
|
+
/**
|
|
3
|
+
* Recursive type for schema children.
|
|
4
|
+
* - For object properties: array of EventProperty objects
|
|
5
|
+
* - For list properties: array of strings (primitive types) or nested structures
|
|
6
|
+
*/
|
|
7
|
+
export type SchemaChild = string | EventProperty | SchemaChild[];
|
|
8
|
+
/**
|
|
9
|
+
* Property schema with optional validation results.
|
|
10
|
+
*/
|
|
11
|
+
export interface EventProperty {
|
|
12
|
+
propertyName: string;
|
|
13
|
+
propertyType: string;
|
|
14
|
+
encryptedPropertyValue?: string;
|
|
15
|
+
children?: SchemaChild[];
|
|
16
|
+
/** Event/variant IDs that FAILED validation (present if smaller or equal to passed) */
|
|
17
|
+
failedEventIds?: string[];
|
|
18
|
+
/** Event/variant IDs that PASSED validation (present if smaller than failed) */
|
|
19
|
+
passedEventIds?: string[];
|
|
20
|
+
}
|
|
1
21
|
export interface BaseBody {
|
|
2
22
|
apiKey: string;
|
|
3
23
|
appName: string;
|
|
@@ -9,23 +29,27 @@ export interface BaseBody {
|
|
|
9
29
|
trackingId: string;
|
|
10
30
|
createdAt: string;
|
|
11
31
|
sessionId: string;
|
|
12
|
-
|
|
32
|
+
streamId: string;
|
|
13
33
|
samplingRate: number;
|
|
34
|
+
/** Event spec metadata from EventSpecResponse (moved from EventSchemaBody) */
|
|
35
|
+
eventSpecMetadata?: EventSpecMetadata;
|
|
36
|
+
/** RSA public encryption key - allows Inspector to validate encrypted values against tracking plan */
|
|
37
|
+
publicEncryptionKey?: string;
|
|
14
38
|
}
|
|
15
39
|
export interface SessionStartedBody extends BaseBody {
|
|
16
40
|
type: "sessionStarted";
|
|
17
41
|
}
|
|
18
42
|
export interface EventSchemaBody extends BaseBody {
|
|
19
43
|
type: "event";
|
|
20
|
-
|
|
21
|
-
eventProperties: Array<{
|
|
22
|
-
propertyName: string;
|
|
23
|
-
propertyType: string;
|
|
24
|
-
children?: any;
|
|
25
|
-
}>;
|
|
26
|
-
avoFunction: boolean;
|
|
44
|
+
/** ID of the base event from spec (null if no spec available) */
|
|
27
45
|
eventId: string | null;
|
|
46
|
+
/** Name seen in code */
|
|
47
|
+
eventName?: string;
|
|
48
|
+
eventProperties: EventProperty[];
|
|
49
|
+
avoFunction: boolean;
|
|
28
50
|
eventHash: string | null;
|
|
51
|
+
/** Branch ID from getEventSpec response when value validation was performed */
|
|
52
|
+
validatedBranchId?: string;
|
|
29
53
|
}
|
|
30
54
|
export declare class AvoNetworkCallsHandler {
|
|
31
55
|
private readonly apiKey;
|
|
@@ -33,17 +57,28 @@ export declare class AvoNetworkCallsHandler {
|
|
|
33
57
|
private readonly appName;
|
|
34
58
|
private readonly appVersion;
|
|
35
59
|
private readonly libVersion;
|
|
60
|
+
private readonly publicEncryptionKey?;
|
|
36
61
|
private samplingRate;
|
|
37
62
|
private sending;
|
|
38
63
|
private static readonly trackingEndpoint;
|
|
39
|
-
constructor(apiKey: string, envName: string, appName: string, appVersion: string, libVersion: string);
|
|
64
|
+
constructor(apiKey: string, envName: string, appName: string, appVersion: string, libVersion: string, publicEncryptionKey?: string);
|
|
40
65
|
callInspectorWithBatchBody(inEvents: Array<SessionStartedBody | EventSchemaBody>, onCompleted: (error: Error | null) => any): void;
|
|
41
|
-
private
|
|
66
|
+
private fixStreamIds;
|
|
42
67
|
bodyForSessionStartedCall(): SessionStartedBody;
|
|
43
|
-
bodyForEventSchemaCall(eventName: string, eventProperties:
|
|
44
|
-
propertyName: string;
|
|
45
|
-
propertyType: string;
|
|
46
|
-
children?: any;
|
|
47
|
-
}>, eventId: string | null, eventHash: string | null): EventSchemaBody;
|
|
68
|
+
bodyForEventSchemaCall(eventName: string, eventProperties: EventProperty[], eventId: string | null, eventHash: string | null, eventSpecMetadata?: EventSpecMetadata, validatedBranchId?: string): EventSchemaBody;
|
|
48
69
|
private createBaseCallBody;
|
|
70
|
+
/**
|
|
71
|
+
* Calls Inspector API immediately with a single event (bypasses batching).
|
|
72
|
+
* Used when event spec validation is available.
|
|
73
|
+
* Note: Does not drop due to sampling - validated events are always sent.
|
|
74
|
+
*/
|
|
75
|
+
callInspectorImmediately(eventBody: EventSchemaBody, onCompleted: (error: Error | null) => any): void;
|
|
76
|
+
/**
|
|
77
|
+
* Check if event should be dropped based on sampling rate.
|
|
78
|
+
*/
|
|
79
|
+
private shouldDropBySampling;
|
|
80
|
+
/**
|
|
81
|
+
* Core Inspector API call logic shared by batch and immediate calls.
|
|
82
|
+
*/
|
|
83
|
+
private callInspectorApi;
|
|
49
84
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import type { EventProperty } from "./AvoNetworkCallsHandler";
|
|
1
2
|
export declare class AvoSchemaParser {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Returns true only if we have a valid encryption key and can send encrypted values.
|
|
5
|
+
* If no key is present, returns false and no property values will be sent.
|
|
6
|
+
*/
|
|
7
|
+
private static canSendEncryptedValues;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the encrypted property value if encryption is enabled, otherwise undefined.
|
|
10
|
+
* Never returns unencrypted values - only encrypted or nothing.
|
|
11
|
+
*/
|
|
12
|
+
private static getEncryptedPropertyValueIfEnabled;
|
|
13
|
+
static extractSchema(eventProperties: Record<string, any>, publicEncryptionKey?: string, env?: string): EventProperty[];
|
|
7
14
|
private static removeDuplicates;
|
|
8
15
|
private static getPropValueType;
|
|
9
16
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AvoStreamId manages an ephemeral stream identifier.
|
|
3
|
+
*
|
|
4
|
+
* The stream ID is generated and stored persistently, but resets after:
|
|
5
|
+
* - 4 hours of total age AND
|
|
6
|
+
* - 2 hours of idle time (no activity)
|
|
7
|
+
*
|
|
8
|
+
* This ensures the ID is truly anonymous by not persisting indefinitely,
|
|
9
|
+
* while still maintaining session continuity during active usage.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AvoStreamId {
|
|
12
|
+
private static _streamId;
|
|
13
|
+
private static _createdAt;
|
|
14
|
+
private static _lastActivityAt;
|
|
15
|
+
private static readonly MAX_AGE_MS;
|
|
16
|
+
private static readonly IDLE_THRESHOLD_MS;
|
|
17
|
+
private static readonly STREAM_ID_KEY;
|
|
18
|
+
private static readonly CREATED_AT_KEY;
|
|
19
|
+
private static readonly LAST_ACTIVITY_KEY;
|
|
20
|
+
/**
|
|
21
|
+
* Get the stream ID. If it doesn't exist or has expired, generates a new one.
|
|
22
|
+
* Returns "unknown" if storage is not initialized.
|
|
23
|
+
*/
|
|
24
|
+
static get streamId(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Check if the stream ID should be reset based on age and idle time.
|
|
27
|
+
*/
|
|
28
|
+
private static shouldReset;
|
|
29
|
+
/**
|
|
30
|
+
* Load stream ID and timestamps from storage into cache.
|
|
31
|
+
*/
|
|
32
|
+
private static loadFromStorage;
|
|
33
|
+
/**
|
|
34
|
+
* Generate a new stream ID and store it with timestamps.
|
|
35
|
+
*/
|
|
36
|
+
private static generateAndStoreNew;
|
|
37
|
+
/**
|
|
38
|
+
* Update the last activity timestamp.
|
|
39
|
+
*/
|
|
40
|
+
private static updateLastActivity;
|
|
41
|
+
/**
|
|
42
|
+
* The storage key used to persist the stream ID.
|
|
43
|
+
*/
|
|
44
|
+
static get storageKey(): string;
|
|
45
|
+
/**
|
|
46
|
+
* The storage key for created at timestamp.
|
|
47
|
+
*/
|
|
48
|
+
static get createdAtKey(): string;
|
|
49
|
+
/**
|
|
50
|
+
* The storage key for last activity timestamp.
|
|
51
|
+
*/
|
|
52
|
+
static get lastActivityKey(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Clear the cached stream ID. The next access will reload from storage.
|
|
55
|
+
* This is primarily useful for testing.
|
|
56
|
+
*/
|
|
57
|
+
static clearCache(): void;
|
|
58
|
+
}
|