闽公网安备 35020302035485号
export class Client extends IPCClient implements IDisposable {
private protocol: Protocol
private static createProtocol(): Protocol {
const onMessage = Event.fromNodeEventEmitter<ELBuffer>(ipcRenderer, 'vscode:message', (_, message) => ELBuffer.wrap(message))
ipcRenderer.send('vscode:hello')
return new Protocol(ipcRenderer, onMessage)
}
// 堆代码 duidaima.com
constructor(id: string) {
const protocol = Client.createProtocol()
super(protocol, id)
this.protocol = protocol
}
override dispose(): void {
this.protocol.disconnect()
super.dispose()
}
}
解释一下,Client先监听了ipcRenderer上的vscode:message事件,然后发送了vscode:hello通知主进程Client连接,然后创建了Protocol类传给IPCClient基类class IPCClient<TContext = string> implements IDisposable {
private channelClient: ChannelClient
private channelServer: ChannelServer
constructor(protocol: IMessagePassingProtocol, ctx: TContext) {
const writer = new BufferWriter()
serialize(writer, ctx)
protocol.send(writer.buffer)
this.channelClient = new ChannelClient(protocol)
this.channelServer = new ChannelServer(protocol, ctx)
}
getChannel<T extends IChannel>(channelName: string): T {
return this.channelClient.getChannel(channelName) as T
}
registerChannel(channelName: string, channel: IServerChannel<string>): void {
this.channelServer.registerChannel(channelName, channel)
}
dispose(): void {
this.channelClient.dispose()
this.channelServer.dispose()
}
}
可以看到IPCClient的作用就是管理Channel.const writer = new BufferWriter() serialize(writer, ctx) // ctx的实际值为windowId rotocol.send(writer.buffer)这里其实是向主进程发送了当前窗口的id作为标识
class Server extends IPCServer {
private static readonly Clients = new Map<number, IDisposable>()
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
const onHello = Event.fromNodeEventEmitter<WebContents>(ipcMain, 'vscode:hello', ({ sender }) => sender)
return Event.map(onHello, (webContents) => {
const id = webContents.id
const client = Server.Clients.get(id)
client?.dispose()
const onDidClientReconnect = new Emitter<void>()
Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire()))
const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<ELBuffer>
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event)
const protocol = new ElectronProtocol(webContents, onMessage)
return { protocol, onDidClientDisconnect }
})
}
constructor() {
super(Server.getOnDidClientConnect())
}
}
这段代码比较简单,就是处理了一下重新连接的问题。
class IPCServer<TContext extends string = string> {
private channels = new Map<string, IServerChannel<TContext>>()
private _connections = new Set<Connection<TContext>>()
private readonly _onDidAddConnection = new Emitter<Connection<TContext>>()
readonly onDidAddConnection: Event<Connection<TContext>> = this._onDidAddConnection.event
private readonly _onDidRemoveConnection = new Emitter<Connection<TContext>>()
readonly onDidRemoveConnection: Event<Connection<TContext>> = this._onDidRemoveConnection.event
private readonly disposables = new DisposableStore()
get connections(): Connection<TContext>[] {
const result: Connection<TContext>[] = []
this._connections.forEach(ctx => result.push(ctx))
return result
}
constructor(onDidClientConnect: Event<ClientConnectionEvent>) {
this.disposables.add(onDidClientConnect(({ protocol, onDidClientDisconnect }) => {
const onFirstMessage = Event.once(protocol.onMessage)
this.disposables.add(onFirstMessage((msg) => {
const reader = new BufferReader(msg)
const ctx = deserialize(reader) as TContext
const channelServer = new ChannelServer<TContext>(protocol, ctx)
const channelClient = new ChannelClient(protocol)
this.channels.forEach((channel, name) => channelServer.registerChannel(name, channel))
const connection: Connection<TContext> = { channelServer, channelClient, ctx }
this._connections.add(connection)
this._onDidAddConnection.fire(connection)
this.disposables.add(onDidClientDisconnect(() => {
channelServer.dispose()
channelClient.dispose()
this._connections.delete(connection)
this._onDidRemoveConnection.fire(connection)
}))
}))
}))
}
getChannel<T extends IChannel>(channelName: string, routerOrClientFilter: IClientRouter<TContext> | ((client: Client<TContext>) => boolean)): T {
}
private getMulticastEvent<T extends IChannel>(channelName: string, clientFilter: (client: Client<TContext>) => boolean, eventName: string, arg: any): Event<T> {
}
registerChannel(channelName: string, channel: IServerChannel<TContext>): void {
}
dispose(): void {
}
可以看到Server上管理了所有的Channel和Connection。
// services/fileSystem.ts
class IFileSystem {
stat:(source:string)=>Promise<Stat>
}
// main.ts
const server = new Server()
server.registerChannel(
"fileSystem",
ProxyChannel.fromService({
stat(source:string){
return fs.stat(source)
}
})) // 这里的ProxyChannel.fromService后面再解释
// renderer.ts
const client = new Client()
const fileSystemChannel = client.getChannel("fileSystem")
const stat = awite fileSystemChannel.call("stat")
// 或者
const client = new Client()
const fileSystemChannel = client.getChannel("fileSystem")
const fileSystemService = ProxyChannel.toService<IFileSystemChannel>(fileSystemChannel)// 后面解释
const stat = awite fileSystemChannel.call("stat")
要搞清楚它们如何调用,首先要明白Channel的构成以及Channel如何被创建interface IChannel {
call: <T>(command: string, arg?: any, cancellationToken?: CancellationToken) => Promise<T>
listen: <T>(event: string, arg?: any) => Event<T>
}
// Channel的创建在ChannelClient类中
getChannel<T extends IChannel>(channelName: string): T {
const that = this
return {
call(command: string, arg?: any, cancellationToken?: CancellationToken) {
if (that.isDisposed) {
return Promise.reject(new CancellationError())
}
return that.requestPromise(channelName, command, arg, cancellationToken)
},
listen(event: string, arg: any) {
if (that.isDisposed) {
return Event.None
}
return that.requestEvent(channelName, event, arg)
},
} as T
}
这里的that.requestPromise其实就是调用ipcRender.invoke('vscode:message',.....),会把channelName,command和其他参数一起传过去private onPromise(request: IRawPromiseRequest): void {
const channel = this.channels.get(request.channelName)
if (!channel) {
return
}
let promise: Promise<any>
try {
promise = channel.call(this.ctx, request.name, request.arg)
}
catch (e) {
promise = Promise.reject(e)
}
const id = request.id
promise.then((data) => {
this.sendResponse({ id, data, type: ResponseType.PromiseSuccess })
}, (err) => {
this.sendResponse({ id, data: err, type: ResponseType.PromiseErrorObj })
})
}
Server在接受request之后找到对应Channel的Command进行调用,然后返回封装后的执行结果,最后我们看一下ProxyChannel.toService和ProxyChannel.fromService的代码 export function fromService<TContext>(service: unknown, disposables: DisposableStore, options?: ICreateServiceChannelOptions): IServerChannel<TContext> {
const handler = service as { [key: string]: unknown }
const disableMarshalling = options && options.disableMarshalling
const mapEventNameToEvent = new Map<string, Event<unknown>>()
for (const key in handler) {
if (propertyIsEvent(key)) {
mapEventNameToEvent.set(key, EventType.buffer(handler[key] as Event<unknown>, true, undefined, disposables))
}
}
return new class implements IServerChannel {
listen<T>(_: unknown, event: string, arg: any): Event<T> {
const eventImpl = mapEventNameToEvent.get(event)
if (eventImpl) {
return eventImpl as Event<T>
}
const target = handler[event]
if (typeof target === 'function') {
if (propertyIsDynamicEvent(event)) {
return target.call(handler, arg)
}
if (propertyIsEvent(event)) {
mapEventNameToEvent.set(event, EventType.buffer(handler[event] as Event<unknown>, true, undefined, disposables))
return mapEventNameToEvent.get(event) as Event<T>
}
}
throw new Error(`Event not found: ${event}`)
}
call(_: unknown, command: string, args?: any[]): Promise<any> {
const target = handler[command]
if (typeof target === 'function') {
// Revive unless marshalling disabled
if (!disableMarshalling && Array.isArray(args)) {
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i])
}
}
let res = target.apply(handler, args)
if (!(res instanceof Promise)) {
res = Promise.resolve(res)
}
return res
}
throw new Error(`Method not found: ${command}`)
}
}()
}
export function toService<T extends object>(channel: IChannel, options?: ICreateProxyServiceOptions): T {
return new Proxy({}, {
get(_target: T, propKey: PropertyKey) {
if (typeof propKey === 'string') {
if (options?.properties?.has(propKey)) {
return options.properties.get(propKey)
}
return async function (...args: any[]) {
const result = await channel.call(propKey, args)
return result
}
}
throw new Error(`Property not found: ${String(propKey)}`)
},
}) as T
}
可以看到fromService是将类转换为Channel,而toService是将Channel转换为有类型提示的Service。