diff --git a/src/IPC.js b/src/IPC.js index 947f63a8a..b13264878 100644 --- a/src/IPC.js +++ b/src/IPC.js @@ -29,6 +29,7 @@ import download from './helpers/download.js'; import path from "./lib/path.js"; import UIContextMenu from './UI/UIContextMenu.js'; import update_mouse_position from './helpers/update_mouse_position.js'; +import launch_app from './helpers/launch_app.js'; /** * In Puter, apps are loaded in iframes and communicate with the graphical user interface (GUI), and each other, using the postMessage API. @@ -730,7 +731,7 @@ window.addEventListener('message', async (event) => { launch_msg_id: msg_id, }; // launch child app - window.launch_app({ + launch_app({ name: event.data.app_name ?? app_name, args: event.data.args ?? {}, parent_instance_id: event.data.appInstanceID, diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index abfedb87b..f8ac72498 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -38,6 +38,7 @@ import UIWindowSettings from "./Settings/UIWindowSettings.js" import UIWindowTaskManager from "./UIWindowTaskManager.js" import truncate_filename from '../helpers/truncate_filename.js'; import UINotification from "./UINotification.js" +import launch_app from "../helpers/launch_app.js" async function UIDesktop(options){ let h = ''; @@ -1012,8 +1013,9 @@ async function UIDesktop(options){ else if(window.app_launched_from_url){ let qparams = new URLSearchParams(window.location.search); if(!qparams.has('c')){ - window.launch_app({ - name: window.app_launched_from_url, + launch_app({ + app: window.app_launched_from_url.name, + app_obj: window.app_launched_from_url, readURL: qparams.get('readURL'), maximized: qparams.get('maximized'), params: window.app_query_params ?? [], @@ -1376,7 +1378,7 @@ $(document).on('click', '.refer-btn', async function(e){ }) $(document).on('click', '.start-app', async function(e){ - window.launch_app({ + launch_app({ name: $(this).attr('data-app-name') }) // close popovers diff --git a/src/UI/UIItem.js b/src/UI/UIItem.js index a21668cf3..c22a3a395 100644 --- a/src/UI/UIItem.js +++ b/src/UI/UIItem.js @@ -27,6 +27,7 @@ import UIContextMenu from './UIContextMenu.js' import UIAlert from './UIAlert.js' import path from "../lib/path.js" import truncate_filename from '../helpers/truncate_filename.js'; +import launch_app from "../helpers/launch_app.js" function UIItem(options){ const matching_appendto_count = $(options.appendTo).length; @@ -426,7 +427,7 @@ function UIItem(options){ // open each item for (let i = 0; i < items_to_open.length; i++) { const item = items_to_open[i]; - window.launch_app({ + launch_app({ name: options.associated_app_name, file_path: item.path, // app_obj: open_item_meta.suggested_apps[0], @@ -1038,7 +1039,7 @@ function UIItem(options){ window.mutate_user_preferences(window.user_preferences); } } - window.launch_app({ + launch_app({ name: suggested_app.name, file_path: $(el_item).attr('data-path'), window_title: $(el_item).attr('data-name'), @@ -1143,7 +1144,7 @@ function UIItem(options){ html: i18n('deploy_as_app'), disabled: !options.is_dir, onClick: async function () { - window.launch_app({ + launch_app({ name: 'dev-center', file_path: $(el_item).attr('data-path'), file_uid: $(el_item).attr('data-uid'), diff --git a/src/UI/UITaskbar.js b/src/UI/UITaskbar.js index cefb78786..f8626a96c 100644 --- a/src/UI/UITaskbar.js +++ b/src/UI/UITaskbar.js @@ -19,6 +19,7 @@ import UITaskbarItem from './UITaskbarItem.js' import UIPopover from './UIPopover.js' +import launch_app from "../helpers/launch_app.js" async function UITaskbar(options){ window.global_element_id++; @@ -175,7 +176,7 @@ async function UITaskbar(options){ onClick: function(){ let open_window_count = parseInt($(`.taskbar-item[data-app="explorer"]`).attr('data-open-windows')); if(open_window_count === 0){ - window.launch_app({ name: 'explorer', path: window.home_path}); + launch_app({ name: 'explorer', path: window.home_path}); }else{ return false; } @@ -197,7 +198,7 @@ async function UITaskbar(options){ onClick: function(){ let open_window_count = parseInt($(`.taskbar-item[data-app="${app_info.name}"]`).attr('data-open-windows')); if(open_window_count === 0){ - window.launch_app({ + launch_app({ name: app_info.name, }) }else{ @@ -226,7 +227,7 @@ async function UITaskbar(options){ onClick: function(){ let open_windows = $(`.window[data-path="${html_encode(window.trash_path)}"]`); if(open_windows.length === 0){ - window.launch_app({ name: 'explorer', path: window.trash_path}); + launch_app({ name: 'explorer', path: window.trash_path}); }else{ open_windows.focusWindow(); } @@ -279,7 +280,7 @@ window.make_taskbar_sortable = function(){ onClick: function(){ let open_window_count = parseInt($(`.taskbar-item[data-app="${$(ui.item).attr('data-app-name')}"]`).attr('data-open-windows')); if(open_window_count === 0){ - window.launch_app({ + launch_app({ name: $(ui.item).attr('data-app-name'), }) }else{ diff --git a/src/UI/UITaskbarItem.js b/src/UI/UITaskbarItem.js index 504c2c8ed..fd293cced 100644 --- a/src/UI/UITaskbarItem.js +++ b/src/UI/UITaskbarItem.js @@ -19,6 +19,7 @@ import UIContextMenu from './UIContextMenu.js'; import path from '../lib/path.js'; +import launch_app from "../helpers/launch_app.js" let tray_item_id = 1; @@ -122,7 +123,7 @@ function UITaskbarItem(options){ val: $(this).attr('data-id'), onClick: function(){ // is trash? - window.launch_app({ + launch_app({ name: options.app, maximized: (isMobile.phone || isMobile.tablet), }) @@ -137,7 +138,7 @@ function UITaskbarItem(options){ html: 'Open Trash', val: $(this).attr('data-id'), onClick: function(){ - window.launch_app({ + launch_app({ name: options.app, path: window.trash_path, maximized: (isMobile.phone || isMobile.tablet), @@ -310,7 +311,7 @@ function UITaskbarItem(options){ // open each item for (let i = 0; i < items_to_sign.length; i++) { const item = items_to_sign[i]; - window.launch_app({ + launch_app({ name: options.app, file_path: item.path, // app_obj: open_item_meta.suggested_apps[0], diff --git a/src/UI/UIWindow.js b/src/UI/UIWindow.js index 66f35ac45..dae151579 100644 --- a/src/UI/UIWindow.js +++ b/src/UI/UIWindow.js @@ -28,6 +28,7 @@ import new_context_menu_item from '../helpers/new_context_menu_item.js'; import refresh_item_container from '../helpers/refresh_item_container.js'; import UIWindowSaveAccount from './UIWindowSaveAccount.js'; import UIWindowEmailConfirmationRequired from './UIWindowEmailConfirmationRequired.js'; +import launch_app from "../helpers/launch_app.js" const el_body = document.getElementsByTagName('body')[0]; @@ -435,7 +436,7 @@ async function UIWindow(options) { onClick: function(){ let open_window_count = parseInt($(`.taskbar-item[data-app="${options.app}"]`).attr('data-open-windows')); if(open_window_count === 0){ - window.launch_app({ + launch_app({ name: options.app, }) }else{ @@ -2038,7 +2039,7 @@ async function UIWindow(options) { html: i18n('deploy_as_app'), disabled: !options.is_dir, onClick: async function () { - window.launch_app({ + launch_app({ name: 'dev-center', file_path: $(el_window).attr('data-path'), file_uid: $(el_window).attr('data-uid'), @@ -2168,7 +2169,7 @@ async function UIWindow(options) { setTimeout(function(){ window.enter_fullpage_mode(el_window); $(el_window).show() - }, 5); + }, 50); } return el_window; diff --git a/src/globals.js b/src/globals.js index 37afdc7b2..900876711 100644 --- a/src/globals.js +++ b/src/globals.js @@ -151,6 +151,8 @@ window.original_window_position = {}; // recalculate desktop height and width on window resize $( window ).on( "resize", function() { + if(window.is_fullpage_mode) return; + const new_desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; const new_desktop_width = window.innerWidth; diff --git a/src/helpers.js b/src/helpers.js index 4e4b9cea0..63dc08efe 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -28,8 +28,8 @@ import update_username_in_gui from './helpers/update_username_in_gui.js'; import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js'; import content_type_to_icon from './helpers/content_type_to_icon.js'; import truncate_filename from './helpers/truncate_filename.js'; -import { PROCESS_RUNNING, PortalProcess, PseudoProcess } from "./definitions.js"; import UIWindowProgress from './UI/UIWindowProgress.js'; +import launch_app from "./helpers/launch_app.js"; window.is_auth = ()=>{ if(localStorage.getItem("auth_token") === null || window.auth_token === null) @@ -1565,300 +1565,6 @@ window.trigger_download = (paths)=>{ }); } -/** - * - * @param {*} options - */ -window.launch_app = async (options)=>{ - const uuid = options.uuid ?? window.uuidv4(); - 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 window.get_apps(options.name); - - //----------------------------------- - // icon - //----------------------------------- - if(app_info.icon) - icon = app_info.icon; - else if(options.name === 'explorer') - icon = window.icons['folder.svg']; - else - icon = window.icons['app-icon-'+options.name+'.svg'] - - //----------------------------------- - // title - //----------------------------------- - if(app_info.title) - title = app_info.title; - else if(options.window_title) - title = options.window_title; - else if(options.name) - title = options.name; - - //----------------------------------- - // maximize on start - //----------------------------------- - if(app_info.maximize_on_start && app_info.maximize_on_start === 1) - options.maximized = 1; - - //----------------------------------- - // if opened a file, sign it - //----------------------------------- - if(options.file_signature) - file_signature = options.file_signature; - else if(options.file_uid){ - file_signature = await puter.fs.sign(app_info.uuid, {uid: options.file_uid, action: 'write'}); - // add token to options - options.token = file_signature.token; - // add file_signature to options - file_signature = file_signature.items; - } - - // ----------------------------------- - // Create entry to track the "portal" - // (portals are processese in Puter's GUI) - // ----------------------------------- - - let el_win; - let process; - - //------------------------------------ - // Explorer - //------------------------------------ - if(options.name === 'explorer' || options.name === 'trash'){ - process = new PseudoProcess({ - uuid, - name: 'explorer', - parent: options.parent_instance_id, - meta: { - launch_options: options, - app_info: app_info, - } - }); - const svc_process = globalThis.services.get('process'); - svc_process.register(process); - if(options.path === window.home_path){ - title = 'Home'; - icon = window.icons['folder-home.svg']; - } - else if(options.path === window.trash_path){ - title = 'Trash'; - icon = window.icons['trash.svg']; - } - else if(!options.path) - title = window.root_dirname; - else - title = path.dirname(options.path); - - // open window - el_win = UIWindow({ - element_uuid: uuid, - icon: icon, - path: options.path ?? window.home_path, - title: title, - uid: null, - is_dir: true, - app: 'explorer', - ...window_options, - is_maximized: options.maximized, - }); - } - //------------------------------------ - // All other apps - //------------------------------------ - else{ - process = new PortalProcess({ - uuid, - name: app_info.name, - parent: options.parent_instance_id, - meta: { - launch_options: options, - app_info: app_info, - } - }); - const svc_process = globalThis.services.get('process'); - svc_process.register(process); - - //----------------------------------- - // iframe_url - //----------------------------------- - let iframe_url; - - // This can be any trusted URL that won't be used for other apps - const BUILTIN_PREFIX = 'https://builtins.namespaces.puter.com/'; - - if(!app_info.index_url){ - iframe_url = new URL('https://'+options.name+'.' + window.app_domain + `/index.html`); - } else if ( app_info.index_url.startsWith(BUILTIN_PREFIX) ) { - const name = app_info.index_url.slice(BUILTIN_PREFIX.length); - iframe_url = new URL(`${window.gui_origin}/builtin/${name}`); - } else { - iframe_url = new URL(app_info.index_url); - } - - // add app_instance_id to URL - iframe_url.searchParams.append('puter.app_instance_id', uuid); - - // 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', privacy_aware_path(options.file_path) || file_signature.path); - iframe_url.searchParams.append('puter.item.name', file_signature.fsentry_name); - iframe_url.searchParams.append('puter.item.read_url', file_signature.read_url); - iframe_url.searchParams.append('puter.item.write_url', file_signature.write_url); - iframe_url.searchParams.append('puter.item.metadata_url', file_signature.metadata_url); - iframe_url.searchParams.append('puter.item.size', file_signature.fsentry_size); - iframe_url.searchParams.append('puter.item.accessed', file_signature.fsentry_accessed); - iframe_url.searchParams.append('puter.item.modified', file_signature.fsentry_modified); - iframe_url.searchParams.append('puter.item.created', file_signature.fsentry_created); - iframe_url.searchParams.append('puter.domain', window.app_domain); - } - else if(options.readURL){ - iframe_url.searchParams.append('puter.item.name', options.filename); - iframe_url.searchParams.append('puter.item.path', privacy_aware_path(options.file_path)); - iframe_url.searchParams.append('puter.item.read_url', options.readURL); - iframe_url.searchParams.append('puter.domain', window.app_domain); - } - - if (app_info.godmode && app_info.godmode === 1){ - // Add auth_token to GODMODE apps - - iframe_url.searchParams.append('puter.auth.token', window.auth_token); - iframe_url.searchParams.append('puter.auth.username', window.user.username); - iframe_url.searchParams.append('puter.domain', window.app_domain); - } else if (options.token){ - // App token. Only add token if it's not a GODMODE app since GODMODE apps already have the super token - // that has access to everything. - - iframe_url.searchParams.append('puter.auth.token', options.token); - } else { - // Try to acquire app token from the server - - let response = await fetch(window.api_origin + "/auth/get-user-app-token", { - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer "+ window.auth_token, - }, - "body": JSON.stringify({app_uid: app_info.uid ?? app_info.uuid}), - "method": "POST", - }); - let res = await response.json(); - if(res.token){ - iframe_url.searchParams.append('puter.auth.token', res.token); - } - } - - if(window.api_origin) - iframe_url.searchParams.append('puter.api_origin', window.api_origin); - - // Add options.params to URL - if(options.params){ - iframe_url.searchParams.append('puter.domain', window.app_domain); - for (const property in options.params) { - iframe_url.searchParams.append(property, options.params[property]); - } - } - - // Add locale to URL - iframe_url.searchParams.append('puter.locale', window.locale); - - // Add options.args to URL - iframe_url.searchParams.append('puter.args', JSON.stringify(options.args ?? {})); - - // ...and finally append utm_source=puter.com to the URL - iframe_url.searchParams.append('utm_source', 'puter.com'); - - // register app_instance_uid - window.app_instance_ids.add(uuid); - - // open window - el_win = UIWindow({ - element_uuid: uuid, - title: title, - iframe_url: iframe_url.href, - params: options.params ?? undefined, - icon: icon, - window_class: 'window-app', - update_window_url: true, - app_uuid: app_info.uuid ?? app_info.uid, - top: options.maximized ? 0 : undefined, - left: options.maximized ? 0 : undefined, - height: options.maximized ? `calc(100% - ${window.taskbar_height + window.toolbar_height + 1}px)` : undefined, - width: options.maximized ? `100%` : undefined, - app: options.name, - is_visible: ! app_info.background, - is_maximized: options.maximized, - is_fullpage: options.is_fullpage, - ...window_options, - show_in_taskbar: app_info.background ? false : window_options?.show_in_taskbar, - }); - - if ( ! app_info.background ) { - $(el_win).show(); - } - - // send post request to /rao to record app open - if(options.name !== 'explorer'){ - // add the app to the beginning of the array - window.launch_apps.recent.unshift(app_info); - - // dedupe the array by uuid, uid, and id - window.launch_apps.recent = _.uniqBy(window.launch_apps.recent, 'name'); - - // limit to window.launch_recent_apps_count - window.launch_apps.recent = window.launch_apps.recent.slice(0, window.launch_recent_apps_count); - - // send post request to /rao to record app open - $.ajax({ - url: window.api_origin + "/rao", - type: 'POST', - data: JSON.stringify({ - original_client_socket_id: window.socket?.id, - app_uid: app_info.uid ?? app_info.uuid, - }), - async: true, - contentType: "application/json", - headers: { - "Authorization": "Bearer "+window.auth_token - }, - }) - } - } - - (async () => { - const el = await el_win; - $(el).on('remove', () => { - const svc_process = globalThis.services.get('process'); - svc_process.unregister(process.uuid); - - // If it's a non-sdk app, report that it launched and closed. - // FIXME: This is awkward. Really, we want some way of knowing when it's launched and reporting that immediately instead. - const $app_iframe = $(el).find('.window-app-iframe'); - if ($app_iframe.attr('data-appUsesSdk') !== 'true') { - window.report_app_launched(process.uuid, { uses_sdk: false }); - // We also have to report an extra close event because the real one was sent already - window.report_app_closed(process.uuid); - } - }); - - process.references.el_win = el; - process.chstatus(PROCESS_RUNNING); - })(); -} - window.open_item = async function(options){ let el_item = options.item; const $el_parent_window = $(el_item).closest('.window'); @@ -1968,7 +1674,7 @@ window.open_item = async function(options){ // Does the user have a preference for this file type? //---------------------------------------------------------------- else if(!associated_app_name && !is_dir && window.user_preferences[`default_apps${path.extname(item_path).toLowerCase()}`]) { - window.launch_app({ + launch_app({ name: window.user_preferences[`default_apps${path.extname(item_path).toLowerCase()}`], file_path: item_path, window_title: path.basename(item_path), @@ -1980,7 +1686,7 @@ window.open_item = async function(options){ // Is there an app associated with this item? //---------------------------------------------------------------- else if(associated_app_name !== ''){ - window.launch_app({ + launch_app({ name: associated_app_name, }) } @@ -2079,7 +1785,7 @@ window.open_item = async function(options){ // First suggested app is default app to open this item //--------------------------------------------- else{ - window.launch_app({ + launch_app({ name: suggested_apps[0].name, token: open_item_meta.token, file_path: item_path, diff --git a/src/helpers/launch_app.js b/src/helpers/launch_app.js new file mode 100644 index 000000000..7754e6ea1 --- /dev/null +++ b/src/helpers/launch_app.js @@ -0,0 +1,327 @@ +/** + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import path from "../lib/path.js" +import { PROCESS_RUNNING, PortalProcess, PseudoProcess } from "../definitions.js"; +import UIWindow from "../UI/UIWindow.js"; + +/** + * Launches an app. + * + * @param {*} options.name - The name of the app to launch. + */ +const launch_app = async (options)=>{ + console.log('launch_app', options); + const uuid = options.uuid ?? window.uuidv4(); + 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; + } + + // If the app object is not provided, get it from the server + let app_info = options.app_obj ?? await window.get_apps(options.name); + + // For backward compatibility reasons we need to make sure that both `uuid` and `uid` are set + app_info.uuid = app_info.uuid ?? app_info.uid; + app_info.uid = app_info.uid ?? app_info.uuid; + + // If no `options.name` is provided, use the app name from the app_info + options.name = options.name ?? app_info.name; + + //----------------------------------- + // icon + //----------------------------------- + if(app_info.icon) + icon = app_info.icon; + else if(options.name === 'explorer') + icon = window.icons['folder.svg']; + else + icon = window.icons['app-icon-'+options.name+'.svg'] + + //----------------------------------- + // title + //----------------------------------- + if(app_info.title) + title = app_info.title; + else if(options.window_title) + title = options.window_title; + else if(options.name) + title = options.name; + + //----------------------------------- + // maximize on start + //----------------------------------- + if(app_info.maximize_on_start && app_info.maximize_on_start === 1) + options.maximized = 1; + + //----------------------------------- + // if opened a file, sign it + //----------------------------------- + if(options.file_signature) + file_signature = options.file_signature; + else if(options.file_uid){ + file_signature = await puter.fs.sign(app_info.uuid, {uid: options.file_uid, action: 'write'}); + // add token to options + options.token = file_signature.token; + // add file_signature to options + file_signature = file_signature.items; + } + + // ----------------------------------- + // Create entry to track the "portal" + // (portals are processese in Puter's GUI) + // ----------------------------------- + + let el_win; + let process; + + //------------------------------------ + // Explorer + //------------------------------------ + if(options.name === 'explorer' || options.name === 'trash'){ + process = new PseudoProcess({ + uuid, + name: 'explorer', + parent: options.parent_instance_id, + meta: { + launch_options: options, + app_info: app_info, + } + }); + const svc_process = globalThis.services.get('process'); + svc_process.register(process); + if(options.path === window.home_path){ + title = 'Home'; + icon = window.icons['folder-home.svg']; + } + else if(options.path === window.trash_path){ + title = 'Trash'; + icon = window.icons['trash.svg']; + } + else if(!options.path) + title = window.root_dirname; + else + title = path.dirname(options.path); + + // open window + el_win = UIWindow({ + element_uuid: uuid, + icon: icon, + path: options.path ?? window.home_path, + title: title, + uid: null, + is_dir: true, + app: 'explorer', + ...window_options, + is_maximized: options.maximized, + }); + } + //------------------------------------ + // All other apps + //------------------------------------ + else{ + process = new PortalProcess({ + uuid, + name: app_info.name, + parent: options.parent_instance_id, + meta: { + launch_options: options, + app_info: app_info, + } + }); + const svc_process = globalThis.services.get('process'); + svc_process.register(process); + + //----------------------------------- + // iframe_url + //----------------------------------- + let iframe_url; + + // This can be any trusted URL that won't be used for other apps + const BUILTIN_PREFIX = 'https://builtins.namespaces.puter.com/'; + + if(!app_info.index_url){ + iframe_url = new URL('https://'+options.name+'.' + window.app_domain + `/index.html`); + } else if ( app_info.index_url.startsWith(BUILTIN_PREFIX) ) { + const name = app_info.index_url.slice(BUILTIN_PREFIX.length); + iframe_url = new URL(`${window.gui_origin}/builtin/${name}`); + } else { + iframe_url = new URL(app_info.index_url); + } + + // add app_instance_id to URL + iframe_url.searchParams.append('puter.app_instance_id', uuid); + + // 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', privacy_aware_path(options.file_path) || file_signature.path); + iframe_url.searchParams.append('puter.item.name', file_signature.fsentry_name); + iframe_url.searchParams.append('puter.item.read_url', file_signature.read_url); + iframe_url.searchParams.append('puter.item.write_url', file_signature.write_url); + iframe_url.searchParams.append('puter.item.metadata_url', file_signature.metadata_url); + iframe_url.searchParams.append('puter.item.size', file_signature.fsentry_size); + iframe_url.searchParams.append('puter.item.accessed', file_signature.fsentry_accessed); + iframe_url.searchParams.append('puter.item.modified', file_signature.fsentry_modified); + iframe_url.searchParams.append('puter.item.created', file_signature.fsentry_created); + iframe_url.searchParams.append('puter.domain', window.app_domain); + } + else if(options.readURL){ + iframe_url.searchParams.append('puter.item.name', options.filename); + iframe_url.searchParams.append('puter.item.path', privacy_aware_path(options.file_path)); + iframe_url.searchParams.append('puter.item.read_url', options.readURL); + iframe_url.searchParams.append('puter.domain', window.app_domain); + } + + // In godmode, we add the super token to the iframe URL + // so that the app can access everything. + if (app_info.godmode && app_info.godmode === 1){ + iframe_url.searchParams.append('puter.auth.token', window.auth_token); + iframe_url.searchParams.append('puter.auth.username', window.user.username); + iframe_url.searchParams.append('puter.domain', window.app_domain); + } + // App token. Only add token if it's not a GODMODE app since GODMODE apps already have the super token + // that has access to everything. + else if (options.token){ + iframe_url.searchParams.append('puter.auth.token', options.token); + } else { + // Try to acquire app token from the server + + let response = await fetch(window.api_origin + "/auth/get-user-app-token", { + "headers": { + "Content-Type": "application/json", + "Authorization": "Bearer "+ window.auth_token, + }, + "body": JSON.stringify({app_uid: app_info.uid ?? app_info.uuid}), + "method": "POST", + }); + let res = await response.json(); + if(res.token){ + iframe_url.searchParams.append('puter.auth.token', res.token); + } + } + + if(window.api_origin) + iframe_url.searchParams.append('puter.api_origin', window.api_origin); + + // Add options.params to URL + if(options.params){ + iframe_url.searchParams.append('puter.domain', window.app_domain); + for (const property in options.params) { + iframe_url.searchParams.append(property, options.params[property]); + } + } + + // Add locale to URL + iframe_url.searchParams.append('puter.locale', window.locale); + + // Add options.args to URL + iframe_url.searchParams.append('puter.args', JSON.stringify(options.args ?? {})); + + // ...and finally append utm_source=puter.com to the URL + iframe_url.searchParams.append('utm_source', 'puter.com'); + + // register app_instance_uid + window.app_instance_ids.add(uuid); + + // open window + el_win = UIWindow({ + element_uuid: uuid, + title: title, + iframe_url: iframe_url.href, + params: options.params ?? undefined, + icon: icon, + window_class: 'window-app', + update_window_url: true, + app_uuid: app_info.uuid ?? app_info.uid, + top: options.maximized ? 0 : undefined, + left: options.maximized ? 0 : undefined, + height: options.maximized ? `calc(100% - ${window.taskbar_height + window.toolbar_height + 1}px)` : undefined, + width: options.maximized ? `100%` : undefined, + app: options.name, + is_visible: ! app_info.background, + is_maximized: options.maximized, + is_fullpage: options.is_fullpage, + ...window_options, + show_in_taskbar: app_info.background ? false : window_options?.show_in_taskbar, + }); + + if ( ! app_info.background ) { + $(el_win).show(); + } + + // send post request to /rao to record app open + if(options.name !== 'explorer'){ + // add the app to the beginning of the array + window.launch_apps.recent.unshift(app_info); + + // dedupe the array by uuid, uid, and id + window.launch_apps.recent = _.uniqBy(window.launch_apps.recent, 'name'); + + // limit to window.launch_recent_apps_count + window.launch_apps.recent = window.launch_apps.recent.slice(0, window.launch_recent_apps_count); + + // send post request to /rao to record app open + $.ajax({ + url: window.api_origin + "/rao", + type: 'POST', + data: JSON.stringify({ + original_client_socket_id: window.socket?.id, + app_uid: app_info.uid ?? app_info.uuid, + }), + async: true, + contentType: "application/json", + headers: { + "Authorization": "Bearer "+window.auth_token + }, + }) + } + } + + (async () => { + const el = await el_win; + $(el).on('remove', () => { + const svc_process = globalThis.services.get('process'); + svc_process.unregister(process.uuid); + + // If it's a non-sdk app, report that it launched and closed. + // FIXME: This is awkward. Really, we want some way of knowing when it's launched and reporting that immediately instead. + const $app_iframe = $(el).find('.window-app-iframe'); + if ($app_iframe.attr('data-appUsesSdk') !== 'true') { + window.report_app_launched(process.uuid, { uses_sdk: false }); + // We also have to report an extra close event because the real one was sent already + window.report_app_closed(process.uuid); + } + }); + + process.references.el_win = el; + process.chstatus(PROCESS_RUNNING); + })(); +} + +export default launch_app; \ No newline at end of file diff --git a/src/initgui.js b/src/initgui.js index 20bffd785..eee07fb97 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -194,6 +194,14 @@ window.initgui = async function(options){ if(url_paths[0]?.toLocaleLowerCase() === 'app' && url_paths[1]){ window.app_launched_from_url = url_paths[1]; + // get app metadata + try{ + window.app_launched_from_url = await puter.apps.get(window.app_launched_from_url) + window.is_fullpage_mode = window.app_launched_from_url.metadata?.fullpage_on_landing ?? false; + }catch(e){ + console.error(e); + } + // get query params, any param that doesn't start with 'puter.' will be passed to the app window.app_query_params = {}; for (let [key, value] of window.url_query_params) { diff --git a/src/keyboard.js b/src/keyboard.js index 89860baee..37d7df148 100644 --- a/src/keyboard.js +++ b/src/keyboard.js @@ -18,6 +18,7 @@ */ import UIAlert from './UI/UIAlert.js'; +import launch_app from './helpers/launch_app.js'; $(document).bind('keydown', async function(e){ const focused_el = document.activeElement; @@ -626,7 +627,7 @@ $(document).bind("keyup keydown", async function(e){ if($('.launch-app-selected').length > 0){ // close launch menu $(".launch-popover").fadeOut(200, function(){ - window.launch_app({ + launch_app({ name: $('.launch-app-selected').attr('data-name'), }) $(".launch-popover").remove();