feat: expose browser receiver pairing API

This commit is contained in:
mirivlad 2026-07-10 03:42:07 +08:00
parent e2d07145d6
commit 2b3c6d0557
14 changed files with 60 additions and 9 deletions

View File

@ -55,6 +55,10 @@ export interface SyncNowResult {
conflicts?: SyncConflict[]; conflicts?: SyncConflict[];
applyErrors?: string[]; applyErrors?: string[];
} }
export interface BrowserReceiverPairing {
receiverUrl: string;
receiverToken: string;
}
export interface VerstakPluginAPI { export interface VerstakPluginAPI {
readonly pluginId: string; readonly pluginId: string;
settings: { settings: {
@ -128,6 +132,10 @@ export interface VerstakPluginAPI {
resetKey(): Promise<void>; resetKey(): Promise<void>;
now(): Promise<SyncNowResult>; now(): Promise<SyncNowResult>;
}; };
browserReceiver: {
pairing(): Promise<BrowserReceiverPairing>;
rotateToken(): Promise<BrowserReceiverPairing>;
};
dispose?: () => void; dispose?: () => void;
} }
export declare function createPluginAPI(_pluginId: string): VerstakPluginAPI; export declare function createPluginAPI(_pluginId: string): VerstakPluginAPI;

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"plugin-api.js","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gDAAgD;AA2KhD,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;AACtF,CAAC"} {"version":3,"file":"plugin-api.js","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gDAAgD;AAqLhD,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;AACtF,CAAC"}

View File

@ -1 +1 @@
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,KAAK,EAAwB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQ3E,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,4BAA4B,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAetF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CASnF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,SAAgB,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CA2VlH;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCxF;AAGD,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,CAAC"} {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,KAAK,EAAwB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQ3E,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,4BAA4B,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAetF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CASnF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,SAAgB,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CAqWlH;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCxF;AAGD,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,CAAC"}

10
dist/test-utils.js vendored
View File

@ -412,6 +412,16 @@ export function createMockPluginAPI(pluginId = 'test.plugin', options = {}) {
resetKey: vi.fn(async () => { }), resetKey: vi.fn(async () => { }),
now: vi.fn(async () => ({ pushed: 0, pulled: 0, serverSequence: 0 })), now: vi.fn(async () => ({ pushed: 0, pulled: 0, serverSequence: 0 })),
}, },
browserReceiver: {
pairing: vi.fn(async () => ({
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
receiverToken: 'mock-browser-receiver-token',
})),
rotateToken: vi.fn(async () => ({
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
receiverToken: 'mock-browser-receiver-token-rotated',
})),
},
dispose: vi.fn(), dispose: vi.fn(),
}; };
} }

File diff suppressed because one or more lines are too long

2
dist/types.d.ts vendored
View File

@ -45,7 +45,7 @@ export interface CapabilityEntry {
pluginId: string; pluginId: string;
status: 'stable' | 'draft' | 'deprecated'; status: 'stable' | 'draft' | 'deprecated';
} }
export type Permission = 'vault.read' | 'vault.write' | 'vault.watch' | 'files.read' | 'files.write' | 'files.delete' | 'files.openExternal' | 'workbench.open' | 'storage.namespace' | 'storage.migrations' | 'events.publish' | 'events.subscribe' | 'ui.register' | 'commands.register' | 'network.local' | 'network.remote' | 'process.spawn' | 'secrets.read' | 'secrets.write' | 'sync.participate'; export type Permission = 'vault.read' | 'vault.write' | 'vault.watch' | 'files.read' | 'files.write' | 'files.delete' | 'files.openExternal' | 'workbench.open' | 'storage.namespace' | 'storage.migrations' | 'events.publish' | 'events.subscribe' | 'ui.register' | 'commands.register' | 'network.local' | 'network.remote' | 'process.spawn' | 'secrets.read' | 'secrets.write' | 'sync.participate' | 'browser.receiver.manage';
export interface PermissionEntry { export interface PermissionEntry {
name: Permission; name: Permission;
description: string; description: string;

2
dist/types.d.ts.map vendored

File diff suppressed because one or more lines are too long

View File

@ -100,7 +100,8 @@
"process.spawn", "process.spawn",
"secrets.read", "secrets.read",
"secrets.write", "secrets.write",
"sync.participate" "sync.participate",
"browser.receiver.manage"
] ]
}, },
"minItems": 1 "minItems": 1

View File

