@flrande/bak-extension 0.6.0 → 0.6.2
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/dist/.bak-e2e-build-stamp +1 -1
- package/dist/background.global.js +692 -266
- package/dist/content.global.js +83 -22
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/public/manifest.json +1 -1
- package/src/background.ts +728 -202
- package/src/content.ts +93 -22
- package/src/network-debugger.ts +1 -1
- package/src/session-binding-storage.ts +50 -68
- package/src/{workspace.ts → session-binding.ts} +228 -213
package/src/content.ts
CHANGED
|
@@ -93,6 +93,13 @@ let longTaskDurationMs = 0;
|
|
|
93
93
|
let performanceBaselineMs = 0;
|
|
94
94
|
const pageLoadedAt = Math.round(performance.timeOrigin || Date.now());
|
|
95
95
|
let lastMutationAt = Date.now();
|
|
96
|
+
|
|
97
|
+
const DISCOVERY_GLOBAL_PATTERN = /(data|table|json|state|store|market|quote|flow|row|timestamp|snapshot|book|signal)/i;
|
|
98
|
+
|
|
99
|
+
interface TimestampCandidateMatch {
|
|
100
|
+
value: string;
|
|
101
|
+
context: string;
|
|
102
|
+
}
|
|
96
103
|
|
|
97
104
|
function isHtmlElement(node: Element | null): node is HTMLElement {
|
|
98
105
|
if (!node) {
|
|
@@ -1692,19 +1699,32 @@ function currentContextSnapshot(): { tabId: null; framePath: string[]; shadowPat
|
|
|
1692
1699
|
};
|
|
1693
1700
|
}
|
|
1694
1701
|
|
|
1695
|
-
function
|
|
1696
|
-
const regexes = (
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1702
|
+
function timestampCandidateMatchesFromText(text: string, patterns?: string[]): TimestampCandidateMatch[] {
|
|
1703
|
+
const regexes = (
|
|
1704
|
+
patterns ?? [
|
|
1705
|
+
String.raw`\b20\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b`,
|
|
1706
|
+
String.raw`\b20\d{2}-\d{2}-\d{2}\b`,
|
|
1707
|
+
String.raw`\b20\d{2}\/\d{2}\/\d{2}\b`
|
|
1708
|
+
]
|
|
1709
|
+
).map((pattern) => new RegExp(pattern, 'gi'));
|
|
1710
|
+
const collected = new Map<string, TimestampCandidateMatch>();
|
|
1700
1711
|
for (const regex of regexes) {
|
|
1701
1712
|
for (const match of text.matchAll(regex)) {
|
|
1702
|
-
|
|
1703
|
-
|
|
1713
|
+
const value = match[0];
|
|
1714
|
+
if (!value) {
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
const index = match.index ?? text.indexOf(value);
|
|
1718
|
+
const start = Math.max(0, index - 28);
|
|
1719
|
+
const end = Math.min(text.length, index + value.length + 28);
|
|
1720
|
+
const context = text.slice(start, end).replace(/\s+/g, ' ').trim();
|
|
1721
|
+
const key = `${value}::${context}`;
|
|
1722
|
+
if (!collected.has(key)) {
|
|
1723
|
+
collected.set(key, { value, context });
|
|
1704
1724
|
}
|
|
1705
1725
|
}
|
|
1706
1726
|
}
|
|
1707
|
-
return [...collected];
|
|
1727
|
+
return [...collected.values()];
|
|
1708
1728
|
}
|
|
1709
1729
|
|
|
1710
1730
|
function listInlineScripts(): Array<{ content: string; suspectedVars: string[] }> {
|
|
@@ -1712,9 +1732,9 @@ function listInlineScripts(): Array<{ content: string; suspectedVars: string[] }
|
|
|
1712
1732
|
.filter((script) => !script.src)
|
|
1713
1733
|
.map((script) => {
|
|
1714
1734
|
const content = script.textContent ?? '';
|
|
1715
|
-
const suspectedVars = [...content.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*
|
|
1716
|
-
(match) => match[1] ?? ''
|
|
1717
|
-
|
|
1735
|
+
const suspectedVars = [...content.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/g)]
|
|
1736
|
+
.map((match) => match[1] ?? '')
|
|
1737
|
+
.filter((candidate) => DISCOVERY_GLOBAL_PATTERN.test(candidate));
|
|
1718
1738
|
return { content, suspectedVars };
|
|
1719
1739
|
});
|
|
1720
1740
|
}
|
|
@@ -2028,6 +2048,20 @@ function runPageWorldRequest<T>(action: string, payload: Record<string, unknown>
|
|
|
2028
2048
|
}
|
|
2029
2049
|
return segments;
|
|
2030
2050
|
};
|
|
2051
|
+
const buildPathExpression = (path) => {
|
|
2052
|
+
const segments = parsePath(path);
|
|
2053
|
+
return segments
|
|
2054
|
+
.map((segment, index) => {
|
|
2055
|
+
if (typeof segment === 'number') {
|
|
2056
|
+
return '[' + segment + ']';
|
|
2057
|
+
}
|
|
2058
|
+
if (index === 0) {
|
|
2059
|
+
return segment;
|
|
2060
|
+
}
|
|
2061
|
+
return '.' + segment;
|
|
2062
|
+
})
|
|
2063
|
+
.join('');
|
|
2064
|
+
};
|
|
2031
2065
|
const readPath = (targetWindow, path) => {
|
|
2032
2066
|
const segments = parsePath(path);
|
|
2033
2067
|
let current = targetWindow;
|
|
@@ -2039,6 +2073,34 @@ function runPageWorldRequest<T>(action: string, payload: Record<string, unknown>
|
|
|
2039
2073
|
}
|
|
2040
2074
|
return current;
|
|
2041
2075
|
};
|
|
2076
|
+
const resolveExtractValue = (targetWindow, path, resolver) => {
|
|
2077
|
+
const strategy = resolver === 'globalThis' || resolver === 'lexical' ? resolver : 'auto';
|
|
2078
|
+
const lexicalExpression = buildPathExpression(path);
|
|
2079
|
+
const readLexical = () => {
|
|
2080
|
+
try {
|
|
2081
|
+
return targetWindow.eval(lexicalExpression);
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
if (error instanceof ReferenceError) {
|
|
2084
|
+
throw { code: 'E_NOT_FOUND', message: 'path not found: ' + path };
|
|
2085
|
+
}
|
|
2086
|
+
throw error;
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
if (strategy === 'globalThis') {
|
|
2090
|
+
return { resolver: 'globalThis', value: readPath(targetWindow, path) };
|
|
2091
|
+
}
|
|
2092
|
+
if (strategy === 'lexical') {
|
|
2093
|
+
return { resolver: 'lexical', value: readLexical() };
|
|
2094
|
+
}
|
|
2095
|
+
try {
|
|
2096
|
+
return { resolver: 'globalThis', value: readPath(targetWindow, path) };
|
|
2097
|
+
} catch (error) {
|
|
2098
|
+
if (!error || typeof error !== 'object' || error.code !== 'E_NOT_FOUND') {
|
|
2099
|
+
throw error;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return { resolver: 'lexical', value: readLexical() };
|
|
2103
|
+
};
|
|
2042
2104
|
const buildScopeTargets = () => {
|
|
2043
2105
|
if (payload.scope === 'main') {
|
|
2044
2106
|
return [{
|
|
@@ -2058,7 +2120,7 @@ function runPageWorldRequest<T>(action: string, payload: Record<string, unknown>
|
|
|
2058
2120
|
};
|
|
2059
2121
|
const toResult = async (target) => {
|
|
2060
2122
|
if (action === 'globals') {
|
|
2061
|
-
const keys = Object.keys(target.targetWindow).filter((key) =>
|
|
2123
|
+
const keys = Object.keys(target.targetWindow).filter((key) => ${DISCOVERY_GLOBAL_PATTERN}.test(key)).slice(0, 50);
|
|
2062
2124
|
return { url: target.url, framePath: target.framePath, value: keys };
|
|
2063
2125
|
}
|
|
2064
2126
|
if (action === 'eval') {
|
|
@@ -2066,8 +2128,9 @@ function runPageWorldRequest<T>(action: string, payload: Record<string, unknown>
|
|
|
2066
2128
|
return { url: target.url, framePath: target.framePath, value: serialized.value, bytes: serialized.bytes };
|
|
2067
2129
|
}
|
|
2068
2130
|
if (action === 'extract') {
|
|
2069
|
-
const
|
|
2070
|
-
|
|
2131
|
+
const extracted = resolveExtractValue(target.targetWindow, payload.path, payload.resolver);
|
|
2132
|
+
const serialized = serializeValue(extracted.value, payload.maxBytes);
|
|
2133
|
+
return { url: target.url, framePath: target.framePath, value: serialized.value, bytes: serialized.bytes, resolver: extracted.resolver };
|
|
2071
2134
|
}
|
|
2072
2135
|
if (action === 'fetch') {
|
|
2073
2136
|
const headers = { ...(payload.headers || {}) };
|
|
@@ -2193,6 +2256,7 @@ function pageEval(expr: string, params: Record<string, unknown>): Promise<{ scop
|
|
|
2193
2256
|
function pageExtract(path: string, params: Record<string, unknown>): Promise<{ scope: PageExecutionScope; result?: PageFrameResult<unknown>; results?: Array<PageFrameResult<unknown>> }> {
|
|
2194
2257
|
return runPageWorldRequest('extract', {
|
|
2195
2258
|
path,
|
|
2259
|
+
resolver: typeof params.resolver === 'string' ? params.resolver : undefined,
|
|
2196
2260
|
scope: resolveScope(params),
|
|
2197
2261
|
maxBytes: typeof params.maxBytes === 'number' ? params.maxBytes : undefined,
|
|
2198
2262
|
framePath: pageWorldFramePath()
|
|
@@ -2216,7 +2280,7 @@ function pageFetch(params: Record<string, unknown>): Promise<{ scope: PageExecut
|
|
|
2216
2280
|
|
|
2217
2281
|
async function globalsPreview(): Promise<string[]> {
|
|
2218
2282
|
return Object.keys(window)
|
|
2219
|
-
.filter((key) =>
|
|
2283
|
+
.filter((key) => DISCOVERY_GLOBAL_PATTERN.test(key))
|
|
2220
2284
|
.slice(0, 50);
|
|
2221
2285
|
}
|
|
2222
2286
|
|
|
@@ -2230,23 +2294,30 @@ async function collectInspectionState(params: Record<string, unknown> = {}): Pro
|
|
|
2230
2294
|
const visibleText = pageTextChunks(root, 40, 320);
|
|
2231
2295
|
const combinedText = visibleText.map((chunk) => chunk.text).join('\n');
|
|
2232
2296
|
const scripts = listInlineScripts();
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2297
|
+
const visibleTimestampCandidates = timestampCandidateMatchesFromText(
|
|
2298
|
+
combinedText,
|
|
2299
|
+
Array.isArray(params.patterns) ? params.patterns.map(String) : undefined
|
|
2300
|
+
);
|
|
2301
|
+
const inlineTimestampCandidates = timestampCandidateMatchesFromText(
|
|
2235
2302
|
scripts.map((script) => script.content).join('\n'),
|
|
2236
2303
|
Array.isArray(params.patterns) ? params.patterns.map(String) : undefined
|
|
2237
2304
|
);
|
|
2305
|
+
const previewGlobals = await globalsPreview();
|
|
2306
|
+
const suspiciousGlobals = [...new Set(scripts.flatMap((script) => script.suspectedVars))].slice(0, 50);
|
|
2238
2307
|
return {
|
|
2239
2308
|
url: metadata.url,
|
|
2240
2309
|
title: metadata.title,
|
|
2241
2310
|
html: redactHtmlSnapshot(document.documentElement),
|
|
2242
2311
|
visibleText,
|
|
2243
|
-
visibleTimestamps,
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2312
|
+
visibleTimestamps: visibleTimestampCandidates.map((candidate) => candidate.value),
|
|
2313
|
+
visibleTimestampCandidates,
|
|
2314
|
+
inlineTimestamps: inlineTimestampCandidates.map((candidate) => candidate.value),
|
|
2315
|
+
inlineTimestampCandidates,
|
|
2316
|
+
suspiciousGlobals,
|
|
2317
|
+
globalsPreview: previewGlobals,
|
|
2247
2318
|
scripts: {
|
|
2248
2319
|
inlineCount: scripts.length,
|
|
2249
|
-
suspectedDataVars:
|
|
2320
|
+
suspectedDataVars: suspiciousGlobals
|
|
2250
2321
|
},
|
|
2251
2322
|
storage: storageMetadata(),
|
|
2252
2323
|
cookies: cookieMetadata(),
|
package/src/network-debugger.ts
CHANGED
|
@@ -446,7 +446,7 @@ export function exportHar(tabId: number, limit = MAX_ENTRIES): Record<string, un
|
|
|
446
446
|
version: '1.2',
|
|
447
447
|
creator: {
|
|
448
448
|
name: 'bak',
|
|
449
|
-
version: '0.6.
|
|
449
|
+
version: '0.6.1'
|
|
450
450
|
},
|
|
451
451
|
entries: entries.map((entry) => ({
|
|
452
452
|
startedDateTime: new Date(entry.startedAt ?? entry.ts).toISOString(),
|
|
@@ -1,68 +1,50 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export const STORAGE_KEY_SESSION_BINDINGS = 'sessionBindings';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
typeof candidate.
|
|
14
|
-
|
|
15
|
-
(typeof candidate.
|
|
16
|
-
(typeof candidate.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (current.found) {
|
|
52
|
-
return current.map;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const legacyMap = normalizeWorkspaceRecordMap(stored[LEGACY_STORAGE_KEY_WORKSPACES]);
|
|
56
|
-
if (legacyMap.found) {
|
|
57
|
-
return legacyMap.map;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const legacySingle = stored[LEGACY_STORAGE_KEY_WORKSPACE];
|
|
61
|
-
if (isWorkspaceRecord(legacySingle)) {
|
|
62
|
-
return {
|
|
63
|
-
[legacySingle.id]: cloneWorkspaceRecord(legacySingle)
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {};
|
|
68
|
-
}
|
|
1
|
+
import type { SessionBindingRecord } from './session-binding.js';
|
|
2
|
+
|
|
3
|
+
export const STORAGE_KEY_SESSION_BINDINGS = 'sessionBindings';
|
|
4
|
+
|
|
5
|
+
function isSessionBindingRecord(value: unknown): value is SessionBindingRecord {
|
|
6
|
+
if (typeof value !== 'object' || value === null) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const candidate = value as Record<string, unknown>;
|
|
10
|
+
return (
|
|
11
|
+
typeof candidate.id === 'string' &&
|
|
12
|
+
Array.isArray(candidate.tabIds) &&
|
|
13
|
+
(typeof candidate.windowId === 'number' || candidate.windowId === null) &&
|
|
14
|
+
(typeof candidate.groupId === 'number' || candidate.groupId === null) &&
|
|
15
|
+
(typeof candidate.activeTabId === 'number' || candidate.activeTabId === null) &&
|
|
16
|
+
(typeof candidate.primaryTabId === 'number' || candidate.primaryTabId === null)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function cloneSessionBindingRecord(state: SessionBindingRecord): SessionBindingRecord {
|
|
21
|
+
return {
|
|
22
|
+
...state,
|
|
23
|
+
tabIds: [...state.tabIds]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeSessionBindingRecordMap(value: unknown): { found: boolean; map: Record<string, SessionBindingRecord> } {
|
|
28
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
29
|
+
return {
|
|
30
|
+
found: false,
|
|
31
|
+
map: {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const normalizedEntries: Array<readonly [string, SessionBindingRecord]> = [];
|
|
35
|
+
for (const [bindingId, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
36
|
+
if (!isSessionBindingRecord(entry)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
normalizedEntries.push([bindingId, cloneSessionBindingRecord(entry)] as const);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
found: true,
|
|
43
|
+
map: Object.fromEntries(normalizedEntries)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveSessionBindingStateMap(stored: Record<string, unknown>): Record<string, SessionBindingRecord> {
|
|
48
|
+
const current = normalizeSessionBindingRecordMap(stored[STORAGE_KEY_SESSION_BINDINGS]);
|
|
49
|
+
return current.found ? current.map : {};
|
|
50
|
+
}
|