Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
91 lines
3.1 KiB
JavaScript
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);
|
|
}
|
|
}
|