cruss-agent 1.0.6 → 1.0.7

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/server.js +29 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cruss-agent",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Local API relay for Cruss — test localhost APIs from cruss-ten.vercel.app",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -6,14 +6,24 @@ const https = require('https')
6
6
  const url = require('url')
7
7
 
8
8
  const PORT = 9119
9
- const VERSION = '1.0.6'
10
-
9
+ const VERSION = '1.0.7'
10
+
11
+ // ── CORS ─────────────────────────────────────────────────────────────────
12
+ // Access-Control-Allow-Private-Network is required by Chrome 104+ (PNA policy).
13
+ // When an HTTPS page (e.g. cruss-ten.vercel.app) makes a non-simple request
14
+ // (POST with Content-Type: application/json) to localhost, Chrome first sends
15
+ // an OPTIONS preflight with "Access-Control-Request-Private-Network: true".
16
+ // The agent MUST respond with "Access-Control-Allow-Private-Network: true"
17
+ // or Chrome silently blocks the actual request. GET /health works without it
18
+ // because simple GET requests bypass the PNA preflight — which is exactly
19
+ // why health checks passed but POST /proxy was never received.
11
20
  function corsHeaders() {
12
21
  return {
13
- 'Access-Control-Allow-Origin': '*',
14
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
15
- 'Access-Control-Allow-Headers': '*',
16
- 'Access-Control-Max-Age': '86400',
22
+ 'Access-Control-Allow-Origin': '*',
23
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
24
+ 'Access-Control-Allow-Headers': '*',
25
+ 'Access-Control-Allow-Private-Network': 'true',
26
+ 'Access-Control-Max-Age': '86400',
17
27
  }
18
28
  }
19
29
 
@@ -50,9 +60,7 @@ function proxyOnce(hostname, port, path, method, outHeaders, body, isHttps) {
50
60
  headers: outHeaders,
51
61
  timeout: 30000,
52
62
  }
53
-
54
63
  console.log(` → trying ${hostname}:${resolvedPort}${path || '/'}`)
55
-
56
64
  const t0 = Date.now()
57
65
  const req = lib.request(options, res => {
58
66
  const chunks = []
@@ -64,7 +72,7 @@ function proxyOnce(hostname, port, path, method, outHeaders, body, isHttps) {
64
72
  const respHeaders = {}
65
73
  for (const [k, v] of Object.entries(res.headers || {})) {
66
74
  if (!['access-control-allow-origin','access-control-allow-headers',
67
- 'access-control-allow-methods'].includes(k.toLowerCase())) {
75
+ 'access-control-allow-methods','access-control-allow-private-network'].includes(k.toLowerCase())) {
68
76
  respHeaders[k] = Array.isArray(v) ? v.join(', ') : v
69
77
  }
70
78
  }
@@ -91,33 +99,25 @@ function proxyOnce(hostname, port, path, method, outHeaders, body, isHttps) {
91
99
  async function proxyRequest(targetUrl, method, reqHeaders, body) {
92
100
  const parsed = url.parse(targetUrl)
93
101
  const isHttps = parsed.protocol === 'https:'
94
-
95
102
  const outHeaders = {}
96
103
  for (const [k, v] of Object.entries(reqHeaders || {})) {
97
104
  if (!HOP_BY_HOP.has(k.toLowerCase())) outHeaders[k] = v
98
105
  }
99
-
100
106
  const candidates = loopbackCandidates(parsed.hostname)
101
107
  console.log(`\n [${new Date().toISOString()}] ${method} ${targetUrl}`)
102
108
  console.log(` candidates: ${candidates.join(', ')}`)
103
-
104
109
  let lastErr
105
110
  for (const host of candidates) {
106
111
  try {
107
112
  return await proxyOnce(host, parsed.port, parsed.path, method, outHeaders, body, isHttps)
108
113
  } catch (err) {
109
114
  lastErr = err
110
- if (err.code !== 'ECONNREFUSED' && err.code !== 'EADDRNOTAVAIL') {
111
- console.log(` ✗ fatal error: ${err.message}`)
112
- throw err
113
- }
115
+ if (err.code !== 'ECONNREFUSED' && err.code !== 'EADDRNOTAVAIL') throw err
114
116
  }
115
117
  }
116
-
117
118
  const port = parsed.port || (isHttps ? 443 : 80)
118
119
  throw new Error(
119
- `Connection refused on all addresses (${candidates.join(', ')}) port ${port}. ` +
120
- `Is your server running on port ${port}?`
120
+ `Connection refused on all addresses (${candidates.join(', ')}) port ${port}. Is your server running on port ${port}?`
121
121
  )
122
122
  }
123
123
 
@@ -125,8 +125,14 @@ const server = http.createServer(async (req, res) => {
125
125
  const cors = corsHeaders()
126
126
  const reqPath = url.parse(req.url).pathname
127
127
 
128
+ // Log ALL incoming requests so we can see preflights
129
+ console.log(` [${req.method}] ${reqPath} — origin: ${req.headers['origin'] || '(none)'} pna: ${req.headers['access-control-request-private-network'] || 'no'}`)
130
+
128
131
  if (req.method === 'OPTIONS') {
129
- res.writeHead(204, cors); res.end(); return
132
+ console.log(` ← preflight OK`)
133
+ res.writeHead(204, cors)
134
+ res.end()
135
+ return
130
136
  }
131
137
 
132
138
  function send(status, body) {
@@ -135,7 +141,6 @@ const server = http.createServer(async (req, res) => {
135
141
  }
136
142
 
137
143
  if (req.method === 'GET' && reqPath === '/health') {
138
- console.log(` [health check] OK`)
139
144
  send(200, { ok: true, version: VERSION, agent: 'cruss-agent', port: PORT })
140
145
  return
141
146
  }
@@ -145,14 +150,12 @@ const server = http.createServer(async (req, res) => {
145
150
  try {
146
151
  payload = JSON.parse(await readBody(req))
147
152
  } catch {
148
- send(400, { error: 'Request body must be valid JSON' }); return
153
+ send(400, { error: 'Request body must be valid JSON' })
154
+ return
149
155
  }
150
-
151
156
  const { method, url: targetUrl, headers, body } = payload || {}
152
-
153
157
  if (!targetUrl || typeof targetUrl !== 'string') { send(400, { error: 'url is required' }); return }
154
158
  if (!method || typeof method !== 'string') { send(400, { error: 'method is required' }); return }
155
-
156
159
  try {
157
160
  const result = await proxyRequest(targetUrl, method, headers || {}, body)
158
161
  send(200, { data: result })
@@ -180,7 +183,7 @@ server.listen(PORT, '0.0.0.0', () => {
180
183
  console.log(' ║ Ctrl+C to stop ║')
181
184
  console.log(' ╚═══════════════════════════════════════╝')
182
185
  console.log('')
183
- console.log(' Waiting for requests... (all activity logged below)')
186
+ console.log(' All requests logged below.')
184
187
  console.log('')
185
188
  })
186
189