feat: add APN relay MVP and experimental push bridge

This commit is contained in:
Ryan Vogel
2026-03-28 13:28:24 -04:00
parent 8ac2fbbd12
commit f276a8db42
17 changed files with 1196 additions and 1 deletions
+33 -1
View File
@@ -5,10 +5,20 @@ import { Flag } from "../../flag/flag"
import { Workspace } from "../../control-plane/workspace"
import { Project } from "../../project/project"
import { Installation } from "../../installation"
import { PushRelay } from "../../server/push-relay"
export const ServeCommand = cmd({
command: "serve",
builder: (yargs) => withNetworkOptions(yargs),
builder: (yargs) =>
withNetworkOptions(yargs)
.option("relay-url", {
type: "string",
describe: "experimental APN relay URL",
})
.option("relay-secret", {
type: "string",
describe: "experimental APN relay secret",
}),
describe: "starts a headless opencode server",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
@@ -18,6 +28,28 @@ export const ServeCommand = cmd({
const server = Server.listen(opts)
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
const relayURL = (
args["relay-url"] ??
process.env.OPENCODE_EXPERIMENTAL_PUSH_RELAY_URL ??
"https://relay.opencode.ai"
).trim()
const relaySecret = (args["relay-secret"] ?? process.env.OPENCODE_EXPERIMENTAL_PUSH_RELAY_SECRET ?? "").trim()
if (relayURL && relaySecret) {
const host = server.hostname ?? opts.hostname
const port = server.port || opts.port || 4096
const pair = PushRelay.start({
relayURL,
relaySecret,
hostname: host,
port,
})
if (pair) {
console.log("experimental push relay enabled")
console.log("qr payload")
console.log(JSON.stringify(pair, null, 2))
}
}
await new Promise(() => {})
await server.stop()
},
+252
View File
@@ -0,0 +1,252 @@
import os from "node:os"
import { Bus } from "@/bus"
import { Log } from "@/util/log"
type Type = "complete" | "permission" | "error"
type Pair = {
v: 1
relayURL: string
relaySecret: string
hosts: string[]
}
type Input = {
relayURL: string
relaySecret: string
hostname: string
port: number
}
type State = {
relayURL: string
relaySecret: string
pair: Pair
stop: () => void
seen: Map<string, number>
gc: number
}
type Event = {
type: string
properties: unknown
}
const log = Log.create({ service: "push-relay" })
let state: State | undefined
function obj(input: unknown): input is Record<string, unknown> {
return typeof input === "object" && input !== null
}
function str(input: unknown) {
return typeof input === "string" && input.length > 0 ? input : undefined
}
function norm(input: string) {
return input.replace(/\/+$/, "")
}
function list(hostname: string, port: number) {
const urls = new Set<string>()
const add = (host: string) => {
if (!host) return
if (host === "0.0.0.0") return
if (host === "::") return
urls.add(`http://${host}:${port}`)
}
add(hostname)
add("127.0.0.1")
const nets = Object.values(os.networkInterfaces())
.flatMap((item) => item ?? [])
.filter((item) => item.family === "IPv4" && !item.internal)
.map((item) => item.address)
nets.forEach(add)
return [...urls]
}
function map(event: Event): { type: Type; sessionID: string } | undefined {
if (!obj(event.properties)) return
if (event.type === "permission.asked") {
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
return { type: "permission", sessionID }
}
if (event.type === "session.error") {
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
return { type: "error", sessionID }
}
if (event.type === "session.idle") {
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
return { type: "complete", sessionID }
}
if (event.type !== "session.status") return
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
if (!obj(event.properties.status)) return
if (event.properties.status.type !== "idle") return
return { type: "complete", sessionID }
}
function dedupe(input: { type: Type; sessionID: string }) {
if (input.type !== "complete") return false
const next = state
if (!next) return false
const now = Date.now()
if (next.seen.size > 2048 || now - next.gc > 60_000) {
next.gc = now
for (const [key, time] of next.seen) {
if (now - time > 60_000) {
next.seen.delete(key)
}
}
const drop = next.seen.size - 2048
if (drop > 0) {
let i = 0
for (const key of next.seen.keys()) {
next.seen.delete(key)
i += 1
if (i >= drop) break
}
}
}
const key = `${input.type}:${input.sessionID}`
const prev = next.seen.get(key)
next.seen.set(key, now)
if (!prev) return false
return now - prev < 5_000
}
function post(input: { type: Type; sessionID: string }) {
const next = state
if (!next) return false
if (dedupe(input)) return true
void fetch(`${next.relayURL}/v1/event`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
secret: next.relaySecret,
eventType: input.type,
sessionID: input.sessionID,
}),
})
.then(async (res) => {
if (res.ok) return
const error = await res.text().catch(() => "")
log.warn("relay post failed", {
status: res.status,
type: input.type,
sessionID: input.sessionID,
error,
})
})
.catch((error) => {
log.warn("relay post failed", {
type: input.type,
sessionID: input.sessionID,
error: String(error),
})
})
return true
}
export namespace PushRelay {
export function start(input: Input) {
const relayURL = norm(input.relayURL.trim())
const relaySecret = input.relaySecret.trim()
if (!relayURL) return
if (!relaySecret) return
stop()
const pair: Pair = {
v: 1,
relayURL,
relaySecret,
hosts: list(input.hostname, input.port),
}
let unsub: (() => void) | undefined
try {
unsub = Bus.subscribeAll((event) => {
const next = map(event)
if (!next) return
post(next)
})
} catch (error) {
log.warn("failed to subscribe", {
error: String(error),
})
return
}
if (!unsub) return
state = {
relayURL,
relaySecret,
pair,
stop: unsub,
seen: new Map(),
gc: 0,
}
log.info("enabled", {
relayURL,
hosts: pair.hosts,
})
return pair
}
export function stop() {
const next = state
if (!next) return
state = undefined
next.stop()
}
export function status() {
const next = state
if (!next) {
return {
enabled: false,
relaySecretSet: false,
} as const
}
return {
enabled: true,
relaySecretSet: next.relaySecret.length > 0,
} as const
}
export function pair() {
return state?.pair
}
export function test(input: { type: Type; sessionID: string }) {
return post(input)
}
export function auth(input: string) {
const next = state
if (!next) return false
return next.relaySecret === input
}
}
@@ -12,6 +12,7 @@ import { zodToJsonSchema } from "zod-to-json-schema"
import { errors } from "../error"
import { lazy } from "../../util/lazy"
import { WorkspaceRoutes } from "./workspace"
import { PushRelay } from "../push-relay"
export const ExperimentalRoutes = lazy(() =>
new Hono()
@@ -267,5 +268,98 @@ export const ExperimentalRoutes = lazy(() =>
async (c) => {
return c.json(await MCP.resources())
},
)
.get(
"/push",
describeRoute({
summary: "Get push relay status",
description: "Get experimental push relay runtime status for this server.",
operationId: "experimental.push.status",
responses: {
200: {
description: "Push relay status",
content: {
"application/json": {
schema: resolver(
z.object({
enabled: z.boolean(),
relaySecretSet: z.boolean(),
}),
),
},
},
},
},
}),
async (c) => {
return c.json(PushRelay.status())
},
)
.post(
"/push/test",
describeRoute({
summary: "Send test push event",
description: "Send a test push event through the experimental APN relay integration.",
operationId: "experimental.push.test",
responses: {
200: {
description: "Test event accepted",
content: {
"application/json": {
schema: resolver(
z.object({
ok: z.boolean(),
enabled: z.boolean(),
}),
),
},
},
},
...errors(400),
},
}),
validator(
"json",
z.object({
secret: z.string(),
sessionID: z.string().optional(),
eventType: z.enum(["complete", "permission", "error"]).optional(),
}),
),
async (c) => {
const body = c.req.valid("json")
const status = PushRelay.status()
if (!status.enabled) {
return c.json(
{
data: { enabled: false },
errors: [{ message: "Push relay is not enabled" }],
success: false,
},
400,
)
}
if (!PushRelay.auth(body.secret)) {
return c.json(
{
data: { enabled: true },
errors: [{ message: "Invalid push relay secret" }],
success: false,
},
400,
)
}
const ok = PushRelay.test({
type: body.eventType ?? "permission",
sessionID: body.sessionID ?? `test-${Date.now()}`,
})
return c.json({
ok,
enabled: true,
})
},
),
)