MCPcopy Create free account
hub / github.com/garrytan/gstack / proxyToDevice

Function proxyToDevice

ios-qa/daemon/src/proxy.ts:30–90  ·  view source on GitHub ↗
(opts: {
  inbound: IncomingMessage;
  body: Buffer;
  tunnel: DeviceTunnel;
  sessionId: string | null;
  agentIdentity?: string;
})

Source from the content-addressed store, hash-verified

28 * response or a ProxyError. Caller writes to the ServerResponse.
29 */
30export async function proxyToDevice(opts: {
31 inbound: IncomingMessage;
32 body: Buffer;
33 tunnel: DeviceTunnel;
34 sessionId: string | null;
35 agentIdentity?: string;
36}): Promise<{ status: number; headers: Record<string, string>; body: Buffer }> {
37 const { inbound, body, tunnel, sessionId, agentIdentity } = opts;
38 if (body.length > MAX_BODY) {
39 return makeError(413, 'body_too_large');
40 }
41
42 const headers: Record<string, string> = {
43 'authorization': `Bearer ${tunnel.bootTokenRotated}`,
44 'content-type': inbound.headers['content-type'] || 'application/json',
45 'content-length': String(body.length),
46 };
47 if (sessionId) headers['x-session-id'] = sessionId;
48 if (agentIdentity) headers['x-agent-identity'] = agentIdentity;
49
50 // Bracket IPv6 literals; pass IPv4 + hostnames bare. The CoreDevice tunnel
51 // is always IPv6 in production, but tests inject 127.0.0.1 to talk to a
52 // local stub. Detect by `:` count (IPv6 has multiple colons) or `:` absence
53 // (IPv4/hostname).
54 const isIPv6 = (tunnel.ipv6Addr.match(/:/g)?.length ?? 0) >= 2;
55 const hostPart = isIPv6 ? `[${tunnel.ipv6Addr}]` : tunnel.ipv6Addr;
56 const url = `http://${hostPart}:${tunnel.port}${inbound.url ?? '/'}`;
57 return new Promise((resolve, reject) => {
58 const req = httpRequest(url, {
59 method: inbound.method,
60 headers,
61 timeout: 30_000,
62 }, (res) => {
63 const chunks: Buffer[] = [];
64 res.on('data', (c) => chunks.push(c));
65 res.on('end', () => {
66 const respHeaders: Record<string, string> = {};
67 for (const [k, v] of Object.entries(res.headers)) {
68 if (typeof v === 'string') respHeaders[k] = v;
69 }
70 resolve({
71 status: res.statusCode ?? 502,
72 headers: respHeaders,
73 body: Buffer.concat(chunks),
74 });
75 });
76 });
77 req.on('error', (err) => {
78 const e = err as { code?: string };
79 if (e.code === 'ECONNREFUSED' || e.code === 'EHOSTUNREACH') {
80 resolve(makeError(503, 'device_disconnected'));
81 } else if (e.code === 'ETIMEDOUT') {
82 resolve(makeError(504, 'upstream_timeout'));
83 } else {
84 reject(err);
85 }
86 });
87 req.write(body);

Callers 2

handleLoopbackFunction · 0.90
handleTailnetFunction · 0.90

Calls 3

makeErrorFunction · 0.85
endMethod · 0.80
pushMethod · 0.45

Tested by

no test coverage detected