fix(opencode-config): tolerate self-signed HTTPS for plugin bridge
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
import http from "http"
|
||||||
|
import https from "https"
|
||||||
|
import { Readable } from "stream"
|
||||||
|
|
||||||
export type PluginEvent = {
|
export type PluginEvent = {
|
||||||
type: string
|
type: string
|
||||||
properties?: Record<string, unknown>
|
properties?: Record<string, unknown>
|
||||||
@@ -16,7 +20,8 @@ export function getCodeNomadConfig(): CodeNomadConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createCodeNomadRequester(config: CodeNomadConfig) {
|
export function createCodeNomadRequester(config: CodeNomadConfig) {
|
||||||
const baseUrl = config.baseUrl.replace(/\/+$/, "")
|
const rawBaseUrl = (config.baseUrl ?? "").trim()
|
||||||
|
const baseUrl = rawBaseUrl.replace(/\/+$/, "")
|
||||||
const pluginBase = `${baseUrl}/workspaces/${encodeURIComponent(config.instanceId)}/plugin`
|
const pluginBase = `${baseUrl}/workspaces/${encodeURIComponent(config.instanceId)}/plugin`
|
||||||
const authorization = buildInstanceAuthorizationHeader()
|
const authorization = buildInstanceAuthorizationHeader()
|
||||||
|
|
||||||
@@ -42,10 +47,10 @@ export function createCodeNomadRequester(config: CodeNomadConfig) {
|
|||||||
const hasBody = init?.body !== undefined
|
const hasBody = init?.body !== undefined
|
||||||
const headers = buildHeaders(init?.headers, hasBody)
|
const headers = buildHeaders(init?.headers, hasBody)
|
||||||
|
|
||||||
return fetch(url, {
|
// The CodeNomad plugin only talks to the local CodeNomad server.
|
||||||
...init,
|
// Use a single request implementation that tolerates custom/self-signed certs
|
||||||
headers,
|
// without disabling TLS verification for the whole Node process.
|
||||||
})
|
return nodeFetch(url, { ...init, headers }, { rejectUnauthorized: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestJson = async <T>(path: string, init?: RequestInit): Promise<T> => {
|
const requestJson = async <T>(path: string, init?: RequestInit): Promise<T> => {
|
||||||
@@ -87,6 +92,91 @@ export function createCodeNomadRequester(config: CodeNomadConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function nodeFetch(
|
||||||
|
url: string,
|
||||||
|
init: RequestInit & { headers?: Record<string, string> },
|
||||||
|
tls: { rejectUnauthorized: boolean },
|
||||||
|
): Promise<Response> {
|
||||||
|
const parsed = new URL(url)
|
||||||
|
const isHttps = parsed.protocol === "https:"
|
||||||
|
const requestFn = isHttps ? https.request : http.request
|
||||||
|
|
||||||
|
const method = (init.method ?? "GET").toUpperCase()
|
||||||
|
const headers = init.headers ?? {}
|
||||||
|
const body = init.body
|
||||||
|
|
||||||
|
return await new Promise<Response>((resolve, reject) => {
|
||||||
|
const req = requestFn(
|
||||||
|
{
|
||||||
|
protocol: parsed.protocol,
|
||||||
|
hostname: parsed.hostname,
|
||||||
|
port: parsed.port ? Number(parsed.port) : undefined,
|
||||||
|
path: `${parsed.pathname}${parsed.search}`,
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
...(isHttps ? { rejectUnauthorized: tls.rejectUnauthorized } : {}),
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
const responseHeaders = new Headers()
|
||||||
|
for (const [key, value] of Object.entries(res.headers)) {
|
||||||
|
if (value === undefined) continue
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
responseHeaders.set(key, value.join(", "))
|
||||||
|
} else {
|
||||||
|
responseHeaders.set(key, String(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Node stream -> Web ReadableStream for Response.
|
||||||
|
const webBody = Readable.toWeb(res) as unknown as ReadableStream<Uint8Array>
|
||||||
|
resolve(new Response(webBody, { status: res.statusCode ?? 0, headers: responseHeaders }))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const signal = init.signal
|
||||||
|
const abort = () => {
|
||||||
|
const err = new Error("Request aborted")
|
||||||
|
;(err as any).name = "AbortError"
|
||||||
|
req.destroy(err)
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signal.addEventListener("abort", abort, { once: true })
|
||||||
|
req.once("close", () => signal.removeEventListener("abort", abort))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.once("error", reject)
|
||||||
|
|
||||||
|
if (body === undefined || body === null) {
|
||||||
|
req.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof body === "string") {
|
||||||
|
req.end(body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body instanceof Uint8Array) {
|
||||||
|
req.end(Buffer.from(body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body instanceof ArrayBuffer) {
|
||||||
|
req.end(Buffer.from(new Uint8Array(body)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for less common BodyInit types.
|
||||||
|
req.end(String(body))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function requireEnv(key: string): string {
|
function requireEnv(key: string): string {
|
||||||
const value = process.env[key]
|
const value = process.env[key]
|
||||||
if (!value || !value.trim()) {
|
if (!value || !value.trim()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user