claude-remote-approver 0.3.4 → 0.3.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ntfy.mjs +26 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-remote-approver",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Approve or deny Claude Code permission prompts remotely from your phone via ntfy.sh",
5
5
  "type": "module",
6
6
  "bin": {
package/src/ntfy.mjs CHANGED
@@ -24,71 +24,44 @@ export async function sendNotification({ server, topic, title, message, actions,
24
24
  }
25
25
 
26
26
  /**
27
- * Subscribe to the response topic via SSE and wait for a matching requestId.
27
+ * Poll the response topic and wait for a matching requestId.
28
28
  *
29
- * @param {{ server: string, topic: string, requestId: string, timeout: number }} params
29
+ * @param {{ server: string, topic: string, requestId: string, timeout: number, pollInterval?: number }} params
30
30
  * @returns {Promise<{ approved: boolean }>}
31
31
  */
32
- export async function waitForResponse({ server, topic, requestId, timeout }) {
32
+ export async function waitForResponse({ server, topic, requestId, timeout, pollInterval = 2000 }) {
33
33
  const baseUrl = server.replace(/\/+$/, '');
34
- const url = `${baseUrl}/${topic}-response/json`;
35
-
36
- const controller = new AbortController();
37
-
38
- /** @type {ReturnType<typeof setTimeout> | undefined} */
39
- let timer;
40
-
41
- try {
42
- const response = await fetch(url, { signal: controller.signal });
43
- const reader = response.body.getReader();
44
- const decoder = new TextDecoder();
45
- let buffer = '';
46
-
47
- // Listen to abort so we can cancel the reader even when the mock stream
48
- // never closes (the real fetch would propagate the signal, but mocks may not).
49
- const onAbort = () => reader.cancel();
50
- controller.signal.addEventListener('abort', onAbort);
51
-
52
- // Start the timeout AFTER fetch resolves so we measure waiting time only.
53
- timer = setTimeout(() => controller.abort(), timeout);
34
+ const sinceTimestamp = Math.floor(Date.now() / 1000);
35
+ const pollUrl = `${baseUrl}/${topic}-response/json?poll=1&since=${sinceTimestamp}`;
36
+ const startTime = Date.now();
54
37
 
38
+ while (Date.now() - startTime < timeout) {
55
39
  try {
56
- while (true) {
57
- const { done, value } = await reader.read();
58
- if (done) break;
59
-
60
- buffer += decoder.decode(value, { stream: true });
61
- const lines = buffer.split('\n');
62
- buffer = lines.pop();
63
-
64
- for (const line of lines) {
65
- if (!line.trim()) continue;
66
- try {
67
- const event = JSON.parse(line);
68
- const parsed = JSON.parse(event.message);
69
- if (parsed.requestId === requestId) {
70
- clearTimeout(timer);
71
- controller.signal.removeEventListener('abort', onAbort);
72
- return { approved: parsed.approved };
73
- }
74
- } catch {
75
- // skip non-JSON lines
40
+ const response = await fetch(pollUrl);
41
+ if (!response.ok) continue;
42
+ const text = await response.text();
43
+ const lines = text.trim().split('\n');
44
+
45
+ for (const line of lines) {
46
+ if (!line.trim()) continue;
47
+ try {
48
+ const event = JSON.parse(line);
49
+ const parsed = JSON.parse(event.message);
50
+ if (parsed.requestId === requestId) {
51
+ return { approved: parsed.approved === true };
76
52
  }
53
+ } catch {
54
+ // skip non-JSON lines
77
55
  }
78
56
  }
79
- } finally {
80
- controller.signal.removeEventListener('abort', onAbort);
57
+ } catch (err) {
58
+ console.error("[claude-remote-approver] poll error:", err);
81
59
  }
82
60
 
83
- clearTimeout(timer);
84
- return { approved: false };
85
- } catch (err) {
86
- if (timer !== undefined) clearTimeout(timer);
87
- if (err?.name !== "AbortError") {
88
- console.error("[claude-remote-approver] waitForResponse error:", err);
89
- }
90
- return { approved: false };
61
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
91
62
  }
63
+
64
+ return { approved: false };
92
65
  }
93
66
 
94
67
  /**