Skip to content

IPC Protocol

How JavaScript and Go communicate in LightShell — message format, requests, responses, and events.

LightShell uses an IPC (Inter-Process Communication) protocol for communication between your JavaScript code and the Go runtime. This page documents the protocol for developers who want to understand how the lightshell.* APIs work internally.

The IPC layer sits between the webview (where your JavaScript runs) and the Go backend (where native operations execute). Communication flows in both directions:

  • JS to Go: API calls (e.g., read a file, show a dialog)
  • Go to JS: Responses to API calls, and push events (e.g., window resized, file changed)

Messages travel over a Unix domain socket (UDS) using a length-prefixed JSON protocol.

Each message is framed as:

[4-byte uint32 big-endian length][JSON payload]

The 4-byte length prefix tells the receiver how many bytes to read for the JSON payload. This avoids delimiter-based parsing issues with newlines or special characters in data.

The Unix domain socket is created in the system temp directory with a unique name per app instance. It is cleaned up on shutdown.

There are three message types: Request, Response, and Event.

When you call a lightshell.* API, the client library creates a request:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"method": "fs.readFile",
"params": {
"path": "/tmp/test.txt",
"encoding": "utf-8"
}
}
FieldTypeDescription
idstringA UUID v4 that uniquely identifies this request. Used to match responses.
methodstringThe API method name, in module.method format.
paramsobjectMethod parameters. Structure varies by method.

The Go runtime processes the request and sends back a response with the same id:

Success:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"result": "file contents here",
"error": null
}

Error:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"result": null,
"error": "open /tmp/test.txt: no such file or directory"
}
FieldTypeDescription
idstringMatches the request id.
resultanyThe return value on success. Type depends on the method. null on error.
errorstring or nullError message on failure. null on success.

Events are push notifications from Go to JavaScript. They have no id because they are not responses to requests.

{
"event": "window.resize",
"data": {
"width": 1024,
"height": 768
}
}
FieldTypeDescription
eventstringEvent name in module.eventName format.
dataobjectEvent payload. Structure varies by event type.

Here is the complete flow for a lightshell.fs.readFile() call:

Your JS lightshell.js Go Runtime
│ │ │
│ readFile('/tmp/f.txt') │ │
├─────────────────────────>│ │
│ │ {id, method, params} │
│ ├─────────────────────────>│
│ │ │ os.ReadFile()
│ │ │
│ │ {id, result, error} │
│ │<─────────────────────────┤
│ Promise resolves │ │
│<─────────────────────────┤ │
  1. Your code calls lightshell.fs.readFile('/tmp/f.txt')
  2. The client library generates a UUID, stores the Promise callbacks in a pending map, and sends the request via window.webkit.messageHandlers.lightshell.postMessage()
  3. Go receives the raw message string through the webview’s message handler
  4. The IPC router parses the JSON, extracts the method, and dispatches to the registered handler
  5. The handler executes os.ReadFile("/tmp/f.txt")
  6. The result (or error) is wrapped in a response JSON
  7. Go calls webview.Eval('__lightshell_receive(' + json + ')') to execute JavaScript in the webview
  8. The __lightshell_receive function looks up the pending Promise by id and resolves or rejects it

Events work differently — they are initiated by Go, not by a JS request:

Go Runtime lightshell.js Your JS
│ │ │
│ window resize detected │ │
│ │ │
│ {event, data} │ │
├─────────────────────────>│ │
│ │ invoke all listeners │
│ ├─────────────────────────>│
│ │ │ callback(data)

To listen for events in your code:

lightshell.window.onResize((data) => {
console.log(data.width, data.height)
})

The onResize function registers a callback in the listeners map under the "window.resize" event key. When Go pushes a resize event, __lightshell_receive invokes all registered callbacks.

The Go-side IPC router maps method names to handler functions:

MethodHandlerDescription
window.setTitleWindowAPISet window title
window.setSizeWindowAPIResize window
window.getSizeWindowAPIGet window dimensions
window.setPositionWindowAPIMove window
window.getPositionWindowAPIGet window position
window.minimizeWindowAPIMinimize window
window.maximizeWindowAPIMaximize window
window.fullscreenWindowAPIEnter fullscreen
window.restoreWindowAPIRestore window
window.closeWindowAPIClose window
fs.readFileFSAPIRead file contents
fs.writeFileFSAPIWrite file contents
fs.readDirFSAPIList directory
fs.existsFSAPICheck path existence
fs.statFSAPIGet file metadata
fs.mkdirFSAPICreate directory
fs.removeFSAPIDelete file/directory
fs.watchFSAPIWatch for changes
dialog.openDialogAPIFile open dialog
dialog.saveDialogAPIFile save dialog
dialog.messageDialogAPIMessage dialog
dialog.confirmDialogAPIConfirmation dialog
dialog.promptDialogAPIText input dialog
clipboard.readClipboardAPIRead clipboard
clipboard.writeClipboardAPIWrite clipboard
shell.openShellAPIOpen URL/file
notify.sendNotifyAPISystem notification
tray.setTrayAPISet tray icon/menu
tray.removeTrayAPIRemove tray icon
menu.setMenuAPISet app menu
system.platformSystemAPIGet OS name
system.archSystemAPIGet CPU arch
system.homeDirSystemAPIGet home directory
system.tempDirSystemAPIGet temp directory
system.hostnameSystemAPIGet hostname
app.quitAppAPIQuit application
app.versionAppAPIGet app version
app.dataDirAppAPIGet data directory
EventTriggerData
window.resizeWindow resized{ width, height }
window.moveWindow moved{ x, y }
window.focusWindow focused{}
window.blurWindow lost focus{}
fs.watchWatched file changed{ path, event }
tray.clickTray menu item clicked{ id }
menu.clickApp menu item clicked{ id }

The IPC server handles multiple concurrent requests. Each request is processed independently — you can fire multiple API calls in parallel:

// These run concurrently
const [size, position, platform] = await Promise.all([
lightshell.window.getSize(),
lightshell.window.getPosition(),
lightshell.system.platform()
])

Responses are matched to requests by their id, so order does not matter.

Typical latencies for IPC round-trips:

OperationLatency
System info (platform, arch)< 1ms
Window operations (getSize, setTitle)< 2ms
File read (small file)< 5ms
File dialog (user interaction)varies
Clipboard read/write< 2ms

The Unix domain socket avoids TCP overhead. JSON serialization is fast for the small payloads involved. The bottleneck is almost always the native operation itself (e.g., disk I/O), not the IPC layer.