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.
Files changed (52) hide show
  1. package/README.md +6 -13
  2. package/dist/AvoBatcher.js +52 -44
  3. package/dist/AvoDeduplicator.js +46 -45
  4. package/dist/AvoEncryption.js +147 -253
  5. package/dist/AvoInspector.js +295 -334
  6. package/dist/AvoNetworkCallsHandler.d.ts +6 -0
  7. package/dist/AvoNetworkCallsHandler.js +180 -136
  8. package/dist/AvoSchemaParser.js +34 -71
  9. package/dist/AvoStreamId.js +67 -129
  10. package/dist/__mocks__/xhr.js +14 -0
  11. package/dist/__mocks__/xhrError.js +14 -0
  12. package/dist/__tests__/AvoEncryption.d.ts +1 -0
  13. package/dist/__tests__/AvoEncryption.js +272 -0
  14. package/dist/__tests__/AvoEncryptionIntegration.d.ts +1 -0
  15. package/dist/__tests__/AvoEncryptionIntegration.js +191 -0
  16. package/dist/__tests__/AvoEventSpecFetcher.d.ts +1 -0
  17. package/dist/__tests__/AvoEventSpecFetcher.js +377 -0
  18. package/dist/__tests__/AvoInspector.d.ts +1 -0
  19. package/dist/__tests__/AvoInspector.js +335 -0
  20. package/dist/__tests__/Batcher.d.ts +1 -0
  21. package/dist/__tests__/Batcher.js +214 -0
  22. package/dist/__tests__/BatcherPublic.d.ts +1 -0
  23. package/dist/__tests__/BatcherPublic.js +139 -0
  24. package/dist/__tests__/Deduplicator.d.ts +1 -0
  25. package/dist/__tests__/Deduplicator.js +190 -0
  26. package/dist/__tests__/EventSpecCache.d.ts +1 -0
  27. package/dist/__tests__/EventSpecCache.js +143 -0
  28. package/dist/__tests__/EventValidator.d.ts +1 -0
  29. package/dist/__tests__/EventValidator.js +543 -0
  30. package/dist/__tests__/InstallationId.d.ts +1 -0
  31. package/dist/__tests__/InstallationId.js +60 -0
  32. package/dist/__tests__/NetworkCallsHandler.d.ts +1 -0
  33. package/dist/__tests__/NetworkCallsHandler.js +222 -0
  34. package/dist/__tests__/NetworkCallsHandlerGzip_test.d.ts +1 -0
  35. package/dist/__tests__/NetworkCallsHandlerModelA.d.ts +1 -0
  36. package/dist/__tests__/NetworkCallsHandlerModelA.js +206 -0
  37. package/dist/__tests__/Parsing.d.ts +1 -0
  38. package/dist/__tests__/Parsing.js +115 -0
  39. package/dist/__tests__/Sessions.d.ts +1 -0
  40. package/dist/__tests__/Sessions.js +10 -0
  41. package/dist/__tests__/Storage.d.ts +1 -0
  42. package/dist/__tests__/Storage.js +173 -0
  43. package/dist/__tests__/StreamId.d.ts +1 -0
  44. package/dist/__tests__/StreamId.js +153 -0
  45. package/dist/__tests__/constants.js +48 -0
  46. package/dist/eventSpec/AvoEventSpecCache.js +94 -82
  47. package/dist/eventSpec/AvoEventSpecFetcher.js +200 -117
  48. package/dist/eventSpec/EventValidator.js +460 -304
  49. package/dist/index.js +1 -1
  50. package/dist/lite/AvoNetworkCallsHandlerLite.d.ts +6 -0
  51. package/dist/lite/AvoNetworkCallsHandlerLite.js +110 -1
  52. package/package.json +2 -2
@@ -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
- * Converts bytes (Uint8Array) to hex string
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
- function bytesToHex(bytes) {
12
- return Array.from(bytes)
13
- .map(b => b.toString(16).padStart(2, '0'))
14
- .join('');
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 bytes (Uint8Array)
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
- const bytes = new Uint8Array(hex.length / 2);
21
- for (let i = 0; i < hex.length; i += 2) {
22
- bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
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
- * Generates a new ECC key pair for encryption/decryption.
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
- * @returns An object containing the private and public keys as hex strings
69
- */
70
- function generateKeyPair() {
71
- // Generate a new random private key (32 bytes)
72
- const privateKeyBytes = p256_1.p256.utils.randomPrivateKey();
73
- // Get public key (uncompressed format: 65 bytes = 0x04 + 32-byte x + 32-byte y)
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 uint8ArrayToBase64(bytes) {
96
- // Check if we're in Node.js and Buffer is available
97
- if (typeof Buffer !== 'undefined' && Buffer.from) {
98
- return Buffer.from(bytes).toString('base64');
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
- // Browser fallback: use chunked conversion to avoid call stack overflow
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 ECC public key encryption (ECIES).
113
- * The encrypted output can only be decrypted by the client using their private key.
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 value to encrypt (any type - will be JSON stringified)
130
- * @param publicKey - The ECC public key in hex format provided by the client
131
- * @returns Promise resolving to base64-encoded encrypted string that can only be decrypted with the private key
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
- async function encryptValue(value, publicKey) {
135
- try {
136
- // Convert the value to a JSON string to support all types
137
- // Note: JSON.stringify(undefined) returns undefined (not a string), so handle it explicitly
138
- const stringValue = value === undefined ? 'null' : JSON.stringify(value);
139
- if (stringValue === undefined) {
140
- throw new Error('Cannot encrypt undefined value');
141
- }
142
- // 1. Prepare Public Key
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
- catch (error) {
197
- throw new Error(`Failed to encrypt value. Please check that the public key is valid. Error: ${error instanceof Error ? error.message : String(error)}`);
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
- * Decrypts a value that was encrypted with encryptValue.
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
- * @param encryptedValue - The base64-encoded encrypted string
210
- * @param privateKey - The ECC private key in hex format
211
- * @returns Promise resolving to the original decrypted value (parsed from JSON)
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
214
- async function decryptValue(encryptedValue, privateKey) {
215
- try {
216
- // Convert encrypted value from base64 to Uint8Array
217
- const binaryString = atob(encryptedValue);
218
- const encryptedBuffer = new Uint8Array(binaryString.length);
219
- for (let i = 0; i < binaryString.length; i++) {
220
- encryptedBuffer[i] = binaryString.charCodeAt(i);
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
- // Minimum size: version(1) + pubkey(33 min) + iv(16) + authTag(16) + ciphertext(1 min) = 67 bytes
223
- if (encryptedBuffer.length < 67) {
224
- throw new Error('Invalid encrypted data: payload too short');
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
- // Ensure privateKey is a string
227
- const privateKeyStr = typeof privateKey === 'string' ? privateKey : String(privateKey);
228
- // 1. Deserialize Input
229
- // Format: [Version(1)] + [Ephemeral Public Key(33 or 65)] + [IV(16)] + [AuthTag(16)] + [Ciphertext]
230
- let offset = 0;
231
- const version = encryptedBuffer[offset];
232
- offset += 1;
233
- if (version !== 0x00) {
234
- throw new Error(`Unsupported encryption version: ${version}`);
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
- // Check header to determine public key size (0x02/0x03 = 33 bytes compressed, 0x04 = 65 bytes uncompressed)
237
- const keyHeader = encryptedBuffer[offset];
238
- const pubKeySize = keyHeader === 0x02 || keyHeader === 0x03 ? 33 : 65;
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
  }