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.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
91
host/Connections.sys.mjs
Normal file
91
host/Connections.sys.mjs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue