avo-inspector 3.2.0 → 3.2.3
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 +10 -0
- package/dist/AvoBatcher.d.ts +9 -3
- package/dist/AvoInspector.d.ts +4 -0
- package/dist/AvoNetworkCallsHandler.d.ts +80 -5
- package/dist/__tests__/UnboundPublicMethods_test.d.ts +1 -0
- package/dist/__tests__/constants.d.ts +2 -1
- package/dist/__tests__/helpers/internalGateway.d.ts +23 -0
- package/dist/eventSpec/AvoEventSpecCache.d.ts +7 -4
- package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +12 -1
- package/dist/eventSpec/AvoEventSpecFetcher.d.ts +6 -1
- package/dist/index.js +1 -1
- package/dist/lite/AvoBatcherLite.d.ts +9 -3
- package/dist/lite/AvoBatcherLite.js +46 -7
- package/dist/lite/AvoInspectorLite.d.ts +2 -0
- package/dist/lite/AvoInspectorLite.js +95 -15
- package/dist/lite/AvoNetworkCallsHandlerLite.d.ts +79 -5
- package/dist/lite/AvoNetworkCallsHandlerLite.js +300 -20
- package/package.json +10 -1
- package/dist/AvoBatcher.js +0 -87
- package/dist/AvoDeduplicator.js +0 -142
- package/dist/AvoEncryption.js +0 -176
- package/dist/AvoInspector.js +0 -435
- package/dist/AvoNetworkCallsHandler.js +0 -229
- package/dist/AvoSchemaParser.js +0 -95
- package/dist/AvoStreamId.js +0 -82
- package/dist/__mocks__/xhr.js +0 -14
- package/dist/__mocks__/xhrError.js +0 -14
- package/dist/__tests__/AvoEncryption.js +0 -272
- package/dist/__tests__/AvoEncryptionIntegration.js +0 -191
- package/dist/__tests__/AvoEventSpecFetcher.js +0 -377
- package/dist/__tests__/AvoInspector.js +0 -335
- package/dist/__tests__/Batcher.js +0 -214
- package/dist/__tests__/BatcherPublic.js +0 -139
- package/dist/__tests__/Deduplicator.js +0 -190
- package/dist/__tests__/EventSpecCache.js +0 -143
- package/dist/__tests__/EventValidator.js +0 -543
- package/dist/__tests__/InstallationId.js +0 -60
- package/dist/__tests__/NetworkCallsHandler.js +0 -222
- package/dist/__tests__/NetworkCallsHandlerModelA.js +0 -206
- package/dist/__tests__/Parsing.js +0 -115
- package/dist/__tests__/Sessions.d.ts +0 -1
- package/dist/__tests__/Sessions.js +0 -10
- package/dist/__tests__/Storage.d.ts +0 -1
- package/dist/__tests__/Storage.js +0 -173
- package/dist/__tests__/StreamId.d.ts +0 -1
- package/dist/__tests__/StreamId.js +0 -153
- package/dist/__tests__/constants.js +0 -48
- package/dist/esm/AvoBatcher.js +0 -75
- package/dist/esm/AvoDeduplicator.js +0 -137
- package/dist/esm/AvoEncryption.js +0 -277
- package/dist/esm/AvoGuid.js +0 -9
- package/dist/esm/AvoInspector.js +0 -470
- package/dist/esm/AvoInspectorEnv.js +0 -5
- package/dist/esm/AvoNetworkCallsHandler.js +0 -181
- package/dist/esm/AvoSchemaParser.js +0 -128
- package/dist/esm/AvoStorage.js +0 -176
- package/dist/esm/AvoStreamId.js +0 -140
- package/dist/esm/eventSpec/AvoEventSpecCache.js +0 -165
- package/dist/esm/eventSpec/AvoEventSpecFetchTypes.js +0 -6
- package/dist/esm/eventSpec/AvoEventSpecFetcher.js +0 -203
- package/dist/esm/eventSpec/EventValidator.js +0 -687
- package/dist/esm/eventSpec/index.js +0 -4
- package/dist/esm/index.js +0 -3
- package/dist/esm/utils.js +0 -38
- package/dist/esm/version.js +0 -2
- package/dist/eventSpec/AvoEventSpecCache.js +0 -181
- package/dist/eventSpec/AvoEventSpecFetcher.js +0 -290
- package/dist/eventSpec/EventValidator.js +0 -846
- package/dist/eventSpec/index.js +0 -20
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -5
- /package/dist/__tests__/{AvoEncryption.d.ts → ApiKeyHeaderInjection_test.d.ts} +0 -0
- /package/dist/__tests__/{AvoEncryptionIntegration.d.ts → BrowserBootstrap_test.d.ts} +0 -0
- /package/dist/__tests__/{AvoEventSpecFetcher.d.ts → EventSpecGatewayCoordinates_test.d.ts} +0 -0
- /package/dist/__tests__/{AvoInspector.d.ts → InFlightQueueGaps_test.d.ts} +0 -0
- /package/dist/__tests__/{Batcher.d.ts → InFlightQueue_test.d.ts} +0 -0
- /package/dist/__tests__/{BatcherPublic.d.ts → NetworkCallsHandlerLite_test.d.ts} +0 -0
- /package/dist/__tests__/{Deduplicator.d.ts → PublicSurface_test.d.ts} +0 -0
- /package/dist/__tests__/{EventSpecCache.d.ts → TrackOptionsIntegration_test.d.ts} +0 -0
- /package/dist/__tests__/{EventValidator.d.ts → TrackOptionsTransport_test.d.ts} +0 -0
- /package/dist/__tests__/{InstallationId.d.ts → TrackingHeaders_test.d.ts} +0 -0
- /package/dist/__tests__/{NetworkCallsHandler.d.ts → V1WireBaseline_test.d.ts} +0 -0
- /package/dist/__tests__/{NetworkCallsHandlerModelA.d.ts → V2QueuedAttribution_test.d.ts} +0 -0
- /package/dist/__tests__/{Parsing.d.ts → V2StopSending_test.d.ts} +0 -0
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
const isArray = (obj) => {
|
|
2
|
-
return Object.prototype.toString.call(obj) === "[object Array]";
|
|
3
|
-
};
|
|
4
|
-
let _encryptValueFn;
|
|
5
|
-
export class AvoSchemaParser {
|
|
6
|
-
/**
|
|
7
|
-
* Returns true only if we have a valid encryption key and can send encrypted values.
|
|
8
|
-
* If no key is present, returns false and no property values will be sent.
|
|
9
|
-
*/
|
|
10
|
-
static canSendEncryptedValues(publicEncryptionKey, env) {
|
|
11
|
-
const hasEncryptionKey = publicEncryptionKey != null && publicEncryptionKey !== "";
|
|
12
|
-
const isDevOrStaging = env === "dev" || env === "staging";
|
|
13
|
-
return hasEncryptionKey && isDevOrStaging;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Returns the encrypted property value if encryption is enabled, otherwise undefined.
|
|
17
|
-
* Never returns unencrypted values - only encrypted or nothing.
|
|
18
|
-
*/
|
|
19
|
-
static async getEncryptedPropertyValueIfEnabled(propertyValue, canEncrypt, publicEncryptionKey) {
|
|
20
|
-
if (!canEncrypt || !publicEncryptionKey) {
|
|
21
|
-
return undefined; // No encryption key: do not send any property values
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
if (!_encryptValueFn) {
|
|
25
|
-
const { encryptValue } = await import("./AvoEncryption");
|
|
26
|
-
_encryptValueFn = encryptValue;
|
|
27
|
-
}
|
|
28
|
-
return await _encryptValueFn(propertyValue, publicEncryptionKey); // Only send encrypted values
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
// If encryption fails, log the error but don't fail the entire schema extraction
|
|
32
|
-
console.error("[Avo Inspector] Failed to encrypt property value:", error instanceof Error ? error.message : String(error));
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
static async extractSchema(eventProperties, publicEncryptionKey, env) {
|
|
37
|
-
if (eventProperties === null || eventProperties === undefined) {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
const canSendEncryptedValues = this.canSendEncryptedValues(publicEncryptionKey, env);
|
|
41
|
-
const mapping = async (object) => {
|
|
42
|
-
if (isArray(object)) {
|
|
43
|
-
const list = await Promise.all(object.map(async (x) => {
|
|
44
|
-
return await mapping(x);
|
|
45
|
-
}));
|
|
46
|
-
return this.removeDuplicates(list);
|
|
47
|
-
}
|
|
48
|
-
else if (typeof object === "object") {
|
|
49
|
-
const mappedResult = [];
|
|
50
|
-
for (const key in object) {
|
|
51
|
-
if (object.hasOwnProperty(key)) {
|
|
52
|
-
const val = object[key];
|
|
53
|
-
const mappedEntry = {
|
|
54
|
-
propertyName: key,
|
|
55
|
-
propertyType: this.getPropValueType(val)
|
|
56
|
-
};
|
|
57
|
-
if (typeof val === "object" && val != null) {
|
|
58
|
-
// Object/array properties: children are encrypted individually, no need to encrypt parent
|
|
59
|
-
mappedEntry.children = (await mapping(val));
|
|
60
|
-
}
|
|
61
|
-
else if (val !== undefined) {
|
|
62
|
-
// Primitive properties: encrypt the value if encryption is enabled
|
|
63
|
-
// Skip undefined values - they can't be encrypted and shouldn't be sent
|
|
64
|
-
const encryptedValue = await this.getEncryptedPropertyValueIfEnabled(val, canSendEncryptedValues, publicEncryptionKey);
|
|
65
|
-
if (encryptedValue !== undefined) {
|
|
66
|
-
mappedEntry.encryptedPropertyValue = encryptedValue;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
mappedResult.push(mappedEntry);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return mappedResult;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
return this.getPropValueType(object);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
// eventProperties is always an object (Record), so mapping returns EventProperty[]
|
|
79
|
-
const mappedEventProps = (await mapping(eventProperties));
|
|
80
|
-
return mappedEventProps;
|
|
81
|
-
}
|
|
82
|
-
static removeDuplicates(array) {
|
|
83
|
-
const primitives = { boolean: {}, number: {}, string: {} };
|
|
84
|
-
const objects = [];
|
|
85
|
-
return array.filter((item) => {
|
|
86
|
-
const type = typeof item;
|
|
87
|
-
if (type in primitives && typeof item === "string") {
|
|
88
|
-
return primitives[type].hasOwnProperty(item)
|
|
89
|
-
? false
|
|
90
|
-
: (primitives[type][item] = true);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
return objects.includes(item) ? false : objects.push(item);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
static getPropValueType(propValue) {
|
|
98
|
-
const propType = typeof propValue;
|
|
99
|
-
if (propValue == null) {
|
|
100
|
-
return "null";
|
|
101
|
-
}
|
|
102
|
-
else if (propType === "string") {
|
|
103
|
-
return "string";
|
|
104
|
-
}
|
|
105
|
-
else if (propType === "number" || propType === "bigint") {
|
|
106
|
-
if ((propValue + "").includes(".")) {
|
|
107
|
-
return "float";
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
return "int";
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else if (propType === "boolean") {
|
|
114
|
-
return "boolean";
|
|
115
|
-
}
|
|
116
|
-
else if (propType === "object") {
|
|
117
|
-
if (isArray(propValue)) {
|
|
118
|
-
return "list";
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
return "object";
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
return "unknown";
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
package/dist/esm/AvoStorage.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
class PlatformAvoStorage {
|
|
2
|
-
parseJson(maybeItem) {
|
|
3
|
-
if (maybeItem !== null && maybeItem !== undefined) {
|
|
4
|
-
return JSON.parse(maybeItem);
|
|
5
|
-
}
|
|
6
|
-
else {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
class BrowserAvoStorage extends PlatformAvoStorage {
|
|
12
|
-
constructor() {
|
|
13
|
-
super(...arguments);
|
|
14
|
-
this.useFallbackStorage = false;
|
|
15
|
-
this.fallbackStorage = {};
|
|
16
|
-
this.storageInitialized = false;
|
|
17
|
-
this.onStorageInitFuncs = [];
|
|
18
|
-
this.shouldLog = false;
|
|
19
|
-
this.suffix = "";
|
|
20
|
-
}
|
|
21
|
-
init(shouldLog, suffix) {
|
|
22
|
-
this.shouldLog = shouldLog;
|
|
23
|
-
this.suffix = suffix;
|
|
24
|
-
this.initializeStorageWeb(this.isLocalStorageAvailable());
|
|
25
|
-
}
|
|
26
|
-
initializeStorageWeb(isLocalStorageAvailable) {
|
|
27
|
-
this.storageInitialized = true;
|
|
28
|
-
if (!isLocalStorageAvailable) {
|
|
29
|
-
this.useFallbackStorage = true;
|
|
30
|
-
}
|
|
31
|
-
this.onStorageInitFuncs.forEach((func) => {
|
|
32
|
-
func();
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
isLocalStorageAvailable() {
|
|
36
|
-
const uid = new Date().toISOString();
|
|
37
|
-
try {
|
|
38
|
-
window.localStorage.setItem(uid, uid);
|
|
39
|
-
if (window.localStorage.getItem(uid) === uid) {
|
|
40
|
-
window.localStorage.removeItem(uid);
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
isInitialized() {
|
|
52
|
-
return this.storageInitialized;
|
|
53
|
-
}
|
|
54
|
-
async getItemAsync(key) {
|
|
55
|
-
return await new Promise((resolve, _reject) => {
|
|
56
|
-
this.runAfterInit(() => {
|
|
57
|
-
if (this.useFallbackStorage) {
|
|
58
|
-
const maybeItem = this.fallbackStorage[key + this.suffix];
|
|
59
|
-
resolve(this.parseJson(maybeItem));
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
if (typeof window !== "undefined") {
|
|
63
|
-
let maybeItem;
|
|
64
|
-
try {
|
|
65
|
-
maybeItem = window.localStorage.getItem(key + this.suffix);
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
if (this.shouldLog) {
|
|
69
|
-
console.error("Avo Inspector Storage getItemAsync error:", error);
|
|
70
|
-
}
|
|
71
|
-
resolve(null);
|
|
72
|
-
}
|
|
73
|
-
resolve(this.parseJson(maybeItem));
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
resolve(null);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
getItem(key) {
|
|
83
|
-
let maybeItem;
|
|
84
|
-
if (!this.storageInitialized) {
|
|
85
|
-
maybeItem = null;
|
|
86
|
-
}
|
|
87
|
-
else if (this.useFallbackStorage) {
|
|
88
|
-
maybeItem = this.fallbackStorage[key + this.suffix];
|
|
89
|
-
}
|
|
90
|
-
else if (process.env.BROWSER) {
|
|
91
|
-
if (typeof window !== "undefined") {
|
|
92
|
-
try {
|
|
93
|
-
maybeItem = window.localStorage.getItem(key + this.suffix);
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
if (this.shouldLog) {
|
|
97
|
-
console.error("Avo Inspector Storage getItem error:", error);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return this.parseJson(maybeItem);
|
|
103
|
-
}
|
|
104
|
-
setItem(key, value) {
|
|
105
|
-
this.runAfterInit(() => {
|
|
106
|
-
if (this.useFallbackStorage) {
|
|
107
|
-
this.fallbackStorage[key + this.suffix] = JSON.stringify(value);
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
if (typeof window !== "undefined") {
|
|
111
|
-
try {
|
|
112
|
-
window.localStorage.setItem(key + this.suffix, JSON.stringify(value));
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
if (this.shouldLog) {
|
|
116
|
-
console.error("Avo Inspector Storage setItem error:", error);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
removeItem(key) {
|
|
124
|
-
this.runAfterInit(() => {
|
|
125
|
-
if (this.useFallbackStorage) {
|
|
126
|
-
this.fallbackStorage[key + this.suffix] = null;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
if (typeof window !== "undefined") {
|
|
130
|
-
try {
|
|
131
|
-
window.localStorage.removeItem(key + this.suffix);
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
if (this.shouldLog) {
|
|
135
|
-
console.error("Avo Inspector Storage removeItem error:", error);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
runAfterInit(func) {
|
|
143
|
-
if (this.storageInitialized) {
|
|
144
|
-
func();
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
this.onStorageInitFuncs.push(func);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
export class AvoStorage {
|
|
152
|
-
constructor(shouldLog, suffix = "") {
|
|
153
|
-
this.Platform = null;
|
|
154
|
-
this.Platform = "browser";
|
|
155
|
-
this.storageImpl = new BrowserAvoStorage();
|
|
156
|
-
this.storageImpl.init(shouldLog, suffix);
|
|
157
|
-
}
|
|
158
|
-
isInitialized() {
|
|
159
|
-
return this.storageImpl.isInitialized();
|
|
160
|
-
}
|
|
161
|
-
async getItemAsync(key) {
|
|
162
|
-
return await this.storageImpl.getItemAsync(key);
|
|
163
|
-
}
|
|
164
|
-
getItem(key) {
|
|
165
|
-
return this.storageImpl.getItem(key);
|
|
166
|
-
}
|
|
167
|
-
setItem(key, value) {
|
|
168
|
-
this.storageImpl.setItem(key, value);
|
|
169
|
-
}
|
|
170
|
-
removeItem(key) {
|
|
171
|
-
this.storageImpl.removeItem(key);
|
|
172
|
-
}
|
|
173
|
-
runAfterInit(func) {
|
|
174
|
-
this.storageImpl.runAfterInit(func);
|
|
175
|
-
}
|
|
176
|
-
}
|
package/dist/esm/AvoStreamId.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import AvoGuid from "./AvoGuid";
|
|
2
|
-
import { AvoInspector } from "./AvoInspector";
|
|
3
|
-
/**
|
|
4
|
-
* AvoStreamId manages an ephemeral stream identifier.
|
|
5
|
-
*
|
|
6
|
-
* The stream ID is generated and stored persistently, but resets after:
|
|
7
|
-
* - 4 hours of total age AND
|
|
8
|
-
* - 2 hours of idle time (no activity)
|
|
9
|
-
*
|
|
10
|
-
* This ensures the ID is truly anonymous by not persisting indefinitely,
|
|
11
|
-
* while still maintaining session continuity during active usage.
|
|
12
|
-
*/
|
|
13
|
-
export class AvoStreamId {
|
|
14
|
-
/**
|
|
15
|
-
* Get the stream ID. If it doesn't exist or has expired, generates a new one.
|
|
16
|
-
* Returns "unknown" if storage is not initialized.
|
|
17
|
-
*/
|
|
18
|
-
static get streamId() {
|
|
19
|
-
// Return cached value if available and not expired
|
|
20
|
-
if (AvoStreamId._streamId !== null && !AvoStreamId.shouldReset()) {
|
|
21
|
-
AvoStreamId.updateLastActivity();
|
|
22
|
-
return AvoStreamId._streamId;
|
|
23
|
-
}
|
|
24
|
-
if (!AvoInspector.avoStorage.isInitialized()) {
|
|
25
|
-
return "unknown";
|
|
26
|
-
}
|
|
27
|
-
// Load from storage
|
|
28
|
-
AvoStreamId.loadFromStorage();
|
|
29
|
-
// Check if we should reset
|
|
30
|
-
if (AvoStreamId._streamId !== null && AvoStreamId.shouldReset()) {
|
|
31
|
-
return AvoStreamId.generateAndStoreNew();
|
|
32
|
-
}
|
|
33
|
-
// If no ID exists, generate new one
|
|
34
|
-
if (AvoStreamId._streamId === null) {
|
|
35
|
-
return AvoStreamId.generateAndStoreNew();
|
|
36
|
-
}
|
|
37
|
-
// Update last activity and return existing
|
|
38
|
-
AvoStreamId.updateLastActivity();
|
|
39
|
-
return AvoStreamId._streamId;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Check if the stream ID should be reset based on age and idle time.
|
|
43
|
-
*/
|
|
44
|
-
static shouldReset() {
|
|
45
|
-
if (AvoStreamId._createdAt === null || AvoStreamId._lastActivityAt === null) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
const now = Date.now();
|
|
49
|
-
const age = now - AvoStreamId._createdAt;
|
|
50
|
-
const idleTime = now - AvoStreamId._lastActivityAt;
|
|
51
|
-
// Reset if older than 4 hours AND idle for 2+ hours
|
|
52
|
-
return age > AvoStreamId.MAX_AGE_MS && idleTime > AvoStreamId.IDLE_THRESHOLD_MS;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Load stream ID and timestamps from storage into cache.
|
|
56
|
-
*/
|
|
57
|
-
static loadFromStorage() {
|
|
58
|
-
try {
|
|
59
|
-
const storedId = AvoInspector.avoStorage.getItem(AvoStreamId.STREAM_ID_KEY);
|
|
60
|
-
const createdAt = AvoInspector.avoStorage.getItem(AvoStreamId.CREATED_AT_KEY);
|
|
61
|
-
const lastActivityAt = AvoInspector.avoStorage.getItem(AvoStreamId.LAST_ACTIVITY_KEY);
|
|
62
|
-
if (storedId !== null && storedId !== undefined) {
|
|
63
|
-
AvoStreamId._streamId = storedId;
|
|
64
|
-
AvoStreamId._createdAt = createdAt ?? Date.now();
|
|
65
|
-
AvoStreamId._lastActivityAt = lastActivityAt ?? Date.now();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
console.error("Avo Inspector: Error reading stream ID from storage. Please report to support@avo.app.", e);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Generate a new stream ID and store it with timestamps.
|
|
74
|
-
*/
|
|
75
|
-
static generateAndStoreNew() {
|
|
76
|
-
const now = Date.now();
|
|
77
|
-
AvoStreamId._streamId = AvoGuid.newGuid();
|
|
78
|
-
AvoStreamId._createdAt = now;
|
|
79
|
-
AvoStreamId._lastActivityAt = now;
|
|
80
|
-
try {
|
|
81
|
-
AvoInspector.avoStorage.setItem(AvoStreamId.STREAM_ID_KEY, AvoStreamId._streamId);
|
|
82
|
-
AvoInspector.avoStorage.setItem(AvoStreamId.CREATED_AT_KEY, AvoStreamId._createdAt);
|
|
83
|
-
AvoInspector.avoStorage.setItem(AvoStreamId.LAST_ACTIVITY_KEY, AvoStreamId._lastActivityAt);
|
|
84
|
-
}
|
|
85
|
-
catch (e) {
|
|
86
|
-
console.error("Avo Inspector: Error saving stream ID to storage. Please report to support@avo.app.", e);
|
|
87
|
-
}
|
|
88
|
-
return AvoStreamId._streamId;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Update the last activity timestamp.
|
|
92
|
-
*/
|
|
93
|
-
static updateLastActivity() {
|
|
94
|
-
const now = Date.now();
|
|
95
|
-
AvoStreamId._lastActivityAt = now;
|
|
96
|
-
try {
|
|
97
|
-
AvoInspector.avoStorage.setItem(AvoStreamId.LAST_ACTIVITY_KEY, now);
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
// Silently fail - not critical
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* The storage key used to persist the stream ID.
|
|
105
|
-
*/
|
|
106
|
-
static get storageKey() {
|
|
107
|
-
return AvoStreamId.STREAM_ID_KEY;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* The storage key for created at timestamp.
|
|
111
|
-
*/
|
|
112
|
-
static get createdAtKey() {
|
|
113
|
-
return AvoStreamId.CREATED_AT_KEY;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* The storage key for last activity timestamp.
|
|
117
|
-
*/
|
|
118
|
-
static get lastActivityKey() {
|
|
119
|
-
return AvoStreamId.LAST_ACTIVITY_KEY;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Clear the cached stream ID. The next access will reload from storage.
|
|
123
|
-
* This is primarily useful for testing.
|
|
124
|
-
*/
|
|
125
|
-
static clearCache() {
|
|
126
|
-
AvoStreamId._streamId = null;
|
|
127
|
-
AvoStreamId._createdAt = null;
|
|
128
|
-
AvoStreamId._lastActivityAt = null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
AvoStreamId._streamId = null;
|
|
132
|
-
AvoStreamId._createdAt = null;
|
|
133
|
-
AvoStreamId._lastActivityAt = null;
|
|
134
|
-
// Ephemeral reset thresholds
|
|
135
|
-
AvoStreamId.MAX_AGE_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
136
|
-
AvoStreamId.IDLE_THRESHOLD_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
137
|
-
// Storage keys
|
|
138
|
-
AvoStreamId.STREAM_ID_KEY = "AvoInspectorStreamId";
|
|
139
|
-
AvoStreamId.CREATED_AT_KEY = "AvoInspectorStreamIdCreatedAt";
|
|
140
|
-
AvoStreamId.LAST_ACTIVITY_KEY = "AvoInspectorStreamIdLastActivityAt";
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EventSpecCache implements a dual-condition cache with LRU eviction.
|
|
3
|
-
*
|
|
4
|
-
* Cache Policy:
|
|
5
|
-
* - Entries expire after 5 minutes OR 50 cache hits, whichever comes first
|
|
6
|
-
* - When 50 cache hits occur globally, the oldest cached entry is evicted
|
|
7
|
-
* - Each cache entry tracks: spec, timestamp, and hit count
|
|
8
|
-
*
|
|
9
|
-
* Cache Key Format: ${apiKey}:${streamId}:${eventName}
|
|
10
|
-
*
|
|
11
|
-
* Note: Event count only increments on cache hits, not on every tracked event.
|
|
12
|
-
* This ensures the cache evicts based on actual usage, not overall tracking volume.
|
|
13
|
-
*/
|
|
14
|
-
export class EventSpecCache {
|
|
15
|
-
constructor(shouldLog = false) {
|
|
16
|
-
/** Time-to-live in milliseconds (1 minute) */
|
|
17
|
-
this.TTL_MS = 60 * 1000;
|
|
18
|
-
/** Maximum cache hit count before rotating cache (50 hits) */
|
|
19
|
-
this.MAX_EVENT_COUNT = 50;
|
|
20
|
-
/** Global cache hit counter to track when to rotate cache */
|
|
21
|
-
this.globalEventCount = 0;
|
|
22
|
-
this.cache = new Map();
|
|
23
|
-
this.shouldLog = shouldLog;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Generates a cache key from the provided parameters.
|
|
27
|
-
*/
|
|
28
|
-
generateKey(apiKey, streamId, eventName) {
|
|
29
|
-
return `${apiKey}:${streamId}:${eventName}`;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Checks if a non-expired cache entry exists for the given key.
|
|
33
|
-
* Returns true even for cached null specs (empty backend responses).
|
|
34
|
-
* Use this to distinguish between "cache miss" and "cached empty response".
|
|
35
|
-
*/
|
|
36
|
-
contains(apiKey, streamId, eventName) {
|
|
37
|
-
const key = this.generateKey(apiKey, streamId, eventName);
|
|
38
|
-
const entry = this.cache.get(key);
|
|
39
|
-
if (!entry) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
if (this.shouldEvict(entry)) {
|
|
43
|
-
this.cache.delete(key);
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Retrieves an event spec response from the cache if it exists and is valid.
|
|
50
|
-
* Returns null if the entry is missing, expired, has exceeded event count,
|
|
51
|
-
* or if the cached spec itself is null (empty backend response).
|
|
52
|
-
*
|
|
53
|
-
* On cache hit, increments the hit count for this entry and the global counter.
|
|
54
|
-
*/
|
|
55
|
-
get(apiKey, streamId, eventName) {
|
|
56
|
-
const key = this.generateKey(apiKey, streamId, eventName);
|
|
57
|
-
const entry = this.cache.get(key);
|
|
58
|
-
if (!entry) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
// Check if entry has expired
|
|
62
|
-
if (this.shouldEvict(entry)) {
|
|
63
|
-
this.cache.delete(key);
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
if (this.shouldLog) {
|
|
67
|
-
console.log(`[Avo Inspector] Cache hit for key: ${key}`);
|
|
68
|
-
}
|
|
69
|
-
// Update lastAccessed for LRU (Least Recently Used) eviction
|
|
70
|
-
entry.lastAccessed = Date.now();
|
|
71
|
-
// Increment hit count for this entry
|
|
72
|
-
entry.eventCount++;
|
|
73
|
-
this.globalEventCount++;
|
|
74
|
-
// Check if we need to evict the oldest entry
|
|
75
|
-
if (this.globalEventCount >= this.MAX_EVENT_COUNT) {
|
|
76
|
-
this.evictOldest();
|
|
77
|
-
this.globalEventCount = 0;
|
|
78
|
-
}
|
|
79
|
-
return entry.spec;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Stores an event spec response in the cache.
|
|
83
|
-
* Pass null to cache an empty response (event not found on backend).
|
|
84
|
-
*/
|
|
85
|
-
set(apiKey, streamId, eventName, spec) {
|
|
86
|
-
const key = this.generateKey(apiKey, streamId, eventName);
|
|
87
|
-
const now = Date.now();
|
|
88
|
-
const entry = {
|
|
89
|
-
spec,
|
|
90
|
-
timestamp: now,
|
|
91
|
-
lastAccessed: now,
|
|
92
|
-
eventCount: 0
|
|
93
|
-
};
|
|
94
|
-
this.cache.set(key, entry);
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Determines if a cache entry should be evicted based on:
|
|
98
|
-
* - Age (older than 5 minutes)
|
|
99
|
-
* - Hit count (50 or more cache hits)
|
|
100
|
-
*/
|
|
101
|
-
shouldEvict(entry) {
|
|
102
|
-
const age = Date.now() - entry.timestamp;
|
|
103
|
-
const ageExpired = age > this.TTL_MS;
|
|
104
|
-
const countExpired = entry.eventCount >= this.MAX_EVENT_COUNT;
|
|
105
|
-
return ageExpired || countExpired;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Evicts the least recently used cache entry based on lastAccessed timestamp.
|
|
109
|
-
* This implements the LRU (Least Recently Used) eviction policy.
|
|
110
|
-
*/
|
|
111
|
-
evictOldest() {
|
|
112
|
-
if (this.cache.size === 0) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
let lruKey = null;
|
|
116
|
-
let oldestAccessTime = Infinity;
|
|
117
|
-
// Find the entry with the oldest lastAccessed time (least recently used)
|
|
118
|
-
this.cache.forEach((entry, key) => {
|
|
119
|
-
if (entry.lastAccessed < oldestAccessTime) {
|
|
120
|
-
oldestAccessTime = entry.lastAccessed;
|
|
121
|
-
lruKey = key;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
// Remove the least recently used entry
|
|
125
|
-
if (lruKey !== null) {
|
|
126
|
-
this.cache.delete(lruKey);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Clears all cached entries. Useful for testing.
|
|
131
|
-
*/
|
|
132
|
-
clear() {
|
|
133
|
-
this.cache.clear();
|
|
134
|
-
this.globalEventCount = 0;
|
|
135
|
-
if (this.shouldLog) {
|
|
136
|
-
console.log("[Avo Inspector] Cache cleared");
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Returns the current size of the cache.
|
|
141
|
-
*/
|
|
142
|
-
size() {
|
|
143
|
-
return this.cache.size;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Returns cache statistics for debugging.
|
|
147
|
-
*/
|
|
148
|
-
getStats() {
|
|
149
|
-
const entries = [];
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
this.cache.forEach((entry, key) => {
|
|
152
|
-
entries.push({
|
|
153
|
-
key,
|
|
154
|
-
age: now - entry.timestamp,
|
|
155
|
-
lastAccessedAgo: now - entry.lastAccessed,
|
|
156
|
-
eventCount: entry.eventCount
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
return {
|
|
160
|
-
size: this.cache.size,
|
|
161
|
-
globalEventCount: this.globalEventCount,
|
|
162
|
-
entries
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is generated. Internal development changes should be made in the generator
|
|
3
|
-
* and the file should be re-generated. External contributions are welcome to submit
|
|
4
|
-
* changes directly to this file, and we'll apply them to the generator internally.
|
|
5
|
-
*/
|
|
6
|
-
export {};
|