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): handle the MESSAGE_TYPE stati…
Browse files Browse the repository at this point in the history
…c field of `Message`
  • Loading branch information
tmorin committed Nov 5, 2021
1 parent ac5ccea commit 0e64d24
Show file tree
Hide file tree
Showing 23 changed files with 156 additions and 130 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Expand Up @@ -16,5 +16,5 @@
"packages/ceb-templating-literal",
"packages/ceb-example-greeting"
],
"version": "4.0.3-alpha.4"
"version": "4.0.3-alpha.6"
}
4 changes: 2 additions & 2 deletions packages/ceb-builders/package.json
@@ -1,6 +1,6 @@
{
"name": "@tmorin/ceb-builders",
"version": "4.0.3-alpha.4",
"version": "4.0.3-alpha.6",
"license": "MIT",
"description": "The package is part of the `<ceb/>` library. It provides a set of builders used to enhance the definition of Custom Elements (v1).",
"keywords": [
Expand Down Expand Up @@ -48,7 +48,7 @@
"test:watch": "karma start --no-single-run --auto-watch --browsers Firefox"
},
"dependencies": {
"@tmorin/ceb-core": "^4.0.3-alpha.4",
"@tmorin/ceb-core": "^4.0.3-alpha.6",
"@tmorin/ceb-utilities": "^4.0.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/ceb-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@tmorin/ceb-core",
"version": "4.0.3-alpha.4",
"version": "4.0.3-alpha.6",
"license": "MIT",
"description": "The package is part of the `<ceb/>` library. It provides the main builder used to define and register Custom Elements (v1).",
"keywords": [
Expand Down
6 changes: 3 additions & 3 deletions packages/ceb-example-greeting/package.json
@@ -1,6 +1,6 @@
{
"name": "@tmorin/ceb-example-greeting",
"version": "4.0.3-alpha.4",
"version": "4.0.3-alpha.6",
"license": "MIT",
"description": "The package is part of the `<ceb/>` library. It provides an example demonstrating the usage of the library to define Custom Elements (v1).",
"keywords": [
Expand Down Expand Up @@ -51,7 +51,7 @@
"test:watch": "karma start --no-single-run --auto-watch --browsers Firefox"
},
"dependencies": {
"@tmorin/ceb-builders": "^4.0.3-alpha.4",
"@tmorin/ceb-core": "^4.0.3-alpha.4"
"@tmorin/ceb-builders": "^4.0.3-alpha.6",
"@tmorin/ceb-core": "^4.0.3-alpha.6"
}
}
6 changes: 3 additions & 3 deletions packages/ceb-messaging-adapter-electron/package.json
@@ -1,6 +1,6 @@
{
"name": "@tmorin/ceb-messaging-adapter-electron",
"version": "4.0.3-alpha.4",
"version": "4.0.3-alpha.6",
"license": "MIT",
"description": "The package is part of the `<ceb/>` library. It provides an adapter for the Bus compliant with the Electron IPC Event Emitter.",
"keywords": [
Expand Down Expand Up @@ -55,13 +55,13 @@
"test:watch": "karma start --no-single-run --auto-watch"
},
"dependencies": {
"@tmorin/ceb-messaging-core": "^4.0.3-alpha.6",
"@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"
},
"devDependencies": {
"@tmorin/ceb-messaging-simple": "^4.0.3-alpha.4",
"@tmorin/ceb-messaging-simple": "^4.0.3-alpha.6",
"@types/node": "^14.14.31",
"@types/promise.any": "^2.0.0"
},
Expand Down
42 changes: 13 additions & 29 deletions packages/ceb-messaging-adapter-electron/src/__TEST/fixture.ts
@@ -1,23 +1,22 @@
import {MessageConstructor, MessageHeaders, MessageType} from "@tmorin/ceb-messaging-core";
import {SimpleIpcMessageConverter} from "../converter";
import {MessageHeaders} from "@tmorin/ceb-messaging-core";
import {AbstractSimpleCommand, AbstractSimpleEvent, AbstractSimpleResult} from "@tmorin/ceb-messaging-simple";

export class CommandA extends AbstractSimpleCommand<string> {
static NAME = "CommandA"
static MESSAGE_TYPE = "CommandA"

constructor(
body: string,
headers: Partial<MessageHeaders> = {}
) {
super(body, {
messageType: CommandA.NAME,
messageType: CommandA.MESSAGE_TYPE,
...headers
})
}
}

export class ResultA extends AbstractSimpleResult<string> {
static NAME = "ResultA"
static MESSAGE_TYPE = "ResultA"

constructor(
body: string,
Expand All @@ -28,28 +27,28 @@ export class ResultA extends AbstractSimpleResult<string> {

static createFromCommand(command: CommandA, body: string) {
return new ResultA(body, {
messageType: ResultA.NAME,
messageType: ResultA.MESSAGE_TYPE,
correlationId: command.headers.messageId,
})
}
}

export class CommandB extends AbstractSimpleCommand<string> {
static NAME = "CommandB"
static MESSAGE_TYPE = "CommandB"

constructor(
body: string,
headers: Partial<MessageHeaders> = {}
) {
super(body, {
messageType: CommandB.NAME,
messageType: CommandB.MESSAGE_TYPE,
...headers
})
}
}

export class ResultB extends AbstractSimpleResult<string> {
static NAME = "ResultB"
static MESSAGE_TYPE = "ResultB"

constructor(
body: string,
Expand All @@ -60,51 +59,36 @@ export class ResultB extends AbstractSimpleResult<string> {

static createFromCommand(command: CommandB, body: string) {
return new ResultB(body, {
messageType: ResultB.NAME,
messageType: ResultB.MESSAGE_TYPE,
correlationId: command.headers.messageId,
})
}
}

export class FromRendererEvent extends AbstractSimpleEvent<string> {
static NAME = "FromRendererEvent"
static MESSAGE_TYPE = "FromRendererEvent"

constructor(
body: string,
headers: Partial<MessageHeaders> = {}
) {
super(body, {
messageType: FromRendererEvent.NAME,
messageType: FromRendererEvent.MESSAGE_TYPE,
...headers
})
}
}

export class FromMainEvent extends AbstractSimpleEvent<string> {
static NAME = "FromMainEvent"
static MESSAGE_TYPE = "FromMainEvent"

constructor(
body: string,
headers: Partial<MessageHeaders> = {}
) {
super(body, {
messageType: FromMainEvent.NAME,
messageType: FromMainEvent.MESSAGE_TYPE,
...headers
})
}
}

export class Converter extends SimpleIpcMessageConverter {
constructor(
types: Map<MessageType, MessageConstructor<any>> = new Map([
[FromRendererEvent.NAME, FromRendererEvent],
[FromMainEvent.NAME, FromMainEvent],
[CommandA.NAME, CommandA],
[ResultA.NAME, ResultA],
[CommandB.NAME, CommandB],
[ResultB.NAME, ResultB],
])
) {
super(types)
}
}
59 changes: 25 additions & 34 deletions packages/ceb-messaging-adapter-electron/src/__TEST/main.ts
@@ -1,41 +1,32 @@
import {ipcMain, webContents} from 'electron'
import {ipcMain} from 'electron'
import {assert} from "chai";
import {SimpleModule} from "@tmorin/ceb-messaging-simple";
import {IpcMainBus} from "../bus-main";
import {InMemorySimpleBus} 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}`))
}
const parent = new InMemorySimpleBus()
const ipcBus = new IpcMainBus(parent, ipcMain)

ContainerBuilder.get()
.module(new SimpleModule())
.module(new ElectronModule())
.build()
.initialize()
.then(_container => {
const ipcBus = _container.registry.resolve<Bus>(BusSymbol)
// 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)
}
})
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, (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, ResultA, async (command: CommandA): Promise<ResultA> => {
return ResultA.createFromCommand(command, `Hello, ${command.body}!`)
})
})
.catch(error => log(`contaier failed to start: ${error.name} ${error.message}`))
ipcBus.handle<CommandA, ResultA>(CommandA, ResultA, async (command: CommandA): Promise<ResultA> => {
return ResultA.createFromCommand(command, `Hello, ${command.body}!`)
})
8 changes: 4 additions & 4 deletions packages/ceb-messaging-adapter-electron/src/bus-main.ts
Expand Up @@ -19,10 +19,10 @@ import {
import {IpcActionError, IpcHandler, IpcMessageMetadata, IpcSubscription} from "./bus";
import {IpcMessageConverter, SimpleIpcMessageConverter} from "./converter";

/**
* The symbol used to register {@link IpcMainBus}.
*/
export const IpcMainBusSymbol = Symbol.for("ceb/inversion/IpcMainBus")
// function log(text: string) {
// webContents.getAllWebContents().forEach(webContent => webContent
// .send("main-log", `${text}`))
// }

/**
* The implementation of {@link Bus} for the Main context of Electron IPC.
Expand Down
16 changes: 10 additions & 6 deletions packages/ceb-messaging-adapter-electron/src/bus-renderer.ts
Expand Up @@ -17,7 +17,7 @@ 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 IpcRendererBus}.
Expand All @@ -30,8 +30,8 @@ export const IpcRendererBusSymbol = Symbol.for("ceb/inversion/IpcRendererBus")
export class IpcRendererBus implements Bus {
constructor(
private readonly parentBus: Bus,
private readonly ipcMessageConverter: IpcMessageConverter,
private readonly ipcRenderer: IpcRenderer,
private readonly ipcMessageConverter: IpcMessageConverter = new SimpleIpcMessageConverter()
) {
}

Expand Down Expand Up @@ -71,9 +71,11 @@ export class IpcRendererBus 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: IpcRendererEvent, 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 @@ -118,9 +120,11 @@ export class IpcRendererBus implements Bus {
options?: SubscribeOptions
): Subscription {
// 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: IpcRendererEvent, data: any, metadata: IpcMessageMetadata) => {
const message = this.ipcMessageConverter.deserialize<E>(channel, {channel, data, metadata})
const message = this.ipcMessageConverter.deserialize<E>(EventType, {channel, data, metadata})
this.parentBus.publish(message)
}
this.ipcRenderer.on(channel, ipcListener)
Expand Down
1 change: 0 additions & 1 deletion packages/ceb-messaging-adapter-electron/src/bus.ts
Expand Up @@ -9,7 +9,6 @@ import {
MessageResult,
Subscription
} from "@tmorin/ceb-messaging-core";
import {MessageType} from "@tmorin/ceb-messaging-core/src";

/**
* The metadata of an IPC message.
Expand Down
30 changes: 21 additions & 9 deletions packages/ceb-messaging-adapter-electron/src/converter.ts
Expand Up @@ -19,30 +19,42 @@ export interface IpcMessageConverter<D = any> {

/**
* Transform a message to an IPC message.
* @param MessageType the type of the message
* @param Type the type of the message
* @param ipcMessage the IPC message
* @return message
*/
deserialize<M extends Message>(MessageType: MessageConstructor<M> | string, ipcMessage: IpcMessage<D>): M
deserialize<M extends Message>(Type: MessageConstructor<M> | MessageType, ipcMessage: IpcMessage<D>): M
}

/**
* A simple implementation of the {@link IpcMessageConverter} which leverages on a map of {@link MessageConstructor}.
* Factory of {@link Message}.
*/
export interface MessageFactory {
/**
* The factory.
* @param ipcMessage the IPC message
*/<M extends Message = Message>(ipcMessage: IpcMessage): M
}

/**
* A simple implementation of the {@link IpcMessageConverter} which expects the bodies of the {@link Message} follow the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
* Special cases can be manage providing a map of {@link MessageConstructor}.
*/
export class SimpleIpcMessageConverter implements IpcMessageConverter<Message> {

constructor(
/**
* The map of {@link MessageConstructor}.
* A a map of Message factories.
*/
readonly types: Map<MessageType, MessageConstructor<any>> = new Map()
readonly types: Map<MessageConstructor | string, MessageFactory> = new Map()
) {
}

deserialize<M extends Message>(MessageType: MessageConstructor<M> | string, ipcMessage: IpcMessage<Message>): M {
if (typeof MessageType === "string") {
let Type = this.types.get(MessageType)
if (Type) {
return new Type(ipcMessage.data.body, ipcMessage.data.headers)
if (typeof MessageType === "string" || this.types.has(MessageType)) {
let factory = this.types.get(MessageType)
if (factory) {
return factory(ipcMessage)
}
throw new Error(`SimpleIpcMessageConverter : cannot found a constructor for the MessageType ${MessageType}.`)
}
Expand Down

0 comments on commit 0e64d24

Please sign in to comment.