diff --git a/AGENTS.md b/AGENTS.md index 847f9a0a4..7a8f33b27 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,8 @@ Keep comments light. Prefer self-documenting code — clear names, small functio - The _why_ is non-obvious (a hidden constraint, a workaround, a subtle invariant). - A non-trivial usage detail would otherwise trip the next reader. +If comments need to be more than a line, its probably too long, and when it does need multiline comments, use multiline `/** ... */` JSDoc style for consistency. Use `//` for single-line comments. + Don't restate what the code already says. Don't write comments that reference the current task or PR or version of something — those rot. **No ticket references in code or test comments.** Don't write `PUT-1234`, `AUTH-3`, `GUI-2`, `// fix for FOO-99`, `// per ROLLOUT-1`, or section headers like `// -- PUT-1020: ... --`. The ticket exists in git history and the issue tracker; the code should describe the _why_ in domain terms ("logout cascade transparently invalidates...") not project-management terms ("the cascade from AUTH-3..."). Same rule for test-block names and section dividers — describe what's being tested, not which ticket asked for it. diff --git a/src/gui/src/UI/UIWindowSignup.js b/src/gui/src/UI/UIWindowSignup.js index cd588567b..4b847b77b 100644 --- a/src/gui/src/UI/UIWindowSignup.js +++ b/src/gui/src/UI/UIWindowSignup.js @@ -24,7 +24,7 @@ import UIWindowPhoneVerificationRequired from './UIWindowPhoneVerificationRequir import UIWindowCardVerificationRequired from './UIWindowCardVerificationRequired.js'; import UIWindowLogin from './UIWindowLogin.js'; -function UIWindowSignup (options) { +function UIWindowSignup(options) { options = options ?? {}; options.reload_on_success = options.reload_on_success ?? true; options.has_head = options.has_head ?? true; @@ -34,15 +34,16 @@ function UIWindowSignup (options) { return new Promise(async (resolve) => { const internal_id = window.uuidv4(); - const logo_clickable = !!options.window_options?.cover_page && !window.embedded_in_popup; + const logo_clickable = + !!options.window_options?.cover_page && !window.embedded_in_popup; let h = ''; - h += '
${html_encode(blockedMsg)}
If you already have an account, try logging in. Otherwise, contact support@puter.com for assistance.
You are using a temporary account and logging out will erase all your data.
', + message: + 'Save account before logging out!You are using a temporary account and logging out will erase all your data.
', buttons: [ { label: i18n('save_account'), @@ -801,19 +902,17 @@ window.initgui = async function (options) { }, ], }); - if ( alert_resp === 'save_account' ) { + if (alert_resp === 'save_account') { let saved = await UIWindowSaveAccount({ send_confirmation_code: false, default_username: window.user.username, }); - if ( saved ) - { + if (saved) { window.logout(); } - } else if ( alert_resp === 'log_out' ) { + } else if (alert_resp === 'log_out') { window.logout(); - } - else { + } else { return; } } @@ -823,17 +922,16 @@ window.initgui = async function (options) { const resp = await fetch(`${window.gui_origin}/get-anticsrf-token`); const { token } = await resp.json(); await $.ajax({ - url: `${window.gui_origin }/logout`, + url: `${window.gui_origin}/logout`, type: 'POST', async: true, contentType: 'application/json', headers: { - 'Authorization': `Bearer ${ window.auth_token}`, + Authorization: `Bearer ${window.auth_token}`, }, data: JSON.stringify({ anti_csrf: token }), statusCode: { - 401: function () { - }, + 401: function () {}, }, }); } catch (e) { @@ -841,15 +939,18 @@ window.initgui = async function (options) { } // remove this user from the array of logged_in_users - for ( let i = 0; i < window.logged_in_users.length; i++ ) { - if ( window.logged_in_users[i].uuid === window.user.uuid ) { + for (let i = 0; i < window.logged_in_users.length; i++) { + if (window.logged_in_users[i].uuid === window.user.uuid) { window.logged_in_users.splice(i, 1); break; } } // update logged_in_users in local storage - localStorage.setItem('logged_in_users', JSON.stringify(window.logged_in_users)); + localStorage.setItem( + 'logged_in_users', + JSON.stringify(window.logged_in_users), + ); // delete this user from local storage window.user = null; @@ -872,15 +973,6 @@ window.initgui = async function (options) { window.location.replace(window.is_dashboard_mode ? '/' : '/desktop'); }); - // ------------------------------------------------------------------------------------- - // Safety net for pending-verification 403s. The backend rejects every - // authenticated call while a signup-time verification (SMS phone, email, - // card) is pending, but the dialogs are normally only opened at boot or - // right after signup. Any session that misses them — temp-user creation, - // save-account, a flag set after this page loaded — lands here, so the - // user gets the right dialog instead of a desktop where every request - // silently fails. - // ------------------------------------------------------------------------------------- const verification_gate_windows = { phone_verification_required: UIWindowPhoneVerificationRequired, email_confirmation_required: UIWindowEmailConfirmationRequired, @@ -888,23 +980,23 @@ window.initgui = async function (options) { }; let verification_gate_open = false; $(document).ajaxError(async function (event, jqxhr) { - if ( jqxhr?.status !== 403 || verification_gate_open ) { + if (jqxhr?.status !== 403 || verification_gate_open) { return; } let body = jqxhr.responseJSON; - if ( ! body && jqxhr.responseText ) { - try { body = JSON.parse(jqxhr.responseText); } catch (e) { body = null; } + if (!body && jqxhr.responseText) { + try { + body = JSON.parse(jqxhr.responseText); + } catch (e) { + body = null; + } } const UIWindowVerificationGate = verification_gate_windows[body?.code]; - if ( ! UIWindowVerificationGate ) { + if (!UIWindowVerificationGate) { return; } verification_gate_open = true; try { - // Shown once, not in a retry loop: if the gate is somehow - // dismissed while still pending, the next rejected call lands - // here again and re-opens it. Looping here could stack a second - // dialog on top of the boot-time or signup-flow gate. const is_verified = await UIWindowVerificationGate({ show_close_button: false, stay_on_top: true, @@ -914,7 +1006,7 @@ window.initgui = async function (options) { is_draggable: false, }, }); - if ( is_verified ) { + if (is_verified) { await window.refresh_user_data(window.auth_token); } } catch (e) { @@ -927,22 +1019,22 @@ window.initgui = async function (options) { // ------------------------------------------------------------------------------------- // Authed // ------------------------------------------------------------------------------------- - if ( window.is_auth() ) { + if (window.is_auth()) { // try to get user data using /whoami, only if that data is missing - if ( ! whoami ) { + if (!whoami) { try { whoami = await puter.os.user({ query: 'icon_size=64' }); } catch (e) { - if ( e.status === 401 ) { + if (e.status === 401) { bad_session_logout(); return; } } } // update local user data - if ( whoami ) { + if (whoami) { // is phone verification required? (hard gate for low-rep signups) - if ( whoami.requires_phone_verification ) { + if (whoami.requires_phone_verification) { let is_verified; do { is_verified = await UIWindowPhoneVerificationRequired({ @@ -955,11 +1047,10 @@ window.initgui = async function (options) { cover_page: window.is_embedded, }, }); - } - while ( !is_verified ); + } while (!is_verified); } // is email confirmation required? - if ( whoami.requires_email_confirmation ) { + if (whoami.requires_email_confirmation) { let is_verified; do { is_verified = await UIWindowEmailConfirmationRequired({ @@ -972,12 +1063,11 @@ window.initgui = async function (options) { cover_page: window.is_embedded, }, }); - } - while ( !is_verified ); + } while (!is_verified); } // Card verification is the last gate: only show it once the email and // phone (SMS) gates are cleared, since those show up first. - if ( whoami.requires_card_verification ) { + if (whoami.requires_card_verification) { let is_verified; do { is_verified = await UIWindowCardVerificationRequired({ @@ -990,26 +1080,32 @@ window.initgui = async function (options) { cover_page: window.is_embedded, }, }); - } - while ( !is_verified ); + } while (!is_verified); } - await window.update_auth_data(whoami.token || window.auth_token, whoami); + await window.update_auth_data( + whoami.token || window.auth_token, + whoami, + ); // ------------------------------------------------------------------------------------- // Action: AuthMe — redirect to a third-party URL with the user's auth token // ------------------------------------------------------------------------------------- - if ( action === 'authme' ) { + if (action === 'authme') { const redirectURL = window.url_query_params.get('redirectURL'); - if ( redirectURL ) { + if (redirectURL) { const approved = await UIWindowAuthMe({ redirect_url: redirectURL, }); - if ( approved ) { + if (approved) { // Hand the app a named, revocable full-API-access // token instead of the raw GUI/session token: it can // use the whole API but can't manage the account. let host = ''; - try { host = new URL(redirectURL).host; } catch ( e ) { /* ignore */ } + try { + host = new URL(redirectURL).host; + } catch (e) { + /* ignore */ + } let token; try { token = await create_access_token({ @@ -1017,7 +1113,7 @@ window.initgui = async function (options) { ? `${i18n('token_label_external_app')} (${host})` : i18n('token_label_external_app'), }); - } catch ( e ) { + } catch (e) { await UIAlert({ message: e?.message ?? String(e) }); return; } @@ -1032,28 +1128,33 @@ window.initgui = async function (options) { // ------------------------------------------------------------------------------------- // Action: CopyAuth — show dialog to copy auth token // ------------------------------------------------------------------------------------- - if ( action === 'copyauth' ) { + if (action === 'copyauth') { await UIWindowCopyToken({ show_header: true }); } // ------------------------------------------------------------------------------------- // Load desktop, only if we're not embedded in a popup and not on the dashboard page // ------------------------------------------------------------------------------------- - if ( !window.embedded_in_popup && !window.is_dashboard_mode ) { - if ( window.is_fullpage_mode ) { + if (!window.embedded_in_popup && !window.is_dashboard_mode) { + if (window.is_fullpage_mode) { // In fullpage mode, skip loading desktop items and background UIDesktop({}); } else { await window.get_auto_arrange_data(); - puter.fs.stat({ path: window.desktop_path, consistency: 'eventual' }).then(desktop_fsentry => { - UIDesktop({ desktop_fsentry: desktop_fsentry }); - }); + puter.fs + .stat({ + path: window.desktop_path, + consistency: 'eventual', + }) + .then((desktop_fsentry) => { + UIDesktop({ desktop_fsentry: desktop_fsentry }); + }); } } // ------------------------------------------------------------------------------------- // Dashboard mode // ------------------------------------------------------------------------------------- - else if ( window.is_dashboard_mode ) { + else if (window.is_dashboard_mode) { UIDashboard(); } // ------------------------------------------------------------------------------------- @@ -1062,32 +1163,40 @@ window.initgui = async function (options) { else { let msg_id = window.url_query_params.get('msg_id'); try { - let data = await window.getUserAppToken(new URL(window.openerOrigin).origin); + let data = await window.getUserAppToken( + new URL(window.openerOrigin).origin, + ); // This is an implicit app and the app_uid is sent back from the server // we cache it here so that we can use it later window.host_app_uid = data.app_uid; // send token to parent - window.opener.postMessage({ - msg: 'puter.token', - success: true, - token: data.token, - app_uid: data.app_uid, - username: window.user.username, - msg_id: msg_id, - }, window.openerOrigin); + window.opener.postMessage( + { + msg: 'puter.token', + success: true, + token: data.token, + app_uid: data.app_uid, + username: window.user.username, + msg_id: msg_id, + }, + window.openerOrigin, + ); // close popup - if ( !action || action === 'sign-in' ) { + if (!action || action === 'sign-in') { window.close(); window.open('', '_self').close(); } - } catch ( err ) { + } catch (err) { // send error to parent - window.opener.postMessage({ - msg: 'puter.token', - success: false, - token: null, - msg_id: msg_id, - }, window.openerOrigin); + window.opener.postMessage( + { + msg: 'puter.token', + success: false, + token: null, + msg_id: msg_id, + }, + window.openerOrigin, + ); // close popup window.close(); window.open('', '_self').close(); @@ -1095,12 +1204,14 @@ window.initgui = async function (options) { let app_uid; - if ( window.openerOrigin ) { - app_uid = await window.getAppUIDFromOrigin(window.openerOrigin); + if (window.openerOrigin) { + app_uid = await window.getAppUIDFromOrigin( + window.openerOrigin, + ); window.host_app_uid = app_uid; } - if ( action === 'show-open-file-picker' ) { + if (action === 'show-open-file-picker') { let options = window.url_query_params.get('options'); options = JSON.parse(options ?? '{}'); @@ -1108,7 +1219,7 @@ window.initgui = async function (options) { UIWindow({ allowed_file_types: options?.accept, selectable_body: options?.multiple, - path: `/${ window.user.username }/Desktop`, + path: `/${window.user.username}/Desktop`, // this is the uuid of the window to which this dialog will return return_to_parent_window: true, show_maximize_button: false, @@ -1124,20 +1235,23 @@ window.initgui = async function (options) { center: true, initiating_app_uuid: app_uid, on_close: function () { - window.opener.postMessage({ - msg: 'fileOpenCanceled', - original_msg_id: msg_id, - }, '*'); + window.opener.postMessage( + { + msg: 'fileOpenCanceled', + original_msg_id: msg_id, + }, + '*', + ); }, }); } //-------------------------------------------------------------------------------------- // Action: Show Directory Picker //-------------------------------------------------------------------------------------- - else if ( action === 'show-directory-picker' ) { + else if (action === 'show-directory-picker') { // open directory picker dialog UIWindow({ - path: `/${ window.user.username }/Desktop`, + path: `/${window.user.username}/Desktop`, // this is the uuid of the window to which this dialog will return // parent_uuid: event.data.appInstanceID, return_to_parent_window: true, @@ -1154,35 +1268,41 @@ window.initgui = async function (options) { center: true, initiating_app_uuid: app_uid, on_close: function () { - window.opener.postMessage({ - msg: 'directoryOpenCanceled', - original_msg_id: msg_id, - }, '*'); + window.opener.postMessage( + { + msg: 'directoryOpenCanceled', + original_msg_id: msg_id, + }, + '*', + ); }, }); } //-------------------------------------------------------------------------------------- // Action: Show Save File Dialog //-------------------------------------------------------------------------------------- - else if ( action === 'show-save-file-picker' ) { - let allowed_file_types = window.url_query_params.get('allowed_file_types'); + else if (action === 'show-save-file-picker') { + let allowed_file_types = + window.url_query_params.get('allowed_file_types'); // send 'sendMeFileData' event to parent - window.opener.postMessage({ - msg: 'sendMeFileData', - }, '*'); + window.opener.postMessage( + { + msg: 'sendMeFileData', + }, + '*', + ); // listen for 'showSaveFilePickerPopup' event from parent window.addEventListener('message', async (event) => { - if ( event.data.msg !== 'showSaveFilePickerPopup' ) - { + if (event.data.msg !== 'showSaveFilePickerPopup') { return; } // Open dialog UIWindow({ allowed_file_types: allowed_file_types, - path: `/${ window.user.username }/Desktop`, + path: `/${window.user.username}/Desktop`, // this is the uuid of the window to which this dialog will return return_to_parent_window: true, show_maximize_button: false, @@ -1198,22 +1318,34 @@ window.initgui = async function (options) { center: true, initiating_app_uuid: app_uid, on_close: function () { - window.opener.postMessage({ - msg: 'fileSaveCanceled', - original_msg_id: msg_id, - }, '*'); + window.opener.postMessage( + { + msg: 'fileSaveCanceled', + original_msg_id: msg_id, + }, + '*', + ); }, - onSaveFileDialogSave: async function (target_path, el_filedialog_window) { - $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').show(); + onSaveFileDialogSave: async function ( + target_path, + el_filedialog_window, + ) { + $(el_filedialog_window) + .find( + '.window-disable-mask, .busy-indicator', + ) + .show(); let busy_init_ts = Date.now(); let overwrite = false; - let file_to_upload = new File([event.data.content], path.basename(target_path)); + 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 ) { + while (item_with_same_name_already_exists) { // overwrite? - if ( overwrite ) - { + if (overwrite) { item_with_same_name_already_exists = false; } // upload @@ -1227,31 +1359,45 @@ window.initgui = async function (options) { }, ); - let file_signature = await puter.fs.sign(app_uid, { uid: res.uid, action: 'write' }); + let file_signature = + await puter.fs.sign(app_uid, { + uid: res.uid, + action: 'write', + }); file_signature = file_signature.items; item_with_same_name_already_exists = false; - window.opener.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: privacy_aware_path(res.path), + window.opener.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: privacy_aware_path( + res.path, + ), + }, }, - }, '*'); + '*', + ); window.close(); window.open('', '_self').close(); - } - catch ( err ) { + } catch (err) { // item with same name exists - if ( err.code === 'item_with_same_name_exists' ) { + if ( + err.code === + 'item_with_same_name_exists' + ) { const alert_resp = await UIAlert({ message: `${html_encode(err.entry_name)} already exists.`, buttons: [ @@ -1265,39 +1411,65 @@ window.initgui = async function (options) { value: 'cancel', }, ], - parent_uuid: $(el_filedialog_window).attr('data-element_uuid'), + parent_uuid: + $( + el_filedialog_window, + ).attr('data-element_uuid'), }); - if ( alert_resp === 'replace' ) { + if (alert_resp === 'replace') { overwrite = true; - } else if ( alert_resp === 'cancel' ) { + } else if ( + alert_resp === 'cancel' + ) { // enable parent window - $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide(); + $(el_filedialog_window) + .find( + '.window-disable-mask, .busy-indicator', + ) + .hide(); return; } - } - else { + } else { console.log(err); // show error await UIAlert({ - message: err.message ?? 'Upload failed.', - parent_uuid: $(el_filedialog_window).attr('data-element_uuid'), + 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(); + $(el_filedialog_window) + .find( + '.window-disable-mask, .busy-indicator', + ) + .hide(); return; } } } // done - let busy_duration = (Date.now() - busy_init_ts); - if ( busy_duration >= window.busy_indicator_hide_delay ) { + let busy_duration = Date.now() - busy_init_ts; + if ( + busy_duration >= + window.busy_indicator_hide_delay + ) { $(el_filedialog_window).close(); } else { - setTimeout(() => { - // close this dialog - $(el_filedialog_window).close(); - }, Math.abs(window.busy_indicator_hide_delay - busy_duration)); + setTimeout( + () => { + // close this dialog + $(el_filedialog_window).close(); + }, + Math.abs( + window.busy_indicator_hide_delay - + busy_duration, + ), + ); } }, }); @@ -1316,32 +1488,34 @@ window.initgui = async function (options) { // If we're in fullpage/emebedded/Auth Popup mode, we don't want to load the custom background // because it's not visible anyway and it's a waste of bandwidth // ------------------------------------------------------------------------------------- - if ( !window.is_fullpage_mode && !window.embedded_in_popup ) { + if (!window.is_fullpage_mode && !window.embedded_in_popup) { window.refresh_desktop_background(); } // ------------------------------------------------------------------------------------- // Un-authed but not first visit -> try to log in/sign up // ------------------------------------------------------------------------------------- - if ( !window.is_auth() && (!window.first_visit_ever || window.disable_temp_users) ) { + if ( + !window.is_auth() && + (!window.first_visit_ever || window.disable_temp_users) + ) { const needs_action = action === 'authme' || action === 'copyauth'; const reload_on_success = needs_action; - if ( window.logged_in_users.length > 0 ) { + if (window.logged_in_users.length > 0) { // dashboard mode skips the wallpaper (fullpage), but the session list // has no cover page — restore the wallpaper so it isn't on a blank page - if ( window.is_dashboard_mode ) { + if (window.is_dashboard_mode) { window.refresh_desktop_background(); } await UIWindowSessionList({ redirect_url: needs_action ? window.location.href : undefined, }); - } - else { - const resp = await fetch(`${window.gui_origin }/whoarewe`); + } else { + const resp = await fetch(`${window.gui_origin}/whoarewe`); const whoarewe = await resp.json(); await UIWindowLogin({ reload_on_success: !window.embedded_in_popup, send_confirmation_code: true, - show_signup_button: ( !whoarewe.disable_user_signup ), + show_signup_button: !whoarewe.disable_user_signup, redirect_url: needs_action ? window.location.href : undefined, window_options: { cover_page: true, @@ -1349,7 +1523,7 @@ window.initgui = async function (options) { }, }); } - if ( !reload_on_success && window.is_auth() ) { + if (!reload_on_success && window.is_auth()) { window.__login_completed = true; } } @@ -1377,17 +1551,15 @@ window.initgui = async function (options) { window.referrerStr = referrer; // in case there is also a referrer query param, add it to the referrer URL - if ( window.url_query_params.has('ref') ) { - if ( ! referrer ) - { + if (window.url_query_params.has('ref')) { + if (!referrer) { referrer = '/'; } - referrer += `?ref=${ html_encode(window.url_query_params.get('ref'))}`; + referrer += `?ref=${html_encode(window.url_query_params.get('ref'))}`; } let headers = {}; - if ( window.custom_headers ) - { + if (window.custom_headers) { headers = window.custom_headers; } @@ -1401,14 +1573,14 @@ window.initgui = async function (options) { }; // Add Turnstile token if available - if ( turnstileToken ) { + if (turnstileToken) { requestData['cf-turnstile-response'] = turnstileToken; } // Device signal for abuse prevention; omitted when unavailable try { const fingerprint = await window.getDeviceFingerprint?.(); - if ( fingerprint ) { + if (fingerprint) { requestData.fingerprint = fingerprint; } } catch (e) { @@ -1416,7 +1588,7 @@ window.initgui = async function (options) { } $.ajax({ - url: `${window.gui_origin }/signup`, + url: `${window.gui_origin}/signup`, type: 'POST', async: true, headers: headers, @@ -1424,63 +1596,74 @@ window.initgui = async function (options) { data: JSON.stringify(requestData), success: async function (data) { /*eslint-disable*/ - const turnstile_duration = Date.now() - window.turnstile_success_ts; + const turnstile_duration = + Date.now() - window.turnstile_success_ts; if (turnstile_duration < 2000) { // Sleep until 2 seconds have passed await window.sleep(2000 - turnstile_duration); } const $captchaModal = $('.captcha-modal'); - if ( $captchaModal.length > 0 ) await new Promise(async resolve => { - // The callback operand for fadeOut could be called - // more than once if there are multiple `.captcha-modal` - // elements, but only the first call to `resolve()` will - // have any effect. - $captchaModal.fadeOut(200, function () { - $(this).remove(); + if ($captchaModal.length > 0) + await new Promise(async (resolve) => { + // The callback operand for fadeOut could be called + // more than once if there are multiple `.captcha-modal` + // elements, but only the first call to `resolve()` will + // have any effect. + $captchaModal.fadeOut(200, function () { + $(this).remove(); + resolve(); + }); + + // Just in case anything fails, also resolve after 500ms + await window.sleep(500); resolve(); }); - - // Just in case anything fails, also resolve after 500ms - await window.sleep(500); - resolve(); - }); await window.update_auth_data(data.token, data.user); // if this is a popup, hide the spinner, make sure it was visible for at least 2 seconds - if(window.embedded_in_popup) await new Promise(async resolve => { - let spinner_duration = (Date.now() - spinner_init_ts); + if (window.embedded_in_popup) + await new Promise(async (resolve) => { + let spinner_duration = Date.now() - spinner_init_ts; - (async () => { - let msg_id = window.url_query_params.get('msg_id'); - let data = await window.getUserAppToken(new URL(window.openerOrigin).origin); - // This is an implicit app and the app_uid is sent back from the server - // we cache it here so that we can use it later - window.host_app_uid = data.app_uid; - // send token to parent - window.opener.postMessage({ - msg: 'puter.token', - success: true, - msg_id: msg_id, - token: data.token, - username: window.user.username, - app_uid: data.app_uid, - }, window.openerOrigin); - // close popup - if ( !action || action === 'sign-in' ) { - window.close(); - window.open('', '_self').close(); + (async () => { + let msg_id = + window.url_query_params.get('msg_id'); + let data = await window.getUserAppToken( + new URL(window.openerOrigin).origin, + ); + // This is an implicit app and the app_uid is sent back from the server + // we cache it here so that we can use it later + window.host_app_uid = data.app_uid; + // send token to parent + window.opener.postMessage( + { + msg: 'puter.token', + success: true, + msg_id: msg_id, + token: data.token, + username: window.user.username, + app_uid: data.app_uid, + }, + window.openerOrigin, + ); + // close popup + if (!action || action === 'sign-in') { + window.close(); + window.open('', '_self').close(); + } + })(); + if (spinner_duration < 2000) { + await window.sleep(2000 - spinner_duration); + resolve(); } - })(); - if (spinner_duration < 2000) { - await window.sleep(2000 - spinner_duration); - resolve(); - } - }); + }); /*eslint-enable*/ - document.dispatchEvent(new Event('login', { bubbles: true })); + document.dispatchEvent( + new Event('login', { bubbles: true }), + ); }, error: async (err) => { let err_obj = null; @@ -1489,7 +1672,7 @@ window.initgui = async function (options) { } catch (e) { err_obj = e; } - if ( err_obj.code === 'must_login_or_signup' ) { + if (err_obj.code === 'must_login_or_signup') { // hide Turnstile challenge $('.captcha-modal').hide(); @@ -1504,26 +1687,31 @@ window.initgui = async function (options) { (async () => { let msg_id = window.url_query_params.get('msg_id'); - let data = await window.getUserAppToken(new URL(window.openerOrigin).origin); + let data = await window.getUserAppToken( + new URL(window.openerOrigin).origin, + ); // This is an implicit app and the app_uid is sent back from the server // we cache it here so that we can use it later window.host_app_uid = data.app_uid; // send token to parent - window.opener.postMessage({ - msg: 'puter.token', - success: true, - msg_id: msg_id, - token: data.token, - username: window.user.username, - app_uid: data.app_uid, - }, window.openerOrigin); + window.opener.postMessage( + { + msg: 'puter.token', + success: true, + msg_id: msg_id, + token: data.token, + username: window.user.username, + app_uid: data.app_uid, + }, + window.openerOrigin, + ); // close popup - if ( !action || action === 'sign-in' ) { + if (!action || action === 'sign-in') { window.close(); window.open('', '_self').close(); } })(); - } else if ( err_obj.code === 'signup_blocked' ) { + } else if (err_obj.code === 'signup_blocked') { // Hide any captcha modal $('.captcha-modal').hide(); @@ -1540,23 +1728,25 @@ window.initgui = async function (options) { document.body.appendChild(overlay); } else { UIAlert({ - message: err_obj.message ?? 'There was an error creating your account. Please try again.', + message: + err_obj.message ?? + 'There was an error creating your account. Please try again.', }); } }, - complete: function () { - }, + complete: function () {}, }); }; // Check if Turnstile is enabled and show challenge - if ( window.gui_params?.turnstileSiteKey ) { + if (window.gui_params?.turnstileSiteKey) { window.showTurnstileChallenge({ onSuccess: createTempUser, onError: (error) => { console.error('Turnstile verification failed:', error); UIAlert({ - message: 'Security verification failed. Please refresh the page and try again.', + message: + 'Security verification failed. Please refresh the page and try again.', }); }, }); @@ -1567,10 +1757,9 @@ window.initgui = async function (options) { } // if there is at least one window open (only non-Explorer windows), ask user for confirmation when navigating away from puter - if ( window.feature_flags.prompt_user_when_navigation_away_from_puter ) { + if (window.feature_flags.prompt_user_when_navigation_away_from_puter) { window.onbeforeunload = function () { - if ( $('.window:not(.window[data-app="explorer"])').length > 0 ) - { + if ($('.window:not(.window[data-app="explorer"])').length > 0) { return true; } }; @@ -1586,18 +1775,22 @@ window.initgui = async function (options) { // ------------------------------------------------------------------------------------- // Action: AuthMe — redirect to a third-party URL with the user's auth token // ------------------------------------------------------------------------------------- - if ( action === 'authme' ) { + if (action === 'authme') { const redirectURL = window.url_query_params.get('redirectURL'); - if ( redirectURL ) { + if (redirectURL) { const approved = await UIWindowAuthMe({ redirect_url: redirectURL, }); - if ( approved ) { + if (approved) { // Hand the app a named, revocable full-API-access token // instead of the raw GUI/session token: it can use the // whole API but can't manage the account. let host = ''; - try { host = new URL(redirectURL).host; } catch ( e ) { /* ignore */ } + try { + host = new URL(redirectURL).host; + } catch (e) { + /* ignore */ + } let token; try { token = await create_access_token({ @@ -1605,7 +1798,7 @@ window.initgui = async function (options) { ? `${i18n('token_label_external_app')} (${host})` : i18n('token_label_external_app'), }); - } catch ( e ) { + } catch (e) { await UIAlert({ message: e?.message ?? String(e) }); return; } @@ -1620,17 +1813,23 @@ window.initgui = async function (options) { // ------------------------------------------------------------------------------------- // Action: CopyAuth — show dialog to copy auth token // ------------------------------------------------------------------------------------- - if ( action === 'copyauth' ) { + if (action === 'copyauth') { await UIWindowCopyToken({ show_header: true }); } // ------------------------------------------------------------------------------------- // Early check for fullpage mode from app metadata (after login) // ------------------------------------------------------------------------------------- - if ( !window.is_fullpage_mode && window.url_paths[0]?.toLocaleLowerCase() === 'app' && window.url_paths[1] ) { + if ( + !window.is_fullpage_mode && + window.url_paths[0]?.toLocaleLowerCase() === 'app' && + window.url_paths[1] + ) { try { - const app_info = await puter.apps.get(window.url_paths[1], { icon_size: 64 }); - if ( app_info?.metadata?.fullpage_on_landing ) { + const app_info = await puter.apps.get(window.url_paths[1], { + icon_size: 64, + }); + if (app_info?.metadata?.fullpage_on_landing) { window.is_fullpage_mode = true; window.taskbar_height = 0; window.app_launched_from_url = app_info; @@ -1643,21 +1842,26 @@ window.initgui = async function (options) { // ------------------------------------------------------------------------------------- // Load desktop, if not embedded in a popup and not on the dashboard page // ------------------------------------------------------------------------------------- - if ( !window.embedded_in_popup && !window.is_dashboard_mode ) { - if ( window.is_fullpage_mode ) { + if (!window.embedded_in_popup && !window.is_dashboard_mode) { + if (window.is_fullpage_mode) { // In fullpage mode, skip loading desktop items and background UIDesktop({}); } else { await window.get_auto_arrange_data(); - puter.fs.stat({ path: window.desktop_path, consistency: 'eventual' }).then(desktop_fsentry => { - UIDesktop({ desktop_fsentry: desktop_fsentry }); - }); + puter.fs + .stat({ + path: window.desktop_path, + consistency: 'eventual', + }) + .then((desktop_fsentry) => { + UIDesktop({ desktop_fsentry: desktop_fsentry }); + }); } } // ------------------------------------------------------------------------------------- // Dashboard mode: open explorer pointing to home directory // ------------------------------------------------------------------------------------- - else if ( window.is_dashboard_mode ) { + else if (window.is_dashboard_mode) { UIDashboard(); } // ------------------------------------------------------------------------------------- @@ -1666,33 +1870,40 @@ window.initgui = async function (options) { else { let msg_id = window.url_query_params.get('msg_id'); try { - - let data = await window.getUserAppToken(new URL(window.openerOrigin).origin); + let data = await window.getUserAppToken( + new URL(window.openerOrigin).origin, + ); // This is an implicit app and the app_uid is sent back from the server // we cache it here so that we can use it later window.host_app_uid = data.app_uid; // send token to parent - window.opener.postMessage({ - msg: 'puter.token', - success: true, - msg_id: msg_id, - token: data.token, - username: window.user.username, - app_uid: data.app_uid, - }, window.openerOrigin); + window.opener.postMessage( + { + msg: 'puter.token', + success: true, + msg_id: msg_id, + token: data.token, + username: window.user.username, + app_uid: data.app_uid, + }, + window.openerOrigin, + ); // close popup - if ( !action || action === 'sign-in' ) { + if (!action || action === 'sign-in') { window.close(); window.open('', '_self').close(); } - } catch ( err ) { + } catch (err) { // send error to parent - window.opener.postMessage({ - msg: 'puter.token', - msg_id: msg_id, - success: false, - token: null, - }, window.openerOrigin); + window.opener.postMessage( + { + msg: 'puter.token', + msg_id: msg_id, + success: false, + token: null, + }, + window.openerOrigin, + ); // close popup window.close(); window.open('', '_self').close(); @@ -1700,7 +1911,7 @@ window.initgui = async function (options) { let app_uid; - if ( window.openerOrigin ) { + if (window.openerOrigin) { app_uid = await window.getAppUIDFromOrigin(window.openerOrigin); window.host_app_uid = app_uid; } @@ -1708,7 +1919,7 @@ window.initgui = async function (options) { //-------------------------------------------------------------------------------------- // Action: Show Open File Picker //-------------------------------------------------------------------------------------- - if ( action === 'show-open-file-picker' ) { + if (action === 'show-open-file-picker') { let options = window.url_query_params.get('options'); options = JSON.parse(options ?? '{}'); @@ -1716,7 +1927,7 @@ window.initgui = async function (options) { UIWindow({ allowed_file_types: options?.accept, selectable_body: options?.multiple, - path: `/${ window.user.username }/Desktop`, + path: `/${window.user.username}/Desktop`, return_to_parent_window: true, show_maximize_button: false, show_minimize_button: false, @@ -1730,20 +1941,23 @@ window.initgui = async function (options) { center: true, initiating_app_uuid: app_uid, on_close: function () { - window.opener.postMessage({ - msg: 'fileOpenCanceled', - original_msg_id: msg_id, - }, '*'); + window.opener.postMessage( + { + msg: 'fileOpenCanceled', + original_msg_id: msg_id, + }, + '*', + ); }, }); } //-------------------------------------------------------------------------------------- // Action: Show Directory Picker //-------------------------------------------------------------------------------------- - else if ( action === 'show-directory-picker' ) { + else if (action === 'show-directory-picker') { // open directory picker dialog UIWindow({ - path: `/${ window.user.username }/Desktop`, + path: `/${window.user.username}/Desktop`, // this is the uuid of the window to which this dialog will return // parent_uuid: event.data.appInstanceID, return_to_parent_window: true, @@ -1760,10 +1974,13 @@ window.initgui = async function (options) { center: true, initiating_app_uuid: app_uid, on_close: function () { - window.opener.postMessage({ - msg: 'directoryOpenCanceled', - original_msg_id: msg_id, - }, '*'); + window.opener.postMessage( + { + msg: 'directoryOpenCanceled', + original_msg_id: msg_id, + }, + '*', + ); }, }); } @@ -1771,25 +1988,28 @@ window.initgui = async function (options) { //-------------------------------------------------------------------------------------- // Action: Show Save File Dialog //-------------------------------------------------------------------------------------- - else if ( action === 'show-save-file-picker' ) { - let allowed_file_types = window.url_query_params.get('allowed_file_types'); + else if (action === 'show-save-file-picker') { + let allowed_file_types = + window.url_query_params.get('allowed_file_types'); // send 'sendMeFileData' event to parent - window.opener.postMessage({ - msg: 'sendMeFileData', - }, '*'); + window.opener.postMessage( + { + msg: 'sendMeFileData', + }, + '*', + ); // listen for 'showSaveFilePickerPopup' event from parent window.addEventListener('message', async (event) => { - if ( event.data.msg !== 'showSaveFilePickerPopup' ) - { + if (event.data.msg !== 'showSaveFilePickerPopup') { return; } // Open dialog UIWindow({ allowed_file_types: allowed_file_types, - path: `/${ window.user.username }/Desktop`, + path: `/${window.user.username}/Desktop`, // this is the uuid of the window to which this dialog will return return_to_parent_window: true, show_maximize_button: false, @@ -1805,22 +2025,32 @@ window.initgui = async function (options) { center: true, initiating_app_uuid: app_uid, on_close: function () { - window.opener.postMessage({ - msg: 'fileSaveCanceled', - original_msg_id: msg_id, - }, '*'); + window.opener.postMessage( + { + msg: 'fileSaveCanceled', + original_msg_id: msg_id, + }, + '*', + ); }, - onSaveFileDialogSave: async function (target_path, el_filedialog_window) { - $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').show(); + onSaveFileDialogSave: async function ( + target_path, + el_filedialog_window, + ) { + $(el_filedialog_window) + .find('.window-disable-mask, .busy-indicator') + .show(); let busy_init_ts = Date.now(); let overwrite = false; - let file_to_upload = new File([event.data.content], path.basename(target_path)); + 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 ) { + while (item_with_same_name_already_exists) { // overwrite? - if ( overwrite ) - { + if (overwrite) { item_with_same_name_already_exists = false; } // upload @@ -1834,32 +2064,45 @@ window.initgui = async function (options) { }, ); - let file_signature = await puter.fs.sign(app_uid, { uid: res.uid, action: 'write' }); + let file_signature = await puter.fs.sign( + app_uid, + { uid: res.uid, action: 'write' }, + ); file_signature = file_signature.items; item_with_same_name_already_exists = false; - window.opener.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: privacy_aware_path(res.path), + window.opener.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: privacy_aware_path( + res.path, + ), + }, }, - }, '*'); + '*', + ); window.close(); window.open('', '_self').close(); // show_save_account_notice_if_needed(); - } - catch ( err ) { + } catch (err) { // item with same name exists - if ( err.code === 'item_with_same_name_exists' ) { + if ( + err.code === + 'item_with_same_name_exists' + ) { const alert_resp = await UIAlert({ message: `${html_encode(err.entry_name)} already exists.`, buttons: [ @@ -1873,51 +2116,71 @@ window.initgui = async function (options) { value: 'cancel', }, ], - parent_uuid: $(el_filedialog_window).attr('data-element_uuid'), + parent_uuid: + $(el_filedialog_window).attr( + 'data-element_uuid', + ), }); - if ( alert_resp === 'replace' ) { + if (alert_resp === 'replace') { overwrite = true; - } else if ( alert_resp === 'cancel' ) { + } else if (alert_resp === 'cancel') { // enable parent window - $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide(); + $(el_filedialog_window) + .find( + '.window-disable-mask, .busy-indicator', + ) + .hide(); return; } - } - else { + } else { console.log(err); // show error await UIAlert({ - message: err.message ?? 'Upload failed.', - parent_uuid: $(el_filedialog_window).attr('data-element_uuid'), + 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(); + $(el_filedialog_window) + .find( + '.window-disable-mask, .busy-indicator', + ) + .hide(); return; } } } // done - let busy_duration = (Date.now() - busy_init_ts); - if ( busy_duration >= window.busy_indicator_hide_delay ) { + let busy_duration = Date.now() - busy_init_ts; + if ( + busy_duration >= + window.busy_indicator_hide_delay + ) { $(el_filedialog_window).close(); } else { - setTimeout(() => { - // close this dialog - $(el_filedialog_window).close(); - }, Math.abs(window.busy_indicator_hide_delay - busy_duration)); + setTimeout( + () => { + // close this dialog + $(el_filedialog_window).close(); + }, + Math.abs( + window.busy_indicator_hide_delay - + busy_duration, + ), + ); } }, - }); }); } - } - }); - if ( window.__login_completed ) { + if (window.__login_completed) { document.dispatchEvent(new Event('login', { bubbles: true })); window.__login_completed = false; } @@ -1932,33 +2195,38 @@ window.initgui = async function (options) { update_last_touch_coordinates(e); // dismiss touchstart on regular devices - if ( e.type === 'touchstart' && !isMobile.phone && !isMobile.tablet ) - { + if (e.type === 'touchstart' && !isMobile.phone && !isMobile.tablet) { return; } // If .item-container clicked, unselect all its item children - if ( $(e.target).hasClass('item-container') && !e.ctrlKey && !e.metaKey ) { + if ( + $(e.target).hasClass('item-container') && + !e.ctrlKey && + !e.metaKey + ) { $(e.target).children('.item-selected').removeClass('item-selected'); window.update_explorer_footer_selected_items_count(e.target); } // If the clicked element is not a context menu, remove all context menus - if ( $(e.target).parents('.context-menu').length === 0 ) { + if ($(e.target).parents('.context-menu').length === 0) { $('.context-menu').fadeOut(200, function () { $(this).remove(); }); } // click on anything will close all popovers, but there are some exceptions - if ( !$(e.target).hasClass('start-app') - && !$(e.target).hasClass('launch-search') - && !$(e.target).hasClass('launch-search-clear') - && $(e.target).closest('.start-app').length === 0 - && !isMobile.phone && !isMobile.tablet - && !$(e.target).hasClass('popover') - && $(e.target).parents('.popover').length === 0 ) { - + if ( + !$(e.target).hasClass('start-app') && + !$(e.target).hasClass('launch-search') && + !$(e.target).hasClass('launch-search-clear') && + $(e.target).closest('.start-app').length === 0 && + !isMobile.phone && + !isMobile.tablet && + !$(e.target).hasClass('popover') && + $(e.target).parents('.popover').length === 0 + ) { $('.popover').fadeOut(200, function () { $('.popover').remove(); }); @@ -1968,21 +2236,21 @@ window.initgui = async function (options) { $('.ui-tooltip').remove(); // rename items whose names were being edited - if ( ! $(e.target).hasClass('item-name-editor') ) { + if (!$(e.target).hasClass('item-name-editor')) { // blurring an Item Name Editor will automatically trigger renaming the item $('.item-name-editor-active').blur(); } // update active_item_container - if ( $(e.target).hasClass('item-container') ) { + if ($(e.target).hasClass('item-container')) { window.active_item_container = e.target; } else { let ic = $(e.target).closest('.item-container'); - if ( ic.length > 0 ) { + if (ic.length > 0) { window.active_item_container = ic.get(0); } else { let pp = $(e.target).find('.item-container'); - if ( pp.length > 0 ) { + if (pp.length > 0) { window.active_item_container = pp.get(0); } } @@ -2002,25 +2270,37 @@ window.initgui = async function (options) { //-------------------------------------------------------- $(document).on('mousedown', function (e) { // if taskbar or any parts of it is clicked, drop the event - if ( $(e.target).hasClass('taskbar') || $(e.target).closest('.taskbar').length > 0 ) { + if ( + $(e.target).hasClass('taskbar') || + $(e.target).closest('.taskbar').length > 0 + ) { return; } // if toolbar or any parts of it is clicked, drop the event - if ( $(e.target).hasClass('toolbar') || $(e.target).closest('.toolbar').length > 0 ) { + if ( + $(e.target).hasClass('toolbar') || + $(e.target).closest('.toolbar').length > 0 + ) { return; } // if close or minimize button clicked, drop the event - if ( document.elementFromPoint(e.clientX, e.clientY).closest('.window-close-btn, .window-minimize-btn') ) { + if ( + document + .elementFromPoint(e.clientX, e.clientY) + .closest('.window-close-btn, .window-minimize-btn') + ) { return; } // if mouse is clicked on a window, activate it - if ( window.mouseover_window !== undefined ) { + if (window.mouseover_window !== undefined) { // if popover clicked on, don't activate window. This is because if an app // is using the popover API to show a popover, the popover will be closed if the window is activated - if ( $(e.target).hasClass('popover') || $(e.target).parents('.popover').length > 0 ) - { + if ( + $(e.target).hasClass('popover') || + $(e.target).parents('.popover').length > 0 + ) { return; } $(window.mouseover_window).focusWindow(e); @@ -2041,21 +2321,20 @@ window.initgui = async function (options) { }); document.addEventListener('visibilitychange', (event) => { - if ( document.visibilityState !== 'visible' ) { + if (document.visibilityState !== 'visible') { window.doc_title_before_blur = document.title; - if ( Object.keys(window.active_uploads).length > 0 ) { + if (Object.keys(window.active_uploads).length > 0) { update_title_based_on_uploads(); } - } else if ( window.active_uploads ) { + } else if (window.active_uploads) { document.title = window.doc_title_before_blur ?? 'Puter'; } }); - }; -function requestOpenerOrigin () { +function requestOpenerOrigin() { return new Promise((resolve, reject) => { - if ( ! window.opener ) { + if (!window.opener) { reject(new Error('No window.opener available')); return; } @@ -2063,7 +2342,7 @@ function requestOpenerOrigin () { // Function to handle the message event const handleMessage = (event) => { // Check if the message is the expected response - if ( event.data.msg === 'originResponse' ) { + if (event.data.msg === 'originResponse') { // Clean up by removing the event listener window.removeEventListener('message', handleMessage); resolve(event.origin); @@ -2089,7 +2368,11 @@ $(document).on('click', '.generic-close-window-button', function (e) { }); $(document).on('click', function (e) { - if ( !$(e.target).hasClass('window-search') && $(e.target).closest('.window-search').length === 0 && !$(e.target).is('.toolbar-btn.search-btn') ) { + if ( + !$(e.target).hasClass('window-search') && + $(e.target).closest('.window-search').length === 0 && + !$(e.target).is('.toolbar-btn.search-btn') + ) { $('.window-search').close(); } }); @@ -2097,14 +2380,14 @@ $(document).on('click', function (e) { // Re-calculate desktop height and width on window resize and re-position the login and signup windows $(window).on('resize', function () { // If host env is popup, don't continue because the popup window has its own resize requirements. - if ( window.embedded_in_popup ) - { + if (window.embedded_in_popup) { return; } const ratio = window.desktop_width / window.innerWidth; - window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; + window.desktop_height = + window.innerHeight - window.toolbar_height - window.taskbar_height; window.desktop_width = window.innerWidth; // Re-center the login window @@ -2125,7 +2408,7 @@ $(window).on('resize', function () { }); $(document).on('contextmenu', '.disable-context-menu', function (e) { - if ( $(e.target).hasClass('disable-context-menu') ) { + if ($(e.target).hasClass('disable-context-menu')) { e.preventDefault(); return false; } diff --git a/src/puter-js/src/index.js b/src/puter-js/src/index.js index 8697836c4..6d69508e2 100644 --- a/src/puter-js/src/index.js +++ b/src/puter-js/src/index.js @@ -946,18 +946,6 @@ const puterInit = function () { return; } if (this.env === 'app') { - // We're inside an iframe in the GUI. Ask the parent to - // surface the reauth modal; once the user signs in there, - // the existing `puter.token` postMessage delivers the - // fresh token back to us. - // - // targetOrigin is locked to the configured GUI origin, by - // exact match. We deliberately do NOT trust a URL-supplied - // origin here: postMessage('*') — or an attacker-chosen - // origin — would leak the reauth signal + auth_id to a - // malicious parent and could let it inject a token. The - // configured GUI origin is the only origin allowed to drive - // reauth. try { globalThis.parent?.postMessage?.( {