feat: advance browser agent integration workflows

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent af5a4db694
commit 0c8e1c7b69
215 changed files with 35137 additions and 5442 deletions
+149 -3
View File
@@ -23,9 +23,11 @@ vi.mock('wxt/browser', () => ({
import {
ACTIVE_SESSION_STORAGE_KEY, BRIDGE_SETTINGS_STORAGE_KEY, FLOATING_UI_STORAGE_KEY,
PROXY_SETTINGS_STORAGE_KEY, USER_AGENT_SETTINGS_STORAGE_KEY,
BRIDGE_SESSION_STORAGE_KEY, PROXY_SETTINGS_STORAGE_KEY, USER_AGENT_SETTINGS_STORAGE_KEY,
} from '@/protocol/storage';
import { DEFAULT_STATE, getState, setState, updateState } from './state';
import {
DEFAULT_STATE, getBridgeRuntimeSession, getState, setState, updateState,
} from './state';
describe('split state storage', () => {
it('writes durable domains to local and grant/handoff to session', async () => {
@@ -35,7 +37,15 @@ describe('split state storage', () => {
activeGrant: {
id: 'grant-1', taskId: 'task-1', createdAt: now, expiresAt: now + 60_000,
scopes: ['browser.tabs.read'],
targets: [{ tabId: 1, frameId: 0, origin: 'https://example.test', grantedUrl: 'https://example.test/', title: 'Example' }],
targets: [{
tabId: 1,
frameId: 0,
isolationContextId: 'browser-profile:store-1',
cookieStoreId: 'store-1',
origin: 'https://example.test',
grantedUrl: 'https://example.test/',
title: 'Example',
}],
},
});
expect(Object.keys(stores.local)).toEqual(expect.arrayContaining([
@@ -56,4 +66,140 @@ describe('split state storage', () => {
expect(state.activeProxyId).toBe('yakit-mitm');
expect(state.floatingPanel.side).toBe('left');
});
it('drops a session grant that is not bound to an isolation context', async () => {
const now = Date.now();
stores.session[ACTIVE_SESSION_STORAGE_KEY] = {
activeGrant: {
id: 'legacy-grant',
taskId: 'legacy-task',
createdAt: now,
expiresAt: now + 60_000,
scopes: ['browser.tabs.read'],
targets: [{
tabId: 1,
frameId: 0,
origin: 'https://example.test',
grantedUrl: 'https://example.test/',
title: 'Example',
}],
},
};
expect((await getState()).activeGrant).toBeUndefined();
});
it('preserves an expired but structurally valid grant for lifecycle cleanup', async () => {
const now = Date.now();
stores.session[ACTIVE_SESSION_STORAGE_KEY] = {
activeGrant: {
id: 'expired-grant',
taskId: 'expired-task',
createdAt: now - 120_000,
expiresAt: now - 60_000,
scopes: ['browser.tabs.read'],
targets: [{
tabId: 7,
frameId: 0,
documentId: 'document-7',
isolationContextId: 'browser-profile:store-7',
cookieStoreId: 'store-7',
origin: 'https://expired.example',
grantedUrl: 'https://expired.example/',
title: 'Expired',
}],
},
};
expect((await getState()).activeGrant).toMatchObject({
id: 'expired-grant',
expiresAt: now - 60_000,
});
});
it('repairs reserved proxy profiles and malformed proxy collections from durable storage', async () => {
stores.local[PROXY_SETTINGS_STORAGE_KEY] = {
proxyProfiles: [
{ id: 'direct', name: 'Hijacked', kind: 'fixed_servers', scheme: 'http', host: '127.0.0.1', port: 9000, bypass: [] },
{ id: 'auto', name: 'Sentinel collision', kind: 'direct', bypass: [] },
{ id: 'custom', name: 'Custom', kind: 'fixed_servers', scheme: 'http', host: '127.0.0.1', port: 8080, bypass: [], builtin: true },
],
proxyRules: null,
proxyRuleSources: { invalid: true },
proxyRouting: { defaultProfileId: 'missing', failMode: 'invalid' },
activeProxyId: 'auto',
};
const state = await getState();
expect(state.proxyProfiles.find((profile) => profile.id === 'direct')).toMatchObject({
name: '直接连接', kind: 'direct', builtin: true,
});
expect(state.proxyProfiles.some((profile) => profile.id === 'auto')).toBe(false);
expect(state.proxyProfiles.find((profile) => profile.id === 'custom')?.builtin).toBe(false);
expect(state.proxyRules).toEqual([]);
expect(state.proxyRuleSources).toEqual([]);
expect(state.proxyRouting).toEqual({ defaultProfileId: 'direct', failMode: 'closed' });
});
it('repairs malformed User-Agent profiles, duplicate hosts and orphan assignments', async () => {
stores.local[USER_AGENT_SETTINGS_STORAGE_KEY] = {
customUserAgentProfiles: [
{ id: 'custom-valid', name: ' Valid ', userAgent: ' Fixture-UA/1.0 ', category: 'desktop', builtin: true },
{ id: 'chrome-windows', name: 'Builtin collision', userAgent: 'Collision/1.0', category: 'custom', builtin: false },
{ id: 'custom-invalid', name: 'Invalid', userAgent: 'Bad\r\nHeader: value', category: 'custom', builtin: false },
],
userAgentAssignments: [
{ id: 'old', hostname: 'APP.EXAMPLE.TEST', profileId: 'custom-valid', createdAt: 1, updatedAt: 2 },
{ id: 'latest', hostname: 'app.example.test', profileId: 'safari-iphone', createdAt: 1, updatedAt: 3 },
{ id: 'orphan', hostname: 'orphan.example.test', profileId: 'missing', createdAt: 1, updatedAt: 2 },
],
};
const state = await getState();
expect(state.customUserAgentProfiles).toEqual([{
id: 'custom-valid',
name: 'Valid',
userAgent: 'Fixture-UA/1.0',
category: 'custom',
builtin: false,
}]);
expect(state.userAgentAssignments).toEqual([{
id: 'latest',
hostname: 'app.example.test',
profileId: 'safari-iphone',
createdAt: 1,
updatedAt: 3,
}]);
});
it('drops a corrupted resumable Bridge session after a Service Worker restart', async () => {
stores.session[BRIDGE_SESSION_STORAGE_KEY] = {
sessionId: 42,
engineInstanceId: 'engine-1',
taskId: { invalid: true },
updatedAt: Number.NaN,
};
await expect(getBridgeRuntimeSession()).resolves.toBeUndefined();
});
it('restores only bounded typed fields from a resumable Bridge session', async () => {
stores.session[BRIDGE_SESSION_STORAGE_KEY] = {
sessionId: 's'.repeat(700),
engineInstanceId: 'engine-1',
engineIdentityId: 'identity-1',
taskId: 'task-1',
grantId: 'grant-1',
updatedAt: Date.now(),
ignoredLegacyField: true,
};
await expect(getBridgeRuntimeSession()).resolves.toMatchObject({
sessionId: 's'.repeat(500),
engineInstanceId: 'engine-1',
engineIdentityId: 'identity-1',
taskId: 'task-1',
grantId: 'grant-1',
});
});
});