Spaces:
Paused
Paused
/** | |
* 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 <https://www.gnu.org/licenses/>. | |
*/ | |
import UIAlert from './UI/UIAlert.js'; | |
import UIWindow from './UI/UIWindow.js'; | |
import UIWindowSignup from './UI/UIWindowSignup.js'; | |
import UIWindowRequestPermission from './UI/UIWindowRequestPermission.js'; | |
import UIItem from './UI/UIItem.js' | |
import UIWindowFontPicker from './UI/UIWindowFontPicker.js'; | |
import UIWindowColorPicker from './UI/UIWindowColorPicker.js'; | |
import UIPrompt from './UI/UIPrompt.js'; | |
import download from './helpers/download.js'; | |
import path from "./lib/path.js"; | |
/** | |
* In Puter, apps are loaded in iframes and communicate with the graphical user interface (GUI) aand each other using the postMessage API. | |
* The following sets up an Inter-Process Messaging System between apps and the GUI that enables communication | |
* for various tasks such as displaying alerts, prompts, managing windows, handling file operations, and more. | |
* | |
* The system listens for 'message' events on the window object, handling different types of messages from the app (which is loaded in an iframe), | |
* such as ALERT, createWindow, showOpenFilePicker, ... | |
* Each message handler performs specific actions, including creating UI windows, handling file saves and reads, and responding to user interactions. | |
* | |
* Precautions are taken to ensure proper usage of appInstanceIDs and other sensitive information. | |
*/ | |
window.addEventListener('message', async (event) => { | |
const app_env = event.data?.env ?? 'app'; | |
// Only process messages from apps | |
if(app_env !== 'app') | |
return; | |
// -------------------------------------------------------- | |
// A response to a GUI message received from the app. | |
// -------------------------------------------------------- | |
if (typeof event.data.original_msg_id !== "undefined" && typeof appCallbackFunctions[event.data.original_msg_id] !== "undefined") { | |
// Execute callback | |
appCallbackFunctions[event.data.original_msg_id](event.data); | |
// Remove this callback function since it won't be needed again | |
delete appCallbackFunctions[event.data.original_msg_id]; | |
// Done | |
return; | |
} | |
// -------------------------------------------------------- | |
// Message from apps | |
// -------------------------------------------------------- | |
// `data` and `msg` are required | |
if(!event.data || !event.data.msg){ | |
return; | |
} | |
// `appInstanceID` is required | |
if(!event.data.appInstanceID){ | |
console.log(`appInstanceID is needed`); | |
return; | |
} | |
const $el_parent_window = $(`.window[data-element_uuid="${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 = $(`.window[data-element_uuid="${event.data.appInstanceID}"]`).find('.window-app-iframe').get(0); | |
const msg_id = event.data.uuid; | |
const app_name = $(target_iframe).attr('data-app'); | |
const app_uuid = $el_parent_window.attr('data-app_uuid'); | |
// todo validate all event.data stuff coming from the client (e.g. event.data.message, .msg, ...) | |
//------------------------------------------------- | |
// READY | |
//------------------------------------------------- | |
if(event.data.msg === 'READY'){ | |
$(target_iframe).attr('data-appUsesSDK', 'true'); | |
} | |
//------------------------------------------------- | |
// windowFocused | |
//------------------------------------------------- | |
else if(event.data.msg === 'windowFocused'){ | |
console.log('windowFocused'); | |
} | |
//-------------------------------------------------------- | |
// ALERT | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'ALERT' && event.data.message !== undefined){ | |
const alert_resp = await UIAlert({ | |
message: html_encode(event.data.message), | |
buttons: event.data.buttons, | |
type: event.data.options?.type, | |
window_options: { | |
parent_uuid: event.data.appInstanceID, | |
disable_parent_window: true, | |
} | |
}) | |
target_iframe.contentWindow.postMessage({ | |
original_msg_id: msg_id, | |
msg: 'alertResponded', | |
response: alert_resp, | |
}, '*'); | |
} | |
//-------------------------------------------------------- | |
// PROMPT | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'PROMPT' && event.data.message !== undefined){ | |
const prompt_resp = await UIPrompt({ | |
message: html_encode(event.data.message), | |
placeholder: html_encode(event.data.placeholder), | |
window_options: { | |
parent_uuid: event.data.appInstanceID, | |
disable_parent_window: true, | |
} | |
}) | |
target_iframe.contentWindow.postMessage({ | |
original_msg_id: msg_id, | |
msg: 'promptResponded', | |
response: prompt_resp, | |
}, '*'); | |
} | |
//-------------------------------------------------------- | |
// env | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'env'){ | |
target_iframe.contentWindow.postMessage({ | |
original_msg_id: msg_id, | |
}, '*'); | |
} | |
//-------------------------------------------------------- | |
// createWindow | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'createWindow'){ | |
// todo: validate as many of these as possible | |
if(event.data.options){ | |
UIWindow({ | |
title: event.data.options.title, | |
disable_parent_window: event.data.options.disable_parent_window, | |
width: event.data.options.width, | |
height: event.data.options.height, | |
is_resizable: event.data.options.is_resizable, | |
has_head: event.data.options.has_head, | |
center: event.data.options.center, | |
show_in_taskbar: event.data.options.show_in_taskbar, | |
iframe_srcdoc: event.data.options.content, | |
iframe_url: event.data.options.url, | |
parent_uuid: event.data.appInstanceID, | |
}) | |
} | |
} | |
//-------------------------------------------------------- | |
// setItem | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'setItem' && event.data.key && event.data.value){ | |
// todo: validate key and value to avoid unnecessary api calls | |
return await $.ajax({ | |
url: api_origin + "/setItem", | |
type: 'POST', | |
data: JSON.stringify({ | |
app: app_uuid, | |
key: event.data.key, | |
value: event.data.value, | |
}), | |
async: true, | |
contentType: "application/json", | |
headers: { | |
"Authorization": "Bearer "+auth_token | |
}, | |
statusCode: { | |
401: function () { | |
logout(); | |
}, | |
}, | |
success: function (fsentry){ | |
} | |
}) | |
} | |
//-------------------------------------------------------- | |
// getItem | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'getItem' && event.data.key){ | |
// todo: validate key to avoid unnecessary api calls | |
$.ajax({ | |
url: api_origin + "/getItem", | |
type: 'POST', | |
data: JSON.stringify({ | |
key: event.data.key, | |
app: app_uuid, | |
}), | |
async: true, | |
contentType: "application/json", | |
headers: { | |
"Authorization": "Bearer "+auth_token | |
}, | |
statusCode: { | |
401: function () { | |
logout(); | |
}, | |
}, | |
success: function (result){ | |
// send confirmation to requester window | |
target_iframe.contentWindow.postMessage({ | |
original_msg_id: msg_id, | |
msg: 'getItemSucceeded', | |
value: result ? result.value : null, | |
}, '*'); | |
} | |
}) | |
} | |
//-------------------------------------------------------- | |
// removeItem | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'removeItem' && event.data.key){ | |
// todo: validate key to avoid unnecessary api calls | |
$.ajax({ | |
url: api_origin + "/removeItem", | |
type: 'POST', | |
data: JSON.stringify({ | |
key: event.data.key, | |
app: app_uuid, | |
}), | |
async: true, | |
contentType: "application/json", | |
headers: { | |
"Authorization": "Bearer "+auth_token | |
}, | |
statusCode: { | |
401: function () { | |
logout(); | |
}, | |
}, | |
success: function (result){ | |
// send confirmation to requester window | |
target_iframe.contentWindow.postMessage({ | |
original_msg_id: msg_id, | |
}, '*'); | |
} | |
}) | |
} | |
//-------------------------------------------------------- | |
// showOpenFilePicker | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'showOpenFilePicker'){ | |
// Auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
// Disable parent window | |
$el_parent_window.addClass('window-disabled') | |
$el_parent_disable_mask.show(); | |
$el_parent_disable_mask.css('z-index', parseInt($el_parent_window.css('z-index')) + 1); | |
$(target_iframe).blur(); | |
// Allowed_file_types | |
let allowed_file_types = ""; | |
if(event.data.options && event.data.options.accept) | |
allowed_file_types = event.data.options.accept; | |
// selectable_body | |
let is_selectable_body = false; | |
if(event.data.options && event.data.options.multiple && event.data.options.multiple === true) | |
is_selectable_body = true; | |
// Open dialog | |
UIWindow({ | |
allowed_file_types: allowed_file_types, | |
path: '/' + window.user.username + '/Desktop', | |
// this is the uuid of the window to which this dialog will return | |
parent_uuid: event.data.appInstanceID, | |
show_maximize_button: false, | |
show_minimize_button: false, | |
title: 'Open', | |
is_dir: true, | |
is_openFileDialog: true, | |
selectable_body: is_selectable_body, | |
iframe_msg_uid: msg_id, | |
initiating_app_uuid: app_uuid, | |
center: true, | |
}); | |
} | |
//-------------------------------------------------------- | |
// showDirectoryPicker | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'showDirectoryPicker'){ | |
// Auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
// Disable parent window | |
$el_parent_window.addClass('window-disabled') | |
$el_parent_disable_mask.show(); | |
$el_parent_disable_mask.css('z-index', parseInt($el_parent_window.css('z-index')) + 1); | |
$(target_iframe).blur(); | |
// allowed_file_types | |
let allowed_file_types = ""; | |
if(event.data.options && event.data.options.accept) | |
allowed_file_types = event.data.options.accept; | |
// selectable_body | |
let is_selectable_body = false; | |
if(event.data.options && event.data.options.multiple && event.data.options.multiple === true) | |
is_selectable_body = true; | |
// open dialog | |
UIWindow({ | |
path: '/' + window.user.username + '/Desktop', | |
// this is the uuid of the window to which this dialog will return | |
parent_uuid: event.data.appInstanceID, | |
show_maximize_button: false, | |
show_minimize_button: false, | |
title: 'Open', | |
is_dir: true, | |
is_directoryPicker: true, | |
selectable_body: is_selectable_body, | |
iframe_msg_uid: msg_id, | |
center: true, | |
initiating_app_uuid: app_uuid, | |
}); | |
} | |
//-------------------------------------------------------- | |
// setWindowTitle | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'setWindowTitle' && event.data.new_title !== undefined){ | |
const el_window = $(`.window[data-element_uuid="${event.data.appInstanceID}"]`).get(0); | |
// set window title | |
$(el_window).find(`.window-head-title`).html(html_encode(event.data.new_title)); | |
// send confirmation to requester window | |
target_iframe.contentWindow.postMessage({ | |
original_msg_id: msg_id, | |
}, '*'); | |
} | |
//-------------------------------------------------------- | |
// watchItem | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'watchItem' && event.data.item_uid !== undefined){ | |
if(!window.watchItems[event.data.item_uid]) | |
window.watchItems[event.data.item_uid] = []; | |
window.watchItems[event.data.item_uid].push(event.data.appInstanceID); | |
} | |
//-------------------------------------------------------- | |
// openItem | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'openItem'){ | |
// check if readURL returns 200 | |
$.ajax({ | |
url: event.data.metadataURL + '&return_suggested_apps=true&return_path=true', | |
type: 'GET', | |
headers: { | |
"Authorization": "Bearer "+auth_token | |
}, | |
success: async function(metadata){ | |
$.ajax({ | |
url: api_origin + "/open_item", | |
type: 'POST', | |
contentType: "application/json", | |
data: JSON.stringify({ | |
uid: metadata.uid ?? undefined, | |
path: metadata.path ?? undefined, | |
}), | |
headers: { | |
"Authorization": "Bearer "+auth_token | |
}, | |
statusCode: { | |
401: function () { | |
logout(); | |
}, | |
}, | |
success: function(open_item_meta){ | |
setTimeout(function(){ | |
launch_app({ | |
name: metadata.name, | |
file_path: metadata.path, | |
app_obj: open_item_meta.suggested_apps[0], | |
window_title: metadata.name, | |
file_uid: metadata.uid, | |
file_signature: open_item_meta.signature, | |
}); | |
// todo: this is done because sometimes other windows such as openFileDialog | |
// bring focus to their apps and steal the focus from the newly-opened app | |
}, 800); | |
}, | |
}); | |
} | |
}) | |
} | |
//-------------------------------------------------------- | |
// launchApp | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'launchApp'){ | |
// launch app | |
launch_app({ | |
name: event.data.app_name ?? app_name, | |
args: event.data.args ?? {}, | |
}); | |
// send confirmation to requester window | |
target_iframe.contentWindow.postMessage({ | |
original_msg_id: msg_id, | |
}, '*'); | |
} | |
//-------------------------------------------------------- | |
// readAppDataFile | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'readAppDataFile' && event.data.path !== undefined){ | |
// resolve path to absolute | |
event.data.path = path.resolve(event.data.path); | |
// join with appdata dir | |
const file_path = path.join(appdata_path, app_uuid, event.data.path); | |
puter.fs.sign(app_uuid, { | |
path: file_path, | |
action: 'write', | |
}, | |
function(signature){ | |
signature = signature.items; | |
signature.signatures = signature.signatures ?? [signature]; | |
if(signature.signatures.length > 0 && signature.signatures[0].path){ | |
signature.signatures[0].path = `~/` + signature.signatures[0].path.split('/').slice(2).join('/') | |
// send confirmation to requester window | |
target_iframe.contentWindow.postMessage({ | |
msg: "readAppDataFileSucceeded", | |
original_msg_id: msg_id, | |
item: signature.signatures[0], | |
}, '*'); | |
}else{ | |
// send error to requester window | |
target_iframe.contentWindow.postMessage({ | |
msg: "readAppDataFileFailed", | |
original_msg_id: msg_id, | |
}, '*'); | |
} | |
} | |
) | |
} | |
//-------------------------------------------------------- | |
// getAppData | |
//-------------------------------------------------------- | |
// todo appdata should be provided from the /open_item api call | |
else if(event.data.msg === 'getAppData'){ | |
if(appdata_signatures[app_uuid]){ | |
target_iframe.contentWindow.postMessage({ | |
msg: "getAppDataSucceeded", | |
original_msg_id: msg_id, | |
item: appdata_signatures[app_uuid], | |
}, '*'); | |
} | |
// make app directory if it doesn't exist | |
puter.fs.mkdir({ | |
path: path.join( appdata_path, app_uuid), | |
rename: false, | |
overwrite: false, | |
success: function(dir){ | |
puter.fs.sign(app_uuid, { | |
uid: dir.uid, | |
action: 'write', | |
success: function(signature){ | |
signature = signature.items; | |
appdata_signatures[app_uuid] = signature; | |
// send confirmation to requester window | |
target_iframe.contentWindow.postMessage({ | |
msg: "getAppDataSucceeded", | |
original_msg_id: msg_id, | |
item: signature, | |
}, '*'); | |
} | |
}) | |
}, | |
error: function(err){ | |
if(err.existing_fsentry || err.code === 'path_exists'){ | |
puter.fs.sign(app_uuid, { | |
uid: err.existing_fsentry.uid, | |
action: 'write', | |
success: function(signature){ | |
signature = signature.items; | |
appdata_signatures[app_uuid] = signature; | |
// send confirmation to requester window | |
target_iframe.contentWindow.postMessage({ | |
msg: "getAppDataSucceeded", | |
original_msg_id: msg_id, | |
item: signature, | |
}, '*'); | |
} | |
}) | |
} | |
} | |
}); | |
} | |
//-------------------------------------------------------- | |
// requestPermission | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'requestPermission'){ | |
// auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
// options must be an object | |
if(event.data.options === undefined || typeof event.data.options !== 'object') | |
event.data.options = {}; | |
// clear window_options for security reasons | |
event.data.options.window_options = {} | |
// Set app as parent window of font picker window | |
event.data.options.window_options.parent_uuid = event.data.appInstanceID; | |
// disable parent window | |
event.data.options.window_options.disable_parent_window = true; | |
let granted = await UIWindowRequestPermission({ | |
origin: event.origin, | |
permission: event.data.options.permission, | |
window_options: event.data.options.window_options, | |
}); | |
// send selected font to requester window | |
target_iframe.contentWindow.postMessage({ | |
msg: "permissionGranted", | |
granted: granted, | |
original_msg_id: msg_id, | |
}, '*'); | |
$(target_iframe).get(0).focus({preventScroll:true}); | |
} | |
//-------------------------------------------------------- | |
// showFontPicker | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'showFontPicker'){ | |
// auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
// set options | |
event.data.options = event.data.options ?? {}; | |
// clear window_options for security reasons | |
event.data.options.window_options = {} | |
// Set app as parent window of font picker window | |
event.data.options.window_options.parent_uuid = event.data.appInstanceID; | |
// Open font picker | |
let selected_font = await UIWindowFontPicker(event.data.options); | |
// send selected font to requester window | |
target_iframe.contentWindow.postMessage({ | |
msg: "fontPicked", | |
original_msg_id: msg_id, | |
font: selected_font, | |
}, '*'); | |
$(target_iframe).get(0).focus({preventScroll:true}); | |
} | |
//-------------------------------------------------------- | |
// showColorPicker | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'showColorPicker'){ | |
// Auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
// set options | |
event.data.options = event.data.options ?? {}; | |
// Clear window_options for security reasons | |
event.data.options.window_options = {} | |
// Set app as parent window of the font picker window | |
event.data.options.window_options.parent_uuid = event.data.appInstanceID; | |
// Open color picker | |
let selected_color = await UIWindowColorPicker(event.data.options); | |
// Send selected color to requester window | |
target_iframe.contentWindow.postMessage({ | |
msg: "colorPicked", | |
original_msg_id: msg_id, | |
color: selected_color ? selected_color.color : undefined, | |
}, '*'); | |
$(target_iframe).get(0).focus({preventScroll:true}); | |
} | |
//-------------------------------------------------------- | |
// setWallpaper | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'setWallpaper'){ | |
// Auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
// No options? | |
if(!event.data.options) | |
event.data.options = {}; | |
// /set-desktop-bg | |
try{ | |
await $.ajax({ | |
url: api_origin + "/set-desktop-bg", | |
type: 'POST', | |
data: JSON.stringify({ | |
url: event.data.readURL, | |
fit: event.data.options.fit ?? 'cover', | |
color: event.data.options.color, | |
}), | |
async: true, | |
contentType: "application/json", | |
headers: { | |
"Authorization": "Bearer "+auth_token | |
}, | |
statusCode: { | |
401: function () { | |
logout(); | |
}, | |
}, | |
}); | |
// Set wallpaper | |
window.set_desktop_background({ | |
url: event.data.readURL, | |
fit: event.data.options.fit ?? 'cover', | |
color: event.data.options.color, | |
}) | |
// Send success to app | |
target_iframe.contentWindow.postMessage({ | |
msg: "wallpaperSet", | |
original_msg_id: msg_id, | |
}, '*'); | |
$(target_iframe).get(0).focus({preventScroll:true}); | |
}catch(err){ | |
console.error(err); | |
} | |
} | |
//-------------------------------------------------------- | |
// showSaveFilePicker | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'showSaveFilePicker'){ | |
//auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
//disable parent window | |
$el_parent_window.addClass('window-disabled') | |
$el_parent_disable_mask.show(); | |
$el_parent_disable_mask.css('z-index', parseInt($el_parent_window.css('z-index')) + 1); | |
$(target_iframe).blur(); | |
await UIWindow({ | |
path: '/' + window.user.username + '/Desktop', | |
// this is the uuid of the window to which this dialog will return | |
parent_uuid: event.data.appInstanceID, | |
show_maximize_button: false, | |
show_minimize_button: false, | |
title: 'Save As…', | |
is_dir: true, | |
is_saveFileDialog: true, | |
saveFileDialog_default_filename: event.data.suggestedName ?? '', | |
selectable_body: false, | |
iframe_msg_uid: msg_id, | |
center: true, | |
initiating_app_uuid: app_uuid, | |
onSaveFileDialogSave: async function(target_path, el_filedialog_window){ | |
$(el_filedialog_window).find('.window-disable-mask, .busy-indicator').show(); | |
let busy_init_ts = Date.now(); | |
// ------------------------------------- | |
// URL | |
// ------------------------------------- | |
if(event.data.url){ | |
// download progress tracker | |
let dl_op_id = operation_id++; | |
// upload progress tracker defaults | |
window.progress_tracker[dl_op_id] = []; | |
window.progress_tracker[dl_op_id][0] = {}; | |
window.progress_tracker[dl_op_id][0].total = 0; | |
window.progress_tracker[dl_op_id][0].ajax_uploaded = 0; | |
window.progress_tracker[dl_op_id][0].cloud_uploaded = 0; | |
let item_with_same_name_already_exists = true; | |
while(item_with_same_name_already_exists){ | |
await download({ | |
url: event.data.url, | |
name: path.basename(target_path), | |
dest_path: path.dirname(target_path), | |
auth_token: auth_token, | |
api_origin: api_origin, | |
dedupe_name: false, | |
overwrite: false, | |
operation_id: dl_op_id, | |
item_upload_id: 0, | |
success: function(res){ | |
}, | |
error: function(err){ | |
UIAlert(err && err.message ? err.message : "Download failed."); | |
} | |
}); | |
item_with_same_name_already_exists = false; | |
} | |
} | |
// ------------------------------------- | |
// File | |
// ------------------------------------- | |
else{ | |
let overwrite = false; | |
let file_to_upload = new File([event.data.content], path.basename(target_path)); | |
let item_with_same_name_already_exists = true; | |
while(item_with_same_name_already_exists){ | |
// overwrite? | |
if(overwrite) | |
item_with_same_name_already_exists = false; | |
// upload | |
try{ | |
const res = await puter.fs.write( | |
target_path, | |
file_to_upload, | |
{ | |
dedupeName: false, | |
overwrite: overwrite | |
} | |
); | |
let file_signature = await puter.fs.sign(app_uuid, {uid: res.uid, action: 'write'}); | |
file_signature = file_signature.items; | |
item_with_same_name_already_exists = false; | |
target_iframe.contentWindow.postMessage({ | |
msg: "fileSaved", | |
original_msg_id: msg_id, | |
filename: res.name, | |
saved_file: { | |
name: file_signature.fsentry_name, | |
readURL: file_signature.read_url, | |
writeURL: file_signature.write_url, | |
metadataURL: file_signature.metadata_url, | |
type: file_signature.type, | |
uid: file_signature.uid, | |
path: `~/` + res.path.split('/').slice(2).join('/'), | |
}, | |
}, '*'); | |
$(target_iframe).get(0).focus({preventScroll:true}); | |
// Update matching items on open windows | |
// todo don't blanket-update, mostly files with thumbnails really need to be updated | |
// first remove overwritten items | |
$(`.item[data-uid="${res.uid}"]`).removeItems(); | |
// now add new items | |
UIItem({ | |
appendTo: $(`.item-container[data-path="${html_encode(path.dirname(target_path))}" i]`), | |
immutable: res.immutable, | |
associated_app_name: res.associated_app?.name, | |
path: target_path, | |
icon: await item_icon(res), | |
name: path.basename(target_path), | |
uid: res.uid, | |
size: res.size, | |
modified: res.modified, | |
type: res.type, | |
is_dir: false, | |
is_shared: res.is_shared, | |
suggested_apps: res.suggested_apps, | |
}); | |
// sort each window | |
$(`.item-container[data-path="${html_encode(path.dirname(target_path))}" i]`).each(function(){ | |
sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order')) | |
}); | |
$(el_filedialog_window).close(); | |
show_save_account_notice_if_needed(); | |
} | |
catch(err){ | |
// item with same name exists | |
if(err.code === 'item_with_same_name_exists'){ | |
const alert_resp = await UIAlert({ | |
message: `<strong>${html_encode(err.entry_name)}</strong> already exists.`, | |
buttons:[ | |
{ | |
label: 'Replace', | |
value: 'replace', | |
type: 'primary', | |
}, | |
{ | |
label: 'Cancel', | |
value: 'cancel', | |
}, | |
], | |
parent_uuid: $(el_filedialog_window).attr('data-element_uuid'), | |
}) | |
if(alert_resp === 'replace'){ | |
overwrite = true; | |
}else if(alert_resp === 'cancel'){ | |
// enable parent window | |
$(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide(); | |
return; | |
} | |
} | |
else{ | |
// show error | |
await UIAlert({ | |
message: err.message ?? "Upload failed.", | |
parent_uuid: $(el_filedialog_window).attr('data-element_uuid'), | |
}); | |
// enable parent window | |
$(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide(); | |
return; | |
} | |
} | |
} | |
} | |
// done | |
let busy_duration = (Date.now() - busy_init_ts); | |
if( busy_duration >= busy_indicator_hide_delay){ | |
$(el_filedialog_window).close(); | |
}else{ | |
setTimeout(() => { | |
// close this dialog | |
$(el_filedialog_window).close(); | |
}, Math.abs(busy_indicator_hide_delay - busy_duration)); | |
} | |
} | |
}); | |
} | |
//-------------------------------------------------------- | |
// saveToPictures/Desktop/Documents/Videos/Audio/AppData | |
//-------------------------------------------------------- | |
else if((event.data.msg === 'saveToPictures' || event.data.msg === 'saveToDesktop' || event.data.msg === 'saveToAppData' || | |
event.data.msg === 'saveToDocuments' || event.data.msg === 'saveToVideos' || event.data.msg === 'saveToAudio')){ | |
let target_path; | |
let create_missing_ancestors = false; | |
if(event.data.msg === 'saveToPictures') | |
target_path = path.join(pictures_path, event.data.filename); | |
else if(event.data.msg === 'saveToDesktop') | |
target_path = path.join(desktop_path, event.data.filename); | |
else if(event.data.msg === 'saveToDocuments') | |
target_path = path.join(documents_path, event.data.filename); | |
else if(event.data.msg === 'saveToVideos') | |
target_path = path.join(videos_path, event.data.filename); | |
else if(event.data.msg === 'saveToAudio') | |
target_path = path.join(audio_path, event.data.filename); | |
else if(event.data.msg === 'saveToAppData'){ | |
target_path = path.join(appdata_path, app_uuid, event.data.filename); | |
create_missing_ancestors = true; | |
} | |
//auth | |
if(!is_auth() && !(await UIWindowSignup({referrer: app_name}))) | |
return; | |
let item_with_same_name_already_exists = true; | |
let overwrite = false; | |
// ------------------------------------- | |
// URL | |
// ------------------------------------- | |
if(event.data.url){ | |
let overwrite = false; | |
// download progress tracker | |
let dl_op_id = operation_id++; | |
// upload progress tracker defaults | |
window.progress_tracker[dl_op_id] = []; | |
window.progress_tracker[dl_op_id][0] = {}; | |
window.progress_tracker[dl_op_id][0].total = 0; | |
window.progress_tracker[dl_op_id][0].ajax_uploaded = 0; | |
window.progress_tracker[dl_op_id][0].cloud_uploaded = 0; | |
let item_with_same_name_already_exists = true; | |
while(item_with_same_name_already_exists){ | |
const res = await download({ | |
url: event.data.url, | |
name: path.basename(target_path), | |
dest_path: path.dirname(target_path), | |
auth_token: auth_token, | |
api_origin: api_origin, | |
dedupe_name: true, | |
overwrite: false, | |
operation_id: dl_op_id, | |
item_upload_id: 0, | |
success: function(res){ | |
}, | |
error: function(err){ | |
UIAlert(err && err.message ? err.message : "Download failed."); | |
} | |
}); | |
item_with_same_name_already_exists = false; | |
} | |
} | |
// ------------------------------------- | |
// File | |
// ------------------------------------- | |
else{ | |
let file_to_upload = new File([event.data.content], path.basename(target_path)); | |
while(item_with_same_name_already_exists){ | |
if(overwrite) | |
item_with_same_name_already_exists = false; | |
try{ | |
const res = await puter.fs.write(target_path, file_to_upload, { | |
dedupeName: true, | |
overwrite: false, | |
createMissingAncestors: create_missing_ancestors, | |
}); | |
item_with_same_name_already_exists = false; | |
let file_signature = await puter.fs.sign(app_uuid, {uid: res.uid, action: 'write'}); | |
file_signature = file_signature.items; | |
target_iframe.contentWindow.postMessage({ | |
msg: "fileSaved", | |
original_msg_id: msg_id, | |
filename: res.name, | |
saved_file: { | |
name: file_signature.fsentry_name, | |
readURL: file_signature.read_url, | |
writeURL: file_signature.write_url, | |
metadataURL: file_signature.metadata_url, | |
uid: file_signature.uid, | |
path: `~/` + res.path.split('/').slice(2).join('/'), | |
}, | |
}, '*'); | |
$(target_iframe).get(0).focus({preventScroll:true}); | |
} | |
catch(err){ | |
if(err.code === 'item_with_same_name_exists'){ | |
const alert_resp = await UIAlert({ | |
message: `<strong>${html_encode(err.entry_name)}</strong> already exists.`, | |
buttons:[ | |
{ | |
label: 'Replace', | |
type: 'primary', | |
}, | |
{ | |
label: 'Cancel' | |
}, | |
], | |
parent_uuid: event.data.appInstanceID, | |
}) | |
if(alert_resp === 'Replace'){ | |
overwrite = true; | |
}else if(alert_resp === 'Cancel'){ | |
item_with_same_name_already_exists = false; | |
} | |
}else{ | |
break; | |
} | |
} | |
} | |
} | |
} | |
//-------------------------------------------------------- | |
// exit | |
//-------------------------------------------------------- | |
else if(event.data.msg === 'exit'){ | |
$(`.window[data-element_uuid="${event.data.appInstanceID}"]`).close({bypass_iframe_messaging: true}); | |
} | |
}); |