Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Commit

Permalink
feat(ceb-messaging-adapter-electron): add an inversion module
Browse files Browse the repository at this point in the history
  • Loading branch information
tmorin committed Nov 5, 2021
1 parent 20be092 commit ac5ccea
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/ceb-messaging-adapter-electron/package.json
Expand Up @@ -55,6 +55,7 @@
"test:watch": "karma start --no-single-run --auto-watch"
},
"dependencies": {
"@tmorin/ceb-inversion": "^4.0.0",
"@tmorin/ceb-messaging-core": "^4.0.3-alpha.4",
"@tmorin/ceb-utilities": "^4.0.0",
"promise.any": "^2.0.2"
Expand Down
53 changes: 31 additions & 22 deletions packages/ceb-messaging-adapter-electron/src/__TEST/main.ts
@@ -1,32 +1,41 @@
import {ipcMain, webContents} from 'electron'
import {assert} from "chai";
import {IpcMainBus} from "../bus-main";
import {InMemorySimpleBus} from "@tmorin/ceb-messaging-simple";
import {CommandA, CommandB, Converter, FromMainEvent, FromRendererEvent, ResultA, ResultB} from "./fixture";

const parent = new InMemorySimpleBus()
const ipcBus = new IpcMainBus(parent, new Converter(), ipcMain)
import {SimpleModule} from "@tmorin/ceb-messaging-simple";
import {CommandA, CommandB, FromMainEvent, FromRendererEvent, ResultA, ResultB} from "./fixture";
import {ElectronModule} from "../inversion";
import {ContainerBuilder} from "@tmorin/ceb-inversion";
import {Bus, BusSymbol} from "@tmorin/ceb-messaging-core";

function log(text: string) {
webContents.getAllWebContents().forEach(webContent => webContent
.send("main-log", `${text}`))
}

ipcMain.on("execute-CommandB", async (event) => {
try {
const resultB = await ipcBus.execute(new CommandB("World"), ResultB)
assert.ok(resultB)
assert.equal(resultB.body, "Hello, World!")
event.reply("execute-CommandB-ok")
} catch (error) {
event.reply("execute-CommandB-ko", error)
}
})
ContainerBuilder.get()
.module(new SimpleModule())
.module(new ElectronModule())
.build()
.initialize()
.then(_container => {
const ipcBus = _container.registry.resolve<Bus>(BusSymbol)

ipcMain.on("execute-CommandB", async (event) => {
try {
const resultB = await ipcBus.execute(new CommandB("World"), ResultB)
assert.ok(resultB)
assert.equal(resultB.body, "Hello, World!")
event.reply("execute-CommandB-ok")
} catch (error) {
event.reply("execute-CommandB-ko", error)
}
})

ipcBus.subscribe<FromRendererEvent>(FromRendererEvent.NAME, (message) => {
return ipcBus.publish(new FromMainEvent(`main[ ${message.body} ]`))
})
ipcBus.subscribe<FromRendererEvent>(FromRendererEvent, (message) => {
return ipcBus.publish(new FromMainEvent(`main[ ${message.body} ]`))
})

ipcBus.handle<CommandA, ResultA>(CommandA.NAME, ResultA, async (command: CommandA): Promise<ResultA> => {
return ResultA.createFromCommand(command, `Hello, ${command.body}!`)
})
ipcBus.handle<CommandA, ResultA>(CommandA, ResultA, async (command: CommandA): Promise<ResultA> => {
return ResultA.createFromCommand(command, `Hello, ${command.body}!`)
})
})
.catch(error => log(`contaier failed to start: ${error.name} ${error.message}`))
21 changes: 15 additions & 6 deletions packages/ceb-messaging-adapter-electron/src/bus-main.ts
Expand Up @@ -17,16 +17,21 @@ import {
SubscriptionListener
} from "@tmorin/ceb-messaging-core";
import {IpcActionError, IpcHandler, IpcMessageMetadata, IpcSubscription} from "./bus";
import {IpcMessageConverter} from "./converter";
import {IpcMessageConverter, SimpleIpcMessageConverter} from "./converter";

/**
* The symbol used to register {@link IpcMainBus}.
*/
export const IpcMainBusSymbol = Symbol.for("ceb/inversion/IpcMainBus")

/**
* The implementation of {@link Bus} for the Main context of Electron IPC.
*/
export class IpcMainBus implements Bus {
constructor(
private readonly parentBus: Bus,
private readonly ipcMessageConverter: IpcMessageConverter,
private readonly ipcMain: IpcMain,
private readonly ipcMessageConverter: IpcMessageConverter = new SimpleIpcMessageConverter()
) {
}

Expand Down Expand Up @@ -66,9 +71,11 @@ export class IpcMainBus implements Bus {
// handle event from parent
const parentHandler = this.parentBus.handle(ActionType, ResultType, handler)
// handle event from IPC
const channel: string = typeof ActionType === "string" ? ActionType : ActionType.name
const channel: string = typeof ActionType === "string"
? ActionType
: ActionType["MESSAGE_TYPE"] || ActionType.prototype["MESSAGE_TYPE"] || ActionType.name
const ipcListener = async (event: IpcMainEvent, data: any, metadata: IpcMessageMetadata) => {
const message = this.ipcMessageConverter.deserialize<M>(channel, {channel, data, metadata})
const message = this.ipcMessageConverter.deserialize<M>(ActionType, {channel, data, metadata})
if (metadata.waitForResult) {
try {
const result = await this.parentBus.execute(message, ResultType, {
Expand Down Expand Up @@ -115,9 +122,11 @@ export class IpcMainBus implements Bus {
// handle event from parent
const parentSubscription = this.parentBus.subscribe(EventType, listener, options)
// handle event from IPC
const channel: string = typeof EventType === "string" ? EventType : EventType.name
const channel: string = typeof EventType === "string"
? EventType
: EventType["MESSAGE_TYPE"] || EventType.prototype["MESSAGE_TYPE"] || EventType.name
const ipcListener = (event: IpcMainEvent, data: any, metadata: IpcMessageMetadata) => {
const message = this.ipcMessageConverter.deserialize<E>(channel, {
const message = this.ipcMessageConverter.deserialize<E>(EventType, {
channel: channel,
data,
metadata
Expand Down
5 changes: 5 additions & 0 deletions packages/ceb-messaging-adapter-electron/src/bus-renderer.ts
Expand Up @@ -19,6 +19,11 @@ import {
import {IpcActionError, IpcHandler, IpcMessageMetadata, IpcSubscription} from "./bus";
import {IpcMessageConverter} from "./converter";

/**
* The symbol used to register {@link IpcRendererBus}.
*/
export const IpcRendererBusSymbol = Symbol.for("ceb/inversion/IpcRendererBus")

/**
* The implementation of {@link Bus} for the Renderer contexts of Electron IPC.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/ceb-messaging-adapter-electron/src/index.ts
Expand Up @@ -2,3 +2,4 @@ export * from "./bus"
export * from "./bus-main"
export * from "./bus-renderer"
export * from "./converter"
export * from "./inversion"
86 changes: 86 additions & 0 deletions packages/ceb-messaging-adapter-electron/src/inversion.ts
@@ -0,0 +1,86 @@
import {AbstractModule, ComponentSymbol} from "@tmorin/ceb-inversion";
import {Bus, BusSymbol} from "@tmorin/ceb-messaging-core";
import {ipcMain, ipcRenderer} from "electron";
import {IpcMessageConverter, IpcMessageConverterSymbol, SimpleIpcMessageConverter} from "./converter";
import {IpcMainBus, IpcMainBusSymbol} from "./bus-main";
import {IpcRendererBus, IpcRendererBusSymbol} from "./bus-renderer";

/**
* The options of {@link ElectronModule}.
*/
export interface ElectronModuleOptions {
/**
* When `true`, the `error` internal events (i.e. `bus.on("error", ...)`) are displayed using `console.error(...)`.
*/
errorToConsole: boolean
}

/**
* The module registers a {@link IpcMainBus} or a {@link IpcRendererBus} bound with the key {@link BusSymbol}
*
* @example Register the ElectronModule
* ```typescript
* import {ContainerBuilder} from "@tmorin/ceb-inversion"
* import {ElectronModule} from "@tmorin/ceb-messaging-adapter-electron"
* const container = ContainerBuilder.get()
* .module(new ElectronModule())
* .build()
* ```
*/
export class ElectronModule extends AbstractModule {
constructor(
private readonly options: ElectronModuleOptions = {
errorToConsole: false
}
) {
super();
}

async configure(): Promise<void> {
this.registry.registerValue(IpcMessageConverterSymbol, new SimpleIpcMessageConverter())
if (ipcMain) {
this.registry.registerFactory(IpcMainBusSymbol, (registry) => {
const bus = new IpcMainBus(
registry.resolve<Bus>(BusSymbol),
ipcMain,
registry.resolve<IpcMessageConverter>(IpcMessageConverterSymbol)
)
this.registry.registerValue(BusSymbol, bus)
return bus
})
this.registry.registerFactory(ComponentSymbol, (registry) => ({
configure: async () => {
const bus = registry.resolve<Bus>(IpcMainBusSymbol)
if (this.options.errorToConsole) {
bus.on("error", error => console.error("IpcMainBus throws an error", error))
}
},
async dispose() {
await registry.resolve<Bus>(IpcMainBusSymbol).dispose()
}
}))
}
if (ipcRenderer) {
this.registry.registerFactory(IpcRendererBusSymbol, (registry) => {
const bus = new IpcRendererBus(
registry.resolve<Bus>(BusSymbol),
ipcRenderer,
registry.resolve<IpcMessageConverter>(IpcMessageConverterSymbol)
)
this.registry.registerValue(BusSymbol, bus)
return bus
})
this.registry.registerFactory(ComponentSymbol, (registry) => ({
configure: async () => {
const bus = registry.resolve<Bus>(IpcRendererBusSymbol)
if (this.options.errorToConsole) {
bus.on("error", error => console.error("IpcRendererBus throws an error", error))
}
},
async dispose() {
await registry.resolve<Bus>(IpcRendererBusSymbol).dispose()
}
}))
}
}
}
29 changes: 18 additions & 11 deletions packages/ceb-messaging-adapter-electron/src/ipc.spec.ts
@@ -1,26 +1,33 @@
import {assert} from "chai"
import {ipcRenderer} from 'electron'
import {InMemorySimpleBus} from "@tmorin/ceb-messaging-simple";
import {Bus} from "@tmorin/ceb-messaging-core";
import {IpcRendererBus} from "./bus-renderer";
import {CommandA, CommandB, Converter, FromMainEvent, FromRendererEvent, ResultA, ResultB} from "./__TEST/fixture";
import {SimpleModule} from "@tmorin/ceb-messaging-simple";
import {Bus, BusSymbol} from "@tmorin/ceb-messaging-core";
import {CommandA, CommandB, FromMainEvent, FromRendererEvent, ResultA, ResultB} from "./__TEST/fixture";
import {ContainerBuilder} from "@tmorin/ceb-inversion";
import {ElectronModule} from "./inversion";

describe("IPC", function () {
this.timeout(5000)
let parentBus: InMemorySimpleBus
let ipcRendererBus: Bus
before(async () => new Promise((resolve) => {
before(async () => new Promise((resolve, reject) => {
ipcRenderer.once("main-ready", () => {
parentBus = new InMemorySimpleBus()
ipcRendererBus = new IpcRendererBus(parentBus, new Converter(), ipcRenderer)
resolve()
ContainerBuilder.get()
.module(new SimpleModule())
.module(new ElectronModule())
.build()
.initialize()
.then(_container => {
ipcRendererBus = _container.registry.resolve<Bus>(BusSymbol)
resolve()
})
.catch(reject)
})
ipcRenderer.on("main-log", (evt, message) => {
console.info(message)
})
}))
it("should publish and subscribe to events", function (done) {
ipcRendererBus.subscribe(FromMainEvent.NAME, (message) => {
ipcRendererBus.subscribe(FromMainEvent, (message) => {
assert.ok(message)
done()
})
Expand All @@ -37,7 +44,7 @@ describe("IPC", function () {
})
describe("from Main to Renderer", function () {
it("should execute command", function (done) {
ipcRendererBus.handle<CommandB, ResultB>(CommandB.NAME, ResultB, async (command) => {
ipcRendererBus.handle<CommandB, ResultB>(CommandB, ResultB, async (command) => {
return ResultB.createFromCommand(command, `Hello, ${command.body}!`)
})
ipcRenderer.on("execute-CommandB-ok", () => done())
Expand Down

0 comments on commit ac5ccea

Please sign in to comment.