openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
671 lines (670 loc) • 27.9 kB
JavaScript
import { c as normalizeOptionalLowercaseString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import "./string-coerce-runtime-GQa0ehRA.js";
import "./constants-DKjTvIBP.js";
import { C as resolveVolatileTabAlias, S as resolveDurableTabExact, T as browserSessionTabRouteKey, _ as hasVolatileTabAlias, a as getBrowserSessionTabStore, b as rememberVolatileTabAliases, c as parseBrowserSessionTabRecord, d as withoutBrowserSessionTabCleanup, f as clearDurableTabAliases, g as hasDurableTabExact, h as hasDurableTabAlias, i as deleteBrowserSessionTabIf, l as sameBrowserSessionTabRecord, m as forgetVolatileTabAlias, n as browserSessionTabStorageKey, o as getOptionalBrowserSessionTabStore, p as clearVolatileTabAliases, r as compareBrowserSessionTabProfileAliases, t as browserSessionTabNativeIdentity, u as updateBrowserSessionTab, v as hasVolatileTabExact, w as resolveVolatileTabExact, x as resolveDurableTabAlias, y as rememberDurableTabAliases } from "./session-tab-store-dEfDhf6J.js";
import { randomUUID } from "node:crypto";
//#region extensions/browser/src/browser/session-tab-process-state.ts
function normalizeBrowserSessionKey(value) {
return normalizeOptionalLowercaseString(value);
}
function volatileSessionTabTargetKey(identity) {
return `${identity.targetId}\u0000${browserSessionTabRouteKey(identity.route)}\u0000${identity.profile ?? ""}`;
}
function sameVolatileSessionTab(left, right) {
return volatileSessionTabTargetKey(left) === volatileSessionTabTargetKey(right) && left.sessionKey === right.sessionKey && left.trackedAt === right.trackedAt && left.lastUsedAt === right.lastUsedAt;
}
const volatileStateSymbol = Symbol.for("openclaw.browser.session-tabs.volatile");
const volatileCleanupStateSymbol = Symbol.for("openclaw.browser.session-tabs.volatile-cleanup");
const activeDurableStateSymbol = Symbol.for("openclaw.browser.session-tabs.active-durable-keys");
const coldNativeActivityStateSymbol = Symbol.for("openclaw.browser.session-tabs.cold-native-activity");
function activeDurableStorageKeys() {
const state = globalThis;
state[activeDurableStateSymbol] ??= /* @__PURE__ */ new Set();
return state[activeDurableStateSymbol];
}
function volatileTabsBySession() {
const state = globalThis;
state[volatileStateSymbol] ??= /* @__PURE__ */ new Map();
return state[volatileStateSymbol];
}
/** Keeps one in-flight volatile target close shared across Browser plugin bundles. */
function volatileTabCleanupByTarget() {
const state = globalThis;
state[volatileCleanupStateSymbol] ??= /* @__PURE__ */ new Map();
return state[volatileCleanupStateSymbol];
}
function volatileRegistrationsForTarget(targetKey) {
const result = [];
for (const tabs of volatileTabsBySession().values()) for (const tab of tabs.values()) if (volatileSessionTabTargetKey(tab) === targetKey) result.push(tab);
return result;
}
function deleteVolatileRegistrations(tabs) {
for (const tab of tabs) {
const targetKey = volatileSessionTabTargetKey(tab);
if ((volatileTabsBySession().get(tab.sessionKey)?.get(targetKey))?.registration === tab.registration) deleteVolatileSessionTab(tab.sessionKey, targetKey);
}
}
function deleteVolatileSessionTab(sessionKey, tabKey) {
const state = volatileTabsBySession();
const tabs = state.get(sessionKey);
tabs?.delete(tabKey);
clearVolatileTabAliases(sessionKey, tabKey);
if (tabs?.size === 0) state.delete(sessionKey);
}
function coldNativeActivity() {
const state = globalThis;
state[coldNativeActivityStateSymbol] ??= /* @__PURE__ */ new Map();
return state[coldNativeActivityStateSymbol];
}
function rememberColdNativeActivity(identity, now) {
coldNativeActivity().set(identity, now);
}
function forgetColdNativeActivity(identity) {
coldNativeActivity().delete(identity);
}
function readColdNativeActivity(identity) {
return coldNativeActivity().get(identity);
}
//#endregion
//#region extensions/browser/src/browser/session-tab-cleanup-claim.ts
/**
* Cleanup claim bookkeeping for durable session tabs. A claim marks a row as
* being closed by exactly one attempt, so a concurrent touch or a competing
* sweep cannot delete a row that another generation now owns.
*/
function isIgnorableTabCloseError(error) {
const message = normalizeLowercaseStringOrEmpty(String(error));
return message.includes("tab not found") || message.includes("target closed") || message.includes("target not found") || message.includes("no such target") || message.includes("no target with given id found");
}
function claimCleanup(tab, now, kind) {
const cleanupAttemptToken = randomUUID();
const cleanupKind = kind === "lifecycle" ? "lifecycle" : tab.cleanupKind ?? kind;
return updateBrowserSessionTab(tab.storageKey, (current) => {
const record = parseBrowserSessionTabRecord(current);
if (!record || !sameBrowserSessionTabRecord(record, tab)) return;
return {
...record,
cleanupRequestedAt: now,
cleanupAttemptToken,
cleanupKind
};
}) ? {
...tab,
cleanupRequestedAt: now,
cleanupAttemptToken,
cleanupKind
} : void 0;
}
function matchesCleanupAttempt(current, tab) {
return Boolean(current && current.cleanupAttemptToken === tab.cleanupAttemptToken && current.cleanupRequestedAt === tab.cleanupRequestedAt && current.cleanupKind === tab.cleanupKind && sameBrowserSessionTabRecord({
...current,
lastUsedAt: tab.lastUsedAt
}, tab));
}
function ownsCleanupAttempt(tab) {
return matchesCleanupAttempt(parseBrowserSessionTabRecord(getBrowserSessionTabStore().lookup(tab.storageKey)), tab);
}
function deleteClaimedTab(tab, onWarn) {
try {
if (deleteBrowserSessionTabIf(tab.storageKey, (current) => {
return matchesCleanupAttempt(parseBrowserSessionTabRecord(current), tab);
})) {
clearDurableTabAliases(tab.storageKey);
activeDurableStorageKeys().delete(tab.storageKey);
}
} catch (error) {
onWarn?.(`failed to delete tracked browser tab ${tab.nativeTargetId}: ${String(error)}`);
}
}
//#endregion
//#region extensions/browser/src/browser/session-tab-sweep-selection.ts
function trackedTabIdentity(tab) {
return tab.kind === "durable" ? `durable:${tab.storageKey}` : `volatile:${tab.sessionKey}:${volatileSessionTabTargetKey(tab)}`;
}
function selectTrackedTabsForSessions(params) {
const sessionKeys = new Set(params.sessionKeys.map((key) => normalizeBrowserSessionKey(key)).filter((key) => key !== void 0));
const volatile = [];
for (const sessionKey of sessionKeys) volatile.push(...volatileTabsBySession().get(sessionKey)?.values() ?? []);
return [...params.durable.filter((tab) => sessionKeys.has(tab.sessionKey)), ...volatile];
}
function selectStaleTrackedTabs(params) {
const selected = /* @__PURE__ */ new Map();
const activeBySession = /* @__PURE__ */ new Map();
const nativeIdentityCounts = /* @__PURE__ */ new Map();
const observedNativeActivity = /* @__PURE__ */ new Map();
for (const tab of params.tabs) {
if (tab.kind !== "durable" || tab.interactionTargetKind !== "native") continue;
const identity = browserSessionTabNativeIdentity(tab);
nativeIdentityCounts.set(identity, (nativeIdentityCounts.get(identity) ?? 0) + 1);
const observedAt = readColdNativeActivity(identity);
if (observedAt !== void 0) observedNativeActivity.set(tab.storageKey, observedAt);
}
const effectiveLastUsedAt = (tab) => tab.kind === "durable" ? Math.max(tab.lastUsedAt, observedNativeActivity.get(tab.storageKey) ?? 0) : tab.lastUsedAt;
for (const tab of params.tabs) {
const observedAt = tab.kind === "durable" ? observedNativeActivity.get(tab.storageKey) : void 0;
const isActiveDurable = tab.kind === "durable" && activeDurableStorageKeys().has(tab.storageKey);
const isUnambiguousNative = tab.kind === "durable" && tab.interactionTargetKind === "native" && nativeIdentityCounts.get(browserSessionTabNativeIdentity(tab)) === 1;
const activitySupersedesSweep = tab.kind === "durable" && tab.cleanupKind === "sweep" && observedAt !== void 0 && observedAt >= (tab.cleanupRequestedAt ?? 0);
if (tab.kind === "durable" && tab.cleanupAttemptToken && !activitySupersedesSweep) {
if (tab.cleanupKind === "lifecycle" || isActiveDurable || isUnambiguousNative) selected.set(trackedTabIdentity(tab), tab);
continue;
}
if (params.sessionFilter && !params.sessionFilter(tab.sessionKey)) continue;
if (tab.kind === "durable" && !isActiveDurable && (tab.interactionTargetKind === "opaque" || !isUnambiguousNative)) continue;
const active = activeBySession.get(tab.sessionKey) ?? [];
active.push(tab);
activeBySession.set(tab.sessionKey, active);
}
for (const tabs of activeBySession.values()) {
tabs.sort((left, right) => effectiveLastUsedAt(left) - effectiveLastUsedAt(right) || left.trackedAt - right.trackedAt);
if (params.idleMs && params.idleMs > 0) {
for (const tab of tabs) if (params.now - effectiveLastUsedAt(tab) >= params.idleMs) selected.set(trackedTabIdentity(tab), tab);
}
const remaining = tabs.filter((tab) => !selected.has(trackedTabIdentity(tab)));
if (params.maxTabsPerSession && params.maxTabsPerSession > 0 && remaining.length > params.maxTabsPerSession) for (const tab of remaining.slice(0, remaining.length - params.maxTabsPerSession)) selected.set(trackedTabIdentity(tab), tab);
}
return [...selected.values()];
}
//#endregion
//#region extensions/browser/src/browser/session-tab-untrack-selection.ts
function selectSessionTabToUntrack(params) {
if (params.volatileIsExact && !params.hasDurableExactCandidate) return "volatile";
if (params.durableIsExact && !params.hasVolatileExactCandidate) return "durable";
if (params.hasVolatileCandidate && params.hasDurableCandidate) return "ambiguous";
if (params.volatileAvailable) return "volatile";
if (params.durableAvailable) return "durable";
if (params.hasVolatileCandidate || params.hasDurableCandidate) return "ambiguous";
return "missing";
}
//#endregion
//#region extensions/browser/src/browser/session-tab-registry.ts
/**
* Session-owned browser tabs. Host-local durable ownership is canonical in
* plugin SQLite; all other tabs remain process-local.
*/
function normalizeProfile(value) {
return normalizeOptionalLowercaseString(value);
}
function normalizeProfileAliases(values) {
return [...new Set((values ?? []).map(normalizeProfile).filter((value) => Boolean(value)))].toSorted(compareBrowserSessionTabProfileAliases);
}
function resolveInteractionIdentity(params) {
const sessionKey = params.sessionKey?.trim();
const targetId = params.targetId?.trim();
if (!sessionKey || !targetId) return;
return {
sessionKey: normalizeBrowserSessionKey(sessionKey) ?? "",
targetId,
route: params.route ?? { kind: "browser-control" },
...normalizeProfile(params.profile) ? { profile: normalizeProfile(params.profile) } : {}
};
}
function isVolatileRoute(route) {
return route.kind === "node-proxy" || Boolean(route.baseUrl);
}
function durableOwnership(params) {
return params.ownership?.status === "durable" ? params.ownership : void 0;
}
function deleteInvalidRecord(key, onWarn) {
try {
if (deleteBrowserSessionTabIf(key, (current) => {
const record = parseBrowserSessionTabRecord(current);
return !record || browserSessionTabStorageKey(record) !== key;
})) {
clearDurableTabAliases(key);
activeDurableStorageKeys().delete(key);
}
} catch (error) {
onWarn?.(`failed to delete invalid browser session tab record: ${String(error)}`);
return;
}
onWarn?.("deleted invalid browser session tab record");
}
function readDurableTabs(onWarn) {
const store = getOptionalBrowserSessionTabStore();
if (!store) return [];
const tabs = [];
for (const entry of store.entries()) {
const record = parseBrowserSessionTabRecord(entry.value);
if (!record || browserSessionTabStorageKey(record) !== entry.key) {
deleteInvalidRecord(entry.key, onWarn);
continue;
}
tabs.push({
...record,
kind: "durable",
storageKey: entry.key
});
}
return tabs;
}
function deleteVolatileMatching(identity) {
const state = volatileTabsBySession();
const tabs = state.get(identity.sessionKey);
if (!tabs) return;
for (const [key, tab] of tabs) if (volatileSessionTabTargetKey(tab) === volatileSessionTabTargetKey(identity)) {
tabs.delete(key);
clearVolatileTabAliases(identity.sessionKey, key);
}
if (tabs.size === 0) state.delete(identity.sessionKey);
}
function resolveVolatile(identity) {
const tabs = volatileTabsBySession().get(identity.sessionKey);
const exactKey = volatileSessionTabTargetKey(identity);
const exact = tabs?.get(exactKey);
if (exact) return {
tab: exact,
tabKey: exactKey,
isExact: true
};
const exactTarget = resolveVolatileTabExact(identity);
if (!exactTarget && hasVolatileTabExact(identity)) return;
const target = exactTarget ?? resolveVolatileTabAlias(identity);
if (!target) {
if (!hasVolatileTabAlias(identity)) forgetVolatileTabAlias(identity);
return;
}
if (target.sessionKey !== identity.sessionKey) {
forgetVolatileTabAlias(identity);
return;
}
const tab = tabs?.get(target.tabKey);
if (!tab) {
forgetVolatileTabAlias(identity);
return;
}
return {
tab,
tabKey: target.tabKey,
isExact: Boolean(exactTarget)
};
}
function upsertVolatile(identity, aliases, profileAliases, ownership, now) {
const state = volatileTabsBySession();
const tabs = state.get(identity.sessionKey) ?? /* @__PURE__ */ new Map();
const key = volatileSessionTabTargetKey(identity);
const existing = tabs.get(key);
tabs.set(key, {
...identity,
kind: "volatile",
registration: {},
...ownership ? { ownership } : {},
trackedAt: existing?.trackedAt ?? now,
lastUsedAt: now
});
state.set(identity.sessionKey, tabs);
rememberVolatileTabAliases(identity, aliases, key, profileAliases);
}
function deleteDurableCandidate(tab) {
const deleted = deleteBrowserSessionTabIf(tab.storageKey, (current) => {
const record = parseBrowserSessionTabRecord(current);
return Boolean(record && sameBrowserSessionTabRecord(record, tab));
});
if (deleted) {
clearDurableTabAliases(tab.storageKey);
activeDurableStorageKeys().delete(tab.storageKey);
}
return deleted;
}
function clearDurableForVolatile(identity) {
const mappedKey = resolveDurableTabExact(identity);
if (!mappedKey) return true;
const record = parseBrowserSessionTabRecord(getBrowserSessionTabStore().lookup(mappedKey));
if (record) return deleteDurableCandidate({
...record,
kind: "durable",
storageKey: mappedKey
});
clearDurableTabAliases(mappedKey);
activeDurableStorageKeys().delete(mappedKey);
return true;
}
/** Starts tracking a browser tab for later session cleanup. */
function trackSessionBrowserTab(params) {
const identity = resolveInteractionIdentity(params);
if (!identity) return;
const ownership = durableOwnership(params);
const profileAliases = normalizeProfileAliases(params.profileAliases);
const now = params.now ?? Date.now();
if (isVolatileRoute(identity.route)) {
upsertVolatile(identity, params.aliases ?? [], profileAliases, params.ownership, now);
return;
}
if (!ownership) {
if (!clearDurableForVolatile(identity)) throw new Error("durable browser tab changed during non-durable transition");
upsertVolatile(identity, params.aliases ?? [], profileAliases, params.ownership, now);
return;
}
if (!identity.profile) throw new Error("durable browser tab tracking requires an explicit profile");
const profile = identity.profile;
const storageKey = browserSessionTabStorageKey({
sessionKey: identity.sessionKey,
nativeTargetId: ownership.nativeTargetId,
profileFingerprint: ownership.profileFingerprint,
browserInstanceFingerprint: ownership.browserInstanceFingerprint
});
let persistedProfileAliases = [];
updateBrowserSessionTab(storageKey, (current) => {
const existing = parseBrowserSessionTabRecord(current);
persistedProfileAliases = normalizeProfileAliases([
...existing?.profileAliases ?? [],
existing?.profile,
...profileAliases
]).filter((alias) => alias !== profile);
return {
version: 1,
sessionKey: identity.sessionKey,
nativeTargetId: ownership.nativeTargetId,
profile,
...persistedProfileAliases.length > 0 ? { profileAliases: persistedProfileAliases } : {},
profileFingerprint: ownership.profileFingerprint,
browserInstanceFingerprint: ownership.browserInstanceFingerprint,
interactionTargetKind: identity.targetId === ownership.nativeTargetId ? "native" : "opaque",
trackedAt: existing?.trackedAt ?? now,
lastUsedAt: now
};
});
rememberDurableTabAliases(identity, params.aliases ?? [], storageKey, persistedProfileAliases);
activeDurableStorageKeys().add(storageKey);
deleteVolatileMatching(identity);
}
function canonicalCandidate(params, identity) {
const ownership = durableOwnership(params);
if (!ownership) {
const mappedKey = resolveDurableTabAlias(identity);
if (mappedKey) {
const mappedRecord = parseBrowserSessionTabRecord(getBrowserSessionTabStore().lookup(mappedKey));
if (mappedRecord) return {
...mappedRecord,
kind: "durable",
storageKey: mappedKey
};
}
return;
}
if (!identity.profile) return;
const key = browserSessionTabStorageKey({
sessionKey: identity.sessionKey,
nativeTargetId: ownership.nativeTargetId,
profileFingerprint: ownership.profileFingerprint,
browserInstanceFingerprint: ownership.browserInstanceFingerprint
});
const record = parseBrowserSessionTabRecord(getBrowserSessionTabStore().lookup(key));
return record ? {
...record,
kind: "durable",
storageKey: key
} : void 0;
}
/** Updates last-used time for an existing tracked browser tab. */
function touchSessionBrowserTab(params) {
const identity = resolveInteractionIdentity(params);
if (!identity) return;
const now = params.now ?? Date.now();
const volatile = resolveVolatile(identity);
if (volatile) volatileTabsBySession().get(identity.sessionKey)?.set(volatile.tabKey, {
...volatile.tab,
lastUsedAt: now
});
if (isVolatileRoute(identity.route)) return;
if (!getOptionalBrowserSessionTabStore()) return;
const candidate = canonicalCandidate(params, identity);
if (candidate) {
activeDurableStorageKeys().add(candidate.storageKey);
updateBrowserSessionTab(candidate.storageKey, (current) => {
const record = parseBrowserSessionTabRecord(current);
if (!record || !sameBrowserSessionTabRecord(record, candidate)) return;
if (record.cleanupKind === "sweep") return {
...withoutBrowserSessionTabCleanup(record),
lastUsedAt: now
};
return {
...record,
lastUsedAt: now
};
});
return;
}
if (identity.profile) {
const nativeTargetId = params.nativeTargetId?.trim() || identity.targetId;
const coldIdentity = browserSessionTabNativeIdentity({
sessionKey: identity.sessionKey,
profile: identity.profile,
nativeTargetId
});
if (readColdNativeActivity(coldIdentity) !== void 0 || readDurableTabs().some((tab) => tab.interactionTargetKind === "native" && browserSessionTabNativeIdentity(tab) === coldIdentity)) rememberColdNativeActivity(coldIdentity, now);
}
}
/** Removes a browser tab from session cleanup tracking. */
function untrackSessionBrowserTab(params) {
const identity = resolveInteractionIdentity(params);
if (!identity) return;
const volatile = resolveVolatile(identity);
if (isVolatileRoute(identity.route)) {
if (volatile) deleteVolatileSessionTab(identity.sessionKey, volatile.tabKey);
return;
}
if (!getOptionalBrowserSessionTabStore()) {
if (volatile) deleteVolatileSessionTab(identity.sessionKey, volatile.tabKey);
return;
}
const durable = canonicalCandidate(params, identity);
if (durable && durableOwnership(params)) {
deleteDurableCandidate(durable);
return;
}
const selection = selectSessionTabToUntrack({
volatileAvailable: Boolean(volatile),
durableAvailable: Boolean(durable),
hasVolatileCandidate: Boolean(volatile) || hasVolatileTabAlias(identity),
hasDurableCandidate: Boolean(durable) || hasDurableTabAlias(identity),
volatileIsExact: volatile?.isExact ?? false,
durableIsExact: Boolean(durable && resolveDurableTabExact(identity) === durable.storageKey),
hasVolatileExactCandidate: hasVolatileTabExact(identity),
hasDurableExactCandidate: hasDurableTabExact(identity)
});
if (selection === "volatile" && volatile) {
deleteVolatileSessionTab(identity.sessionKey, volatile.tabKey);
return;
}
if (selection === "durable" && durable) {
deleteDurableCandidate(durable);
return;
}
if (selection !== "missing") return;
if (identity.profile) forgetColdNativeActivity(browserSessionTabNativeIdentity({
sessionKey: identity.sessionKey,
profile: identity.profile,
nativeTargetId: params.nativeTargetId?.trim() || identity.targetId
}));
}
async function closeCurrentDurableTab(tab, shouldClose, getResolvedBrowserConfig) {
const [{ getRuntimeConfig }, { resolveCdpControlPolicy }, { closeTrackedCdpTarget }, config] = await Promise.all([
import("./config-Cvdw9jGi.js"),
import("./cdp-reachability-policy-D3O16L64.js"),
import("./cdp.helpers-BkCQx_ZM.js"),
import("./config-Sln-fipn.js")
]);
let resolved = await getResolvedBrowserConfig?.();
if (!shouldClose()) return { status: "cancelled" };
if (!resolved) {
const cfg = getRuntimeConfig();
resolved = config.resolveBrowserConfig(cfg.browser, cfg);
}
const profile = config.resolveProfile(resolved, tab.profile);
if (!profile?.cdpUrl) return { status: "ownership-mismatch" };
if (profile.driver === "extension" && !resolved.extensionRelayInternalTokens[profile.name]) return {
status: "unavailable",
reason: "extension-relay-unavailable"
};
const cdpControlPolicy = resolveCdpControlPolicy(profile, resolved.ssrfPolicy);
return await closeTrackedCdpTarget({
profileName: profile.name,
cdpUrl: profile.cdpUrl,
nativeTargetId: tab.nativeTargetId,
timeoutMs: resolved.remoteCdpTimeoutMs,
ssrfPolicy: cdpControlPolicy,
expectedProfileFingerprint: tab.profileFingerprint,
expectedBrowserInstanceFingerprint: tab.browserInstanceFingerprint,
shouldClose
});
}
async function closeDurableTab(candidate, params, now, cleanupKind) {
const tab = claimCleanup(candidate, now, cleanupKind);
if (!tab) return 0;
const shouldClose = () => ownsCleanupAttempt(tab);
let outcome;
try {
if (params.closeDurableTab) outcome = await params.closeDurableTab(tab, { shouldClose });
else if (params.closeTab) {
if (!shouldClose()) return 0;
await params.closeTab({
targetId: tab.nativeTargetId,
nativeTargetId: tab.nativeTargetId,
profile: tab.profile
});
outcome = { status: "closed" };
} else outcome = await closeCurrentDurableTab(tab, shouldClose, params.getResolvedBrowserConfig);
} catch (error) {
if (isIgnorableTabCloseError(error)) {
deleteClaimedTab(tab, params.onWarn);
return 0;
}
params.onWarn?.(`failed to close tracked browser tab ${tab.nativeTargetId}: ${String(error)}`);
return 0;
}
if (outcome.status === "cancelled") return 0;
if (outcome.status === "unavailable") {
if (outcome.reason === "extension-relay-unavailable") {
params.onWarn?.(`deferred tracked browser tab ${tab.nativeTargetId}: extension relay runtime unavailable`);
return 0;
}
if (now - tab.lastUsedAt >= 864e5) {
params.onWarn?.(`retired unreachable tracked browser tab ${tab.nativeTargetId}: ${outcome.reason}`);
deleteClaimedTab(tab, params.onWarn);
return 0;
}
params.onWarn?.(`deferred tracked browser tab ${tab.nativeTargetId}: ${outcome.reason}`);
return 0;
}
if (outcome.status === "ownership-mismatch") {
params.onWarn?.(`retired tracked browser tab ${tab.nativeTargetId}: ownership mismatch`);
deleteClaimedTab(tab, params.onWarn);
return 0;
}
deleteClaimedTab(tab, params.onWarn);
return outcome.status === "closed" ? 1 : 0;
}
async function performVolatileCleanup(candidate, params, cleanupKind) {
const inFlight = volatileTabCleanupByTarget();
const targetKey = volatileSessionTabTargetKey(candidate);
const resolveCurrent = () => {
const current = resolveVolatile(candidate)?.tab;
return current?.registration === candidate.registration && (cleanupKind !== "sweep" || sameVolatileSessionTab(current, candidate)) ? current : void 0;
};
while (true) {
const current = resolveCurrent();
if (!current) return 0;
const existing = inFlight.get(targetKey);
if (existing) {
await existing.promise;
if (existing.registrations.some((owned) => owned.registration === candidate.registration)) return 0;
continue;
}
let complete;
const cleanup = new Promise((resolve) => {
complete = resolve;
});
const owner = {
registrations: volatileRegistrationsForTarget(targetKey),
promise: cleanup
};
const performClose = async () => {
let tab = current;
let closeTab = params.closeTab;
try {
if (!closeTab && tab.route.kind === "browser-control") {
const { browserCloseTabByRawTargetId } = await import("./client-BDhP8zNJ.js");
const latest = resolveCurrent();
if (!latest) {
owner.registrations = [];
return 0;
}
tab = latest;
closeTab = ({ baseUrl, targetId, profile }) => browserCloseTabByRawTargetId(baseUrl, targetId, { profile });
}
if (closeTab) await closeTab({
targetId: tab.targetId,
...tab.route.kind === "browser-control" && tab.route.baseUrl ? { baseUrl: tab.route.baseUrl } : {},
...tab.route.kind === "node-proxy" ? { route: tab.route } : {},
...tab.profile ? { profile: tab.profile } : {}
});
else if (tab.route.kind === "node-proxy") {
const outcome = await tab.route.closeTarget({
targetId: tab.targetId,
profile: tab.profile,
ownership: tab.ownership
});
if (outcome.status === "cancelled" || outcome.status === "unavailable") {
params.onWarn?.(`deferred tracked browser tab ${tab.targetId}: ${outcome.status === "unavailable" ? outcome.reason : "cleanup cancelled"}`);
return 0;
}
if (outcome.status === "ownership-mismatch") params.onWarn?.(`retired tracked browser tab ${tab.targetId}: ownership mismatch`);
deleteVolatileRegistrations(owner.registrations);
return outcome.status === "closed" ? 1 : 0;
}
} catch (error) {
if (closeTab && tab.route.kind === "browser-control" && isIgnorableTabCloseError(error)) {
deleteVolatileRegistrations(owner.registrations);
return 0;
}
params.onWarn?.(`failed to close tracked browser tab ${tab.targetId}: ${String(error)}`);
return 0;
}
deleteVolatileRegistrations(owner.registrations);
return 1;
};
inFlight.set(targetKey, owner);
try {
complete(performClose());
return await cleanup;
} finally {
if (inFlight.get(targetKey) === owner) inFlight.delete(targetKey);
}
}
}
async function closeTrackedTabs(tabs, params) {
let closed = 0;
const now = params.now ?? Date.now();
for (const tab of tabs) closed += tab.kind === "durable" ? await closeDurableTab(tab, params, now, params.cleanupKind) : await performVolatileCleanup(tab, params, params.cleanupKind);
return closed;
}
/** Closes and untracks tabs for the supplied session keys. */
async function closeTrackedBrowserTabsForSessions(params) {
return await closeTrackedTabs(selectTrackedTabsForSessions({
durable: readDurableTabs(params.onWarn),
sessionKeys: params.sessionKeys
}), {
...params,
cleanupKind: "lifecycle"
});
}
/** Closes and untracks stale, pending, or excess browser tabs. */
async function sweepTrackedBrowserTabs(params) {
const now = params.now ?? Date.now();
const volatile = [];
for (const tabs of volatileTabsBySession().values()) volatile.push(...tabs.values());
return await closeTrackedTabs(selectStaleTrackedTabs({
tabs: [...readDurableTabs(params.onWarn), ...volatile],
now,
idleMs: params.idleMs,
maxTabsPerSession: params.maxTabsPerSession,
sessionFilter: params.sessionFilter
}), {
...params,
now,
cleanupKind: "sweep"
});
}
//#endregion
export { untrackSessionBrowserTab as a, trackSessionBrowserTab as i, sweepTrackedBrowserTabs as n, touchSessionBrowserTab as r, closeTrackedBrowserTabsForSessions as t };