feat(browser): expose proxy state and browser identity (#9)

* feat(proxy): reflect and release browser proxy control

* feat(browser): report browser product identity
This commit is contained in:
go0p
2026-09-16 15:51:33 +08:00
committed by GitHub
parent 2000d59054
commit 4b0df8c706
22 changed files with 441 additions and 32 deletions
+10
View File
@@ -119,6 +119,16 @@ describe('extension request schemas', () => {
});
it('validates manager-owned browser instance binding', () => {
const binding = {
manager: 'ytray', instanceId: 'instance-a', badge: 'A',
browserName: 'Chrome for Testing', browserVersion: '152.0.7977.82',
};
for (const startupProxy of ['direct', 'http://127.0.0.1:8083', 'https://proxy.example:443']) {
expect(parseExtensionRequest({ action: 'bridge.managed-instance.bind', payload: { ...binding, startupProxy } }).action).toBe('bridge.managed-instance.bind');
}
for (const startupProxy of ['http://user:[email protected]:8083', 'http://proxy.example/path', 'javascript:alert(1)']) {
expect(() => parseExtensionRequest({ action: 'bridge.managed-instance.bind', payload: { ...binding, startupProxy } })).toThrow();
}
expect(parseExtensionRequest({
action: 'bridge.managed-instance.bind',
payload: { manager: 'ytray', instanceId: '13367db6-232a-40d1-ad84-81ee5d97634f', badge: 'B' },
+13 -1
View File
@@ -179,6 +179,8 @@ const bridgeConfig = v.strictObject({
endpoint: v.pipe(v.string(), v.trim(), v.maxLength(2_048)),
autoConnect: v.boolean(),
installationId: v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(160)),
browserName: v.optional(v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(120))),
browserVersion: v.optional(v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(80))),
managedInstance: v.optional(managedInstance),
pairedEngine: v.optional(v.strictObject({
engineIdentityId: id,
@@ -301,6 +303,8 @@ const payloadSchemas = {
'proxy.save': proxyProfile,
'proxy.delete': v.strictObject({ id }),
'proxy.switch': v.strictObject({ id }),
'proxy.status': noPayload,
'proxy.release': noPayload,
'proxy.rule.save': proxyRule,
'proxy.rule.delete': v.strictObject({ id }),
'proxy.auto.apply': noPayload,
@@ -471,7 +475,15 @@ const payloadSchemas = {
'metrics.get': noPayload,
'metrics.reset': noPayload,
'bridge.config.save': bridgeConfig,
'bridge.managed-instance.bind': managedInstance,
'bridge.managed-instance.bind': v.strictObject({
...managedInstance.entries,
browserName: v.optional(v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(120))),
browserVersion: v.optional(v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(80))),
startupProxy: v.optional(v.union([v.literal('direct'), v.pipe(httpUrl, v.check((value) => {
const parsed = new URL(value);
return !parsed.username && !parsed.password && !parsed.search && !parsed.hash && parsed.pathname === '/';
}, '启动代理只能包含协议、主机和端口'))])),
}),
'bridge.pair': noPayload,
'bridge.pair.cancel': noPayload,
'bridge.pair.status': noPayload,