Skip to content

Chrome Extension

Chrome extension support uses native transports backed by chrome.runtime.Port for persistent two-way communication.

types.ts
export interface BackgroundAPI {
getExtensionVersion(): Promise<string>
}
export interface ContentAPI {
getPageTitle(): Promise<string>
}
background.ts
import { RPCChannel } from "kkrpc"
import { chromePortTransport } from "kkrpc/chrome-extension"
import type { BackgroundAPI, ContentAPI } from "./types"
const backgroundAPI: BackgroundAPI = {
async getExtensionVersion() {
return chrome.runtime.getManifest().version
}
}
const contentChannels = new Map<number, RPCChannel<BackgroundAPI, ContentAPI>>()
chrome.runtime.onConnect.addListener((port) => {
if (port.name !== "content-to-background") return
const tabId = port.sender?.tab?.id
if (!tabId) return
const transport = chromePortTransport(port)
const channel = new RPCChannel<BackgroundAPI, ContentAPI>(transport, { expose: backgroundAPI })
contentChannels.set(tabId, channel)
port.onDisconnect.addListener(() => {
channel.destroy()
transport.close?.()
contentChannels.delete(tabId)
})
})
content.ts
import { RPCChannel } from "kkrpc"
import { chromePortTransport } from "kkrpc/chrome-extension"
import type { BackgroundAPI, ContentAPI } from "./types"
const contentAPI: ContentAPI = {
async getPageTitle() {
return document.title
}
}
const port = chrome.runtime.connect({ name: "content-to-background" })
const transport = chromePortTransport(port)
const channel = new RPCChannel<ContentAPI, BackgroundAPI>(transport, { expose: contentAPI })
const backgroundAPI = channel.getAPI()
console.log(await backgroundAPI.getExtensionVersion())

Use one port-backed transport per long-lived connection. Call channel.destroy() and transport.close?.() when the connection should be cleaned up manually.