mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-24 06:05:40 +00:00
dev(puter.js): add support to request app dir
Add support for requesting access to the root directory of an app's associated subdomain.
This commit is contained in:
@@ -145,7 +145,7 @@ async function setup_window_events (el_window, options, resolve) {
|
||||
|
||||
/**
|
||||
* Generates user-friendly description of permission string in HTML format.
|
||||
*
|
||||
*
|
||||
* @param {string} permission - The permission string to describe
|
||||
* @returns {string} The user-friendly description of the permission in HTML format
|
||||
*/
|
||||
@@ -231,7 +231,17 @@ async function get_permission_description (permission) {
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
if ( parts[0] === 'app-root-dir' ) {
|
||||
// Format: app-root-dir:resource_request_code:access
|
||||
if ( parts[2] === 'read' ) {
|
||||
return i18n('perm_app_root_dir_read');
|
||||
}
|
||||
if ( parts[2] === 'write' ) {
|
||||
return i18n('perm_app_root_dir_write');
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -116,7 +116,6 @@ const en = {
|
||||
desktop_background_fit: 'Fit',
|
||||
developers: 'Developers',
|
||||
dir_published_as_website: '%strong% has been published to:',
|
||||
directory_depth_limit_exceeded: 'Cannot create folder. The maximum directory depth has been reached. Please create the folder in a higher-level directory.',
|
||||
disable_2fa: 'Disable 2FA',
|
||||
disable_2fa_confirm: 'Are you sure you want to disable 2FA?',
|
||||
disable_2fa_instructions: 'Enter your password to disable 2FA.',
|
||||
@@ -169,7 +168,6 @@ const en = {
|
||||
item: 'item',
|
||||
items_in_trash_cannot_be_renamed: 'This item can\'t be renamed because it\'s in the trash. To rename this item, first drag it out of the Trash.',
|
||||
jpeg_image: 'JPEG image',
|
||||
add_to_desktop: 'Add to Desktop',
|
||||
keep_in_taskbar: 'Keep in Taskbar',
|
||||
language: 'Language',
|
||||
license: 'License',
|
||||
@@ -203,6 +201,7 @@ const en = {
|
||||
no_dir_associated_with_site: 'No directory associated with this address.',
|
||||
no_websites_published: 'You have not published any websites yet. Right click on a folder to get started.',
|
||||
ok: 'OK',
|
||||
or: 'or',
|
||||
open: 'Open',
|
||||
new_window: 'New Window',
|
||||
open_in_ai: 'Open in AI',
|
||||
@@ -483,6 +482,8 @@ const en = {
|
||||
|
||||
// Signup Window
|
||||
'signup_confirm_password': 'Confirm Password',
|
||||
sign_in_with_google: 'Sign in with Google',
|
||||
sign_up_with_google: 'Sign up with Google',
|
||||
|
||||
// Login Window
|
||||
'login_email_username_required': 'Email or username is required',
|
||||
@@ -550,6 +551,8 @@ const en = {
|
||||
'perm_apps_write': 'manage your apps',
|
||||
'perm_subdomains_read': 'see your subdomains',
|
||||
'perm_subdomains_write': 'manage your subdomains',
|
||||
'perm_app_root_dir_read': 'read the root directory of one of your apps',
|
||||
'perm_app_root_dir_write': 'read and write to the root directory of one of your apps',
|
||||
|
||||
'error_user_or_path_not_found': 'User or path not found.',
|
||||
'error_invalid_username': 'Invalid username.',
|
||||
|
||||
@@ -21,11 +21,15 @@ export default class Perms {
|
||||
...(body ? { body: JSON.stringify(body) } : {}),
|
||||
});
|
||||
if ( resp.headers.get('content-type')?.includes('application/json') ) {
|
||||
return await resp.json();
|
||||
const jsonResult = await resp.json();
|
||||
if ( resp.status !== 200 ) {
|
||||
jsonResult.error = true;
|
||||
}
|
||||
return jsonResult;
|
||||
}
|
||||
return { message: await resp.text(), code: 'unknown_error' };
|
||||
return { error: true, message: await resp.text(), code: 'unknown_error' };
|
||||
} catch (e) {
|
||||
return { message: e.message, code: 'internal_error' };
|
||||
return { error: true, message: e.message, code: 'internal_error' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +321,76 @@ export default class Perms {
|
||||
return granted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request read access to the root directory of one of the user's apps,
|
||||
* identified by its resource request code (e.g. from app.resource_request_code).
|
||||
* If the user grants permission, returns the filesystem item for that directory.
|
||||
* If the user denies or an error occurs, returns undefined.
|
||||
*
|
||||
* @param {string} resourceRequestCode - The resource request code (e.g. `${app.uid}:root_dir`)
|
||||
* @return {Promise<object|undefined>} The directory fs item (stat) or undefined
|
||||
*/
|
||||
async requestReadAppRootDir (app_uid) {
|
||||
return await this.#requestAppRootDir('read', app_uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request write access to the root directory of one of the user's apps,
|
||||
* identified by its resource request code (e.g. from app.resource_request_code).
|
||||
* If the user grants permission, returns the filesystem item for that directory.
|
||||
* If the user denies or an error occurs, returns undefined.
|
||||
*
|
||||
* @param {string} resourceRequestCode - The resource request code (e.g. `${app.uid}:root_dir`)
|
||||
* @return {Promise<object|undefined>} The directory fs item (stat) or undefined
|
||||
*/
|
||||
async requestWriteAppRootDir (app_uid) {
|
||||
return await this.#requestAppRootDir('write', app_uid);
|
||||
}
|
||||
|
||||
async #requestAppRootDir (access, app_uid) {
|
||||
if ( typeof app_uid === 'object' && app_uid !== null ) {
|
||||
app_uid = app_uid.uid;
|
||||
}
|
||||
if ( typeof app_uid !== 'string' ) {
|
||||
throw new Error('parameter app_uid must be a strinkg');
|
||||
}
|
||||
|
||||
let result;
|
||||
const fetchIt = async () => result = await this.req_('/auth/request-app-root-dir', {
|
||||
app_uid,
|
||||
access: 'read',
|
||||
});
|
||||
await fetchIt();
|
||||
if ( ! result.error ) return result;
|
||||
|
||||
// Request permission
|
||||
app_uid = (typeof app_or_uuid === 'object') && app_uid !== null
|
||||
? app_uid.uid : app_uid;
|
||||
const readAppRootDirPermission = `app-root-dir:${app_uid}:read`;
|
||||
const granted = await this.puter.ui.requestPermission({
|
||||
permission: readAppRootDirPermission,
|
||||
});
|
||||
|
||||
if ( granted ) {
|
||||
await fetchIt();
|
||||
|
||||
// If the server has cache invalidation issues, we still want this
|
||||
// to work anyway. This is a hack, but also a reasonable safeguard.
|
||||
let delay = 100;
|
||||
const maxTotalWait = 5000;
|
||||
let totalWaited = 0;
|
||||
while ( result.error && totalWaited < maxTotalWait ) {
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
totalWaited += delay;
|
||||
await fetchIt();
|
||||
if ( ! result.error ) break;
|
||||
delay = Math.min(delay * 2, Math.max(100, maxTotalWait - totalWaited));
|
||||
}
|
||||
}
|
||||
if ( ! result.error ) return result;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to request access to a user's special folder.
|
||||
* @private
|
||||
|
||||
Reference in New Issue
Block a user