Skip to content

Allow @converse/headless to run in NodeJS - #4135

Open
jcbrand wants to merge 4 commits into
masterfrom
jcbrand/headless-node
Open

Allow @converse/headless to run in NodeJS#4135
jcbrand wants to merge 4 commits into
masterfrom
jcbrand/headless-node

Conversation

@jcbrand

@jcbrand jcbrand commented Aug 6, 2026

Copy link
Copy Markdown
Member

No description provided.

@jcbrand
jcbrand force-pushed the jcbrand/headless-node branch 4 times, most recently from 0baa7cc to c6bf1f8 Compare August 7, 2026 02:40
Comment thread src/headless/utils/html.js Fixed
@jcbrand
jcbrand force-pushed the jcbrand/headless-node branch 2 times, most recently from 334098c to f689758 Compare August 7, 2026 03:01
jcbrand and others added 4 commits August 7, 2026 05:33
One NodeSQLiteStorage instance backs every store (as one localForage does in the
browser), so a login opens a single database instead of ~40 files; keys can't
collide because getItemName() prefixes each with its store name. It lives at
$XDG_STATE_HOME/converse, overridable via storage_path, and needs skeletor 3.1.6.
Declaring globalThis.sessionStorage = undefined silences skeletor's probe, which
otherwise prints a stack trace on every start and would corrupt a TUI's render.
getCrypto() imported ./libomemo.esm.min.js, a dist/-only sibling, so under Node
it raised ERR_MODULE_NOT_FOUND and the floated rejection killed the process. It
takes a pluggable loader now: the browser fetches the companion file, Node does
`() => import('libomemo.js')`, whose SINGLE_FILE build inlines the wasm, and a
no-op catch keeps a floated failure to "no OMEMO" rather than fatal.

3.0.0 ships no separate curve25519_compiled.wasm, so the rspack builds copy only
the wasm-inlined libomemo.esm.min.js, and the browser loader drops the now-unread
__WASM_BASE__ hint it used to set for the old separate-wasm fetch.
…moji run under Node

converse-bosh reports itself disabled off-browser (it's an XMLHttpRequest
transport). File upload (XEP-0363) PUTs via fetch under Node, keeping XHR in the
browser for upload progress. decodeHTMLEntities and unescapeHTML manage without a
DOM, so they no longer throw or return undefined under Node: entities are decoded
with `he`, and tags are stripped by a scanner that reads markup the way an HTML
tokeniser does. A tag-shaped regex would have been shorter, but it needs the
closing `>` and so leaves a truncated `<script src="x` intact, where the DOMPurify
call it replaces dropped it. dompurify moves out of headless (the UI still
declares it), `he` is added, and the unused DOM-only stringToElement util is
dropped from `u`. The microblog plugin and shared/http-upload switch their stray
real-`sizzle` imports to `#sizzle`; utils/following.js also loses a stray NUL byte
in a local dedup key that had left the file classified as binary.

The emoji plugin fetched emoji.json from the asset directory, which has no URL
under Node; a pluggable loader reads it from the package instead (via
shims/node-emoji.js) while the browser keeps fetching the companion file.

The HTML helpers get a spec of their own, which the headless-node project runs
alongside the browser one, since the whole point of them is that they're
isomorphic.
@jcbrand
jcbrand force-pushed the jcbrand/headless-node branch from f689758 to 157354d Compare August 7, 2026 03:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants