Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 2 additions & 16 deletions apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@
"src/renderer/use-new-task-choice.ts",
"src/renderer/use-onboarding-snapshot.ts",
"src/renderer/use-project-context.ts",
"src/renderer/use-session-collaboration-dialog.ts",
"src/renderer/use-session-setting-intent.ts",
"src/renderer/use-settings-modal.ts",
"src/renderer/use-shell-appearance.ts",
Expand Down Expand Up @@ -985,6 +984,7 @@
"./error-boundary": 1,
"./features/goals": 1,
"./features/module-hub": 1,
"./features/session-collaboration": 1,
"./features/session-navigation": 1,
"./features/task-entry": 1,
"./features/workbar": 1,
Expand Down Expand Up @@ -1020,7 +1020,6 @@
"./use-new-task-choice": 1,
"./use-onboarding-snapshot": 1,
"./use-project-context": 1,
"./use-session-collaboration-dialog": 1,
"./use-session-setting-intent": 1,
"./use-settings-modal": 1,
"./use-shell-appearance": 1,
Expand Down Expand Up @@ -1058,7 +1057,7 @@
"react": 1
},
"importSpecifiers": 186,
"nonTriviaTokens": 15825
"nonTriviaTokens": 15800
},
"src/renderer/use-app-shell-composer-quotes.ts": {
"importDeclarations": 3,
Expand Down Expand Up @@ -4927,19 +4926,6 @@
"react": 1
}
},
"src/renderer/use-session-collaboration-dialog.ts": {
"bridgePaths": {},
"environmentCapabilities": {},
"hookCalls": {
"useState": 1
},
"lifecycleMethods": {},
"unresolvedDependencies": 0,
"actionFactories": [],
"dependencyPaths": {
"react": 1
}
},
"src/renderer/use-session-setting-intent.ts": {
"bridgePaths": {},
"environmentCapabilities": {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ import { registerRuntimeHostCollaborationIpc } from '../runtime-host-collaborati

const ROOT_ID = 'a'.repeat(64);

test('requires Owner confirmation before issuing a plaintext collaboration invitation', async () => {
test('requires plaintext confirmation and reports the issued invitation routes', async () => {
const handlers = new Map<string, IpcHandler>();
const ipcMain: ReconnectableReadIpcMain = {
handle(channel, listener) {
handlers.set(channel, listener);
},
};
let prepareCalls = 0;
const queryCalls: Array<string | undefined> = [];
const client = {
async prepareCollaborationInvitation(sessionId: string, grantKinds: readonly string[]) {
prepareCalls += 1;
Expand All @@ -56,6 +57,10 @@ test('requires Owner confirmation before issuing a plaintext collaboration invit
async queryCollaborationAccess() {
return { principals: [], grants: [] };
},
async queryCollaborationTurnRequests(sessionId?: string) {
queryCalls.push(sessionId);
return { canRequestTurns: false, requests: [] };
},
async revokeCollaborationPrincipal() {
return { revoked: false };
},
Expand Down Expand Up @@ -92,9 +97,55 @@ test('requires Owner confirmation before issuing a plaintext collaboration invit
assert.equal(prepareCalls, 1);
assert.equal((result as { kind?: unknown }).kind, 'prepared');
const invitation = (result as {
invitation: { invitationCode: string };
invitation: { invitationCode: string; connectivity: unknown };
}).invitation;
assert.deepEqual(invitation.connectivity, { kind: 'configured' });
const bundle = decodeDesktopCollaborationInvitation(invitation.invitationCode);
assert.equal(decodeCollaborationInvitationCode(bundle.invitationCode).rootId, ROOT_ID);
assert.equal(bundle.target.transport.kind, 'plaintext');

const query = handlers.get('session-collaboration:turn-request:query');
assert.ok(query);
assert.deepEqual(await query({} as Parameters<IpcHandler>[0]), {
canRequestTurns: false,
requests: [],
});
assert.deepEqual(await query({} as Parameters<IpcHandler>[0], 'session-1'), {
canRequestTurns: false,
requests: [],
});
assert.deepEqual(queryCalls, [undefined, 'session-1']);

const peerHandlers = new Map<string, IpcHandler>();
registerRuntimeHostCollaborationIpc(
client as unknown as Parameters<typeof registerRuntimeHostCollaborationIpc>[0],
{
handle(channel, listener) {
peerHandlers.set(channel, listener);
},
},
async () => ({
name: 'Peer Lab',
transport: {
kind: 'libp2p-direct',
peerId: '12D3KooWpeer',
routeHints: ['/ip4/192.0.2.1/udp/41000/quic-v1'],
coordinationRelays: [
'/dns4/relay.example/udp/443/quic-v1/p2p/12D3KooWrelay',
],
},
}),
);
const preparePeer = peerHandlers.get('session-collaboration:prepare');
assert.ok(preparePeer);
const peerResult = await preparePeer(
{} as Parameters<IpcHandler>[0],
'session-1',
'observe',
false,
);
assert.deepEqual(
(peerResult as { invitation: { connectivity: unknown } }).invitation.connectivity,
{ kind: 'peer', coordinationRelayCount: 1 },
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@

import assert from 'node:assert/strict';
import test from 'node:test';
import { encodeCollaborationInvitationCode } from '@maka/runtime-host/protocol';
import type { ResolvedRuntimeHostProfile } from '@maka/runtime-host/client';
import {
encodeCollaborationInvitationCode,
type HostPeerEndpoint,
} from '@maka/runtime-host/protocol';
import { encodeDesktopCollaborationInvitation } from '../runtime-host-collaboration-invitation.js';
import {
createDesktopGuestSessionMountService,
type GuestSessionMount,
type GuestSessionMountStore,
registerDesktopGuestSessionMountIpc,
} from '../runtime-host-guest-session-mounts.js';
import { RuntimeHostPairingFinalizationInterruptedError } from '../runtime-host-desktop-manager.js';

Expand Down Expand Up @@ -56,6 +61,49 @@ test('retains a successful Guest mount and rehydrates the same authority after r
await second!.close();
});

test('persists authenticated route rotation for reconnect and restart', async () => {
const store = memoryStore();
let observePeerEndpoint!: (endpoint: HostPeerEndpoint) => void;
const first = service(store, {
mount: async (target, _signal, _onConnectionPhase, onPeerEndpoint) => {
assert.deepEqual(
target.profile.kind === 'remote' ? target.profile.transport : undefined,
{
kind: 'libp2p-direct',
peerId: '12D3KooWpeer',
routeHints: ['/ip4/192.0.2.1/udp/41000/quic-v1'],
coordinationRelays: ['/memory/stale-relay'],
},
);
assert.ok(onPeerEndpoint);
observePeerEndpoint = onPeerEndpoint;
},
});

const imported = await first.importInvitation(peerInvitation('guest-routes'), false, 'routes');
assert.equal(imported.kind, 'connected');
observePeerEndpoint({
peerId: '12D3KooWpeer',
routeHints: ['/ip4/198.51.100.2/udp/42000/quic-v1'],
coordinationRelays: ['/memory/fresh-relay'],
});
await first.close();

let restarted!: ReturnType<typeof service>;
const restartedTarget = new Promise<ResolvedRuntimeHostProfile>((resolve) => {
restarted = service(store, { mount: async (target) => resolve(target) });
void restarted.start();
});
const target = await restartedTarget;
assert.deepEqual(target.profile.kind === 'remote' ? target.profile.transport : undefined, {
kind: 'libp2p-direct',
peerId: '12D3KooWpeer',
routeHints: ['/ip4/198.51.100.2/udp/42000/quic-v1'],
coordinationRelays: ['/memory/fresh-relay'],
});
await restarted.close();
});

test('removes failed activation desire instead of creating recoverable profile state', async () => {
const store = memoryStore();
const unmounted: string[] = [];
Expand Down Expand Up @@ -273,6 +321,52 @@ test('cancels an in-flight import and removes its durable mount desire', async (
await mounts.close();
});

test('reads an invitation from the clipboard only on explicit IPC invocation', async () => {
type IpcHandler = Parameters<Pick<Electron.IpcMain, 'handle'>['handle']>[1];
const handlers = new Map<string, IpcHandler>();
let clipboardReads = 0;
const clipboardInvitation = invitation('guest-clipboard');
let clipboardText = ` ${clipboardInvitation} `;
const mounts = service(memoryStore());
const dispose = registerDesktopGuestSessionMountIpc(
{
handle(channel, handler) {
handlers.set(channel, handler);
},
removeHandler(channel) {
handlers.delete(channel);
},
},
mounts,
() => {
clipboardReads += 1;
return clipboardText;
},
);

assert.equal(clipboardReads, 0);
const handler = handlers.get('session-collaboration:invitation:read-clipboard');
assert.ok(handler);
assert.equal(await handler({} as never), clipboardInvitation);
assert.equal(clipboardReads, 1);

clipboardText = 'an unrelated clipboard secret';
assert.throws(() => handler({} as never), /Invalid Desktop collaboration invitation/);

clipboardText = ' ';
assert.equal(await handler({} as never), '');

clipboardText = 'x'.repeat(32 * 1024 + 1);
assert.throws(
() => handler({} as never),
/Clipboard content is too large to be a shared Session invitation/,
);

dispose();
assert.equal(handlers.size, 0);
await mounts.close();
});

function service(
store: GuestSessionMountStore,
overrides: {
Expand Down Expand Up @@ -314,6 +408,25 @@ function invitation(credential: string): string {
});
}

function peerInvitation(credential: string): string {
return encodeDesktopCollaborationInvitation({
invitationCode: encodeCollaborationInvitationCode({
schemaVersion: 1,
rootId: ROOT_ID,
credential,
}),
target: {
name: 'Shared Host',
transport: {
kind: 'libp2p-direct',
peerId: '12D3KooWpeer',
routeHints: ['/ip4/192.0.2.1/udp/41000/quic-v1'],
coordinationRelays: ['/memory/stale-relay'],
},
},
});
}

function retainedMount(mountId: string): GuestSessionMount {
return {
mountId,
Expand Down
Loading
Loading