dashcam 0.8.4 → 1.0.1-beta.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/.dashcam/cli-config.json +3 -0
- package/.dashcam/recording.log +135 -0
- package/.dashcam/web-config.json +11 -0
- package/.github/RELEASE.md +59 -0
- package/.github/workflows/publish.yml +43 -0
- package/BACKWARD_COMPATIBILITY.md +177 -0
- package/LOG_TRACKING_GUIDE.md +225 -0
- package/README.md +709 -155
- package/bin/dashcam-background.js +177 -0
- package/bin/dashcam.cjs +8 -0
- package/bin/dashcam.js +696 -0
- package/bin/index.js +63 -0
- package/examples/execute-script.js +152 -0
- package/examples/simple-test.js +37 -0
- package/lib/applicationTracker.js +311 -0
- package/lib/auth.js +222 -0
- package/lib/binaries.js +21 -0
- package/lib/config.js +34 -0
- package/lib/extension-logs/helpers.js +182 -0
- package/lib/extension-logs/index.js +347 -0
- package/lib/extension-logs/manager.js +344 -0
- package/lib/ffmpeg.js +155 -0
- package/lib/logTracker.js +23 -0
- package/lib/logger.js +118 -0
- package/lib/logs/index.js +488 -0
- package/lib/permissions.js +85 -0
- package/lib/processManager.js +317 -0
- package/lib/recorder.js +690 -0
- package/lib/store.js +58 -0
- package/lib/tracking/FileTracker.js +105 -0
- package/lib/tracking/FileTrackerManager.js +62 -0
- package/lib/tracking/LogsTracker.js +161 -0
- package/lib/tracking/active-win.js +212 -0
- package/lib/tracking/icons/darwin.js +39 -0
- package/lib/tracking/icons/index.js +167 -0
- package/lib/tracking/icons/windows.js +27 -0
- package/lib/tracking/idle.js +82 -0
- package/lib/tracking.js +23 -0
- package/lib/uploader.js +456 -0
- package/lib/utilities/jsonl.js +77 -0
- package/lib/webLogsDaemon.js +234 -0
- package/lib/websocket/server.js +223 -0
- package/package.json +53 -21
- package/recording.log +814 -0
- package/sea-bundle.mjs +34595 -0
- package/test-page.html +15 -0
- package/test.log +1 -0
- package/test_run.log +48 -0
- package/test_workflow.sh +154 -0
- package/examples/crash-test.js +0 -11
- package/examples/github-issue.sh +0 -1
- package/examples/protocol.html +0 -22
- package/index.js +0 -158
- package/lib.js +0 -199
- package/recorder.js +0 -85
package/lib/auth.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import got from 'got';
|
|
2
|
+
import { auth0Config } from './config.js';
|
|
3
|
+
import { logger, logFunctionCall } from './logger.js';
|
|
4
|
+
import { Store } from './store.js';
|
|
5
|
+
|
|
6
|
+
const tokenStore = new Store('auth0-store');
|
|
7
|
+
const TOKEN_KEY = 'tokens';
|
|
8
|
+
|
|
9
|
+
const auth = {
|
|
10
|
+
async login(apiKey) {
|
|
11
|
+
const logExit = logFunctionCall('auth.login');
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
logger.info('Authenticating with API key');
|
|
15
|
+
logger.verbose('Starting API key exchange', {
|
|
16
|
+
apiKeyLength: apiKey?.length,
|
|
17
|
+
hasApiKey: !!apiKey
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Exchange API key for token
|
|
21
|
+
const { token } = await got.post('https://api.testdriver.ai/auth/exchange-api-key', {
|
|
22
|
+
json: { apiKey },
|
|
23
|
+
timeout: 30000 // 30 second timeout
|
|
24
|
+
}).json();
|
|
25
|
+
|
|
26
|
+
if (!token) {
|
|
27
|
+
throw new Error('Failed to exchange API key for token');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
logger.verbose('Successfully exchanged API key for token', {
|
|
31
|
+
tokenLength: token.length,
|
|
32
|
+
tokenPrefix: token.substring(0, 10) + '...'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Get user info to verify the token works
|
|
36
|
+
logger.debug('Fetching user information to validate token...');
|
|
37
|
+
const user = await got.get('https://api.testdriver.ai/api/v1/whoami', {
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${token}`
|
|
40
|
+
},
|
|
41
|
+
timeout: 30000
|
|
42
|
+
}).json();
|
|
43
|
+
|
|
44
|
+
logger.verbose('User information retrieved', {
|
|
45
|
+
userId: user.id,
|
|
46
|
+
userEmail: user.email || 'not provided',
|
|
47
|
+
userName: user.name || 'not provided'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Store both token and user info
|
|
51
|
+
const tokenData = {
|
|
52
|
+
token,
|
|
53
|
+
user,
|
|
54
|
+
expires_at: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
tokenStore.set(TOKEN_KEY, tokenData);
|
|
58
|
+
|
|
59
|
+
logger.info('Successfully authenticated and stored token', {
|
|
60
|
+
expiresAt: new Date(tokenData.expires_at).toISOString(),
|
|
61
|
+
userId: user.id
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
logExit();
|
|
65
|
+
return token;
|
|
66
|
+
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error('Authentication failed:', {
|
|
69
|
+
message: error.message,
|
|
70
|
+
statusCode: error.response?.statusCode,
|
|
71
|
+
responseBody: error.response?.body
|
|
72
|
+
});
|
|
73
|
+
logExit();
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
async logout() {
|
|
79
|
+
try {
|
|
80
|
+
tokenStore.delete(TOKEN_KEY);
|
|
81
|
+
logger.info('Successfully logged out');
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error('Failed to logout:', error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async getToken() {
|
|
89
|
+
const tokens = tokenStore.get(TOKEN_KEY);
|
|
90
|
+
if (!tokens || Date.now() >= tokens.expires_at) {
|
|
91
|
+
throw new Error('No valid token found. Please login with an API key first');
|
|
92
|
+
}
|
|
93
|
+
return tokens.token;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
async isAuthenticated() {
|
|
97
|
+
const tokens = tokenStore.get(TOKEN_KEY);
|
|
98
|
+
return tokens && tokens.expires_at && Date.now() < tokens.expires_at;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async getProjects() {
|
|
102
|
+
const logExit = logFunctionCall('auth.getProjects');
|
|
103
|
+
|
|
104
|
+
logger.debug('Fetching user projects...');
|
|
105
|
+
const token = await this.getToken();
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await got.get('https://api.testdriver.ai/api/v1/projects', {
|
|
109
|
+
headers: {
|
|
110
|
+
Authorization: `Bearer ${token}`
|
|
111
|
+
},
|
|
112
|
+
timeout: 30000
|
|
113
|
+
}).json();
|
|
114
|
+
|
|
115
|
+
logger.verbose('Projects fetched successfully', {
|
|
116
|
+
projectCount: response.length
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
logExit();
|
|
120
|
+
return response;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
logger.error('Failed to fetch projects:', {
|
|
123
|
+
message: error.message,
|
|
124
|
+
statusCode: error.response?.statusCode
|
|
125
|
+
});
|
|
126
|
+
logExit();
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async getStsCredentials(replayData = {}) {
|
|
132
|
+
const logExit = logFunctionCall('auth.getStsCredentials');
|
|
133
|
+
|
|
134
|
+
logger.debug('Fetching STS credentials for upload...');
|
|
135
|
+
const token = await this.getToken();
|
|
136
|
+
|
|
137
|
+
logger.verbose('Making STS request', {
|
|
138
|
+
tokenPrefix: token.substring(0, 10) + '...',
|
|
139
|
+
replayData: {
|
|
140
|
+
id: replayData.id,
|
|
141
|
+
duration: replayData.duration,
|
|
142
|
+
title: replayData.title,
|
|
143
|
+
hasApps: !!replayData.apps,
|
|
144
|
+
hasIcons: !!replayData.icons,
|
|
145
|
+
hasProject: !!replayData.project
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Prepare the request body to match the desktop app
|
|
150
|
+
const requestBody = {
|
|
151
|
+
id: replayData.id,
|
|
152
|
+
duration: replayData.duration || 0,
|
|
153
|
+
apps: replayData.apps || [],
|
|
154
|
+
title: replayData.title || 'CLI Recording',
|
|
155
|
+
icons: replayData.icons || []
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Include project if provided
|
|
159
|
+
if (replayData.project) {
|
|
160
|
+
requestBody.project = replayData.project;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const response = await got.post('https://api.testdriver.ai/api/v1/replay/upload', {
|
|
164
|
+
headers: {
|
|
165
|
+
Authorization: `Bearer ${token}`
|
|
166
|
+
},
|
|
167
|
+
json: requestBody,
|
|
168
|
+
timeout: 30000
|
|
169
|
+
}).json();
|
|
170
|
+
|
|
171
|
+
logger.verbose('STS response received', {
|
|
172
|
+
hasVideo: !!response.video,
|
|
173
|
+
hasGif: !!response.gif,
|
|
174
|
+
hasImage: !!response.image,
|
|
175
|
+
hasIcons: !!response.icons
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// The API returns separate STS credentials for video, gif, and image
|
|
179
|
+
// Each contains: accessKeyId, secretAccessKey, sessionToken, bucket, region, file
|
|
180
|
+
logExit();
|
|
181
|
+
return response;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
async createLogSts(replayId, appId, name, type) {
|
|
185
|
+
const logExit = logFunctionCall('auth.createLogSts');
|
|
186
|
+
|
|
187
|
+
logger.debug('Creating log STS credentials', { replayId, appId, name, type });
|
|
188
|
+
const token = await this.getToken();
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const response = await got.post('https://api.testdriver.ai/api/v1/logs', {
|
|
192
|
+
headers: {
|
|
193
|
+
Authorization: `Bearer ${token}`
|
|
194
|
+
},
|
|
195
|
+
json: {
|
|
196
|
+
replayId,
|
|
197
|
+
appId,
|
|
198
|
+
name,
|
|
199
|
+
type
|
|
200
|
+
},
|
|
201
|
+
timeout: 30000
|
|
202
|
+
}).json();
|
|
203
|
+
|
|
204
|
+
logger.verbose('Log STS credentials created', {
|
|
205
|
+
logId: response.id,
|
|
206
|
+
hasCredentials: !!response.bucket
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
logExit();
|
|
210
|
+
return response;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logger.error('Failed to create log STS credentials:', {
|
|
213
|
+
message: error.message,
|
|
214
|
+
statusCode: error.response?.statusCode
|
|
215
|
+
});
|
|
216
|
+
logExit();
|
|
217
|
+
throw error;
|
|
218
|
+
} // Return the full response with video, gif, image objects
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export { auth };
|
package/lib/binaries.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import ffmpegStatic from 'ffmpeg-static';
|
|
2
|
+
import ffprobeStatic from 'ffprobe-static';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the path to the ffmpeg binary
|
|
7
|
+
* @returns {Promise<string>} Path to ffmpeg
|
|
8
|
+
*/
|
|
9
|
+
export async function getFfmpegPath() {
|
|
10
|
+
logger.debug('Getting ffmpeg path from ffmpeg-static', { path: ffmpegStatic });
|
|
11
|
+
return ffmpegStatic;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the path to the ffprobe binary
|
|
16
|
+
* @returns {Promise<string>} Path to ffprobe
|
|
17
|
+
*/
|
|
18
|
+
export async function getFfprobePath() {
|
|
19
|
+
logger.debug('Getting ffprobe path from ffprobe-static', { path: ffprobeStatic.path });
|
|
20
|
+
return ffprobeStatic.path;
|
|
21
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
export const ENV = process.env.NODE_ENV || 'production';
|
|
9
|
+
|
|
10
|
+
export const auth0Config = {
|
|
11
|
+
domain: 'replayable.us.auth0.com',
|
|
12
|
+
clientId: 'aYo59XVgKhhfrY9lFb35quLdMtF2j6WJ',
|
|
13
|
+
audience: 'https://replayable.us.auth0.com/api/v2/',
|
|
14
|
+
scopes: 'given_name profile email offline_access',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const apiEndpoints = {
|
|
18
|
+
development: process.env.API_ENDPOINT || 'http://localhost:3000',
|
|
19
|
+
staging: 'https://replayable-api-staging.herokuapp.com',
|
|
20
|
+
production: 'https://api.testdriver.ai'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const API_ENDPOINT = apiEndpoints[ENV];
|
|
24
|
+
|
|
25
|
+
// App configuration
|
|
26
|
+
export const APP = {
|
|
27
|
+
id: 'dashcam-cli',
|
|
28
|
+
name: ENV === 'production' ? 'Dashcam CLI' : `Dashcam CLI - ${ENV}`,
|
|
29
|
+
version: process.env.npm_package_version || '1.0.0',
|
|
30
|
+
configDir: join(homedir(), '.dashcam'),
|
|
31
|
+
logsDir: join(homedir(), '.dashcam', 'logs'),
|
|
32
|
+
recordingsDir: join(homedir(), '.dashcam', 'recordings'),
|
|
33
|
+
minRecordingDuration: 3000 // 3 seconds, matching desktop
|
|
34
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import maskSensitiveData from 'mask-sensitive-data';
|
|
2
|
+
|
|
3
|
+
const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
4
|
+
|
|
5
|
+
const shouldCountEvent = (eventType) => {
|
|
6
|
+
return ['LOG_ERROR', 'LOG_EVENT', 'NETWORK_BEFORE_REQUEST'].includes(
|
|
7
|
+
eventType
|
|
8
|
+
);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const eventTypeToStatType = {
|
|
12
|
+
LOG_EVENT: 'logs',
|
|
13
|
+
LOG_ERROR: 'errors',
|
|
14
|
+
NETWORK_BEFORE_REQUEST: 'network',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const verifyPattern = (pattern, str = '') => {
|
|
18
|
+
if (typeof pattern !== 'string' || typeof str !== 'string')
|
|
19
|
+
throw new Error(
|
|
20
|
+
`verifyPattern expects two string arguments but instead got "pattern" of type ${typeof pattern} and "str" of type ${typeof str}`
|
|
21
|
+
);
|
|
22
|
+
return new RegExp(
|
|
23
|
+
'^' + pattern.split('*').map(escapeRegExp).join('.*'),
|
|
24
|
+
'i'
|
|
25
|
+
).test(str);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const updateTabsState = (event, tabs) => {
|
|
29
|
+
const { type, payload } = event;
|
|
30
|
+
switch (type) {
|
|
31
|
+
case 'INITIAL_TABS':
|
|
32
|
+
tabs = payload.reduce((tabs, tab) => {
|
|
33
|
+
if (tab.url) tabs[tab.tabId] = { ...tab, previousUrl: '' };
|
|
34
|
+
return tabs;
|
|
35
|
+
}, {});
|
|
36
|
+
break;
|
|
37
|
+
case 'TAB_REMOVED':
|
|
38
|
+
delete tabs[payload.tabId];
|
|
39
|
+
break;
|
|
40
|
+
case 'TAB_ACTIVATED':
|
|
41
|
+
tabs[payload.tabId] ??= payload;
|
|
42
|
+
case 'NAVIGATION_STARTED':
|
|
43
|
+
case 'NAVIGATION_COMPLETED':
|
|
44
|
+
if (tabs[payload.tabId] && tabs[payload.tabId].url !== payload.url) {
|
|
45
|
+
tabs[payload.tabId].previousUrl = tabs[payload.tabId].url;
|
|
46
|
+
tabs[payload.tabId].url = payload.url;
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
if (tabs[payload.tabId]) tabs[payload.tabId].previousUrl = '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return tabs;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function sanitizeWebLogEventPayload(obj) {
|
|
57
|
+
let result = obj;
|
|
58
|
+
if (obj === null || obj === undefined) {
|
|
59
|
+
} else if (typeof obj === 'string')
|
|
60
|
+
result = maskSensitiveData.default(obj, {
|
|
61
|
+
...maskSensitiveData.defaultMaskOptions,
|
|
62
|
+
jwtPattern: /\b(?:[A-Za-z0-9\-_=]{40,}|[A-Fa-f0-9\-_=]{40,})\b/g,
|
|
63
|
+
});
|
|
64
|
+
else if (Array.isArray(obj)) {
|
|
65
|
+
result = obj.map((element) => sanitizeWebLogEventPayload(element));
|
|
66
|
+
} else if (typeof obj === 'object') {
|
|
67
|
+
result = Object.entries(obj).reduce((result, [key, value]) => {
|
|
68
|
+
if (!key.toLowerCase().includes('url'))
|
|
69
|
+
result[key] = sanitizeWebLogEventPayload(value);
|
|
70
|
+
else result[key] = value;
|
|
71
|
+
return result;
|
|
72
|
+
}, {});
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function filterWebEvents(
|
|
78
|
+
events,
|
|
79
|
+
groupLogsStatuses,
|
|
80
|
+
startMs = events[0]?.time ?? 0,
|
|
81
|
+
endMs = events[events.length - 1]?.time ?? 0
|
|
82
|
+
) {
|
|
83
|
+
const tempEvents = events.filter(
|
|
84
|
+
event => event.type === 'INITIAL_TABS' || event.payload.tabId
|
|
85
|
+
);
|
|
86
|
+
const patterns = groupLogsStatuses
|
|
87
|
+
.map((status) => status.items.map((item) => item.item))
|
|
88
|
+
.flat();
|
|
89
|
+
|
|
90
|
+
const newEvents = [];
|
|
91
|
+
let tabs = {};
|
|
92
|
+
let tracked;
|
|
93
|
+
let map = {};
|
|
94
|
+
|
|
95
|
+
tempEvents
|
|
96
|
+
.filter((event) => event.time <= startMs)
|
|
97
|
+
.forEach((event) => (tabs = updateTabsState(event, tabs)));
|
|
98
|
+
|
|
99
|
+
tempEvents.push({
|
|
100
|
+
type: 'INITIAL_TABS',
|
|
101
|
+
time: startMs,
|
|
102
|
+
payload: Object.values(tabs).map(({ previousUrl, ...tab }) => tab),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
for (const event of tempEvents.filter(
|
|
106
|
+
(event) => event.time >= startMs && event.time <= endMs
|
|
107
|
+
)) {
|
|
108
|
+
try {
|
|
109
|
+
switch (event.type) {
|
|
110
|
+
case 'NAVIGATION_STARTED':
|
|
111
|
+
case 'NAVIGATION_COMPLETED':
|
|
112
|
+
tracked = patterns.some((pattern) =>
|
|
113
|
+
verifyPattern(pattern, event.payload.url)
|
|
114
|
+
);
|
|
115
|
+
if (tracked) newEvents.push(event);
|
|
116
|
+
map[event.payload.tabId] = event.payload.url;
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case 'NETWORK_BEFORE_REQUEST':
|
|
120
|
+
tracked = patterns.some((pattern) =>
|
|
121
|
+
verifyPattern(pattern, map[event.payload.tabId])
|
|
122
|
+
);
|
|
123
|
+
if (tracked) newEvents.push(event);
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 'NETWORK_COMPLETED_REQUEST':
|
|
127
|
+
case 'NETWORK_ERROR_REQUEST':
|
|
128
|
+
const startedEvent = newEvents.find(
|
|
129
|
+
(e) =>
|
|
130
|
+
e.payload.requestId === event.payload.requestId &&
|
|
131
|
+
e.type === 'NETWORK_BEFORE_REQUEST'
|
|
132
|
+
);
|
|
133
|
+
if (startedEvent) newEvents.push(event);
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'NETWORK_RESPONSE_BODY':
|
|
137
|
+
const completedEvent = newEvents.find(
|
|
138
|
+
(e) =>
|
|
139
|
+
e.payload.requestId === event.payload.requestId &&
|
|
140
|
+
e.type === 'NETWORK_COMPLETED_REQUEST'
|
|
141
|
+
);
|
|
142
|
+
if (completedEvent) newEvents.push(event);
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case 'LOG_ERROR':
|
|
146
|
+
case 'LOG_EVENT':
|
|
147
|
+
case 'SPA_NAVIGATION':
|
|
148
|
+
tracked = patterns.some((pattern) =>
|
|
149
|
+
verifyPattern(pattern, map[event.payload.tabId])
|
|
150
|
+
);
|
|
151
|
+
if (tracked) newEvents.push(event);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case 'TAB_ACTIVATED':
|
|
155
|
+
tracked = patterns.some((pattern) =>
|
|
156
|
+
verifyPattern(pattern, event.payload.url)
|
|
157
|
+
);
|
|
158
|
+
if (tracked) {
|
|
159
|
+
map[event.payload.tabId] = event.payload.url;
|
|
160
|
+
newEvents.push(event);
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
case 'INITIAL_TABS':
|
|
165
|
+
newEvents.push(event);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error(err);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return newEvents;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export {
|
|
176
|
+
verifyPattern,
|
|
177
|
+
updateTabsState,
|
|
178
|
+
filterWebEvents,
|
|
179
|
+
shouldCountEvent,
|
|
180
|
+
eventTypeToStatType,
|
|
181
|
+
sanitizeWebLogEventPayload,
|
|
182
|
+
};
|