55 lines
2.0 KiB
JavaScript
55 lines
2.0 KiB
JavaScript
// PowerSync 同域反代 (0.2.1): 把 /sync、/socket 转发到内网同步服务,
|
|
// 对外只暴露应用本身一个端口 (无需单独的 powersync 端口)。
|
|
// - 未配置 POWERSYNC_UPSTREAM 时直接跳过 (本地开发/无同步服务时行为不变)
|
|
// - 必须挂在 express.json()/cors 之前: 同步走长连接流式响应, 不能被 body 解析打断
|
|
import http from 'node:http'
|
|
import https from 'node:https'
|
|
|
|
const PREFIXES = ['/sync', '/socket']
|
|
|
|
export function powersyncProxy() {
|
|
const upstream = process.env.POWERSYNC_UPSTREAM
|
|
if (!upstream) return (_req, _res, next) => next()
|
|
|
|
let target
|
|
try {
|
|
target = new URL(upstream)
|
|
} catch {
|
|
console.error('[powersync-proxy] POWERSYNC_UPSTREAM 非法:', upstream)
|
|
return (_req, _res, next) => next()
|
|
}
|
|
const transport = target.protocol === 'https:' ? https : http
|
|
const basePort = Number(target.port) || (target.protocol === 'https:' ? 443 : 80)
|
|
console.log('[powersync-proxy] 同步请求转发到', target.origin)
|
|
|
|
return (req, res, next) => {
|
|
if (!PREFIXES.some((p) => req.url === p || req.url.startsWith(p + '/') || req.url.startsWith(p + '?'))) {
|
|
return next()
|
|
}
|
|
const headers = { ...req.headers, host: target.host }
|
|
const upstreamReq = transport.request(
|
|
{
|
|
protocol: target.protocol,
|
|
hostname: target.hostname,
|
|
port: basePort,
|
|
method: req.method,
|
|
path: req.url,
|
|
headers,
|
|
},
|
|
(upstreamRes) => {
|
|
res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers)
|
|
upstreamRes.pipe(res)
|
|
},
|
|
)
|
|
upstreamReq.on('error', (e) => {
|
|
console.error('[powersync-proxy] 转发失败:', e.message)
|
|
if (!res.headersSent) res.writeHead(502, { 'content-type': 'text/plain' })
|
|
res.end('powersync upstream error')
|
|
})
|
|
// 长连接: 客户端断开时同步断开上游
|
|
req.on('aborted', () => upstreamReq.destroy())
|
|
res.on('close', () => upstreamReq.destroy())
|
|
req.pipe(upstreamReq)
|
|
}
|
|
}
|