VulpesOS/host/Connections.sys.mjs
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

91 lines
3.1 KiB
JavaScript

// Actor-bound local inter-app channels. No caller-supplied identity or port owner.
const routes = {
search: ['system', 'search'],
'search-results': ['search', 'system'],
'app-metrics': ['search', 'system'],
mediacomms: ['music', 'system'],
};
export class Connections {
constructor(identity, notify) {
Object.assign(this, { identity, notify });
this.ports = new Map();
this.listeners = new Map();
}
subscribe(actor) {
this.listeners.set(actor, this.identity(actor).id);
for (const port of this.ports.values()) this.attach(port);
}
attach(port) {
if (port.receiver || !this.ports.has(port.id)) return;
const target = [...this.listeners.keys()].find(
(actor) =>
this.identity(actor)?.id === port.target &&
new URL(actor.manager.documentURI.spec).pathname === port.path,
);
if (!target) return;
port.receiver = target;
this.notify(target, 'connection', { keyword: port.keyword, id: port.id, side: 'receiver' });
for (const data of port.pending) this.notify(target, 'portmessage', { id: port.id, data });
port.pending = [];
}
connect(actor, keyword, apps) {
const route = routes[keyword],
app = this.identity(actor);
if (
!route ||
route[0] !== app.id ||
!apps.find((a) => a.id === route[1])?.manifest.connections?.[keyword]
)
throw new Error('CONNECTION_DENIED');
if ([...this.ports.values()].filter((p) => p.sender === actor).length >= 32)
throw new Error('QUOTA_EXCEEDED');
const receiver = apps.find((a) => a.id === route[1]);
const path = new URL(
receiver.manifest.connections[keyword].handler_path || '/index.html',
receiver.origin + '/',
).pathname;
const port = {
id: Services.uuid.generateUUID().toString(),
keyword,
sender: actor,
target: route[1],
path,
pending: [],
};
this.ports.set(port.id, port);
this.attach(port);
return [{ id: port.id, side: 'sender' }];
}
send(actor, { id, data }) {
const port = this.ports.get(id);
if (!port || (actor !== port.sender && actor !== port.receiver))
throw new Error('CONNECTION_DENIED');
if ((JSON.stringify(data)?.length || 0) > 1024 * 1024) throw new Error('VALUE_TOO_LARGE');
const target = actor === port.sender ? port.receiver : port.sender;
if (target) this.notify(target, 'portmessage', { id, data });
else {
if (port.pending.length >= 128) throw new Error('QUOTA_EXCEEDED');
port.pending.push(data);
}
return null;
}
close(actor, id) {
const port = this.ports.get(id);
if (!port || (actor !== port.sender && actor !== port.receiver))
throw new Error('CONNECTION_DENIED');
this.ports.delete(id);
for (const target of [port.sender, port.receiver])
if (target && target !== actor) {
try {
this.notify(target, 'portclose', { id });
} catch (_) {
/* Peer already destroyed. */
}
}
}
cleanup(actor) {
this.listeners.delete(actor);
for (const port of this.ports.values())
if (port.sender === actor || port.receiver === actor) this.close(actor, port.id);
}
}