unbrowse 3.1.0 → 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/dist/cli.js +455 -96
- package/dist/index.js +2 -6
- package/dist/mcp.js +695 -46
- package/dist/server.js +25811 -0
- package/package.json +1 -2
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/manifest.json +7 -10
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/analytics-session.ts +0 -55
- package/runtime-src/api/browse-index.ts +0 -317
- package/runtime-src/api/browse-session.ts +0 -572
- package/runtime-src/api/browse-submit-prereqs.ts +0 -48
- package/runtime-src/api/browse-submit.ts +0 -1184
- package/runtime-src/api/routes.ts +0 -1823
- package/runtime-src/auth/browser-cookies.ts +0 -423
- package/runtime-src/auth/index.ts +0 -535
- package/runtime-src/auth/runtime.ts +0 -116
- package/runtime-src/browser/index.ts +0 -659
- package/runtime-src/browser/types.ts +0 -41
- package/runtime-src/build-info.generated.ts +0 -6
- package/runtime-src/capture/index.ts +0 -1794
- package/runtime-src/capture/prefetch.ts +0 -95
- package/runtime-src/capture/rsc.ts +0 -45
- package/runtime-src/cli/shortcuts.ts +0 -273
- package/runtime-src/cli.ts +0 -1572
- package/runtime-src/client/graph-client.ts +0 -100
- package/runtime-src/client/index.ts +0 -1425
- package/runtime-src/debug-trace.ts +0 -18
- package/runtime-src/domain.ts +0 -38
- package/runtime-src/execution/index.ts +0 -3397
- package/runtime-src/execution/retry.ts +0 -46
- package/runtime-src/execution/robots.ts +0 -167
- package/runtime-src/execution/search-forms.ts +0 -188
- package/runtime-src/extraction/index.ts +0 -1507
- package/runtime-src/foundry/publish-bundle.ts +0 -392
- package/runtime-src/graph/agent-augment.ts +0 -315
- package/runtime-src/graph/index.ts +0 -1524
- package/runtime-src/graph/local-fixtures.ts +0 -393
- package/runtime-src/graph/local-harness.ts +0 -646
- package/runtime-src/graph/planner.ts +0 -411
- package/runtime-src/graph/session.ts +0 -294
- package/runtime-src/graph/trace-store.ts +0 -136
- package/runtime-src/index.ts +0 -24
- package/runtime-src/indexer/index.ts +0 -465
- package/runtime-src/intent-match.ts +0 -1515
- package/runtime-src/kuri/client.ts +0 -1839
- package/runtime-src/logger.ts +0 -30
- package/runtime-src/marketplace/index.ts +0 -103
- package/runtime-src/mcp.ts +0 -1747
- package/runtime-src/orchestrator/browser-agent.ts +0 -374
- package/runtime-src/orchestrator/dag-advisor.ts +0 -59
- package/runtime-src/orchestrator/dag-feedback.ts +0 -257
- package/runtime-src/orchestrator/first-pass-action.ts +0 -403
- package/runtime-src/orchestrator/index.ts +0 -4480
- package/runtime-src/orchestrator/passive-publish.ts +0 -187
- package/runtime-src/orchestrator/timing-economics.ts +0 -80
- package/runtime-src/payments/cascade.ts +0 -137
- package/runtime-src/payments/index.ts +0 -270
- package/runtime-src/payments/lobster-pay.ts +0 -182
- package/runtime-src/payments/wallet.ts +0 -98
- package/runtime-src/publish/review-context.ts +0 -93
- package/runtime-src/publish/sanitize.ts +0 -197
- package/runtime-src/publish/schema-review.ts +0 -192
- package/runtime-src/publish-admission.ts +0 -388
- package/runtime-src/ratelimit/index.ts +0 -23
- package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
- package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
- package/runtime-src/reverse-engineer/index.ts +0 -1551
- package/runtime-src/router.ts +0 -17
- package/runtime-src/routing-telemetry.ts +0 -395
- package/runtime-src/runtime/browser-access.ts +0 -11
- package/runtime-src/runtime/browser-auth.ts +0 -12
- package/runtime-src/runtime/browser-host.ts +0 -48
- package/runtime-src/runtime/lifecycle.ts +0 -17
- package/runtime-src/runtime/local-server.ts +0 -311
- package/runtime-src/runtime/paths.ts +0 -99
- package/runtime-src/runtime/setup.ts +0 -251
- package/runtime-src/runtime/supervisor.ts +0 -69
- package/runtime-src/runtime/update-hints.ts +0 -351
- package/runtime-src/server.ts +0 -100
- package/runtime-src/session-logs.ts +0 -142
- package/runtime-src/settings.ts +0 -221
- package/runtime-src/single-binary.ts +0 -143
- package/runtime-src/site-policy.ts +0 -54
- package/runtime-src/stale-cleanup-runner.ts +0 -144
- package/runtime-src/stale-cleanup.ts +0 -133
- package/runtime-src/telemetry-attribution.ts +0 -120
- package/runtime-src/telemetry.ts +0 -253
- package/runtime-src/template-params.ts +0 -141
- package/runtime-src/transform/drift.ts +0 -60
- package/runtime-src/transform/index.ts +0 -277
- package/runtime-src/types/index.ts +0 -1
- package/runtime-src/types/skill.ts +0 -912
- package/runtime-src/vault/index.ts +0 -196
- package/runtime-src/verification/auth-gate.ts +0 -8
- package/runtime-src/verification/candidates.ts +0 -27
- package/runtime-src/verification/index.ts +0 -120
- package/runtime-src/verification/matrix.ts +0 -30
- package/runtime-src/version.ts +0 -148
- package/runtime-src/workflow/artifact.ts +0 -161
- package/runtime-src/workflow/compile.ts +0 -808
- package/runtime-src/workflow/publish.ts +0 -225
- package/runtime-src/workflow/runtime.ts +0 -213
- package/vendor/kuri/win-x64/kuri.exe +0 -0
|
@@ -1,572 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { getKuriErrorMessage } from "../kuri/client.js";
|
|
3
|
-
|
|
4
|
-
export interface BrowseSession {
|
|
5
|
-
sessionId: string;
|
|
6
|
-
tabId: string;
|
|
7
|
-
url: string;
|
|
8
|
-
harActive: boolean;
|
|
9
|
-
domain: string;
|
|
10
|
-
brokerPort?: number;
|
|
11
|
-
client?: BrowseSessionClient;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface BrowseTabRef {
|
|
15
|
-
id: string;
|
|
16
|
-
url?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface BrowseSessionClient {
|
|
20
|
-
start(): Promise<void>;
|
|
21
|
-
newTab(): Promise<string>;
|
|
22
|
-
harStart(tabId: string): Promise<void>;
|
|
23
|
-
closeTab(tabId: string): Promise<void>;
|
|
24
|
-
discoverTabs(): Promise<BrowseTabRef[]>;
|
|
25
|
-
getCurrentUrl(tabId: string): Promise<string>;
|
|
26
|
-
getPort?(): number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type BrowseSessionErrorCode =
|
|
30
|
-
| "no_active_session"
|
|
31
|
-
| "session_id_required"
|
|
32
|
-
| "session_not_found"
|
|
33
|
-
| "session_expired";
|
|
34
|
-
|
|
35
|
-
export class BrowseSessionError extends Error {
|
|
36
|
-
code: BrowseSessionErrorCode;
|
|
37
|
-
statusCode: number;
|
|
38
|
-
|
|
39
|
-
constructor(code: BrowseSessionErrorCode, message?: string) {
|
|
40
|
-
super(message ?? code);
|
|
41
|
-
this.name = "BrowseSessionError";
|
|
42
|
-
this.code = code;
|
|
43
|
-
this.statusCode = browseSessionErrorStatus(code);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const RECOVERABLE_BROWSE_FAILURES = [
|
|
48
|
-
"cdp command failed",
|
|
49
|
-
"transport closed",
|
|
50
|
-
"target closed",
|
|
51
|
-
"tab not found",
|
|
52
|
-
"session closed",
|
|
53
|
-
"connection refused",
|
|
54
|
-
"unable to connect",
|
|
55
|
-
"operation was aborted",
|
|
56
|
-
"aborterror",
|
|
57
|
-
"execution context was destroyed",
|
|
58
|
-
"cannot find context with specified id",
|
|
59
|
-
"no such target",
|
|
60
|
-
"socket connection was closed unexpectedly",
|
|
61
|
-
"econnreset",
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
const LIVE_CHECK_RETRIES = 8;
|
|
65
|
-
const LIVE_CHECK_RETRY_DELAY_MS = 250;
|
|
66
|
-
|
|
67
|
-
const sessionQueues = new Map<string, Promise<void>>();
|
|
68
|
-
|
|
69
|
-
function browseSessionErrorStatus(code: BrowseSessionErrorCode): number {
|
|
70
|
-
switch (code) {
|
|
71
|
-
case "session_not_found":
|
|
72
|
-
return 404;
|
|
73
|
-
case "no_active_session":
|
|
74
|
-
return 404;
|
|
75
|
-
case "session_id_required":
|
|
76
|
-
return 409;
|
|
77
|
-
case "session_expired":
|
|
78
|
-
return 409;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function extractBrowseFailureMessage(value: unknown): string | null {
|
|
83
|
-
return typeof value === "string" ? value : getKuriErrorMessage(value);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function isRecoverableBrowseFailure(value: unknown): boolean {
|
|
87
|
-
const message = extractBrowseFailureMessage(value);
|
|
88
|
-
if (!message) return false;
|
|
89
|
-
const normalized = message.toLowerCase();
|
|
90
|
-
return RECOVERABLE_BROWSE_FAILURES.some((needle) => normalized.includes(needle));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function normalizeSessionId(sessionId?: string): string {
|
|
94
|
-
return sessionId?.trim() || randomUUID();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function extractDomain(url: string | undefined): string {
|
|
98
|
-
if (!url) return "";
|
|
99
|
-
try {
|
|
100
|
-
return new URL(url).hostname.replace(/^www\./, "");
|
|
101
|
-
} catch {
|
|
102
|
-
return "";
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function normalizeBrowseUrl(url: string | undefined): URL | null {
|
|
107
|
-
if (!url) return null;
|
|
108
|
-
try {
|
|
109
|
-
return new URL(url);
|
|
110
|
-
} catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function normalizeBrowsePathname(pathname: string): string {
|
|
116
|
-
if (!pathname) return "/";
|
|
117
|
-
return pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function matchesPreferredBrowseTab(tabUrl: string | undefined, preferredUrl: string | undefined): boolean {
|
|
121
|
-
const candidate = normalizeBrowseUrl(tabUrl);
|
|
122
|
-
const preferred = normalizeBrowseUrl(preferredUrl);
|
|
123
|
-
if (!candidate || !preferred) return false;
|
|
124
|
-
if (candidate.hostname.replace(/^www\./, "") !== preferred.hostname.replace(/^www\./, "")) return false;
|
|
125
|
-
return normalizeBrowsePathname(candidate.pathname) === normalizeBrowsePathname(preferred.pathname);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function matchesPreferredBrowseDomain(tabUrl: string | undefined, preferredUrl: string | undefined): boolean {
|
|
129
|
-
const candidate = normalizeBrowseUrl(tabUrl);
|
|
130
|
-
const preferred = normalizeBrowseUrl(preferredUrl);
|
|
131
|
-
if (!candidate || !preferred) return false;
|
|
132
|
-
return candidate.hostname.replace(/^www\./, "") === preferred.hostname.replace(/^www\./, "");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function isPlaceholderBrowseUrl(url: string | undefined): boolean {
|
|
136
|
-
if (!url) return true;
|
|
137
|
-
const normalized = url.trim().toLowerCase();
|
|
138
|
-
return normalized === "about:blank"
|
|
139
|
-
|| normalized.startsWith("chrome://newtab")
|
|
140
|
-
|| normalized.startsWith("chrome://new-tab-page")
|
|
141
|
-
|| normalized.startsWith("edge://newtab");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function hasMeaningfulBrowseUrl(url: string | undefined): boolean {
|
|
145
|
-
return hasKnownBrowseUrl(url) && !isPlaceholderBrowseUrl(url);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function cleanupSessionQueue(sessionId: string): void {
|
|
149
|
-
sessionQueues.delete(sessionId);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function sleep(ms: number): Promise<void> {
|
|
153
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function hasKnownBrowseUrl(value: string | undefined): boolean {
|
|
157
|
-
return typeof value === "string" && value.length > 0;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function resolveSessionClient(session: BrowseSession | undefined, fallback: BrowseSessionClient): BrowseSessionClient {
|
|
161
|
-
const fallbackPort = fallback.getPort?.();
|
|
162
|
-
if (session?.brokerPort !== undefined && fallbackPort === session.brokerPort) return fallback;
|
|
163
|
-
return session?.client ?? fallback;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function createRegisteredBrowseSession(
|
|
167
|
-
sessions: Map<string, BrowseSession>,
|
|
168
|
-
input: {
|
|
169
|
-
sessionId?: string;
|
|
170
|
-
tabId: string;
|
|
171
|
-
url: string;
|
|
172
|
-
domain: string;
|
|
173
|
-
harActive?: boolean;
|
|
174
|
-
brokerPort?: number;
|
|
175
|
-
client?: BrowseSessionClient;
|
|
176
|
-
},
|
|
177
|
-
): BrowseSession {
|
|
178
|
-
const existing = [...sessions.values()].find((session) => session.tabId === input.tabId);
|
|
179
|
-
if (existing) {
|
|
180
|
-
existing.url = input.url;
|
|
181
|
-
existing.domain = input.domain;
|
|
182
|
-
existing.harActive = input.harActive ?? existing.harActive;
|
|
183
|
-
existing.brokerPort = input.brokerPort ?? existing.brokerPort;
|
|
184
|
-
existing.client = input.client ?? existing.client;
|
|
185
|
-
return existing;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const sessionId = normalizeSessionId(input.sessionId);
|
|
189
|
-
const session: BrowseSession = {
|
|
190
|
-
sessionId,
|
|
191
|
-
tabId: input.tabId,
|
|
192
|
-
url: input.url,
|
|
193
|
-
domain: input.domain,
|
|
194
|
-
harActive: input.harActive ?? true,
|
|
195
|
-
brokerPort: input.brokerPort,
|
|
196
|
-
client: input.client,
|
|
197
|
-
};
|
|
198
|
-
sessions.set(sessionId, session);
|
|
199
|
-
return session;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function removeBrowseSession(
|
|
203
|
-
sessions: Map<string, BrowseSession>,
|
|
204
|
-
sessionId: string,
|
|
205
|
-
): BrowseSession | undefined {
|
|
206
|
-
const existing = sessions.get(sessionId);
|
|
207
|
-
sessions.delete(sessionId);
|
|
208
|
-
cleanupSessionQueue(sessionId);
|
|
209
|
-
return existing;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function ownedTabIds(
|
|
213
|
-
sessions: Map<string, BrowseSession>,
|
|
214
|
-
excludeSessionId?: string,
|
|
215
|
-
): Set<string> {
|
|
216
|
-
return new Set(
|
|
217
|
-
[...sessions.values()]
|
|
218
|
-
.filter((session) => session.sessionId !== excludeSessionId)
|
|
219
|
-
.map((session) => session.tabId),
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async function createBrowseSession(
|
|
224
|
-
sessions: Map<string, BrowseSession>,
|
|
225
|
-
client: BrowseSessionClient,
|
|
226
|
-
injectInterceptor: (tabId: string) => Promise<unknown>,
|
|
227
|
-
sessionId?: string,
|
|
228
|
-
): Promise<BrowseSession> {
|
|
229
|
-
await client.start();
|
|
230
|
-
const tabId = await client.newTab();
|
|
231
|
-
if (!tabId) throw new Error("Failed to create browser tab");
|
|
232
|
-
await client.harStart(tabId).catch(() => {});
|
|
233
|
-
await injectInterceptor(tabId);
|
|
234
|
-
return createRegisteredBrowseSession(sessions, {
|
|
235
|
-
sessionId,
|
|
236
|
-
tabId,
|
|
237
|
-
url: "about:blank",
|
|
238
|
-
domain: "",
|
|
239
|
-
harActive: true,
|
|
240
|
-
brokerPort: client.getPort?.(),
|
|
241
|
-
client,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async function adoptExistingBrowseTab(
|
|
246
|
-
sessions: Map<string, BrowseSession>,
|
|
247
|
-
client: BrowseSessionClient,
|
|
248
|
-
injectInterceptor: (tabId: string) => Promise<unknown>,
|
|
249
|
-
preferredUrl?: string,
|
|
250
|
-
sessionId?: string,
|
|
251
|
-
): Promise<BrowseSession | null> {
|
|
252
|
-
try {
|
|
253
|
-
await client.start();
|
|
254
|
-
const tabs = await client.discoverTabs();
|
|
255
|
-
if (!preferredUrl) return null;
|
|
256
|
-
const reservedTabs = ownedTabIds(sessions, sessionId);
|
|
257
|
-
const candidate = tabs.find((tab) => {
|
|
258
|
-
if (!tab.id || reservedTabs.has(tab.id)) return false;
|
|
259
|
-
return matchesPreferredBrowseTab(tab.url, preferredUrl);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (!candidate?.id) return null;
|
|
263
|
-
await client.harStart(candidate.id).catch(() => {});
|
|
264
|
-
await injectInterceptor(candidate.id);
|
|
265
|
-
return createRegisteredBrowseSession(sessions, {
|
|
266
|
-
sessionId,
|
|
267
|
-
tabId: candidate.id,
|
|
268
|
-
url: candidate.url ?? "about:blank",
|
|
269
|
-
domain: extractDomain(candidate.url),
|
|
270
|
-
harActive: true,
|
|
271
|
-
brokerPort: client.getPort?.(),
|
|
272
|
-
client,
|
|
273
|
-
});
|
|
274
|
-
} catch {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function dropBrowseSession(
|
|
280
|
-
sessions: Map<string, BrowseSession>,
|
|
281
|
-
client: BrowseSessionClient,
|
|
282
|
-
session: BrowseSession | undefined,
|
|
283
|
-
): Promise<void> {
|
|
284
|
-
if (!session) return;
|
|
285
|
-
await resolveSessionClient(session, client).closeTab(session.tabId).catch(() => {});
|
|
286
|
-
removeBrowseSession(sessions, session.sessionId);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export async function isBrowseSessionLive(
|
|
290
|
-
session: BrowseSession,
|
|
291
|
-
client: BrowseSessionClient,
|
|
292
|
-
): Promise<boolean> {
|
|
293
|
-
if (!session.tabId) return false;
|
|
294
|
-
const sessionClient = resolveSessionClient(session, client);
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
await sessionClient.start();
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (isRecoverableBrowseFailure(error)) return false;
|
|
300
|
-
throw error;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
for (let attempt = 0; attempt < LIVE_CHECK_RETRIES; attempt += 1) {
|
|
304
|
-
try {
|
|
305
|
-
const tabs = await sessionClient.discoverTabs();
|
|
306
|
-
const exactTab = tabs.find((tab) => tab.id === session.tabId);
|
|
307
|
-
if (!exactTab) {
|
|
308
|
-
if (attempt < LIVE_CHECK_RETRIES - 1) {
|
|
309
|
-
await sleep(LIVE_CHECK_RETRY_DELAY_MS);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
session.brokerPort = sessionClient.getPort?.() ?? session.brokerPort;
|
|
315
|
-
session.client = sessionClient;
|
|
316
|
-
if (hasMeaningfulBrowseUrl(exactTab.url)) {
|
|
317
|
-
session.url = exactTab.url!;
|
|
318
|
-
session.domain = extractDomain(session.url);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
try {
|
|
322
|
-
const currentUrl = await sessionClient.getCurrentUrl(session.tabId);
|
|
323
|
-
if (hasMeaningfulBrowseUrl(currentUrl)) {
|
|
324
|
-
session.url = currentUrl;
|
|
325
|
-
session.domain = extractDomain(currentUrl);
|
|
326
|
-
session.brokerPort = sessionClient.getPort?.() ?? session.brokerPort;
|
|
327
|
-
session.client = sessionClient;
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
if (
|
|
331
|
-
isPlaceholderBrowseUrl(currentUrl)
|
|
332
|
-
&& isPlaceholderBrowseUrl(exactTab.url)
|
|
333
|
-
&& isPlaceholderBrowseUrl(session.url)
|
|
334
|
-
) {
|
|
335
|
-
session.url = currentUrl || exactTab.url || session.url;
|
|
336
|
-
session.domain = extractDomain(session.url);
|
|
337
|
-
session.brokerPort = sessionClient.getPort?.() ?? session.brokerPort;
|
|
338
|
-
session.client = sessionClient;
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
} catch (error) {
|
|
342
|
-
if (!isRecoverableBrowseFailure(error)) return false;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (hasMeaningfulBrowseUrl(session.url) || hasMeaningfulBrowseUrl(exactTab.url)) {
|
|
346
|
-
if (hasMeaningfulBrowseUrl(exactTab.url)) {
|
|
347
|
-
session.url = exactTab.url!;
|
|
348
|
-
session.domain = extractDomain(session.url);
|
|
349
|
-
}
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
} catch (error) {
|
|
353
|
-
if (!isRecoverableBrowseFailure(error)) return false;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (attempt < LIVE_CHECK_RETRIES - 1) await sleep(LIVE_CHECK_RETRY_DELAY_MS);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async function listLiveBrowseSessions(
|
|
363
|
-
sessions: Map<string, BrowseSession>,
|
|
364
|
-
client: BrowseSessionClient,
|
|
365
|
-
): Promise<BrowseSession[]> {
|
|
366
|
-
const live: BrowseSession[] = [];
|
|
367
|
-
const stale: string[] = [];
|
|
368
|
-
|
|
369
|
-
for (const session of sessions.values()) {
|
|
370
|
-
if (await isBrowseSessionLive(session, client)) {
|
|
371
|
-
live.push(session);
|
|
372
|
-
} else {
|
|
373
|
-
stale.push(session.sessionId);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
for (const sessionId of stale) removeBrowseSession(sessions, sessionId);
|
|
378
|
-
return live;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
export async function resolveRequestedBrowseSession(
|
|
382
|
-
sessions: Map<string, BrowseSession>,
|
|
383
|
-
client: BrowseSessionClient,
|
|
384
|
-
requestedSessionId?: string,
|
|
385
|
-
): Promise<BrowseSession> {
|
|
386
|
-
if (requestedSessionId) {
|
|
387
|
-
const session = sessions.get(requestedSessionId);
|
|
388
|
-
if (!session) throw new BrowseSessionError("session_not_found");
|
|
389
|
-
return session;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const live = await listLiveBrowseSessions(sessions, client);
|
|
393
|
-
if (live.length === 0) throw new BrowseSessionError("no_active_session");
|
|
394
|
-
if (live.length > 1) throw new BrowseSessionError("session_id_required");
|
|
395
|
-
return live[0];
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export async function getOrCreateBrowseSession(
|
|
399
|
-
sessions: Map<string, BrowseSession>,
|
|
400
|
-
client: BrowseSessionClient,
|
|
401
|
-
injectInterceptor: (tabId: string) => Promise<unknown>,
|
|
402
|
-
): Promise<BrowseSession> {
|
|
403
|
-
await client.start();
|
|
404
|
-
const existingSessions = [...sessions.values()];
|
|
405
|
-
if (existingSessions.length === 1) {
|
|
406
|
-
const existing = existingSessions[0];
|
|
407
|
-
if (await isBrowseSessionLive(existing, client)) return existing;
|
|
408
|
-
const preferredUrl = existing.url;
|
|
409
|
-
const targetSessionId = existing.sessionId;
|
|
410
|
-
const existingClient = resolveSessionClient(existing, client);
|
|
411
|
-
await dropBrowseSession(sessions, existingClient, existing);
|
|
412
|
-
const adopted = await adoptExistingBrowseTab(sessions, existingClient, injectInterceptor, preferredUrl, targetSessionId);
|
|
413
|
-
if (adopted) return adopted;
|
|
414
|
-
return createBrowseSession(sessions, existingClient, injectInterceptor, targetSessionId);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const live = await listLiveBrowseSessions(sessions, client);
|
|
418
|
-
if (live.length === 1) return live[0];
|
|
419
|
-
if (live.length > 1) throw new BrowseSessionError("session_id_required");
|
|
420
|
-
|
|
421
|
-
const existing = existingSessions[0];
|
|
422
|
-
const targetSessionId = existing?.sessionId;
|
|
423
|
-
const preferredUrl = existing?.url;
|
|
424
|
-
if (existing) await dropBrowseSession(sessions, client, existing);
|
|
425
|
-
const adopted = await adoptExistingBrowseTab(sessions, client, injectInterceptor, preferredUrl, targetSessionId);
|
|
426
|
-
if (adopted) return adopted;
|
|
427
|
-
return createBrowseSession(sessions, client, injectInterceptor, targetSessionId);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
export async function resetBrowseSession(
|
|
431
|
-
sessions: Map<string, BrowseSession>,
|
|
432
|
-
client: BrowseSessionClient,
|
|
433
|
-
injectInterceptor: (tabId: string) => Promise<unknown>,
|
|
434
|
-
sessionId?: string,
|
|
435
|
-
): Promise<BrowseSession> {
|
|
436
|
-
await client.start();
|
|
437
|
-
const existing = sessionId
|
|
438
|
-
? sessions.get(sessionId)
|
|
439
|
-
: [...sessions.values()][0];
|
|
440
|
-
const targetSessionId = sessionId ?? existing?.sessionId;
|
|
441
|
-
const preferredUrl = existing?.url;
|
|
442
|
-
const existingClient = resolveSessionClient(existing, client);
|
|
443
|
-
await dropBrowseSession(sessions, existingClient, existing);
|
|
444
|
-
const adopted = await adoptExistingBrowseTab(sessions, existingClient, injectInterceptor, preferredUrl, targetSessionId);
|
|
445
|
-
if (adopted) return adopted;
|
|
446
|
-
return createBrowseSession(sessions, existingClient, injectInterceptor, targetSessionId);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async function withSessionQueue<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {
|
|
450
|
-
const prev = sessionQueues.get(sessionId) ?? Promise.resolve();
|
|
451
|
-
let release!: () => void;
|
|
452
|
-
const waitTurn = new Promise<void>((resolve) => {
|
|
453
|
-
release = resolve;
|
|
454
|
-
});
|
|
455
|
-
const gate = prev.catch(() => {}).then(() => waitTurn);
|
|
456
|
-
sessionQueues.set(sessionId, gate);
|
|
457
|
-
await prev.catch(() => {});
|
|
458
|
-
try {
|
|
459
|
-
return await fn();
|
|
460
|
-
} finally {
|
|
461
|
-
release();
|
|
462
|
-
if (sessionQueues.get(sessionId) === gate) cleanupSessionQueue(sessionId);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
export async function withRecoveredBrowseSession<T>(
|
|
467
|
-
sessions: Map<string, BrowseSession>,
|
|
468
|
-
client: BrowseSessionClient,
|
|
469
|
-
injectInterceptor: (tabId: string) => Promise<unknown>,
|
|
470
|
-
run: (session: BrowseSession) => Promise<T>,
|
|
471
|
-
shouldReset?: (result: T) => boolean,
|
|
472
|
-
): Promise<{ session: BrowseSession; result: T; recovered: boolean }> {
|
|
473
|
-
let session = await getOrCreateBrowseSession(sessions, client, injectInterceptor);
|
|
474
|
-
|
|
475
|
-
try {
|
|
476
|
-
const result = await run(session);
|
|
477
|
-
if (!shouldReset || !shouldReset(result)) {
|
|
478
|
-
return { session, result, recovered: false };
|
|
479
|
-
}
|
|
480
|
-
} catch (error) {
|
|
481
|
-
if (!isRecoverableBrowseFailure(error)) throw error;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
session = await resetBrowseSession(sessions, client, injectInterceptor, session.sessionId);
|
|
485
|
-
const result = await run(session);
|
|
486
|
-
return { session, result, recovered: true };
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
export async function withSerializedRecoveredBrowseSession<T>(
|
|
490
|
-
sessions: Map<string, BrowseSession>,
|
|
491
|
-
client: BrowseSessionClient,
|
|
492
|
-
injectInterceptor: (tabId: string) => Promise<unknown>,
|
|
493
|
-
requestedSessionId: string | undefined,
|
|
494
|
-
run: (session: BrowseSession) => Promise<T>,
|
|
495
|
-
shouldReset?: (result: T) => boolean,
|
|
496
|
-
): Promise<{ session: BrowseSession; result: T; recovered: boolean }> {
|
|
497
|
-
const resolved = await resolveRequestedBrowseSession(sessions, client, requestedSessionId);
|
|
498
|
-
return withSessionQueue(resolved.sessionId, async () => {
|
|
499
|
-
let session = sessions.get(resolved.sessionId);
|
|
500
|
-
if (!session) throw new BrowseSessionError("session_expired");
|
|
501
|
-
const sessionClient = resolveSessionClient(session, client);
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
const result = await run(session);
|
|
505
|
-
if (!shouldReset || !shouldReset(result)) {
|
|
506
|
-
return { session, result, recovered: false };
|
|
507
|
-
}
|
|
508
|
-
} catch (error) {
|
|
509
|
-
if (!isRecoverableBrowseFailure(error)) throw error;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
session = await resetBrowseSession(sessions, sessionClient, injectInterceptor, resolved.sessionId);
|
|
513
|
-
const result = await run(session);
|
|
514
|
-
return { session, result, recovered: true };
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export async function withSerializedStrictBrowseSession<T>(
|
|
519
|
-
sessions: Map<string, BrowseSession>,
|
|
520
|
-
client: BrowseSessionClient,
|
|
521
|
-
requestedSessionId: string | undefined,
|
|
522
|
-
run: (session: BrowseSession) => Promise<T>,
|
|
523
|
-
shouldExpire?: (result: T) => boolean,
|
|
524
|
-
): Promise<{ session: BrowseSession; result: T; recovered: false }> {
|
|
525
|
-
const resolved = await resolveRequestedBrowseSession(sessions, client, requestedSessionId);
|
|
526
|
-
return withSessionQueue(resolved.sessionId, async () => {
|
|
527
|
-
const session = sessions.get(resolved.sessionId);
|
|
528
|
-
if (!session) throw new BrowseSessionError("session_expired");
|
|
529
|
-
|
|
530
|
-
const live = await isBrowseSessionLive(session, client);
|
|
531
|
-
if (!live) {
|
|
532
|
-
removeBrowseSession(sessions, resolved.sessionId);
|
|
533
|
-
throw new BrowseSessionError("session_expired");
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
try {
|
|
537
|
-
const result = await run(session);
|
|
538
|
-
if (shouldExpire?.(result)) {
|
|
539
|
-
const stillLive = await isBrowseSessionLive(session, client);
|
|
540
|
-
if (!stillLive) {
|
|
541
|
-
removeBrowseSession(sessions, resolved.sessionId);
|
|
542
|
-
throw new BrowseSessionError("session_expired");
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
return { session, result, recovered: false };
|
|
546
|
-
} catch (error) {
|
|
547
|
-
if (error instanceof BrowseSessionError) throw error;
|
|
548
|
-
if (isRecoverableBrowseFailure(error)) {
|
|
549
|
-
const stillLive = await isBrowseSessionLive(session, client);
|
|
550
|
-
if (!stillLive) {
|
|
551
|
-
removeBrowseSession(sessions, resolved.sessionId);
|
|
552
|
-
throw new BrowseSessionError("session_expired");
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
throw error;
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
export async function getOrCreateNavigateBrowseSession(
|
|
561
|
-
sessions: Map<string, BrowseSession>,
|
|
562
|
-
client: BrowseSessionClient,
|
|
563
|
-
injectInterceptor: (tabId: string) => Promise<unknown>,
|
|
564
|
-
requestedSessionId?: string,
|
|
565
|
-
): Promise<BrowseSession> {
|
|
566
|
-
if (requestedSessionId) {
|
|
567
|
-
const session = sessions.get(requestedSessionId);
|
|
568
|
-
if (!session) throw new BrowseSessionError("session_not_found");
|
|
569
|
-
return session;
|
|
570
|
-
}
|
|
571
|
-
return createBrowseSession(sessions, client, injectInterceptor);
|
|
572
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export const MONTH_INDEX: Record<string, string> = {
|
|
2
|
-
jan: "01",
|
|
3
|
-
january: "01",
|
|
4
|
-
feb: "02",
|
|
5
|
-
february: "02",
|
|
6
|
-
mar: "03",
|
|
7
|
-
march: "03",
|
|
8
|
-
apr: "04",
|
|
9
|
-
april: "04",
|
|
10
|
-
may: "05",
|
|
11
|
-
jun: "06",
|
|
12
|
-
june: "06",
|
|
13
|
-
jul: "07",
|
|
14
|
-
july: "07",
|
|
15
|
-
aug: "08",
|
|
16
|
-
august: "08",
|
|
17
|
-
sep: "09",
|
|
18
|
-
sept: "09",
|
|
19
|
-
september: "09",
|
|
20
|
-
oct: "10",
|
|
21
|
-
october: "10",
|
|
22
|
-
nov: "11",
|
|
23
|
-
november: "11",
|
|
24
|
-
dec: "12",
|
|
25
|
-
december: "12",
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export function inferCalendarIsoDate(monthLabel: string, dayLabel: string): string | null {
|
|
29
|
-
const day = Number.parseInt(dayLabel.trim(), 10);
|
|
30
|
-
if (!Number.isFinite(day) || day < 1 || day > 31) return null;
|
|
31
|
-
|
|
32
|
-
const year = monthLabel.match(/\b(\d{4})\b/)?.[1];
|
|
33
|
-
if (!year) return null;
|
|
34
|
-
|
|
35
|
-
const tokens = monthLabel
|
|
36
|
-
.toLowerCase()
|
|
37
|
-
.replace(/[^a-z0-9\s]+/g, " ")
|
|
38
|
-
.split(/\s+/)
|
|
39
|
-
.map((token) => token.trim())
|
|
40
|
-
.filter(Boolean);
|
|
41
|
-
|
|
42
|
-
const month = tokens
|
|
43
|
-
.map((token) => MONTH_INDEX[token] ?? MONTH_INDEX[token.slice(0, 3)])
|
|
44
|
-
.find(Boolean);
|
|
45
|
-
if (!month) return null;
|
|
46
|
-
|
|
47
|
-
return `${year}-${month}-${String(day).padStart(2, "0")}`;
|
|
48
|
-
}
|