I needed a way to let users write JavaScript to create plugins for a site I'm building.<p>I couldn't find a solution I was happy with, so ended up building one that run's it in a web worker on a separate domain from your main site.<p>Hopefully I haven't missed anything. If so, please let me know!<p>My website has an interactive demo you can write code in the browser textarea, and see the output on the right.<p>Interactive Demo: <a href="https://workerbox.net/" rel="nofollow">https://workerbox.net/</a><p>Github: <a href="https://github.com/markwylde/workerbox" rel="nofollow">https://github.com/markwylde/workerbox</a>
Figma has a great blog post on some different user-generated javascript isolation approaches [1]. They discuss this iframe approach, running a webassembly javascript interpreter (also suggested in these thread comments), and using javascript realms. In the post they originally opted for javascript realms but there's an edit at the top - it seems like they ended up switching to the webassembly javascript interpreter approach.<p>[1]: <a href="https://www.figma.com/blog/how-we-built-the-figma-plugin-system/" rel="nofollow">https://www.figma.com/blog/how-we-built-the-figma-plugin-sys...</a>
If you need to call into user-generated Javascript <i>synchronously</i> or have greater control over the sandbox environment, you can use WebAssembly to run a Javascript interpreter: <a href="https://github.com/justjake/quickjs-emscripten#quickjs-emscripten" rel="nofollow">https://github.com/justjake/quickjs-emscripten#quickjs-emscr...</a><p>QuickJS in WebAssembly is much slower than your browser's native Javascript runtime, but possibly faster than async calls using postMessage. As an added bonus, it can make <i>async functions</i> in the host <i>appear to be synchronous</i> inside the sandbox using asyncify: <a href="https://github.com/justjake/quickjs-emscripten#async-on-host-sync-in-quickjs" rel="nofollow">https://github.com/justjake/quickjs-emscripten#async-on-host...</a>
I'll chime in with my random bit of knowledge about a similar topic. Chrome has a bug where if you dynamically create an iframe (document.write(), .createElement(), .innerHTML, etc.), the parent page's Service Worker isn't inherited. It just fails. However, if you use a blank page loaded via the src attribute coming from the same domain, it works as expected. Service workers are really nothing more than client-side network proxies, so once you've loaded that blank page, you can do whatever you want to it (add styles, images, scripts, what have you), then use the service worker to control its access to the network. I use this in an editor app to seamlessly load local assets by intercepting the iframe's network requests. I'm not going for strict security as there's no server-side data involved, so I'm not sure how secure this approach would be for user generated content, I just wanted to toss it out there for those interested. It might save you some time at some point.<p>(I think it would be <i>wonderful</i> if the Chrome guys fixed this 2+ year old bug so the rest of us don't have to spend a week or so trying to figure out WTF is going on before realizing the obvious-in-retrospect workaround. Just saying.)<p><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=880768#c15" rel="nofollow">https://bugs.chromium.org/p/chromium/issues/detail?id=880768...</a>
The upcoming JavaScript Shadow Realms proposal looks like it solves a similar problem: <a href="https://github.com/tc39/proposal-shadowrealm/blob/main/explainer.md" rel="nofollow">https://github.com/tc39/proposal-shadowrealm/blob/main/expla...</a>
I did something similar but without needing web workers, iframes, or any type of vm. I used Proxy to intercept Function and hide all scopes outside of the closure. I don't know if it's 100% safe, but it's just for a small game demo, letting users control in-game units in the browser.<p><a href="https://ai-arena.com/" rel="nofollow">https://ai-arena.com/</a><p>If anyone knows how to break my game, please let me know!
Once upon a time there was a private retro gaming BitTorrent tracker called UG. They had a few browser games, one of which was blackjack. I wrote a greasemonkey script to play blackjack for me unattended, folks were not amused =(
One upon a time, Crockford worked on ADSafe for a similar goal.<p><a href="https://www.crockford.com/adsafe/" rel="nofollow">https://www.crockford.com/adsafe/</a>
Another example is the sandbox we have built for Peergos[1] for user apps
Combination of double iframe, service worker and writable streams.
Not without issue due to browser behaviour. Chrome (...annoyances), Firefox (...feature not necessarily available), Safari (...blocking bugs)
[1] <a href="https://peergos.org/posts/a-better-web" rel="nofollow">https://peergos.org/posts/a-better-web</a>
Oh, dude! Genius. I needed something exactly like this.
Is there a similar lightweight approach in node land?<p>Edit: my usecase is a form builder where users can specify fields that have their values computed from other fields. I've had to hobble it by using templating languages for the custom code, or just let them use javascript and be okay with the risk since the users get their own subdomain, so it's kinda like their own website.
Nice, I'm building a plugin system for my webapp too! I gave up completely on security [0] though because my plugins want DOM access, and may possibly manipulate elements outside their specific control, so I was like "fuck it". Thankfully there's a clear difference between my webapp and website, so hopefully I'll be able to educate my users to not trust stuff on the app side. We'll see :|<p>Separately, how are you thinking about designing your plugin system? I built a PoC here [1] that basically revolves around combining a DI container with the decorator pattern. We start with an initial default DI container/object, then that container is passed to a plugin which wraps certain methods or adds new ones. That new container is then passed to the next plugin which wraps it, etc, etc. Actual implementation is here [2]. The only thing that sucks about my design is that plugins must call `.bind(this)` when wrapping a method. So basically plugins have unfettered access to my DI container. Perhaps I'm giving plugins too much power? :shrug_shoulders:<p>0: <a href="https://github.com/AlexErrant/Pentive/blob/main/design-decisions/frontend.md#plugins" rel="nofollow">https://github.com/AlexErrant/Pentive/blob/main/design-decis...</a><p>1: <a href="https://github.com/AlexErrant/Pentive/blob/main/app/src/plugin-manager.proof-of-concept.ts" rel="nofollow">https://github.com/AlexErrant/Pentive/blob/main/app/src/plug...</a><p>2: <a href="https://github.com/AlexErrant/Pentive/blob/main/app/src/plugin-manager.ts" rel="nofollow">https://github.com/AlexErrant/Pentive/blob/main/app/src/plug...</a>
This came really handy, as I was in need for a way to run sandboxed user code in a coding game!<p>Another cool thing with WebWorkers is that the code is run in a separate thread and allows you to terminate it if it runs into an infinite loop, for instance.
This is actually what the Azure Portal does [1] for embedding 3rd party applications. They even developed a custom HTML language to describe how to layout applications to give a consistent look.<p>The downside was the portal started to become slow, and sometimes would have 20+ iframes open.<p>[1] <a href="https://github.com/Azure/portaldocs/blob/main/portal-sdk/generated/portalfx-blades-appblades.md" rel="nofollow">https://github.com/Azure/portaldocs/blob/main/portal-sdk/gen...</a>
What advantages does workerbox have over existing solutions like jailed[1]?<p>[1] <a href="https://github.com/asvd/jailed" rel="nofollow">https://github.com/asvd/jailed</a>
Really awesome!! Just this week I was wondering how I was going to approach this for a project of mine. I recalled the figma article but your approach seems simpler. I’ll definitely give this a shot! Thank you!!
Wow. The article did not match the expectations I got from the title. I thought this was a joke that allows you to run dangerous code that browsers would normally block by default or something.
> in a web worker on a separate domain<p>Does it need a separate domain for every script to prevent two or more user generated scripts from influencing each other?