From 725cbf2e20103312b0840ba5361f077125dfe341 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 5 Apr 2024 16:55:49 +0100 Subject: [PATCH] Add support for launching child apps Calling `puter.ui.launchApp()` now treats the new app as a child of the one that launched it. A child app is given a `puter.parent_instance_id` URL param, containing its parent's instance ID. Previously, `launchApp()` would resolve as soon as the app was launched. This commit changes that, so that it is notified after the child app sends its READY event to Puter. This means that as soon as `launchApp()` has completed, the child app is ready to receive messages. The downside is that launching an app that does not include Puter.js will now not cause a notification, so `await puter.ui.launchApp()` will not resolve in that case. --- src/IPC.js | 29 +++++++++++++++++++++++------ src/globals.js | 3 +++ src/helpers.js | 7 ++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/IPC.js b/src/IPC.js index 7f771699..de771e44 100644 --- a/src/IPC.js +++ b/src/IPC.js @@ -92,6 +92,19 @@ window.addEventListener('message', async (event) => { //------------------------------------------------- if(event.data.msg === 'READY'){ $(target_iframe).attr('data-appUsesSDK', 'true'); + + // If we were waiting to launch this as a child app, report to the parent that it succeeded. + const child_launch_callback = window.child_launch_callbacks[event.data.appInstanceID]; + if (child_launch_callback) { + const parent_iframe = iframe_for_app_instance(child_launch_callback.parent_instance_id); + // send confirmation to requester window + parent_iframe.contentWindow.postMessage({ + msg: 'childAppLaunched', + original_msg_id: child_launch_callback.launch_msg_id, + child_instance_id: event.data.appInstanceID, + }, '*'); + delete window.child_launch_callbacks[event.data.appInstanceID]; + } } //------------------------------------------------- // windowFocused @@ -500,16 +513,20 @@ window.addEventListener('message', async (event) => { // launchApp //-------------------------------------------------------- else if(event.data.msg === 'launchApp'){ - // launch app + // TODO: Determine if the app is allowed to launch child apps? We may want to limit this to prevent abuse. + // remember app for launch callback later + const child_instance_id = uuidv4(); + window.child_launch_callbacks[child_instance_id] = { + parent_instance_id: event.data.appInstanceID, + launch_msg_id: msg_id, + }; + // launch child app launch_app({ name: event.data.app_name ?? app_name, args: event.data.args ?? {}, + parent_instance_id: event.data.appInstanceID, + uuid: child_instance_id, }); - - // send confirmation to requester window - target_iframe.contentWindow.postMessage({ - original_msg_id: msg_id, - }, '*'); } //-------------------------------------------------------- // readAppDataFile diff --git a/src/globals.js b/src/globals.js index 5d91c0af..ea429a28 100644 --- a/src/globals.js +++ b/src/globals.js @@ -130,6 +130,9 @@ window.launch_apps = []; window.launch_apps.recent = [] window.launch_apps.recommended = [] +// Map of { child_instance_id -> { parent_instance_id, launch_msg_id } } +window.child_launch_callbacks = {}; + // Is puter being loaded inside an iframe? if (window.location !== window.parent.location) { window.is_embedded = true; diff --git a/src/helpers.js b/src/helpers.js index 47bf3741..a8486d11 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1849,7 +1849,7 @@ window.trigger_download = (paths)=>{ * @param {*} options */ window.launch_app = async (options)=>{ - const uuid = uuidv4(); + const uuid = options.uuid ?? uuidv4(); let icon, title, file_signature; const window_options = options.window_options ?? {}; @@ -1945,6 +1945,11 @@ window.launch_app = async (options)=>{ // add app_id to URL iframe_url.searchParams.append('puter.app.id', app_info.uuid); + // add parent_app_instance_id to URL + if (options.parent_instance_id) { + iframe_url.searchParams.append('puter.parent_instance_id', options.parent_instance_id); + } + if(file_signature){ iframe_url.searchParams.append('puter.item.uid', file_signature.uid); iframe_url.searchParams.append('puter.item.path', options.file_path ? `~/` + options.file_path.split('/').slice(1).join('/') : file_signature.path);