patchcord 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "patchcord",
3
3
  "description": "Cross-machine agent messaging with push delivery. Messages from other agents arrive as native channel notifications.",
4
- "version": "0.5.3",
4
+ "version": "0.5.4",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -68,7 +68,7 @@ export function connect(urlStr, { headers = {} } = {}) {
68
68
  socket.on("close", () => {
69
69
  if (!closed) {
70
70
  closed = true;
71
- emitter.emit("close");
71
+ emitter.emit("close", { code: null, reason: "socket-ended" });
72
72
  }
73
73
  });
74
74
 
@@ -115,8 +115,16 @@ export function connect(urlStr, { headers = {} } = {}) {
115
115
  if (opcode === 0x1) {
116
116
  emitter.emit("message", payload.toString("utf8"));
117
117
  } else if (opcode === 0x8) {
118
- // close frame
119
- emitter.emit("close");
118
+ // close frame — parse code/reason for diagnostics
119
+ let code = null;
120
+ let reason = "";
121
+ if (payload.length >= 2) {
122
+ code = payload.readUInt16BE(0);
123
+ if (payload.length > 2) {
124
+ reason = payload.slice(2).toString("utf8");
125
+ }
126
+ }
127
+ emitter.emit("close", { code, reason });
120
128
  closed = true;
121
129
  try {
122
130
  socket.write(encodeFrame(0x8, closePayload(1000, ""), true));
@@ -246,14 +246,29 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
246
246
  } catch (_) {}
247
247
  }, HEARTBEAT_INTERVAL_MS);
248
248
 
249
- const refreshIn = Math.max(
250
- (ticket.jwt_expires_in - JWT_REFRESH_SAFETY_MARGIN_SEC) * 1000,
251
- 30_000
252
- );
253
- refreshTimer = setTimeout(async () => {
249
+ const scheduleRefresh = (ttlSec) => {
250
+ const refreshIn = Math.max((ttlSec - JWT_REFRESH_SAFETY_MARGIN_SEC) * 1000, 30_000);
251
+ refreshTimer = setTimeout(doRefresh, refreshIn);
252
+ };
253
+
254
+ const doRefresh = async () => {
255
+ if (settled) return;
254
256
  try {
255
257
  const fresh = await refreshTicket();
256
258
  currentJwt = fresh.jwt;
259
+ // Socket-level auth update (phoenix topic) — what Supabase
260
+ // actually uses for the connection's own JWT expiry check.
261
+ // Without this, the server closes the socket at the original
262
+ // JWT's exp regardless of per-channel updates.
263
+ ws.send(
264
+ JSON.stringify({
265
+ topic: "phoenix",
266
+ event: "access_token",
267
+ payload: { access_token: currentJwt },
268
+ ref: String(ref++),
269
+ })
270
+ );
271
+ // Channel-level updates — matches supabase-js's setAuth() pattern.
257
272
  for (const topic of fresh.topics) {
258
273
  ws.send(
259
274
  JSON.stringify({
@@ -265,11 +280,17 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
265
280
  );
266
281
  }
267
282
  process.stderr.write("subscribe: token refreshed\n");
283
+ scheduleRefresh(fresh.jwt_expires_in);
268
284
  } catch (e) {
269
- process.stderr.write(`subscribe: token refresh failed: ${e.message}\n`);
270
- done(e);
285
+ // Transient network/server error — do NOT close the live
286
+ // connection. The current JWT is still valid for ~2 more min
287
+ // (JWT_REFRESH_SAFETY_MARGIN_SEC). Retry sooner.
288
+ process.stderr.write(`subscribe: token refresh failed, retrying in 30s: ${e.message}\n`);
289
+ refreshTimer = setTimeout(doRefresh, 30_000);
271
290
  }
272
- }, refreshIn);
291
+ };
292
+
293
+ scheduleRefresh(ticket.jwt_expires_in);
273
294
  });
274
295
 
275
296
  ws.on("message", (raw) => {
@@ -297,8 +318,10 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
297
318
  done(err);
298
319
  });
299
320
 
300
- ws.on("close", () => {
301
- process.stderr.write("subscribe: ws closed\n");
321
+ ws.on("close", (info) => {
322
+ const codeStr = info?.code != null ? `code=${info.code}` : "code=none";
323
+ const reasonStr = info?.reason ? ` reason=${JSON.stringify(info.reason)}` : "";
324
+ process.stderr.write(`subscribe: ws closed (${codeStr}${reasonStr})\n`);
302
325
  done();
303
326
  });
304
327
  });