Convert an existing app
Turn an HTML/JS app or a Python script into a sandboxed module — the mechanical diff.
Most existing apps become modules with a small, mechanical diff. There are two paths:
- a UI app (HTML/CSS/JS) → a browser module (its UI runs in the sandboxed iframe),
- a script / CLI (e.g. Python) → a worker module (its logic runs in the sandbox).
Worked, runnable before/after pairs live in examples/converting/.
Part 1 — an HTML/JS app → a browser module #
Worked example: examples/converting/html-todo (before/index.html is a plain localStorage to-do; after/ is the module).
A module's UI runs in a sandboxed, opaque-origin iframe, so a few browser APIs are unavailable and are replaced by capability calls. Three changes:
1) Add a manifest
Add hanabi.module.json at the root and put your page at ui/index.html. Declare the permissions for the capabilities you'll use (see step 2). See manifest-reference.md.
2) Swap browser-only APIs for capabilities
| Your app does… | …becomes (bridge message / SDK) | Why |
|---|---|---|
localStorage / sessionStorage | settings.self (HANABI_GET/SET_SETTING) for a value, or data.store (HANABI_DATA_*) for records | the opaque-origin iframe has no persistent storage; these are durable + cross-device |
<a download> / save a Blob | HANABI_WRITE_FILE → the module's Exports (downloads also still work) | files land in the user's workspace, not just the browser |
<input type=file> to open a file | HANABI_PICK_FILE + HANABI_READ_FILE | scoped to the module's folders + the file the user opened it with |
fetch() to an API | declare the origin in dependencies.services + network.fetch (UI), or do it in a workerAn optional Python program that does heavy or native work off the browser, in a sealed container. | the served CSP blocks undeclared connect-src |
<video src="file…"> for a big local file | HANABI_OPEN_MEDIA (a range URL) | streams + seeks without copying bytes |
| cookies / its own auth | (remove) — the platform already authenticates the user | a module never sees credentials |
3) Add the bridge client
Paste the ~12-line call() client (top of any demo-* ui/index.html) and run your init code after HANABI_READY. The after/ to-do shows the entire diff — the DOM/render code is untouched; only persistence (localStorage → settings.self) and export (download → writeFile) change.
createHanabiClient() from @hanabi/module-sdk wraps the same messages.Part 2 — a Python script/CLI → a worker module #
Worked example: examples/converting/python-wordcount (before/wordcount.py is a plain CLI; after/ is the module).
A worker is `run(inputs, options, ctx=None)` — keep your logic, replace the I/O shell:
| Your script does… | …becomes |
|---|---|
read sys.argv / input() | read `options` (the dict the UI passed to createJob) |
open(path) / read files | iterate `inputs` — {filename: bytes} the platform supplies |
print(...) / write output files | return {filename: bytes|str} — each becomes a file in Exports |
requests.get(url) / sockets | declare the third `ctx` arg + network.fetch; call ctx.fetch(url) (the container has no network) |
pip install foo | declare it in dependencies.python: ["foo>=1.0"] |
if __name__ == "__main__": | (delete) — the platform calls run |
subprocess to ffmpeg/imagemagick | declare worker.native (admin-HIGH) to get them on PATH |
The after/worker.py keeps count_words() byte-for-byte; only the wrapper changes. Declare entrypoints.worker + worker.execute in the manifest. The worker runs isolated (no db, no network, no other user) — see worker-guide.md.
What you DON'T get in a worker
No host filesystem, no environment/secrets, no persistent process (one-shot bytes→bytes), no network except host-mediated ctx.fetch. A worker that needs to stay resident is a persistent service (worker.service, admin-HIGH) — a separate, admin-managed path.
Then: package + test #
npm --workspace packages/module-cli run hanabi -- module validate ./my-module npm --workspace packages/module-cli run hanabi -- module pack ./my-module --out ./my-module.zip
Upload it in Module Store → Upload. See packaging.md for details and limitations.md for the full list of sandbox constraints to design around.