agent-relay-server 0.4.19 → 0.4.21

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -95,7 +95,7 @@
95
95
 
96
96
  function upsertById(list, item) {
97
97
  const idx = list.findIndex((existing) => existing.id === item.id);
98
- if (idx >= 0) list[idx] = item;
98
+ if (idx >= 0) list.splice(idx, 1, item);
99
99
  else list.push(item);
100
100
  }
101
101
 
@@ -126,11 +126,18 @@
126
126
  function createLifecycleMethods() {
127
127
  return {
128
128
  async init() {
129
+ this.startClock();
130
+ watchPersistedPrefs(this);
131
+
132
+ try {
133
+ this.stats = await this.api("GET", "/stats");
134
+ } catch {
135
+ if (this.authNeeded) return;
136
+ }
137
+
129
138
  await this.refresh();
130
139
  this.connectSSE();
131
- this.startClock();
132
140
  this.startAutoRefresh();
133
- watchPersistedPrefs(this);
134
141
  },
135
142
 
136
143
  save(key, value) {
@@ -192,8 +199,13 @@
192
199
  registerTaskEvents(this, es);
193
200
  }
194
201
 
202
+ function baseUrl() {
203
+ const href = window.location.href.split("?")[0].split("#")[0];
204
+ return href.endsWith("/") ? href : href + "/";
205
+ }
206
+
195
207
  function buildEventsUrl(authToken) {
196
- const eventUrl = new URL(window.location.origin + "/api/events");
208
+ const eventUrl = new URL("api/events", baseUrl());
197
209
  if (authToken) eventUrl.searchParams.set("token", authToken);
198
210
  return eventUrl.toString();
199
211
  }
@@ -258,7 +270,7 @@
258
270
  opts.body = JSON.stringify(body);
259
271
  }
260
272
 
261
- const response = await fetch(window.location.origin + "/api" + path, opts);
273
+ const response = await fetch(new URL("api" + path, baseUrl()), opts);
262
274
  if (!response.ok) {
263
275
  if (response.status === 401) this.authNeeded = true;
264
276
  const text = await response.text();
package/public/index.html CHANGED
@@ -102,7 +102,7 @@
102
102
  <div class="ar-sidebar-footer">
103
103
  <div class="d-flex align-items-center gap-2 mb-2">
104
104
  <span class="status-dot" :class="connected ? 'online' : 'offline'"></span>
105
- <span class="small" x-text="connected ? 'Live' : 'Reconnecting…'"></span>
105
+ <span class="small" x-text="authNeeded ? 'Auth required' : connected ? 'Live' : 'Reconnecting…'"></span>
106
106
  </div>
107
107
  <div class="d-flex align-items-center gap-2 mb-2">
108
108
  <label class="form-check form-switch mb-0">
@@ -855,7 +855,7 @@
855
855
  <script src="https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
856
856
  <script src="https://cdn.jsdelivr.net/npm/apexcharts@latest/dist/apexcharts.min.js"></script>
857
857
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>
858
- <script src="/dashboard.js"></script>
858
+ <script src="dashboard.js"></script>
859
859
 
860
860
  </body>
861
861
  </html>
package/src/security.ts CHANGED
@@ -19,10 +19,21 @@ export function isOriginAllowed(req: Request): boolean {
19
19
  if (!origin) return true;
20
20
  const origins = corsOrigins();
21
21
  if (origins.includes("*")) return true;
22
+ if (origins.includes(origin)) return true;
22
23
 
23
24
  const url = new URL(req.url);
24
25
  const sameOrigin = `${url.protocol}//${url.host}`;
25
- return origin === sameOrigin || origins.includes(origin);
26
+ if (origin === sameOrigin) return true;
27
+
28
+ // Behind a reverse proxy, req.url is the backend address (e.g. http://127.0.0.1:4850)
29
+ // while the browser sends the public origin. Check forwarded headers.
30
+ try {
31
+ const originHost = new URL(origin).host;
32
+ const fwdHost = req.headers.get("x-forwarded-host");
33
+ if (fwdHost && originHost === fwdHost) return true;
34
+ } catch {}
35
+
36
+ return false;
26
37
  }
27
38
 
28
39
  export function applyCors(req: Request, response: Response): Response {
@@ -117,7 +128,7 @@ export function unauthorized(req: Request): Response {
117
128
  }
118
129
 
119
130
  function authToken(): string {
120
- return process.env.AGENT_RELAY_TOKEN || AUTH_TOKEN;
131
+ return process.env.AGENT_RELAY_TOKEN ?? AUTH_TOKEN;
121
132
  }
122
133
 
123
134
  function extractToken(req: Request): string | null {