diff --git a/extensions/whoami/routes.js b/extensions/whoami/routes.js index 2421b6454..9184ce50e 100644 --- a/extensions/whoami/routes.js +++ b/extensions/whoami/routes.js @@ -98,6 +98,7 @@ extension.get('/whoami', { subdomain: 'api' }, async (req, res, next) => { human_readable_age: timeago.format(new Date(req.user.timestamp)), hasDevAccountAccess: !!req.actor.type.user.metadata?.hasDevAccountAccess, ...(req.new_token ? { token: req.token } : {}), + is_user_token: true, // gets deleted if not a user token }; // TODO: redundant? GetUserService already puts these values on 'user' @@ -128,6 +129,7 @@ extension.get('/whoami', { subdomain: 'api' }, async (req, res, next) => { delete details.taskbar_items; delete details.token; delete details.human_readable_age; + delete details.is_user_token; } if ( actor.type instanceof AppUnderUserActorType ) { diff --git a/extensions/worker-sandbox.js b/extensions/worker-sandbox.js new file mode 100644 index 000000000..b0926cf7d --- /dev/null +++ b/extensions/worker-sandbox.js @@ -0,0 +1,201 @@ +const page = ` + + + + + + Puter Worker Sandbox Playground + + + +
+

Puter Worker Sandbox Playground

+

Use this page to interact with the puter APIs in the same sandbox as your worker.

+
+ + +
+
+
+

Code

+ +
+
+

Logs

+

+            
+
+
+ + + + +`; + +extension.get('/', { noauth: true, subdomain: 'worker-sandbox' }, (req, res) => { + res.type('html').send(page); +}); diff --git a/src/gui/src/UI/UIWindowPublishWorker.js b/src/gui/src/UI/UIWindowPublishWorker.js index 2c3404763..14ffbc6e5 100644 --- a/src/gui/src/UI/UIWindowPublishWorker.js +++ b/src/gui/src/UI/UIWindowPublishWorker.js @@ -41,6 +41,15 @@ async function UIWindowPublishWorker (target_dir_uid, target_dir_name, target_di h += ''; // uid h += ``; + // Advanced (collapsed by default) + h += '
'; + h += 'Advanced'; + h += '
'; + h += ''; + h += '
'; + h += '
'; // Publish h += ``; h += ''; @@ -91,9 +100,13 @@ async function UIWindowPublishWorker (target_dir_uid, target_dir_name, target_di
`); + const sandboxed = $(el_window).find('.publish-worker-sandboxed').is(':checked'); + const createOptions = sandboxed ? { sandbox: true } : { sandbox: false }; + puter.workers.create(worker_name, - target_dir_path).then((res) => { - let url = `https://${ worker_name }.puter.work`; + target_dir_path, + createOptions).then((res) => { + let url = `https://${ worker_name }.puter.work`; $(el_window).find('.window-publishWorker-form').hide(100, function () { $(el_window).find('.publishWorker-published-link').attr('href', url); $(el_window).find('.publishWorker-published-link').text(url); @@ -108,17 +121,25 @@ async function UIWindowPublishWorker (target_dir_uid, target_dir_name, target_di // update item's website_url attribute $(this).attr('data-website_url', url + $(this).attr('data-path').substring(target_dir_path.length)); }); - }).catch((err) => { - err = err.error; - $(el_window).find('.publish-worker-error-msg').html( - err.message + ( - err.code === 'subdomain_limit_reached' ? - ` ${ i18n('manage_your_subdomains') }` : '' - )); + }).catch((err) => { + let errorHtml; + // Handle worker service errors (result.success === false) + if ( ! (err instanceof Error) ) { + // Handle regular API errors + const error = err.error || err; + errorHtml = error.message + ( + error.code === 'subdomain_limit_reached' ? + ` ${ i18n('manage_your_subdomains') }` : '' + ); + } else { + errorHtml = `
${html_encode(err)}
`; + } + + $(el_window).find('.publish-worker-error-msg').html(errorHtml); $(el_window).find('.publish-worker-error-msg').fadeIn(); // re-enable 'Publish' button and restore original text $(el_window).find('.publish-btn').prop('disabled', false).text(originalText); - }); + }); }); $(el_window).find('.publish-window-ok-btn').on('click', function () { diff --git a/src/gui/src/css/style.css b/src/gui/src/css/style.css index 0c2758bb1..37288da0b 100644 --- a/src/gui/src/css/style.css +++ b/src/gui/src/css/style.css @@ -1953,6 +1953,10 @@ label { font-size: 13px; } +.publish-worker-error-msg{ + text-align: left; +} + .error { display: none; color: red; diff --git a/src/puter-js/src/modules/Workers.js b/src/puter-js/src/modules/Workers.js index 92c3c81ed..f78c4f92f 100644 --- a/src/puter-js/src/modules/Workers.js +++ b/src/puter-js/src/modules/Workers.js @@ -18,6 +18,22 @@ export class WorkersHandler { } let appId; + if ( typeof (appName) === 'object' || typeof (appName) === 'undefined' ) { + const user = (puter.whoami || await puter.getUser()); + + if ( user.is_user_token && (appName === undefined || appName?.sandbox !== false) ) { + let sandboxApp; + try { + sandboxApp = await puter.apps.get(`sandbox-${ workerName }`); + } catch ( e ) { + sandboxApp = await puter.apps.create(`sandbox-${ workerName }`, 'https://worker-sandbox.puter.com/'); + } + if ( sandboxApp.owner.uuid !== user.uuid ) { + throw new Error(`Sandbox context is not owned by you! This worker's sandbox is currently owned by: ${ sandboxApp.owner.username }`); + } + appId = sandboxApp.uid; + } + } if ( typeof (appName) === 'string' ) { appId = ((await puter.apps.list()).find(el => el.name === appName)).uid; } @@ -121,7 +137,7 @@ export class WorkersHandler { const loggingEndpoint = await utils.make_driver_method([], 'workers', 'worker-service', 'getLoggingUrl')(puter.authToken, workerName); const socket = new WebSocket(`${loggingEndpoint}/${puter.authToken}/${workerName}`); const logStreamObject = new EventTarget(); - logStreamObject.onLog = (data) => { + logStreamObject.onLog = (_data) => { }; @@ -132,11 +148,11 @@ export class WorkersHandler { socket.addEventListener('message', (event) => { controller.enqueue(JSON.parse(event.data)); }); - socket.addEventListener('close', (event) => { + socket.addEventListener('close', () => { try { controller.close(); } catch (e) { - + // no-op } }); },