avo-inspector 3.1.0-alpha.2 → 3.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/README.md +6 -13
- package/dist/AvoBatcher.js +52 -44
- package/dist/AvoDeduplicator.js +46 -45
- package/dist/AvoEncryption.js +147 -253
- package/dist/AvoInspector.js +295 -334
- package/dist/AvoNetworkCallsHandler.d.ts +6 -0
- package/dist/AvoNetworkCallsHandler.js +180 -136
- package/dist/AvoSchemaParser.js +34 -71
- package/dist/AvoStreamId.js +67 -129
- package/dist/__mocks__/xhr.js +14 -0
- package/dist/__mocks__/xhrError.js +14 -0
- package/dist/__tests__/AvoEncryption.d.ts +1 -0
- package/dist/__tests__/AvoEncryption.js +272 -0
- package/dist/__tests__/AvoEncryptionIntegration.d.ts +1 -0
- package/dist/__tests__/AvoEncryptionIntegration.js +191 -0
- package/dist/__tests__/AvoEventSpecFetcher.d.ts +1 -0
- package/dist/__tests__/AvoEventSpecFetcher.js +377 -0
- package/dist/__tests__/AvoInspector.d.ts +1 -0
- package/dist/__tests__/AvoInspector.js +335 -0
- package/dist/__tests__/Batcher.d.ts +1 -0
- package/dist/__tests__/Batcher.js +214 -0
- package/dist/__tests__/BatcherPublic.d.ts +1 -0
- package/dist/__tests__/BatcherPublic.js +139 -0
- package/dist/__tests__/Deduplicator.d.ts +1 -0
- package/dist/__tests__/Deduplicator.js +190 -0
- package/dist/__tests__/EventSpecCache.d.ts +1 -0
- package/dist/__tests__/EventSpecCache.js +143 -0
- package/dist/__tests__/EventValidator.d.ts +1 -0
- package/dist/__tests__/EventValidator.js +543 -0
- package/dist/__tests__/InstallationId.d.ts +1 -0
- package/dist/__tests__/InstallationId.js +60 -0
- package/dist/__tests__/NetworkCallsHandler.d.ts +1 -0
- package/dist/__tests__/NetworkCallsHandler.js +222 -0
- package/dist/__tests__/NetworkCallsHandlerGzip_test.d.ts +1 -0
- package/dist/__tests__/NetworkCallsHandlerModelA.d.ts +1 -0
- package/dist/__tests__/NetworkCallsHandlerModelA.js +206 -0
- package/dist/__tests__/Parsing.d.ts +1 -0
- package/dist/__tests__/Parsing.js +115 -0
- package/dist/__tests__/Sessions.d.ts +1 -0
- package/dist/__tests__/Sessions.js +10 -0
- package/dist/__tests__/Storage.d.ts +1 -0
- package/dist/__tests__/Storage.js +173 -0
- package/dist/__tests__/StreamId.d.ts +1 -0
- package/dist/__tests__/StreamId.js +153 -0
- package/dist/__tests__/constants.js +48 -0
- package/dist/eventSpec/AvoEventSpecCache.js +94 -82
- package/dist/eventSpec/AvoEventSpecFetcher.js +200 -117
- package/dist/eventSpec/EventValidator.js +460 -304
- package/dist/index.js +1 -1
- package/dist/lite/AvoNetworkCallsHandlerLite.d.ts +6 -0
- package/dist/lite/AvoNetworkCallsHandlerLite.js +110 -1
- package/package.json +2 -2
package/dist/AvoEncryption.js
CHANGED
|
@@ -1,282 +1,176 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateKeyPair = generateKeyPair;
|
|
4
|
-
exports.encryptValue = encryptValue;
|
|
5
|
-
exports.decryptValue = decryptValue;
|
|
6
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
-
const p256_1 = require("@noble/curves/p256");
|
|
8
2
|
/**
|
|
9
|
-
*
|
|
3
|
+
* AvoEncryption — React Native ECIES encryption using @noble/ciphers.
|
|
4
|
+
*
|
|
5
|
+
* CRITICAL: React Native's Hermes runtime does NOT have crypto.subtle.
|
|
6
|
+
* This module uses @noble/ciphers/aes gcm() for AES-GCM instead — fully synchronous.
|
|
7
|
+
* Ephemeral key generation and ECDH use @noble/curves/p256.
|
|
8
|
+
*
|
|
9
|
+
* Wire format (MUST match all SDKs):
|
|
10
|
+
* [0x00][65-byte ephemeral pubkey (uncompressed)][16-byte IV][16-byte auth tag][ciphertext]
|
|
11
|
+
* → base64
|
|
12
|
+
*
|
|
13
|
+
* Algorithm:
|
|
14
|
+
* 1. Generate ephemeral P-256 key pair
|
|
15
|
+
* 2. ECDH: ephemeral private key + recipient public key → shared secret (X-coordinate)
|
|
16
|
+
* 3. KDF: SHA-256(shared secret) → 32-byte AES key
|
|
17
|
+
* 4. AES-256-GCM encrypt with random 16-byte IV
|
|
18
|
+
* 5. Serialize: version(1) + ephemeralPubKey(65) + IV(16) + authTag(16) + ciphertext
|
|
19
|
+
* 6. Base64 encode
|
|
10
20
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.shouldEncrypt = shouldEncrypt;
|
|
23
|
+
exports.encryptValue = encryptValue;
|
|
24
|
+
exports.encryptEventProperties = encryptEventProperties;
|
|
25
|
+
var p256_1 = require("@noble/curves/p256");
|
|
26
|
+
var aes_1 = require("@noble/ciphers/aes");
|
|
27
|
+
var sha256_1 = require("@noble/hashes/sha256");
|
|
28
|
+
var webcrypto_1 = require("@noble/ciphers/webcrypto");
|
|
16
29
|
/**
|
|
17
|
-
* Converts hex string to
|
|
30
|
+
* Converts a hex string to a Uint8Array.
|
|
31
|
+
* Strips optional 0x/0X prefix (matching Android SDK behavior).
|
|
18
32
|
*/
|
|
19
33
|
function hexToBytes(hex) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
if (hex.startsWith("0x") || hex.startsWith("0X")) {
|
|
35
|
+
hex = hex.substring(2);
|
|
36
|
+
}
|
|
37
|
+
var bytes = new Uint8Array(hex.length / 2);
|
|
38
|
+
for (var i = 0; i < hex.length; i += 2) {
|
|
39
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
23
40
|
}
|
|
24
41
|
return bytes;
|
|
25
42
|
}
|
|
26
|
-
// Get crypto API - works in both browser and Node.js environments
|
|
27
|
-
// In browser: window.crypto or globalThis.crypto
|
|
28
|
-
// In Node.js: global.crypto or globalThis.crypto (polyfilled in tests)
|
|
29
|
-
const getCrypto = () => {
|
|
30
|
-
// Try globalThis first (works in both Node.js and browsers)
|
|
31
|
-
if (typeof globalThis !== 'undefined' && globalThis.crypto) {
|
|
32
|
-
const crypto = globalThis.crypto;
|
|
33
|
-
if (crypto && crypto.subtle) {
|
|
34
|
-
return crypto;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// Try window (browser)
|
|
38
|
-
if (typeof window !== 'undefined' && window.crypto) {
|
|
39
|
-
const crypto = window.crypto;
|
|
40
|
-
if (crypto && crypto.subtle) {
|
|
41
|
-
return crypto;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Try global (Node.js)
|
|
45
|
-
if (typeof global !== 'undefined' && global.crypto) {
|
|
46
|
-
const crypto = global.crypto;
|
|
47
|
-
if (crypto && crypto.subtle) {
|
|
48
|
-
return crypto;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Fallback - should not happen in proper environments
|
|
52
|
-
const debugInfo = {
|
|
53
|
-
hasGlobalThis: typeof globalThis !== 'undefined',
|
|
54
|
-
hasGlobalThisCrypto: typeof globalThis !== 'undefined' && !!globalThis.crypto,
|
|
55
|
-
hasGlobalThisCryptoSubtle: typeof globalThis !== 'undefined' && !!globalThis.crypto && !!globalThis.crypto.subtle,
|
|
56
|
-
hasWindow: typeof window !== 'undefined',
|
|
57
|
-
hasWindowCrypto: typeof window !== 'undefined' && !!window.crypto,
|
|
58
|
-
hasGlobal: typeof global !== 'undefined',
|
|
59
|
-
hasGlobalCrypto: typeof global !== 'undefined' && !!global.crypto,
|
|
60
|
-
hasGlobalCryptoSubtle: typeof global !== 'undefined' && !!global.crypto && !!global.crypto.subtle
|
|
61
|
-
};
|
|
62
|
-
throw new Error('crypto.subtle not available. Debug: ' + JSON.stringify(debugInfo));
|
|
63
|
-
};
|
|
64
43
|
/**
|
|
65
|
-
*
|
|
66
|
-
* Uses P-256 (prime256v1 / NIST P-256) curve which is standard for Web Crypto API.
|
|
44
|
+
* Determines whether encryption should be performed.
|
|
67
45
|
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const publicKeyBytes = p256_1.p256.getPublicKey(privateKeyBytes, false);
|
|
75
|
-
// Convert to hex strings
|
|
76
|
-
const privateKey = bytesToHex(privateKeyBytes).padStart(64, '0');
|
|
77
|
-
const publicKey = bytesToHex(publicKeyBytes);
|
|
78
|
-
return {
|
|
79
|
-
privateKey,
|
|
80
|
-
publicKey
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Derives a key from shared secret using SHA-256
|
|
85
|
-
*/
|
|
86
|
-
async function deriveKey(sharedSecret) {
|
|
87
|
-
const cryptoAPI = getCrypto();
|
|
88
|
-
return await cryptoAPI.subtle.digest('SHA-256', sharedSecret);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Safely converts Uint8Array to base64 string.
|
|
92
|
-
* Uses Buffer in Node.js for efficiency, or chunked conversion in browsers
|
|
93
|
-
* to avoid call stack overflow with large arrays.
|
|
46
|
+
* Truth table:
|
|
47
|
+
* dev + key → true
|
|
48
|
+
* staging + key → true
|
|
49
|
+
* prod + key → false
|
|
50
|
+
* dev + null → false
|
|
51
|
+
* dev + empty → false
|
|
94
52
|
*/
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
53
|
+
function shouldEncrypt(env, publicEncryptionKey) {
|
|
54
|
+
if (env === "prod")
|
|
55
|
+
return false;
|
|
56
|
+
if (publicEncryptionKey === null ||
|
|
57
|
+
publicEncryptionKey === undefined ||
|
|
58
|
+
publicEncryptionKey.trim().length === 0) {
|
|
59
|
+
return false;
|
|
99
60
|
}
|
|
100
|
-
|
|
101
|
-
// Chunk size of 8192 (8k) is safe for String.fromCharCode.apply
|
|
102
|
-
const CHUNK_SIZE = 8192;
|
|
103
|
-
let binaryString = '';
|
|
104
|
-
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
105
|
-
const chunk = bytes.slice(i, i + CHUNK_SIZE);
|
|
106
|
-
// Convert chunk to array and use apply for this small chunk
|
|
107
|
-
binaryString += String.fromCharCode.apply(null, Array.from(chunk));
|
|
108
|
-
}
|
|
109
|
-
return btoa(binaryString);
|
|
61
|
+
return true;
|
|
110
62
|
}
|
|
111
63
|
/**
|
|
112
|
-
* Encrypts a value using
|
|
113
|
-
*
|
|
114
|
-
* This ensures that Avo cannot decrypt the values on the backend.
|
|
115
|
-
*
|
|
116
|
-
* ECIES uses hybrid encryption (ECDH + AES-256-GCM) which provides:
|
|
117
|
-
* - No message size limitations
|
|
118
|
-
* - Fast encryption even for large values
|
|
119
|
-
* - Strong authentication via GCM
|
|
120
|
-
*
|
|
121
|
-
* SPECIFICATION (Standard Web Crypto Profile):
|
|
122
|
-
* 1. Curve: P-256 (prime256v1 / NIST P-256)
|
|
123
|
-
* 2. Key Derivation (KDF): SHA-256(SharedSecret)
|
|
124
|
-
* 3. Cipher: AES-256-GCM
|
|
125
|
-
* 4. Serialization: [Version(1b)] + [EphemeralPubKey(33 or 65b)] + [IV(16b)] + [AuthTag(16b)] + [Ciphertext]
|
|
126
|
-
* Version 0x00 = Standard Web Profile
|
|
127
|
-
* EphemeralPubKey: 0x04 (uncompressed) + 64 bytes = 65 bytes total, or compressed format (33 bytes)
|
|
64
|
+
* Encrypts a value using ECIES (P-256 ECDH + AES-256-GCM).
|
|
65
|
+
* Fully synchronous — no async/await.
|
|
128
66
|
*
|
|
129
|
-
* @param value - The
|
|
130
|
-
* @param
|
|
131
|
-
* @returns
|
|
67
|
+
* @param value - The plaintext string to encrypt (already JSON-stringified by caller if needed)
|
|
68
|
+
* @param publicKeyHex - The recipient's P-256 public key as a hex string (uncompressed, 04...)
|
|
69
|
+
* @returns Base64-encoded ciphertext in Avo ECIES wire format
|
|
132
70
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// Ensure publicKey is a string
|
|
144
|
-
const publicKeyStr = typeof publicKey === 'string' ? publicKey : String(publicKey);
|
|
145
|
-
// Convert recipient's public key from hex to bytes
|
|
146
|
-
const recipientPublicKeyBytes = hexToBytes(publicKeyStr);
|
|
147
|
-
// 2. Generate Ephemeral Key Pair on P-256
|
|
148
|
-
const ephemeralPrivateKeyBytes = p256_1.p256.utils.randomPrivateKey();
|
|
149
|
-
const ephemeralPublicKeyBytes = p256_1.p256.getPublicKey(ephemeralPrivateKeyBytes, false); // Uncompressed format (65 bytes)
|
|
150
|
-
// 3. Derive Shared Secret (ECDH)
|
|
151
|
-
// getSharedSecret returns compressed point (33 bytes: 0x02/0x03 + X-coordinate)
|
|
152
|
-
// Extract X-coordinate (last 32 bytes) to match elliptic's derive().toArray("be", 32)
|
|
153
|
-
const sharedSecretPoint = p256_1.p256.getSharedSecret(ephemeralPrivateKeyBytes, recipientPublicKeyBytes);
|
|
154
|
-
const sharedSecret = sharedSecretPoint.slice(-32); // Extract X-coordinate (last 32 bytes)
|
|
155
|
-
// 4. Key Derivation (Hash with SHA-256)
|
|
156
|
-
const derivedKeyBuffer = await deriveKey(sharedSecret);
|
|
157
|
-
// 5. Import Key for Web Crypto AES-GCM
|
|
158
|
-
const cryptoAPI = getCrypto();
|
|
159
|
-
const keyMaterial = await cryptoAPI.subtle.importKey('raw', derivedKeyBuffer, { name: 'AES-GCM' }, false, ['encrypt']);
|
|
160
|
-
// 6. Generate random IV (16 bytes)
|
|
161
|
-
const iv = cryptoAPI.getRandomValues(new Uint8Array(16));
|
|
162
|
-
// 7. Encrypt using AES-256-GCM
|
|
163
|
-
// Convert string to Uint8Array
|
|
164
|
-
const plaintext = new TextEncoder().encode(stringValue);
|
|
165
|
-
const encryptedData = await cryptoAPI.subtle.encrypt({
|
|
166
|
-
name: 'AES-GCM',
|
|
167
|
-
iv,
|
|
168
|
-
tagLength: 128 // 16 bytes = 128 bits
|
|
169
|
-
}, keyMaterial, plaintext);
|
|
170
|
-
// Web Crypto API appends the auth tag to the ciphertext
|
|
171
|
-
// We need to extract it separately for our format
|
|
172
|
-
const encryptedArray = new Uint8Array(encryptedData);
|
|
173
|
-
const authTagLength = 16; // 128 bits = 16 bytes
|
|
174
|
-
const ciphertextLength = encryptedArray.length - authTagLength;
|
|
175
|
-
const ciphertext = encryptedArray.slice(0, ciphertextLength);
|
|
176
|
-
const authTag = encryptedArray.slice(ciphertextLength);
|
|
177
|
-
// 8. Serialize Output
|
|
178
|
-
// Format: [Version(1)] + [Ephemeral Public Key(65)] + [IV(16)] + [AuthTag(16)] + [Ciphertext]
|
|
179
|
-
const version = new Uint8Array([0x00]); // Version 0: Standard Web Profile
|
|
180
|
-
// Combine all parts
|
|
181
|
-
const resultLength = 1 + ephemeralPublicKeyBytes.length + 16 + 16 + ciphertext.length;
|
|
182
|
-
const result = new Uint8Array(resultLength);
|
|
183
|
-
let offset = 0;
|
|
184
|
-
result.set(version, offset);
|
|
185
|
-
offset += 1;
|
|
186
|
-
result.set(ephemeralPublicKeyBytes, offset);
|
|
187
|
-
offset += ephemeralPublicKeyBytes.length;
|
|
188
|
-
result.set(iv, offset);
|
|
189
|
-
offset += 16;
|
|
190
|
-
result.set(authTag, offset);
|
|
191
|
-
offset += 16;
|
|
192
|
-
result.set(ciphertext, offset);
|
|
193
|
-
// Convert to base64 using safe conversion that handles large arrays
|
|
194
|
-
return uint8ArrayToBase64(result);
|
|
71
|
+
function encryptValue(value, publicKeyHex) {
|
|
72
|
+
// Fail fast with a helpful message when running in React Native without the polyfill.
|
|
73
|
+
// Node.js and browsers provide crypto natively; only Hermes (RN) lacks it.
|
|
74
|
+
if (!process.env.BROWSER &&
|
|
75
|
+
typeof navigator !== "undefined" && navigator.product === "ReactNative" &&
|
|
76
|
+
(typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.getRandomValues !== "function")) {
|
|
77
|
+
throw new Error("crypto.getRandomValues is not available. " +
|
|
78
|
+
"If you are using React Native, add 'react-native-get-random-values' to your project " +
|
|
79
|
+
"and import it at the top of your entry file (index.js) before any other imports: " +
|
|
80
|
+
"import 'react-native-get-random-values';");
|
|
195
81
|
}
|
|
196
|
-
|
|
197
|
-
|
|
82
|
+
// 1. Parse recipient public key from hex
|
|
83
|
+
var recipientPublicKeyBytes = hexToBytes(publicKeyHex);
|
|
84
|
+
// 2. Generate ephemeral P-256 key pair
|
|
85
|
+
var ephemeralPrivateKeyBytes = p256_1.p256.utils.randomPrivateKey();
|
|
86
|
+
var ephemeralPublicKeyBytes = p256_1.p256.getPublicKey(ephemeralPrivateKeyBytes, false // uncompressed = 65 bytes (0x04 + 32-byte X + 32-byte Y)
|
|
87
|
+
);
|
|
88
|
+
// 3. ECDH: compute shared secret
|
|
89
|
+
// getSharedSecret returns the full point; extract X-coordinate (last 32 bytes)
|
|
90
|
+
var sharedSecretPoint = p256_1.p256.getSharedSecret(ephemeralPrivateKeyBytes, recipientPublicKeyBytes);
|
|
91
|
+
var sharedSecret = sharedSecretPoint.slice(-32);
|
|
92
|
+
// 4. KDF: SHA-256(sharedSecret) → 32-byte AES key
|
|
93
|
+
var aesKey = (0, sha256_1.sha256)(sharedSecret);
|
|
94
|
+
// 5. Generate random 16-byte IV
|
|
95
|
+
var iv = (0, webcrypto_1.randomBytes)(16);
|
|
96
|
+
// 6. AES-256-GCM encrypt
|
|
97
|
+
var plaintext = new TextEncoder().encode(value);
|
|
98
|
+
var aes = (0, aes_1.gcm)(aesKey, iv);
|
|
99
|
+
var ciphertextWithTag = aes.encrypt(plaintext);
|
|
100
|
+
// @noble/ciphers gcm() returns ciphertext + authTag concatenated
|
|
101
|
+
// Split the LAST 16 bytes as the auth tag
|
|
102
|
+
var authTagLength = 16;
|
|
103
|
+
var ciphertextLength = ciphertextWithTag.length - authTagLength;
|
|
104
|
+
var ciphertext = ciphertextWithTag.slice(0, ciphertextLength);
|
|
105
|
+
var authTag = ciphertextWithTag.slice(ciphertextLength);
|
|
106
|
+
// 7. Serialize: [Version 0x00 (1B)] + [EphemeralPubKey (65B)] + [IV (16B)] + [AuthTag (16B)] + [Ciphertext]
|
|
107
|
+
var resultLength = 1 + ephemeralPublicKeyBytes.length + 16 + 16 + ciphertext.length;
|
|
108
|
+
var result = new Uint8Array(resultLength);
|
|
109
|
+
var offset = 0;
|
|
110
|
+
result[0] = 0x00; // Version byte
|
|
111
|
+
offset += 1;
|
|
112
|
+
result.set(ephemeralPublicKeyBytes, offset);
|
|
113
|
+
offset += ephemeralPublicKeyBytes.length;
|
|
114
|
+
result.set(iv, offset);
|
|
115
|
+
offset += 16;
|
|
116
|
+
result.set(authTag, offset);
|
|
117
|
+
offset += 16;
|
|
118
|
+
result.set(ciphertext, offset);
|
|
119
|
+
// 8. Base64 encode (platform-safe: avoids Buffer which is unavailable in Hermes/React Native)
|
|
120
|
+
var binary = "";
|
|
121
|
+
for (var i = 0; i < result.length; i++) {
|
|
122
|
+
binary += String.fromCharCode(result[i]);
|
|
198
123
|
}
|
|
124
|
+
return btoa(binary);
|
|
199
125
|
}
|
|
200
126
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* SPECIFICATION (Standard Web Crypto Profile):
|
|
204
|
-
* 1. Curve: P-256 (prime256v1 / NIST P-256)
|
|
205
|
-
* 2. Key Derivation (KDF): SHA-256(SharedSecret)
|
|
206
|
-
* 3. Cipher: AES-256-GCM
|
|
207
|
-
* 4. Deserialization: [Version(1b)] + [EphemeralPubKey(33 or 65b)] + [IV(16b)] + [AuthTag(16b)] + [Ciphertext]
|
|
127
|
+
* Encrypts event property values for transmission.
|
|
208
128
|
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
129
|
+
* Rules:
|
|
130
|
+
* - List-type properties are omitted entirely
|
|
131
|
+
* - On encryption failure: console.warn, omit the property, continue
|
|
132
|
+
* - Returns new array with encryptedPropertyValue set (propertyType/propertyName preserved)
|
|
212
133
|
*/
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
134
|
+
function encryptEventProperties(properties, eventProps, publicKeyHex) {
|
|
135
|
+
var result = [];
|
|
136
|
+
var encryptionFailed = false;
|
|
137
|
+
for (var _i = 0, properties_1 = properties; _i < properties_1.length; _i++) {
|
|
138
|
+
var prop = properties_1[_i];
|
|
139
|
+
// Omit list-type properties entirely
|
|
140
|
+
if (prop.propertyType === "list") {
|
|
141
|
+
continue;
|
|
221
142
|
}
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
143
|
+
// If encryption already failed for this batch, skip remaining properties
|
|
144
|
+
// to avoid spamming the console with the same error
|
|
145
|
+
if (encryptionFailed) {
|
|
146
|
+
continue;
|
|
225
147
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
148
|
+
try {
|
|
149
|
+
var value = eventProps[prop.propertyName];
|
|
150
|
+
var jsonValue = JSON.stringify(value === undefined ? null : value);
|
|
151
|
+
var encrypted = encryptValue(jsonValue, publicKeyHex);
|
|
152
|
+
var entry = {
|
|
153
|
+
propertyName: prop.propertyName,
|
|
154
|
+
propertyType: prop.propertyType,
|
|
155
|
+
encryptedPropertyValue: encrypted,
|
|
156
|
+
};
|
|
157
|
+
// Preserve children if present
|
|
158
|
+
if (prop.children !== undefined) {
|
|
159
|
+
entry.children = prop.children;
|
|
160
|
+
}
|
|
161
|
+
// Preserve validation fields if present
|
|
162
|
+
if (prop.failedEventIds !== undefined) {
|
|
163
|
+
entry.failedEventIds = prop.failedEventIds;
|
|
164
|
+
}
|
|
165
|
+
if (prop.passedEventIds !== undefined) {
|
|
166
|
+
entry.passedEventIds = prop.passedEventIds;
|
|
167
|
+
}
|
|
168
|
+
result.push(entry);
|
|
235
169
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// Validate buffer has enough bytes for this key format
|
|
240
|
-
const minRequired = 1 + pubKeySize + 16 + 16 + 1;
|
|
241
|
-
if (encryptedBuffer.length < minRequired) {
|
|
242
|
-
throw new Error(`Invalid encrypted data: expected at least ${minRequired} bytes, got ${encryptedBuffer.length}`);
|
|
170
|
+
catch (e) {
|
|
171
|
+
encryptionFailed = true;
|
|
172
|
+
console.warn("[Avo Inspector] Encryption failed: ".concat(e instanceof Error ? e.message : String(e), ". Remaining event properties will be omitted from the encrypted payload."));
|
|
243
173
|
}
|
|
244
|
-
const ephemeralPublicKey = encryptedBuffer.slice(offset, offset + pubKeySize);
|
|
245
|
-
offset += pubKeySize;
|
|
246
|
-
const iv = encryptedBuffer.slice(offset, offset + 16); // IV (16 bytes)
|
|
247
|
-
offset += 16;
|
|
248
|
-
const authTag = encryptedBuffer.slice(offset, offset + 16); // Auth tag (16 bytes)
|
|
249
|
-
offset += 16;
|
|
250
|
-
const ciphertext = encryptedBuffer.slice(offset); // Remaining bytes are ciphertext
|
|
251
|
-
// 2. Prepare Private Key
|
|
252
|
-
const recipientPrivateKeyBytes = hexToBytes(privateKeyStr);
|
|
253
|
-
// 3. Derive Shared Secret (ECDH)
|
|
254
|
-
// getSharedSecret returns compressed point (33 bytes: 0x02/0x03 + X-coordinate)
|
|
255
|
-
// Extract X-coordinate (last 32 bytes) to match elliptic's derive().toArray("be", 32)
|
|
256
|
-
const sharedSecretPoint = p256_1.p256.getSharedSecret(recipientPrivateKeyBytes, ephemeralPublicKey);
|
|
257
|
-
const sharedSecret = sharedSecretPoint.slice(-32); // Extract X-coordinate (last 32 bytes)
|
|
258
|
-
// 4. Key Derivation (Hash with SHA-256)
|
|
259
|
-
const derivedKeyBuffer = await deriveKey(sharedSecret);
|
|
260
|
-
// 5. Import Key for Web Crypto AES-GCM
|
|
261
|
-
const cryptoAPI = getCrypto();
|
|
262
|
-
const keyMaterial = await cryptoAPI.subtle.importKey('raw', derivedKeyBuffer, { name: 'AES-GCM' }, false, ['decrypt']);
|
|
263
|
-
// 6. Decrypt using AES-256-GCM
|
|
264
|
-
// Web Crypto API expects the auth tag appended to ciphertext
|
|
265
|
-
const combinedLength = ciphertext.length + authTag.length;
|
|
266
|
-
const data = new Uint8Array(combinedLength);
|
|
267
|
-
data.set(ciphertext, 0);
|
|
268
|
-
data.set(authTag, ciphertext.length);
|
|
269
|
-
const decryptedBuffer = await cryptoAPI.subtle.decrypt({
|
|
270
|
-
name: 'AES-GCM',
|
|
271
|
-
iv,
|
|
272
|
-
tagLength: 128 // 16 bytes = 128 bits
|
|
273
|
-
}, keyMaterial, data);
|
|
274
|
-
// 7. Convert to string and parse JSON
|
|
275
|
-
const decoder = new TextDecoder();
|
|
276
|
-
const stringValue = decoder.decode(decryptedBuffer);
|
|
277
|
-
return JSON.parse(stringValue);
|
|
278
|
-
}
|
|
279
|
-
catch (error) {
|
|
280
|
-
throw new Error(`Failed to decrypt value. Please check that the private key is valid and matches the public key used for encryption. Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
281
174
|
}
|
|
175
|
+
return result;
|
|
282
176
|
}
|