Stephan Richter ad0c231032 feat(jsonrpc2): add manual receive mode to prevent message loss
In the default 'on-start' receive mode, signal-cli's daemon
auto-pulls inbound messages and pushes them to its JSON-RPC clients
the moment they arrive from the Signal servers. If no websocket
subscriber is currently attached to /v1/receive/{number} — for
example during a brief subscriber redeploy — the receiver loop in
signal-cli-rest-api drops the message via a non-blocking channel
send (see the 'no receiver' debug log in ReceiveData). signal-cli
has already acknowledged the message to Signal, so it is never
redelivered.

Add support for signal-cli's --receive-mode=manual, opt-in via the
new JSON_RPC_RECEIVE_MODE=manual environment variable. In manual
mode signal-cli does NOT auto-receive; signal-cli-rest-api now
issues subscribeReceive when a websocket subscriber attaches and
unsubscribeReceive when the last subscriber detaches. While no
subscriber is attached, signal-cli does not pull messages from the
Signal servers, so they remain buffered server-side under Signal's
normal retention rules and are delivered to the next subscriber
that attaches.

Implementation:

* jsonrpc2-helper.go: thread JSON_RPC_RECEIVE_MODE through to the
  daemon launch command line.
* jsonrpc2.go: add per-account subscription tracking with
  refcounting; subscribeReceive/unsubscribeReceive helpers; an
  unwrap step in ReceiveData so manual-mode notifications
  ({subscription,result}) and auto-mode notifications (envelope
  directly) reach downstream consumers in the same shape.
* client.go, api.go: thread the account number through
  GetReceiveChannel so the JsonRpc2Client can attach the right
  subscription.

The change is fully backward compatible: when JSON_RPC_RECEIVE_MODE
is unset or set to 'on-start', signal-cli runs in auto mode and the
new subscribeReceive code path is bypassed (account is empty, no
RPC issued).

Closes #255 (the same root cause: no subscriber → 'no receiver'
drop).
2026-04-08 16:16:55 -04:00
..