diff --git a/src/IPC.js b/src/IPC.js index 56e257876..d9a2eb4af 100644 --- a/src/IPC.js +++ b/src/IPC.js @@ -74,11 +74,15 @@ window.addEventListener('message', async (event) => { return; } - const iframe_for_app_instance = (instanceID) => { - return $(`.window[data-element_uuid="${instanceID}"]`).find('.window-app-iframe').get(0) + const window_for_app_instance = (instance_id) => { + return $(`.window[data-element_uuid="${instance_id}"]`).get(0); }; - const $el_parent_window = $(`.window[data-element_uuid="${event.data.appInstanceID}"]`); + const iframe_for_app_instance = (instance_id) => { + return $(window_for_app_instance(instance_id)).find('.window-app-iframe').get(0); + }; + + const $el_parent_window = $(window_for_app_instance(event.data.appInstanceID)); const parent_window_id = $el_parent_window.attr('data-id'); const $el_parent_disable_mask = $el_parent_window.find('.window-disable-mask'); const target_iframe = iframe_for_app_instance(event.data.appInstanceID); @@ -357,7 +361,7 @@ window.addEventListener('message', async (event) => { // setWindowTitle //-------------------------------------------------------- else if(event.data.msg === 'setWindowTitle' && event.data.new_title !== undefined){ - const el_window = $(`.window[data-element_uuid="${event.data.appInstanceID}"]`).get(0); + const el_window = window_for_app_instance(event.data.appInstanceID); // set window title $(el_window).find(`.window-head-title`).html(html_encode(event.data.new_title)); // send confirmation to requester window @@ -1099,11 +1103,48 @@ window.addEventListener('message', async (event) => { contents, }, targetAppOrigin); } + //-------------------------------------------------------- + // closeApp + //-------------------------------------------------------- + else if (event.data.msg === 'closeApp') { + const { appInstanceID, targetAppInstanceID } = event.data; + + const target_window = window_for_app_instance(targetAppInstanceID); + if (!target_window) { + console.warn(`Failed to close non-existent app ${targetAppInstanceID}`); + return; + } + + // Check permissions + const allowed = (() => { + // Parents can close their children + if (target_window.dataset['parent_instance_id']) { + console.log(`⚠️ Allowing app ${appInstanceID} to close child app ${targetAppInstanceID}`); + return true; + } + + // God-mode apps can close anything + const app_info = await get_apps(app_name); + if (app_info.godmode === 1) { + console.log(`⚠️ Allowing GODMODE app ${appInstanceID} to close app ${targetAppInstanceID}`); + return true; + } + + // TODO: What other situations should we allow? + return false; + })(); + + if (allowed) { + $(target_window).close(); + } else { + console.warn(`⚠️ App ${appInstanceID} is not permitted to close app ${targetAppInstanceID}`); + } + } //-------------------------------------------------------- // exit //-------------------------------------------------------- else if(event.data.msg === 'exit'){ - $(`.window[data-element_uuid="${event.data.appInstanceID}"]`).close({bypass_iframe_messaging: true}); + $(window_for_app_instance(event.data.appInstanceID)).close({bypass_iframe_messaging: true}); } }); \ No newline at end of file diff --git a/src/UI/UIWindow.js b/src/UI/UIWindow.js index a922e4033..3134c23e7 100644 --- a/src/UI/UIWindow.js +++ b/src/UI/UIWindow.js @@ -185,6 +185,7 @@ async function UIWindow(options) { data-uid ="${options.uid}" data-element_uuid="${options.element_uuid}" data-parent_uuid="${options.parent_uuid}" + ${options.parent_instance_id ? `data-parent_instance_id="${options.parent_instance_id}"` : ''} data-id ="${win_id}" data-iframe_msg_uid ="${options.iframe_msg_uid}" data-is_dir ="${options.is_dir}" @@ -2745,8 +2746,9 @@ $.fn.close = async function(options) { options = options || {}; $(this).each(async function() { const el_iframe = $(this).find('.window-app-iframe'); + const app_uses_sdk = el_iframe.length > 0 && el_iframe.attr('data-appUsesSDK') === 'true'; // tell child app that this window is about to close, get its response - if(el_iframe.length > 0 && el_iframe.attr('data-appUsesSDK') === 'true'){ + if(app_uses_sdk){ if(!options.bypass_iframe_messaging){ const resp = await sendWindowWillCloseMsg(el_iframe.get(0)); if(!resp.msg){ @@ -2819,6 +2821,30 @@ $.fn.close = async function(options) { } // close child windows $(`.window[data-parent_uuid="${window_uuid}"]`).close(); + + // notify other apps that we're closing + if (app_uses_sdk) { + // notify parent app, if we have one, that we're closing + const parent_id = this.dataset['parent_instance_id']; + const parent = $(`.window[data-element_uuid="${parent_id}"] .window-app-iframe`).get(0); + if (parent) { + parent.contentWindow.postMessage({ + msg: 'appClosed', + appInstanceID: window_uuid, + }, '*'); + } + + // notify child apps, if we have them, that we're closing + const children = $(`.window[data-parent_instance_id="${window_uuid}"] .window-app-iframe`); + children.each((_, child) => { + child.contentWindow.postMessage({ + msg: 'appClosed', + appInstanceID: window_uuid, + }, '*'); + }); + // TODO: Once other AppConnections exist, those will need notifying too. + } + // remove backdrop $(this).closest('.window-backdrop').remove(); // remove DOM element diff --git a/src/helpers.js b/src/helpers.js index 7a8731b85..f3a88a9bc 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1860,6 +1860,10 @@ window.launch_app = async (options)=>{ let icon, title, file_signature; const window_options = options.window_options ?? {}; + if (options.parent_instance_id) { + window_options.parent_instance_id = options.parent_instance_id; + } + // try to get 3rd-party app info let app_info = options.app_obj ?? await get_apps(options.name);