2025-12-10 03:32:04 +08:00
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
2025-06-01 02:41:00 +08:00
import { Log } from "../util/log"
2025-11-08 09:59:02 +08:00
import { describeRoute , generateSpecs , validator , resolver , openAPIRouteHandler } from "hono-openapi"
2025-06-01 02:41:00 +08:00
import { Hono } from "hono"
2025-09-15 15:28:08 +08:00
import { cors } from "hono/cors"
2026-01-17 05:21:13 +08:00
import { streamSSE } from "hono/streaming"
2025-12-22 20:05:10 +08:00
import { proxy } from "hono/proxy"
2026-01-13 04:23:12 +08:00
import { basicAuth } from "hono/basic-auth"
2025-10-27 03:50:41 +08:00
import z from "zod"
2025-06-01 02:41:00 +08:00
import { Provider } from "../provider/provider"
2025-11-25 01:56:00 +08:00
import { NamedError } from "@opencode-ai/util/error"
2025-07-03 05:08:06 +08:00
import { LSP } from "../lsp"
2025-11-01 23:14:39 +08:00
import { Format } from "../format"
2026-01-17 05:21:13 +08:00
import { TuiRoutes } from "./routes/tui"
2025-09-02 05:15:49 +08:00
import { Instance } from "../project/instance"
2025-11-26 11:39:20 +08:00
import { Vcs } from "../project/vcs"
2025-08-08 04:32:12 +08:00
import { Agent } from "../agent/agent"
2026-01-17 05:21:13 +08:00
import { Skill } from "../skill/skill"
2025-08-15 04:24:46 +08:00
import { Auth } from "../auth"
2026-01-13 04:23:12 +08:00
import { Flag } from "../flag/flag"
2025-08-23 05:04:28 +08:00
import { Command } from "../command"
2025-09-02 05:15:49 +08:00
import { Global } from "../global"
2026-01-17 05:21:13 +08:00
import { ProjectRoutes } from "./routes/project"
import { SessionRoutes } from "./routes/session"
import { PtyRoutes } from "./routes/pty"
import { McpRoutes } from "./routes/mcp"
import { FileRoutes } from "./routes/file"
import { ConfigRoutes } from "./routes/config"
import { ExperimentalRoutes } from "./routes/experimental"
import { ProviderRoutes } from "./routes/provider"
2025-09-17 13:16:49 +08:00
import { lazy } from "../util/lazy"
2025-09-19 17:11:29 +08:00
import { InstanceBootstrap } from "../project/bootstrap"
2025-10-15 23:53:09 +08:00
import { Storage } from "../storage/storage"
import type { ContentfulStatusCode } from "hono/utils/http-status"
2026-01-17 05:21:13 +08:00
import { websocket } from "hono/bun"
2026-01-13 04:23:12 +08:00
import { HTTPException } from "hono/http-exception"
2025-12-05 10:32:08 +08:00
import { errors } from "./error"
2026-01-17 05:21:13 +08:00
import { QuestionRoutes } from "./routes/question"
import { PermissionRoutes } from "./routes/permission"
import { GlobalRoutes } from "./routes/global"
2025-12-27 04:24:44 +08:00
import { MDNS } from "./mdns"
2025-06-10 02:01:11 +08:00
2025-11-20 22:42:24 +08:00
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
globalThis . AI_SDK_LOG_WARNINGS = false
2025-05-19 02:13:04 +08:00
export namespace Server {
2025-06-01 02:41:00 +08:00
const log = Log . create ( { service : "server" } )
2025-05-19 02:13:04 +08:00
2025-12-30 11:05:08 +08:00
let _url : URL | undefined
2025-12-31 17:50:29 +08:00
let _corsWhitelist : string [ ] = [ ]
2025-12-30 11:05:08 +08:00
export function url ( ) : URL {
return _url ? ? new URL ( "http://localhost:4096" )
}
2025-09-02 05:15:49 +08:00
const app = new Hono ( )
2026-01-08 11:29:42 +08:00
export const App : ( ) = > Hono = lazy (
( ) = >
2026-01-10 07:47:37 +08:00
// TODO: Break server.ts into smaller route files to fix type inference
2026-01-08 11:29:42 +08:00
app
. onError ( ( err , c ) = > {
log . error ( "failed" , {
error : err ,
} )
if ( err instanceof NamedError ) {
let status : ContentfulStatusCode
if ( err instanceof Storage . NotFoundError ) status = 404
else if ( err instanceof Provider . ModelNotFoundError ) status = 400
else if ( err . name . startsWith ( "Worktree" ) ) status = 400
else status = 500
return c . json ( err . toObject ( ) , { status } )
}
2026-01-13 04:43:48 +08:00
if ( err instanceof HTTPException ) return err . getResponse ( )
2026-01-08 11:29:42 +08:00
const message = err instanceof Error && err . stack ? err.stack : err.toString ( )
return c . json ( new NamedError . Unknown ( { message } ) . toObject ( ) , {
status : 500 ,
} )
2025-07-08 03:53:43 +08:00
} )
2026-01-13 04:23:12 +08:00
. use ( ( c , next ) = > {
2026-01-13 04:59:17 +08:00
const password = Flag . OPENCODE_SERVER_PASSWORD
2026-01-13 04:23:12 +08:00
if ( ! password ) return next ( )
2026-01-13 04:59:17 +08:00
const username = Flag . OPENCODE_SERVER_USERNAME ? ? "opencode"
return basicAuth ( { username , password } ) ( c , next )
2026-01-13 04:23:12 +08:00
} )
2026-01-08 11:29:42 +08:00
. use ( async ( c , next ) = > {
const skipLogging = c . req . path === "/log"
if ( ! skipLogging ) {
log . info ( "request" , {
method : c.req.method ,
path : c.req.path ,
} )
}
const timer = log . time ( "request" , {
2025-09-17 13:16:49 +08:00
method : c.req.method ,
path : c.req.path ,
} )
2026-01-08 11:29:42 +08:00
await next ( )
if ( ! skipLogging ) {
timer . stop ( )
}
2025-11-09 09:17:46 +08:00
} )
2026-01-08 11:29:42 +08:00
. use (
cors ( {
origin ( input ) {
if ( ! input ) return
2025-12-31 02:04:18 +08:00
2026-01-08 11:29:42 +08:00
if ( input . startsWith ( "http://localhost:" ) ) return input
if ( input . startsWith ( "http://127.0.0.1:" ) ) return input
if ( input === "tauri://localhost" || input === "http://tauri.localhost" ) return input
2025-12-31 02:04:18 +08:00
2026-01-08 11:29:42 +08:00
// *.opencode.ai (https only, adjust if needed)
if ( /^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/ . test ( input ) ) {
return input
}
if ( _corsWhitelist . includes ( input ) ) {
return input
}
2025-12-31 17:50:29 +08:00
2026-01-08 11:29:42 +08:00
return
} ,
} ) ,
)
2026-01-17 05:21:13 +08:00
. route ( "/global" , GlobalRoutes ( ) )
2026-01-28 06:53:35 +08:00
. put (
"/auth/:providerID" ,
describeRoute ( {
summary : "Set auth credentials" ,
description : "Set authentication credentials" ,
operationId : "auth.set" ,
responses : {
200 : {
description : "Successfully set authentication credentials" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
providerID : z.string ( ) ,
} ) ,
) ,
validator ( "json" , Auth . Info ) ,
async ( c ) = > {
const providerID = c . req . valid ( "param" ) . providerID
const info = c . req . valid ( "json" )
await Auth . set ( providerID , info )
return c . json ( true )
} ,
)
. delete (
"/auth/:providerID" ,
describeRoute ( {
summary : "Remove auth credentials" ,
description : "Remove authentication credentials" ,
operationId : "auth.remove" ,
responses : {
200 : {
description : "Successfully removed authentication credentials" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
providerID : z.string ( ) ,
} ) ,
) ,
async ( c ) = > {
const providerID = c . req . valid ( "param" ) . providerID
await Auth . remove ( providerID )
return c . json ( true )
} ,
)
2026-01-08 11:29:42 +08:00
. use ( async ( c , next ) = > {
let directory = c . req . query ( "directory" ) || c . req . header ( "x-opencode-directory" ) || process . cwd ( )
try {
directory = decodeURIComponent ( directory )
} catch {
// fallback to original value
}
return Instance . provide ( {
directory ,
init : InstanceBootstrap ,
async fn() {
return next ( )
2025-12-11 11:22:00 +08:00
} ,
} )
2025-09-02 05:15:49 +08:00
} )
2026-01-08 11:29:42 +08:00
. get (
"/doc" ,
openAPIRouteHandler ( app , {
documentation : {
info : {
title : "opencode" ,
version : "0.0.3" ,
description : "opencode api" ,
} ,
openapi : "3.1.1" ,
} ,
} ) ,
)
. use ( validator ( "query" , z . object ( { directory : z.string ( ) . optional ( ) } ) ) )
2026-01-17 05:21:13 +08:00
. route ( "/project" , ProjectRoutes ( ) )
. route ( "/pty" , PtyRoutes ( ) )
. route ( "/config" , ConfigRoutes ( ) )
. route ( "/experimental" , ExperimentalRoutes ( ) )
. route ( "/session" , SessionRoutes ( ) )
. route ( "/permission" , PermissionRoutes ( ) )
. route ( "/question" , QuestionRoutes ( ) )
. route ( "/provider" , ProviderRoutes ( ) )
. route ( "/" , FileRoutes ( ) )
. route ( "/mcp" , McpRoutes ( ) )
. route ( "/tui" , TuiRoutes ( ) )
2026-01-08 11:29:42 +08:00
. post (
2026-01-17 05:21:13 +08:00
"/instance/dispose" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "Dispose instance" ,
description : "Clean up and dispose the current OpenCode instance, releasing all resources." ,
operationId : "instance.dispose" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "Instance disposed" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver ( z . boolean ( ) ) ,
2026-01-08 11:29:42 +08:00
} ,
} ,
} ,
2025-12-05 10:32:08 +08:00
} ,
2026-01-08 11:29:42 +08:00
} ) ,
async ( c ) = > {
2026-01-17 05:21:13 +08:00
await Instance . dispose ( )
return c . json ( true )
2026-01-08 11:29:42 +08:00
} ,
)
. get (
2026-01-17 05:21:13 +08:00
"/path" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "Get paths" ,
description :
"Retrieve the current working directory and related path information for the OpenCode instance." ,
operationId : "path.get" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "Path" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver (
z
. object ( {
home : z.string ( ) ,
state : z.string ( ) ,
config : z.string ( ) ,
worktree : z.string ( ) ,
directory : z.string ( ) ,
} )
. meta ( {
ref : "Path" ,
} ) ,
) ,
2026-01-08 11:29:42 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
2026-01-17 05:21:13 +08:00
return c . json ( {
home : Global.Path.home ,
state : Global.Path.state ,
config : Global.Path.config ,
worktree : Instance.worktree ,
directory : Instance.directory ,
} )
2026-01-08 11:29:42 +08:00
} ,
)
2026-01-17 05:21:13 +08:00
. get (
"/vcs" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "Get VCS info" ,
description :
"Retrieve version control system (VCS) information for the current project, such as git branch." ,
operationId : "vcs.get" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "VCS info" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver ( Vcs . Info ) ,
2026-01-08 11:29:42 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
2026-01-17 05:21:13 +08:00
const branch = await Vcs . branch ( )
return c . json ( {
branch ,
} )
2026-01-08 11:29:42 +08:00
} ,
)
2026-01-17 05:21:13 +08:00
. get (
"/command" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "List commands" ,
description : "Get a list of all available commands in the OpenCode system." ,
operationId : "command.list" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "List of commands" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver ( Command . Info . array ( ) ) ,
2026-01-08 11:29:42 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
2026-01-17 05:21:13 +08:00
const commands = await Command . list ( )
return c . json ( commands )
2026-01-08 11:29:42 +08:00
} ,
)
2026-01-17 05:21:13 +08:00
. post (
"/log" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "Write log" ,
description : "Write a log entry to the server logs with specified level and metadata." ,
operationId : "app.log" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "Log entry written successfully" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
2026-01-17 05:21:13 +08:00
. . . errors ( 400 ) ,
2026-01-08 11:29:42 +08:00
} ,
} ) ,
2026-01-17 05:21:13 +08:00
validator (
"json" ,
z . object ( {
service : z.string ( ) . meta ( { description : "Service name for the log entry" } ) ,
level : z.enum ( [ "debug" , "info" , "error" , "warn" ] ) . meta ( { description : "Log level" } ) ,
message : z.string ( ) . meta ( { description : "Log message" } ) ,
extra : z
. record ( z . string ( ) , z . any ( ) )
. optional ( )
. meta ( { description : "Additional metadata for the log entry" } ) ,
} ) ,
) ,
async ( c ) = > {
const { service , level , message , extra } = c . req . valid ( "json" )
const logger = Log . create ( { service } )
switch ( level ) {
case "debug" :
logger . debug ( message , extra )
break
case "info" :
logger . info ( message , extra )
break
case "error" :
logger . error ( message , extra )
break
case "warn" :
logger . warn ( message , extra )
break
2026-01-08 11:29:42 +08:00
}
2025-12-05 10:32:08 +08:00
2026-01-17 05:21:13 +08:00
return c . json ( true )
2025-06-03 23:59:03 +08:00
} ,
2026-01-08 11:29:42 +08:00
)
2026-01-17 05:21:13 +08:00
. get (
"/agent" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "List agents" ,
description : "Get a list of all available AI agents in the OpenCode system." ,
operationId : "app.agents" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "List of agents" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver ( Agent . Info . array ( ) ) ,
2026-01-08 11:29:42 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
2026-01-17 05:21:13 +08:00
const modes = await Agent . list ( )
return c . json ( modes )
2026-01-08 11:29:42 +08:00
} ,
)
. get (
2026-01-17 05:21:13 +08:00
"/skill" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "List skills" ,
description : "Get a list of all available skills in the OpenCode system." ,
operationId : "app.skills" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "List of skills" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver ( Skill . Info . array ( ) ) ,
2026-01-08 11:29:42 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
2026-01-17 05:21:13 +08:00
const skills = await Skill . all ( )
return c . json ( skills )
2026-01-08 11:29:42 +08:00
} ,
)
. get (
2026-01-17 05:21:13 +08:00
"/lsp" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "Get LSP status" ,
description : "Get LSP server status" ,
operationId : "lsp.status" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "LSP server status" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver ( LSP . Status . array ( ) ) ,
2026-01-08 11:29:42 +08:00
} ,
2025-09-17 13:16:49 +08:00
} ,
2025-06-04 02:24:45 +08:00
} ,
} ,
2026-01-08 11:29:42 +08:00
} ) ,
async ( c ) = > {
2026-01-17 05:21:13 +08:00
return c . json ( await LSP . status ( ) )
2025-06-04 02:24:45 +08:00
} ,
2026-01-08 11:29:42 +08:00
)
2026-01-17 05:21:13 +08:00
. get (
"/formatter" ,
2026-01-08 11:29:42 +08:00
describeRoute ( {
2026-01-17 05:21:13 +08:00
summary : "Get formatter status" ,
description : "Get formatter status" ,
operationId : "formatter.status" ,
2026-01-08 11:29:42 +08:00
responses : {
200 : {
2026-01-17 05:21:13 +08:00
description : "Formatter status" ,
2026-01-08 11:29:42 +08:00
content : {
"application/json" : {
2026-01-17 05:21:13 +08:00
schema : resolver ( Format . Status . array ( ) ) ,
2026-01-08 11:29:42 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
return c . json ( await Format . status ( ) )
} ,
)
. get (
"/event" ,
describeRoute ( {
summary : "Subscribe to events" ,
description : "Get events" ,
operationId : "event.subscribe" ,
responses : {
200 : {
description : "Event stream" ,
content : {
"text/event-stream" : {
schema : resolver ( BusEvent . payloads ( ) ) ,
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
log . info ( "event connected" )
return streamSSE ( c , async ( stream ) = > {
2025-12-17 03:45:03 +08:00
stream . writeSSE ( {
data : JSON.stringify ( {
2026-01-08 11:29:42 +08:00
type : "server.connected" ,
2025-12-17 03:45:03 +08:00
properties : { } ,
} ) ,
} )
2026-01-08 11:29:42 +08:00
const unsub = Bus . subscribeAll ( async ( event ) = > {
await stream . writeSSE ( {
data : JSON.stringify ( event ) ,
} )
if ( event . type === Bus . InstanceDisposed . type ) {
stream . close ( )
}
} )
// Send heartbeat every 30s to prevent WKWebView timeout (60s default)
const heartbeat = setInterval ( ( ) = > {
stream . writeSSE ( {
data : JSON.stringify ( {
type : "server.heartbeat" ,
properties : { } ,
} ) ,
} )
} , 30000 )
2025-12-17 03:45:03 +08:00
2026-01-08 11:29:42 +08:00
await new Promise < void > ( ( resolve ) = > {
stream . onAbort ( ( ) = > {
clearInterval ( heartbeat )
unsub ( )
resolve ( )
log . info ( "event disconnected" )
} )
2025-09-17 13:16:49 +08:00
} )
} )
2025-11-05 01:33:03 +08:00
} ,
2026-01-08 11:29:42 +08:00
)
. all ( "/*" , async ( c ) = > {
const path = c . req . path
2026-01-21 20:49:46 +08:00
2026-01-08 11:29:42 +08:00
const response = await proxy ( ` https://app.opencode.ai ${ path } ` , {
. . . c . req ,
headers : {
. . . c . req . raw . headers ,
host : "app.opencode.ai" ,
} ,
} )
2026-01-10 05:33:46 +08:00
response . headers . set (
"Content-Security-Policy" ,
2026-01-29 11:21:49 +08:00
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:" ,
2026-01-10 05:33:46 +08:00
)
2026-01-08 11:29:42 +08:00
return response
} ) as unknown as Hono ,
2025-09-17 13:16:49 +08:00
)
2025-05-19 10:30:41 +08:00
export async function openapi() {
2026-01-08 11:29:42 +08:00
// Cast to break excessive type recursion from long route chains
const result = await generateSpecs ( App ( ) as Hono , {
2025-05-19 10:30:41 +08:00
documentation : {
info : {
title : "opencode" ,
version : "1.0.0" ,
description : "opencode api" ,
} ,
2025-08-15 00:10:32 +08:00
openapi : "3.1.1" ,
2025-05-19 10:30:41 +08:00
} ,
2025-06-01 02:41:00 +08:00
} )
return result
2025-05-19 02:28:08 +08:00
}
2025-05-19 02:13:04 +08:00
2025-12-31 17:50:29 +08:00
export function listen ( opts : { port : number ; hostname : string ; mdns? : boolean ; cors? : string [ ] } ) {
_corsWhitelist = opts . cors ? ? [ ]
2025-12-22 03:57:55 +08:00
const args = {
2025-06-25 08:52:09 +08:00
hostname : opts.hostname ,
2025-05-19 02:13:04 +08:00
idleTimeout : 0 ,
2025-09-17 13:16:49 +08:00
fetch : App ( ) . fetch ,
2025-12-05 10:32:08 +08:00
websocket : websocket ,
2025-12-22 20:05:10 +08:00
} as const
2025-12-27 04:24:44 +08:00
const tryServe = ( port : number ) = > {
2025-12-22 20:05:10 +08:00
try {
2025-12-27 04:24:44 +08:00
return Bun . serve ( { . . . args , port } )
2025-12-22 20:05:10 +08:00
} catch {
2025-12-27 04:24:44 +08:00
return undefined
2025-12-22 20:05:10 +08:00
}
}
2025-12-27 04:24:44 +08:00
const server = opts . port === 0 ? ( tryServe ( 4096 ) ? ? tryServe ( 0 ) ) : tryServe ( opts . port )
if ( ! server ) throw new Error ( ` Failed to start server on port ${ opts . port } ` )
2025-12-30 11:05:08 +08:00
_url = server . url
2025-12-27 04:24:44 +08:00
const shouldPublishMDNS =
opts . mdns &&
server . port &&
opts . hostname !== "127.0.0.1" &&
opts . hostname !== "localhost" &&
opts . hostname !== "::1"
if ( shouldPublishMDNS ) {
2026-01-17 19:47:19 +08:00
MDNS . publish ( server . port ! )
2025-12-27 04:24:44 +08:00
} else if ( opts . mdns ) {
log . warn ( "mDNS enabled but hostname is loopback; skipping mDNS publish" )
}
const originalStop = server . stop . bind ( server )
server . stop = async ( closeActiveConnections? : boolean ) = > {
if ( shouldPublishMDNS ) MDNS . unpublish ( )
return originalStop ( closeActiveConnections )
}
return server
2025-05-18 09:31:42 +08:00
}
}