mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
dev: multi-instance many-to-many app-to-app comms
I'll elaborate here because the commit name is confusing. Any time an app gets a connection to another app, either because it launched that app (or was launched by that app) or requested a connection to that app, the ID the app gets to represent the app it's communicating with is now a pseudo app id rather than the app instance ID. This accomplishes two things: 1. It's more secure. There are multiple places where GUI assumes that knowing an app's instance ID means you can do things as that app. 2. Between the same two apps, there may now be more than one connection. This is useful for situations like Phoenix shell talking to the emulator in multiple separate instances to pipe commands. (this is coming later)
This commit is contained in:
parent
62634b0afe
commit
e3d4a5f3b3
@ -1439,6 +1439,22 @@ window.addEventListener('message', async (event) => {
|
|||||||
const { appInstanceID, targetAppInstanceID, targetAppOrigin, contents } = event.data;
|
const { appInstanceID, targetAppInstanceID, targetAppOrigin, contents } = event.data;
|
||||||
// TODO: Determine if we should allow the message
|
// TODO: Determine if we should allow the message
|
||||||
// TODO: Track message traffic between apps
|
// TODO: Track message traffic between apps
|
||||||
|
const svc_ipc = globalThis.services.get('ipc');
|
||||||
|
// const svc_exec = globalThis.services()
|
||||||
|
|
||||||
|
const conn = svc_ipc.get_connection(targetAppInstanceID);
|
||||||
|
if ( conn ) {
|
||||||
|
conn.send(contents);
|
||||||
|
// conn.send({
|
||||||
|
// msg: 'messageToApp',
|
||||||
|
// appInstanceID,
|
||||||
|
// targetAppInstanceID,
|
||||||
|
// contents,
|
||||||
|
// }, targetAppOrigin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🔒 App ${appInstanceID} is sending to ${targetAppInstanceID} insecurely`);
|
||||||
|
|
||||||
// pass on the message
|
// pass on the message
|
||||||
const target_iframe = window.iframe_for_app_instance(targetAppInstanceID);
|
const target_iframe = window.iframe_for_app_instance(targetAppInstanceID);
|
||||||
|
@ -35,6 +35,9 @@ export class Service extends concepts.Service {
|
|||||||
this.services = a[0].services;
|
this.services = a[0].services;
|
||||||
return this._init(...a)
|
return this._init(...a)
|
||||||
}
|
}
|
||||||
|
get context () {
|
||||||
|
return { services: this.services };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PROCESS_INITIALIZING = { i18n_key: 'initializing' };
|
export const PROCESS_INITIALIZING = { i18n_key: 'initializing' };
|
||||||
@ -139,16 +142,22 @@ export class PortalProcess extends Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send (channel, object, context) {
|
send (channel, data, context) {
|
||||||
const target = this.references.iframe.contentWindow;
|
const target = this.references.iframe.contentWindow;
|
||||||
// NEXT: ...
|
target.postMessage({
|
||||||
|
msg: 'messageToApp',
|
||||||
|
appInstanceID: channel.returnAddress,
|
||||||
|
targetAppInstanceID: this.uuid,
|
||||||
|
contents: data,
|
||||||
|
// }, new URL(this.references.iframe.src).origin);
|
||||||
|
}, '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_connection (other_process, args) {
|
handle_connection (connection, args) {
|
||||||
const target = this.references.iframe.contentWindow;
|
const target = this.references.iframe.contentWindow;
|
||||||
target.postMessage({
|
target.postMessage({
|
||||||
msg: 'connection',
|
msg: 'connection',
|
||||||
appInstanceID: other_process.uuid,
|
appInstanceID: connection.uuid,
|
||||||
args,
|
args,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ const launch_app = async (options)=>{
|
|||||||
|
|
||||||
// add parent_app_instance_id to URL
|
// add parent_app_instance_id to URL
|
||||||
if (options.parent_instance_id) {
|
if (options.parent_instance_id) {
|
||||||
iframe_url.searchParams.append('puter.parent_instance_id', options.parent_instance_id);
|
iframe_url.searchParams.append('puter.parent_instance_id', options.parent_pseudo_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(file_signature){
|
if(file_signature){
|
||||||
|
@ -24,15 +24,26 @@ export class ExecService extends Service {
|
|||||||
// This mechanism will be replated with xdrpc soon
|
// This mechanism will be replated with xdrpc soon
|
||||||
const child_instance_id = window.uuidv4();
|
const child_instance_id = window.uuidv4();
|
||||||
|
|
||||||
|
const svc_ipc = this.services.get('ipc');
|
||||||
|
const connection = ipc_context ? svc_ipc.add_connection({
|
||||||
|
source: process.uuid,
|
||||||
|
target: child_instance_id,
|
||||||
|
}) : undefined;
|
||||||
|
|
||||||
// The "body" of this method is in a separate file
|
// The "body" of this method is in a separate file
|
||||||
const child_process = await launch_app({
|
const child_process = await launch_app({
|
||||||
name: app_name,
|
name: app_name,
|
||||||
args: args ?? {},
|
args: args ?? {},
|
||||||
parent_instance_id: app?.appInstanceID,
|
parent_instance_id: app?.appInstanceID,
|
||||||
uuid: child_instance_id,
|
uuid: child_instance_id,
|
||||||
|
...(connection ? {
|
||||||
|
parent_pseudo_id: connection.backward.uuid,
|
||||||
|
} : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const send_child_launched_msg = (...a) => {
|
const send_child_launched_msg = (...a) => {
|
||||||
|
if ( ! process ) return;
|
||||||
|
// TODO: (maybe) message process instead of iframe
|
||||||
const parent_iframe = process?.references?.iframe;
|
const parent_iframe = process?.references?.iframe;
|
||||||
parent_iframe.contentWindow.postMessage({
|
parent_iframe.contentWindow.postMessage({
|
||||||
msg: 'childAppLaunched',
|
msg: 'childAppLaunched',
|
||||||
@ -69,7 +80,8 @@ export class ExecService extends Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appInstanceID: child_instance_id,
|
appInstanceID: connection ?
|
||||||
|
connection.forward.uuid : child_instance_id,
|
||||||
usesSDK: true,
|
usesSDK: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -96,10 +108,17 @@ export class ExecService extends Service {
|
|||||||
const options = svc_process.select_by_name(app_name);
|
const options = svc_process.select_by_name(app_name);
|
||||||
const process = options[0];
|
const process = options[0];
|
||||||
|
|
||||||
await process.handle_connection(caller_process, args);
|
const svc_ipc = this.services.get('ipc');
|
||||||
|
const connection = svc_ipc.add_connection({
|
||||||
|
source: caller_process.uuid,
|
||||||
|
target: process.uuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await process.handle_connection(
|
||||||
|
connection.backward, args);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appInstanceID: process.uuid,
|
appInstanceID: connection.forward.uuid,
|
||||||
response,
|
response,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,52 @@
|
|||||||
import { Service } from "../definitions.js";
|
import { Service } from "../definitions.js";
|
||||||
|
|
||||||
|
class InternalConnection {
|
||||||
|
constructor ({ source, target, uuid, reverse }, { services }) {
|
||||||
|
this.services = services;
|
||||||
|
this.source = source;
|
||||||
|
this.target = target;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.reverse = reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
send (data) {
|
||||||
|
const svc_process = this.services.get('process');
|
||||||
|
const process = svc_process.get_by_uuid(this.target);
|
||||||
|
const channel = {
|
||||||
|
returnAddress: this.reverse,
|
||||||
|
};
|
||||||
|
process.send(channel, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class IPCService extends Service {
|
export class IPCService extends Service {
|
||||||
static description = `
|
static description = `
|
||||||
Allows other services to expose methods to apps.
|
Allows other services to expose methods to apps.
|
||||||
`
|
`
|
||||||
|
|
||||||
async _init () {
|
async _init () {
|
||||||
//
|
this.connections_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
add_connection ({ source, target }) {
|
||||||
|
const uuid = window.uuidv4();
|
||||||
|
const r_uuid = window.uuidv4();
|
||||||
|
const forward = this.connections_[uuid] = {
|
||||||
|
source, target,
|
||||||
|
uuid: uuid, reverse: r_uuid,
|
||||||
|
};
|
||||||
|
const backward = this.connections_[r_uuid] = {
|
||||||
|
source: target, target: source,
|
||||||
|
uuid: r_uuid, reverse: uuid,
|
||||||
|
};
|
||||||
|
return { forward, backward };
|
||||||
|
}
|
||||||
|
|
||||||
|
get_connection (uuid) {
|
||||||
|
const entry = this.connections_[uuid];
|
||||||
|
if ( ! entry ) return;
|
||||||
|
if ( entry.object ) return entry.object;
|
||||||
|
return entry.object = new InternalConnection(entry, this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
register_ipc_handler (name, spec) {
|
register_ipc_handler (name, spec) {
|
||||||
|
@ -53,7 +53,6 @@ window.main_shell = async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
terminal.on('close', () => {
|
terminal.on('close', () => {
|
||||||
console.log('Terminal closed; exiting Phoenix...');
|
|
||||||
puter.exit();
|
puter.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ class AppConnection extends EventListener {
|
|||||||
// Message is from a different AppConnection; ignore it.
|
// Message is from a different AppConnection; ignore it.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO: does this check really make sense?
|
||||||
if (event.data.targetAppInstanceID !== this.appInstanceID) {
|
if (event.data.targetAppInstanceID !== this.appInstanceID) {
|
||||||
console.error(`AppConnection received message intended for wrong app! appInstanceID=${this.appInstanceID}, target=${event.data.targetAppInstanceID}`);
|
console.error(`AppConnection received message intended for wrong app! appInstanceID=${this.appInstanceID}, target=${event.data.targetAppInstanceID}`);
|
||||||
return;
|
return;
|
||||||
@ -89,7 +90,10 @@ class AppConnection extends EventListener {
|
|||||||
msg: 'messageToApp',
|
msg: 'messageToApp',
|
||||||
appInstanceID: this.appInstanceID,
|
appInstanceID: this.appInstanceID,
|
||||||
targetAppInstanceID: this.targetAppInstanceID,
|
targetAppInstanceID: this.targetAppInstanceID,
|
||||||
targetAppOrigin: '*', // TODO: Specify this somehow
|
// Note: there was a TODO comment here about specifying the origin,
|
||||||
|
// but this should not happen here; the origin should be specified
|
||||||
|
// on the other side where the expected origin for the app is known.
|
||||||
|
targetAppOrigin: '*',
|
||||||
contents: message,
|
contents: message,
|
||||||
}, this.#puterOrigin);
|
}, this.#puterOrigin);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user