Extended daemon command with JSON-RPC interface via UNIX- and TCP socket #799
Replies: 5 comments 9 replies
-
that sounds awesome! may I bring your attention to my simple question at #788? |
Beta Was this translation helpful? Give feedback.
-
Awesome indeed – I think JSON RPC over sockets will be easier to set up and operate than the DBus service. A quick dumb question, as I have no experience with JSON RPC and low level socket operations – are client and server messages always terminated with a newline? Does the following sequence of events look OK?
I have an experimental Python snippet for sending messages, which seems to work but I'm not sure about the newline handling, and timeout handling yet: import json
import socket
payload = {
"jsonrpc": "2.0",
"method": "send",
"params": {"recipient": ["+xxx"], "message": "Hey"},
"id": 4,
}
payload_str = json.dumps(payload) + "\n"
payload_bytes = payload_str.encode()
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect("/run/user/1000/signal-cli/socket")
s.sendall(payload_bytes)
buf = ""
while not buf.endswith("\n"):
buf += s.recv(1024).decode()
print("Received: ", buf) |
Beta Was this translation helpful? Give feedback.
-
Based on the responses from @AsamK, I've revised my Python proof-of-concept code: import json
import socket
import time
import uuid
class SendException(Exception):
pass
def send(recipient, message):
payload = {
"jsonrpc": "2.0",
"method": "send",
"params": {"recipient": [recipient], "message": message},
"id": str(uuid.uuid4()),
}
payload_bytes = (json.dumps(payload) + "\n").encode()
for reply_bytes in _send(payload_bytes):
try:
reply = json.loads(reply_bytes.decode())
except ValueError:
continue
if reply["id"] == payload["id"]:
return reply
def _send(payload_bytes):
start = time.time()
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.settimeout(10)
try:
s.connect("/tmp/socket")
s.sendall(payload_bytes)
s.shutdown(socket.SHUT_WR) # we are done sending
buffer = []
while True:
ch = s.recv(1)
buffer.append(ch)
if ch == b"\n":
yield b"".join(buffer)
buffer = []
if time.time() - start > 10:
raise SendException("out of time budget")
except OSError:
raise SendException("oserror")
print(send("+123", "Hey")) The
The
Reading one byte at the time is obviously not efficient, but it keeps the logic somewhat simple. |
Beta Was this translation helpful? Give feedback.
-
I have another question, about the socket file permissions. On my development machine, I was running everything as my local user, and so there were no permission issues. In production environment, I want to use separate system users for signal-cli and the client application. I start signal-cli using systemd, the ExecStart line looks like so:
This works, signal-cli starts up, creates the socket in /tmp/ and listens. The issue is, when the client application tries to connect to the socket, it gets "Permission denied" error, because the socket is owned by signal-cli's user. I'm not sure what's the best way to fix this. I looked into socket activation – systemd supports it, signal-cli also seems to support it (DaemonCommand.java has a Another idea, which would require code changes, would be additional syntax for the --socket parameter (or additional new parameters) to specify socket ownership and permissions. Here's a similar ticket for a different project: coder/code-server#1466 |
Beta Was this translation helpful? Give feedback.
-
Jumping on the JSON RPC wagon too, was fiddling with DBus before. JSON RPC is much easier to setup and to use, especially on containerized environments, where running DBus works, but feels ao much antipattern to containers ;) Main reason i'm looking at JSON RPC interface is the availability of user mentions. By looking at the code base (and keep in mind that i'm a java novice, like wrote the last java code ages ago while studying ;)), the JSON RPC interface looks easier to maintain as it's closer to the command line interface. Just correct me, if i'm wrong. How likely is is that more future features will not end up in DBus interface? Just asking for a friend ;) |
Beta Was this translation helpful? Give feedback.
-
I've extended the
daemon
command to not only provide a DBus interface, but also a JSON-RPC interface via a UNIX-socket or TCP.I'd be happy for feedback, the API might still change before the next release.
Like for dbus, there's a single-account and multi-account mode. In single-account mode, connections to the socket behave similar to the current
jsonRpc
command. In multi-account mode all local accounts are available and requests need to specify which account to use with a separate"account"
param.The socket uses Java 16 native channels and also works with graalvm native image. Socket activation is also supported via Java's
System.inheritedChannel()
method. For systemd socket activation the service unit file requires an additional config (StandardInput=socket
), I've added an example file as well:data/signal-cli-socket.service
.For testing sockets the
socat
tool can be used.Single-account example
signal-cli -u +XXX daemon --socket
Multi-account example
signal-cli daemon --socket
Options
The
daemon
command supports multiple interfaces at the same time:signal-cli daemon --socket --dbus --dbus-system --tcp
The listening socket address for UNIX- and TCP sockets can be configured:
signal-cli daemon --socket /tmp/some-socket --tcp 0.0.0.0:12345
Receiving messages
By default receiving messages in daemon mode works as before, i.e. on start signal-cli will immediately begin receiving messages from the server. When clients connect to the socket they will only receive messages that were received after they connected.
For use-cases where the client needs to see all messages, the daemon command has a new parameter
--receive-mode
with the following options:on-start
(the default)on-connection
: receiving messages starts when the first client connects to the socket, so that client will see all messagesmanual
: messages are never received automatically, the client needs to subscribe to start receiving messages with the newsubscribeReceive
method. The Signal protocol expects messages to be received regularly so in this mode the client is responsible for this.Beta Was this translation helpful? Give feedback.
All reactions