aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-04 19:38:27 -0700
committerAdam Soltys <[email protected]>2026-09-04 19:38:27 -0700
commit3e36c6f50d5f5d182ae139431270841723c1fc33 (patch)
treea16c6cf03253e3f71befe092a6b08247f75b668f /lib
parent08c9eca46469af21aeb209066401aad8808920d7 (diff)
downloadcoinos-server-3e36c6f50d5f5d182ae139431270841723c1fc33.tar.gz
coinos-server-3e36c6f50d5f5d182ae139431270841723c1fc33.zip
sockets: accept read-only tokens on login, deliver payment events only
POS terminals log into the websocket with the "<uid>-ro" token that the web flasher pre-fills. track() looked that id up as a user, failed, and closed the socket a second later, dropping the invoice subscription, so devices were listening about one second in six. Strip the suffix the way lib/auth.ts does for HTTP, flag the socket read-only, and forward only "payment" events to it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_014gVYGi29FjTeLdNTiutkZx
Diffstat (limited to 'lib')
-rw-r--r--lib/sockets.ts13
1 files changed, 12 insertions, 1 deletions
diff --git a/lib/sockets.ts b/lib/sockets.ts
index 58673986..610d38fd 100644
--- a/lib/sockets.ts
+++ b/lib/sockets.ts
@@ -35,6 +35,9 @@ export const emit = (uid, type, data) => {
if (!store.sockets[uid]) return;
for (const id in store.sockets[uid]) {
const ws = store.sockets[uid][id];
+ // Read-only sockets (POS terminals logged in with a "-ro" token) only
+ // get payment notifications, mirroring the HTTP read-only whitelist.
+ if (ws.readonly && type !== "payment") continue;
ws.send(JSON.stringify({ type, data }));
}
};
@@ -56,9 +59,16 @@ export const broadcast = (type, data) => {
const track = async (ws, token) => {
const { id } = ws;
- const { id: uid } = verifyToken(token) || {};
+ let { id: uid } = verifyToken(token) || {};
if (!uid) fail("Invalid JWT token");
+
+ // Read-only tokens (see users.ro) carry "<uid>-ro". The HTTP auth layer
+ // strips the suffix for whitelisted routes; do the same here so a POS
+ // device can log in and receive payment events, but nothing else.
+ const readonly = uid.endsWith("-ro");
+ if (readonly) uid = uid.slice(0, -3);
+
const user = await getUser(uid);
if (!user) fail(`User not found ${uid}`);
@@ -73,6 +83,7 @@ const track = async (ws, token) => {
store.sockets[uid][id] = ws;
users[id] = uid;
ws.user = user;
+ ws.readonly = readonly;
};
Bun.serve({