@ -46,6 +46,7 @@
{ "name": "process.spawn", "description": "Spawn external processes", "dangerous": true }, { "name": "process.spawn", "description": "Spawn external processes", "dangerous": true },
{ "name": "secrets.read", "description": "Read secrets from the secret store", "dangerous": true }, { "name": "secrets.read", "description": "Read secrets from the secret store", "dangerous": true },
{ "name": "secrets.write", "description": "Write secrets to the secret store", "dangerous": true }, { "name": "secrets.write", "description": "Write secrets to the secret store", "dangerous": true },
{ "name": "sync.participate", "description": "Participate in vault sync", "dangerous": true } { "name": "sync.participate", "description": "Participate in vault sync", "dangerous": true },
{ "name": "browser.receiver.manage", "description": "View and rotate the local browser receiver pairing token", "dangerous": true }
] ]
} }

View File

@ -43,6 +43,8 @@ describe('VerstakPluginAPI contract', () => {
expect(typeof api.sync.configure).toBe('function'); expect(typeof api.sync.configure).toBe('function');
expect(typeof api.sync.resetKey).toBe('function'); expect(typeof api.sync.resetKey).toBe('function');
expect(typeof api.sync.now).toBe('function'); expect(typeof api.sync.now).toBe('function');
expect(typeof api.browserReceiver.pairing).toBe('function');
expect(typeof api.browserReceiver.rotateToken).toBe('function');
}); });
test('manifest schema accepts files permissions used by platform-test', () => { test('manifest schema accepts files permissions used by platform-test', () => {
@ -68,6 +70,14 @@ describe('VerstakPluginAPI contract', () => {
expect(permissionEnum).toEqual(expect.arrayContaining(['secrets.read', 'secrets.write'])); expect(permissionEnum).toEqual(expect.arrayContaining(['secrets.read', 'secrets.write']));
}); });
test('browser receiver token management is declared as a dangerous platform contract', () => {
const permissions = ((permissionsSchema as any).permissions || []) as Array<{ name: string; dangerous: boolean }>;
const permissionEnum = ((manifestSchema as any).properties.permissions.items.enum || []) as string[];
expect(permissions).toContainEqual(expect.objectContaining({ name: 'browser.receiver.manage', dangerous: true }));
expect(permissionEnum).toContain('browser.receiver.manage');
});
test('file.changed schema documents watcher refresh payload', () => { test('file.changed schema documents watcher refresh payload', () => {
const fileChanged = (vaultEventsSchema as any).events.find((event: any) => event.name === 'file.changed'); const fileChanged = (vaultEventsSchema as any).events.find((event: any) => event.name === 'file.changed');

View File

@ -87,6 +87,11 @@ export interface SyncNowResult {
applyErrors?: string[]; applyErrors?: string[];
} }
export interface BrowserReceiverPairing {
receiverUrl: string;
receiverToken: string;
}
export interface VerstakPluginAPI { export interface VerstakPluginAPI {
readonly pluginId: string; readonly pluginId: string;
@ -171,6 +176,11 @@ export interface VerstakPluginAPI {
now(): Promise<SyncNowResult>; now(): Promise<SyncNowResult>;
}; };
browserReceiver: {
pairing(): Promise<BrowserReceiverPairing>;
rotateToken(): Promise<BrowserReceiverPairing>;
};
dispose?: () => void; dispose?: () => void;
} }

View File

@ -395,6 +395,16 @@ export function createMockPluginAPI(pluginId = 'test.plugin', options: MockPlugi
resetKey: vi.fn(async () => {}), resetKey: vi.fn(async () => {}),
now: vi.fn(async () => ({ pushed: 0, pulled: 0, serverSequence: 0 })), now: vi.fn(async () => ({ pushed: 0, pulled: 0, serverSequence: 0 })),
}, },
browserReceiver: {
pairing: vi.fn(async () => ({
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
receiverToken: 'mock-browser-receiver-token',
})),
rotateToken: vi.fn(async () => ({
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
receiverToken: 'mock-browser-receiver-token-rotated',
})),
},
dispose: vi.fn(), dispose: vi.fn(),
}; };
} }

View File

@ -82,7 +82,8 @@ export type Permission =
| 'process.spawn' | 'process.spawn'
| 'secrets.read' | 'secrets.read'
| 'secrets.write' | 'secrets.write'
| 'sync.participate'; | 'sync.participate'
| 'browser.receiver.manage';
export interface PermissionEntry { export interface PermissionEntry {
name: Permission; name: Permission;