Skip to content

nime: keep the Node.js backend alive on uninitialized clients and malformed lines (#936, #937) - #971

Merged
ottokang merged 3 commits into
EasyIME:masterfrom
ilwu:fix-nime-uninitialized-client
Oct 25, 2026
Merged

ottokang merged 3 commits into
EasyIME:masterfrom
ilwu:fix-nime-uninitialized-client

Conversation

@ilwu

@ilwu ilwu commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Complements #945/#946/#955: those cover the createServer callback and the top-level exception handler, but two paths inside nime itself still terminate the backend (details in my test comments on those PRs).

  • handleClientRequest() answers {success: false, seqNum} when the client has no service — its init named an unknown GUID, or it never sent init (a client that outlives a backend restart). Previously handleRequest() dereferenced null and the TypeError killed the process.
  • The stdin line handler wraps JSON.parse() in try/catch and answers {success: false} for a line it cannot parse, instead of throwing. It also splits on the first | only, so a payload containing | is no longer truncated (line.split('|', 2) drops the rest).
  • node/tests/nimeServer.test.js (node:test, no new dependency) covers both fixes plus a baseline; npm test in node/ runs it. Against the previous nimeServer.js 4 of the 5 cases fail.

Keeping the backend alive matters because one bad request otherwise disables every Node.js input method until the launcher restarts the process, and the client that sent it waits for its RPC timeout instead of getting an answer.

Verified on Windows 11 with node v24 (and the bundled node 22) by driving server.js directly; the normal meow init → onActivate → filterKeyDown path is unchanged.

Generated with assistance from Claude Code (Claude Fable 5.1).

🤖 Generated with Claude Code

ilwu and others added 3 commits October 19, 2026 21:33
…ing (EasyIME#937)

* handleClientRequest() replies {success: false, seqNum} when the
  client has no service, i.e. its init named an unknown GUID or it
  never sent init.

nime creates a {service: null, state: null} entry for every new client
id and hands the next request straight to handleRequest(), whose
`service = textService` default does not apply to null. The resulting
TypeError is uncaught and terminates the Node.js backend, taking every
Node.js input method down. The TSF client always sends init first, but
a client that outlives a backend restart hits this path.

Generated with assistance from Claude Code (Claude Fable 5.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hing (EasyIME#936)

* Wrap JSON.parse() of the request payload in try/catch; a line that
  does not parse gets a {success: false} reply and an error on stderr.
* Split the line on the first '|' only, so a payload containing '|'
  is no longer truncated before parsing.

JSON.parse() throwing inside the stdin line handler was an uncaught
exception, which terminates the Node.js backend and disables every
Node.js input method until the launcher restarts it. Answering the bad
line keeps the backend alive and lets the client fail fast instead of
waiting for its RPC timeout.

Generated with assistance from Claude Code (Claude Fable 5.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* tests/nimeServer.test.js drives server.js over stdin/stdout like
  PIMELauncher and checks the replies for a normal init, a client that
  never sent init, an init with an unknown GUID, a malformed line and a
  payload containing '|'.
* `npm test` in node/ now runs it with the built-in node:test runner,
  so no extra dependency is needed.

The two preceding fixes change how the backend reacts to bad input;
these tests pin that behaviour down and fail against the previous
nimeServer.js (4 of 5 cases).

Generated with assistance from Claude Code (Claude Fable 5.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
asdf80730 added a commit to asdf80730/PIME that referenced this pull request Oct 20, 2026
EasyIME#961 is fixed via EasyIME#964; EasyIME#942 now has EasyIME#972; EasyIME#936/EasyIME#937 gained EasyIME#971, which
patches the vendored nime. ARM64 installs register an ARM64X forwarder,
so the architecture summary and installer card say so. Add the new EasyIME#966
issue and a short note on what moved upstream since 24e4dff.
@asdf80730

asdf80730 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Verified on node v24: node --test tests/nimeServer.test.js → 5/5 pass. I also re-ran the crash scenarios against 1458cea and against this branch:

  • unknown GUID init, then onActivate: 1458cea exits; here both replies are success:false and the process stays alive.
  • no init, then onActivate: 1458cea exits; here {"success":false,"seqNum":0}.
  • malformed JSON line: 1458cea exits; here {"success":false} plus a stderr line.
  • payload containing |: 1458cea truncates at the pipe (split('|', 2)) and dies in JSON.parse; the indexOf split fixes it.

One thing to settle before merge: node/node_modules/nime is committed while package.json still pins nime: 0.5.0, so a future npm install or dependency bump silently drops this patch. Should the patch be documented next to the dependency, or should nime be vendored under a local path so the pin cannot overwrite it?

If this lands, the dummy service in #946 becomes redundant, so #946 could be dropped.

@PCMan PCMan assigned PCMan and jessy1092 and unassigned PCMan Oct 20, 2026
@PCMan

PCMan commented Oct 20, 2026

Copy link
Copy Markdown
Member

@jessy1092 could you help review nime server changes, please? Thank you!

@ilwu

ilwu commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for re-running it. On the pin question, what I found:

  • build.bat never runs npm install in node/ itself — only in McBopomofoWeb/McFoximWeb/McTabimWeb and node/input_methods/emojime. The installer packages node/ as it is in the tree, so a build does not overwrite the vendored copy. Someone running npm install in node/ by hand would, but with the hidden .package-lock.json matching 0.5.0 npm leaves the existing directory alone and does not re-extract it, so even that is unlikely to drop the patch.
  • The vendored copy already diverges from the npm package: nime@0.5.0 on the registry (and EasyIME/NIME master, last commit 2017) has no seqNum echo on close, which d0e4235 added here. So this PR follows the existing practice rather than starting it.

That said, I agree it is fragile. Two options, happy to do either:

  1. Send the same changes (this PR's two fixes plus the close reply from d0e4235) to EasyIME/NIME, publish 0.5.1, and bump the pin here. Cleanest, but needs @jessy1092's time for the release.
  2. Move the copy to a local path, e.g. node/vendor/nime, with "nime": "file:vendor/nime" in package.json, so the registry can never replace it and the divergence is explicit in the tree.

I would not mix either into this PR, since it would turn a 2-file fix into a move; a follow-up PR seems better once you pick one.

(Written with assistance from Claude Code (Claude Fable 5.1))

@PCMan PCMan assigned ottokang and unassigned jessy1092 Oct 24, 2026
@PCMan

PCMan commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Very solid resilience improvement for #936 and #937. Splitting on line.indexOf('|') instead of line.split('|', 2) prevents payload truncation when JSON contains |, and answering uninitialized clients with {success: false} stops null dereference exceptions from taking down the entire Node process. The unit tests via node:test are also great. Assigned to @ottokang for review. LGTM!

(Review comment generated with assistance from Antigravity + Gemini 3.8 Flash)

@PCMan

PCMan commented Oct 24, 2026

Copy link
Copy Markdown
Member

Note on repository boundaries:
node/node_modules/nime is a dependency specified in node/package.json ("nime": "0.5.0"), with upstream repository at EasyIME/NIME. Modifying files directly inside node/node_modules/ in the PIME tree is risky because any npm install or clean build can overwrite them with the published npm package.

The fixes in nimeServer.js should ideally be upstreamed to EasyIME/NIME so a new version can be published to npm and consumed cleanly in PIME.

Assigning @ottokang and @jessy1092 to review and coordinate the upstream fix.

(Comment generated with assistance from Antigravity + Gemini 3.8 Flash)

@ottokang
ottokang merged commit aa40637 into EasyIME:master Oct 25, 2026
3 checks passed
asdf80730 added a commit to asdf80730/PIME that referenced this pull request Oct 25, 2026
Upstream landed EasyIME#997/EasyIME#998 (launcher close forwarding and client-ID
validation), EasyIME#988 (Python user-dir ACL fallback), EasyIME#971/EasyIME#951/EasyIME#937/EasyIME#936
(node guards and tests), EasyIME#996 (AGENTS.md AI declaration and header
rules), and dropped the vendored inetc plugin in EasyIME#966.

Conflict resolutions:
- node/server.js: take upstream, which supersedes our EasyIME#937 implementation
  with an uncaughtException handler and the same fallback service.
- node/loadServices.js: keep upstream's guards and re-thread the fork's
  `baseDir` parameter so test/loadServices.test.js still drives a fixture.
- node/package.json: run upstream's nimeServer suite plus the two local
  suites, matching the CI steps.
- PIMELauncher/src/protocol.rs: keep upstream's parse_backend_input and
  the rustfmt layout from local master.

Generated with assistance from omp (DeepSeek V4.1 Flash).
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.

5 participants