add rpc system to improve ipc

This commit is contained in:
Thomas Nordquist
2022-02-27 18:44:17 +01:00
parent 205ea00c41
commit e1493db7c8
27 changed files with 252 additions and 303 deletions

View File

@@ -11,7 +11,7 @@
"mochatest": "mocha --require ts-node/register src/**/*.spec.ts" "mochatest": "mocha --require ts-node/register src/**/*.spec.ts"
}, },
"engines": { "engines": {
"npm": "16" "node": "16"
}, },
"author": "", "author": "",
"license": "CC-BY-ND-4.0", "license": "CC-BY-ND-4.0",

View File

@@ -9,14 +9,13 @@ import {
import { default as persistentStorage, StorageIdentifier } from '../utils/PersistentStorage' import { default as persistentStorage, StorageIdentifier } from '../utils/PersistentStorage'
import { Dispatch } from 'redux' import { Dispatch } from 'redux'
import { showError } from './Global' import { showError } from './Global'
import * as electron from 'electron'
import { promises as fsPromise } from 'fs' import { promises as fsPromise } from 'fs'
import * as path from 'path' import * as path from 'path'
import { ActionTypes, Action } from '../reducers/ConnectionManager' import { ActionTypes, Action } from '../reducers/ConnectionManager'
import { Subscription } from '../../../backend/src/DataSource/MqttSource' import { Subscription } from '../../../backend/src/DataSource/MqttSource'
import { connectionsMigrator } from './migrations/Connection' import { connectionsMigrator } from './migrations/Connection'
import { EventDispatcher, openDialogResponse, OpenDialogResponse, rendererEvents, requestOpenDialog } from '../../../events' import { rendererRpc } from '../../../events'
import { v4 } from 'uuid' import { makeOpenDialogRpc } from '../../../events/OpenDialogRequest'
export interface ConnectionDictionary { export interface ConnectionDictionary {
[s: string]: ConnectionOptions [s: string]: ConnectionOptions
@@ -74,29 +73,11 @@ async function openCertificate(): Promise<CertificateParameters> {
certificateSizeDoesNotMatch: 'Certificate size larger/smaller then expected.', certificateSizeDoesNotMatch: 'Certificate size larger/smaller then expected.',
} }
let requestId = v4(); const openDialogReturnValue = await rendererRpc.call(makeOpenDialogRpc(), {
properties: ['openFile'],
const response = new Promise<OpenDialogResponse>((resolve, reject) => { securityScopedBookmarks: true,
let callback = (result: OpenDialogResponse) => {
rendererEvents.unsubscribe(openDialogResponse(), callback)
if (result.identifier == requestId) {
resolve(result)
} else {
reject(new Error("Unexpected file select"))
}
}
rendererEvents.subscribe(openDialogResponse(), callback)
}) })
rendererEvents.emit(requestOpenDialog(), {
identifier: requestId,
options: {
properties: ['openFile'],
securityScopedBookmarks: true,
}
})
const openDialogReturnValue = (await response).result;
const selectedFile = openDialogReturnValue.filePaths && openDialogReturnValue.filePaths[0] const selectedFile = openDialogReturnValue.filePaths && openDialogReturnValue.filePaths[0]
if (!selectedFile) { if (!selectedFile) {
throw rejectReasons.noCertificateSelected throw rejectReasons.noCertificateSelected

View File

@@ -1,7 +1,5 @@
import * as React from 'react' import * as React from 'react'
import Add from '@material-ui/icons/Add'
import ClearAdornment from '../helper/ClearAdornment' import ClearAdornment from '../helper/ClearAdornment'
import Delete from '@material-ui/icons/Delete'
import Lock from '@material-ui/icons/Lock' import Lock from '@material-ui/icons/Lock'
import { bindActionCreators } from 'redux' import { bindActionCreators } from 'redux'
import { Button, Theme, Tooltip, Typography } from '@material-ui/core' import { Button, Theme, Tooltip, Typography } from '@material-ui/core'

View File

@@ -5,7 +5,7 @@ let heapdump: any
function writeHeapdump(path?: string) { function writeHeapdump(path?: string) {
if (!heapdump) { if (!heapdump) {
//heapdump = require('heapdump') //<heapdump = require('heapdump')
} }
heapdump.writeSnapshot(path || `${Date.now()}.heapsnapshot`) heapdump.writeSnapshot(path || `${Date.now()}.heapsnapshot`)

View File

@@ -13,6 +13,7 @@ import { Theme, withStyles } from '@material-ui/core/styles'
import { updateNotifierActions } from '../actions' import { updateNotifierActions } from '../actions'
import { Button, IconButton, Modal, Paper, Snackbar, SnackbarContent, Typography } from '@material-ui/core' import { Button, IconButton, Modal, Paper, Snackbar, SnackbarContent, Typography } from '@material-ui/core'
import { rendererRpc, getAppVersion } from '../../../events'
interface Props { interface Props {
showUpdateNotification: boolean showUpdateNotification: boolean
@@ -50,18 +51,21 @@ class UpdateNotifier extends React.PureComponent<Props, State> {
super(props) super(props)
this.state = { newerVersions: [] } this.state = { newerVersions: [] }
const ownVersion = electron.app.getVersion() this.checkForUpdates()
this.fetchReleases().then(releases => { }
const newerVersions = releases
.filter(release => this.allowPrereleaseIfOwnVersionIsBeta(release, ownVersion))
.filter(release => compareVersions(release.tag_name, ownVersion) > 0)
.sort((a, b) => compareVersions(b.tag_name, a.tag_name))
if (newerVersions.length > 0) { private async checkForUpdates() {
this.setState({ newerVersions }) const ownVersion = await rendererRpc.call(getAppVersion, undefined, 10000);
this.props.actions.showUpdateNotification(true) const releases = await this.fetchReleases();
} const newerVersions = releases
}) .filter(release => this.allowPrereleaseIfOwnVersionIsBeta(release, ownVersion))
.filter(release => compareVersions(release.tag_name, ownVersion) > 0)
.sort((a, b) => compareVersions(b.tag_name, a.tag_name))
if (newerVersions.length > 0) {
this.setState({ newerVersions })
this.props.actions.showUpdateNotification(true)
}
} }
private allowPrereleaseIfOwnVersionIsBeta(release: GithubRelease, ownVersion: string) { private allowPrereleaseIfOwnVersionIsBeta(release: GithubRelease, ownVersion: string) {

View File

@@ -1,12 +1,9 @@
import { rendererEvents } from '../../../events' import { rendererRpc } from '../../../events'
import { v4 } from 'uuid'
import { import {
storageStoreEvent, storageStoreEvent,
makeStorageResponseEvent,
storageLoadEvent, storageLoadEvent,
storageClearEvent, storageClearEvent,
makeStorageAcknowledgementEvent,
} from '../../../events/StorageEvents' } from '../../../events/StorageEvents'
export interface StorageIdentifier<Model> { export interface StorageIdentifier<Model> {
@@ -20,71 +17,23 @@ export interface PersistentStorage {
} }
class RemoteStorage implements PersistentStorage { class RemoteStorage implements PersistentStorage {
private timeoutCallback(event: any, callback: any, reject: any) {
setTimeout(() => {
reject('remote storage timeout')
rendererEvents.unsubscribe(event, callback)
}, 10000)
}
private expectAck(transactionId: string): Promise<void> {
const ack = makeStorageAcknowledgementEvent(transactionId)
return new Promise<void>((resolve, reject) => {
const callback = (msg: any) => {
if (msg && msg.error) {
reject(msg.error)
} else {
resolve()
}
rendererEvents.unsubscribe(ack, callback)
}
rendererEvents.subscribe(ack, callback)
this.timeoutCallback(ack, callback, reject)
})
}
public store<Model>(identifier: StorageIdentifier<Model>, data: Model): Promise<void> { public store<Model>(identifier: StorageIdentifier<Model>, data: Model): Promise<void> {
const transactionId = v4() return rendererRpc.call(storageStoreEvent, {
const expectation = this.expectAck(transactionId)
rendererEvents.emit(storageStoreEvent, {
data, data,
transactionId,
store: identifier.id, store: identifier.id,
}) })
return expectation
} }
public load<Model>(identifier: StorageIdentifier<Model>): Promise<Model | undefined> { public async load<Model>(identifier: StorageIdentifier<Model>): Promise<Model | undefined> {
const transactionId = v4() const result = await rendererRpc.call(storageLoadEvent, {
const responseEvent = makeStorageResponseEvent(transactionId)
const promise = new Promise<Model>((resolve, reject) => {
const callback = (msg: any) => {
if (msg.error) {
reject(msg.error)
} else {
resolve(msg.data)
}
rendererEvents.unsubscribe(responseEvent, callback)
}
rendererEvents.subscribe(responseEvent, callback)
this.timeoutCallback(responseEvent, callback, reject)
})
rendererEvents.emit(storageLoadEvent, {
transactionId,
store: identifier.id, store: identifier.id,
}) }, 10000)
return promise return (result as any).data
} }
public clear(): Promise<void> { public clear(): Promise<void> {
const transactionId = v4() return rendererRpc.call(storageClearEvent, undefined, 10000)
const expectation = this.expectAck(transactionId)
rendererEvents.emit(storageClearEvent, { transactionId })
return expectation
} }
} }

View File

@@ -30,6 +30,7 @@ module.exports = {
priority: -10, priority: -10,
}, },
default: { default: {
name: 'default',
minChunks: 2, minChunks: 2,
priority: -20, priority: -20,
reuseExistingChunk: true, reuseExistingChunk: true,

View File

@@ -12,7 +12,7 @@
"postinstall": "yarn build" "postinstall": "yarn build"
}, },
"engines": { "engines": {
"npm": "16" "node": "16"
}, },
"author": "", "author": "",
"license": "CC-BY-ND-4.0", "license": "CC-BY-ND-4.0",

View File

@@ -2,10 +2,8 @@ import * as FileAsync from 'lowdb/adapters/FileAsync'
import * as fs from 'fs-extra' import * as fs from 'fs-extra'
import * as lowdb from 'lowdb' import * as lowdb from 'lowdb'
import * as path from 'path' import * as path from 'path'
import { backendEvents } from '../../events' import { backendRpc } from '../../events'
import { import {
makeStorageAcknowledgementEvent,
makeStorageResponseEvent,
storageClearEvent, storageClearEvent,
storageLoadEvent, storageLoadEvent,
storageStoreEvent, storageStoreEvent,
@@ -32,56 +30,26 @@ export default class ConfigStorage {
} }
public async init() { public async init() {
backendEvents.subscribe(storageStoreEvent, async event => { backendRpc.on(storageStoreEvent, async event => {
const ack = makeStorageAcknowledgementEvent(event.transactionId) const db = await this.getDb()
try { await db.set(event.store, event.data).write()
const db = await this.getDb() return
await db.set(event.store, event.data).write() })
backendEvents.emit(ack, undefined)
} catch (error) { backendRpc.on(storageLoadEvent, async event => {
backendEvents.emit(ack, { const db = await this.getDb()
error, const data = await db.get(event.store).value()
transactionId: event.transactionId, return {
store: event.store, data,
}) store: event.store,
throw error
} }
}) })
backendEvents.subscribe(storageLoadEvent, async event => { backendRpc.on(storageClearEvent, async event => {
const responseEvent = makeStorageResponseEvent(event.transactionId) const db = await this.getDb()
try { const keys = await db.keys().value()
const db = await this.getDb() for (const key of keys) {
const data = await db.get(event.store).value() await db.unset(key).write()
backendEvents.emit(responseEvent, {
data,
transactionId: event.transactionId,
store: event.store,
})
} catch (error) {
backendEvents.emit(responseEvent, {
error,
transactionId: event.transactionId,
store: event.store,
})
throw error
}
})
backendEvents.subscribe(storageClearEvent, async event => {
try {
const db = await this.getDb()
const keys = await db.keys().value()
for (const key of keys) {
await db.unset(key).write()
}
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), undefined)
} catch (error) {
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), {
error,
transactionId: event.transactionId,
})
throw error
} }
}) })
} }

View File

@@ -1,6 +1,6 @@
import { ChangeBuffer } from './ChangeBuffer' import { ChangeBuffer } from './ChangeBuffer'
import { Destroyable } from './Destroyable' import { Destroyable } from './Destroyable'
import { EventBusInterface, EventDispatcher, makeConnectionMessageEvent, MqttMessage } from '../../../events' import { EventDispatcher, makeConnectionMessageEvent, MqttMessage, EventBusInterface } from '../../../events'
import { TreeNode } from './' import { TreeNode } from './'
import { TreeNodeFactory } from './TreeNodeFactory' import { TreeNodeFactory } from './TreeNodeFactory'
@@ -31,7 +31,7 @@ export class Tree<ViewModel extends Destroyable> extends TreeNode<ViewModel> {
if (!this.paused && this.applyChangesHasCompleted) { if (!this.paused && this.applyChangesHasCompleted) {
this.applyChangesHasCompleted = false this.applyChangesHasCompleted = false
if ((window as any).requestIdleCallback) { if ((window as any).requestIdleCallback) {
;(window as any).requestIdleCallback(() => this.applyUnmergedChanges(), { timeout: 500 }) ; (window as any).requestIdleCallback(() => this.applyUnmergedChanges(), { timeout: 500 })
} else { } else {
this.applyUnmergedChanges() this.applyUnmergedChanges()
} }

View File

@@ -1,50 +0,0 @@
import { IpcMain, ipcMain, ipcRenderer } from 'electron'
import { Event } from './Events'
import { IpcRendererEventBus } from './IpcRendererEventBus'
export interface EventBusInterface {
subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void): void
unsubscribeAll<MessageType>(event: Event<MessageType>): void
emit<MessageType>(event: Event<MessageType>, msg: MessageType): void
unsubscribe<MessageType>(event: Event<MessageType>, callback: any): void
}
export interface CallbackStore {
wrappedCallback: any
callback: any
}
class IpcMainEventBus implements EventBusInterface {
private ipc: IpcMain
private client: any
constructor(ipc: IpcMain) {
this.ipc = ipc
}
public subscribe<MessageType>(subscribeEvent: Event<MessageType>, callback: (msg: MessageType) => void) {
console.log('subscribing', subscribeEvent.topic)
this.ipc.on(subscribeEvent.topic, (event: any, arg: any) => {
this.client = event.sender
callback(arg)
})
}
public unsubscribeAll<MessageType>(event: Event<MessageType>) {
console.log('unsubscribeAll', event.topic)
this.ipc.removeAllListeners(event.topic)
}
public unsubscribe<MessageType>(event: Event<MessageType>, callback: any) {
throw new Error('Not implemented') // Todo: implement
}
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
if (!this.client.isDestroyed()) {
this.client.send(event.topic, msg)
}
}
}
export const rendererEvents = new IpcRendererEventBus(ipcRenderer)
export const backendEvents = new IpcMainEventBus(ipcMain)

View File

@@ -0,0 +1,4 @@
export interface CallbackStore {
wrappedCallback: any;
callback: any;
}

View File

@@ -0,0 +1,12 @@
import { ipcMain, ipcRenderer } from 'electron'
import { IpcMainEventBus } from './IpcMainEventBus'
import { IpcRendererEventBus } from './IpcRendererEventBus'
import { Rpc } from './Rpc'
export const rendererEvents = new IpcRendererEventBus(ipcRenderer)
export const backendEvents = new IpcMainEventBus(ipcMain)
// Preferred way to communicate typesafe
export const rendererRpc = new Rpc(rendererEvents)
export const backendRpc = new Rpc(backendEvents)

View File

@@ -0,0 +1,8 @@
import { Event } from '../Events';
export interface EventBusInterface {
subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void): void;
unsubscribeAll<MessageType>(event: Event<MessageType>): void;
emit<MessageType>(event: Event<MessageType>, msg: MessageType): void;
unsubscribe<MessageType>(event: Event<MessageType>, callback: any): void;
}

View File

@@ -0,0 +1,34 @@
import { IpcMain } from 'electron';
import { Event } from '../Events';
import { EventBusInterface } from "./EventBusInterface";
export class IpcMainEventBus implements EventBusInterface {
private ipc: IpcMain;
private client: any;
constructor(ipc: IpcMain) {
this.ipc = ipc;
}
public subscribe<MessageType>(subscribeEvent: Event<MessageType>, callback: (msg: MessageType) => void) {
console.log('subscribing', subscribeEvent.topic);
this.ipc.on(subscribeEvent.topic, (event: any, arg: any) => {
this.client = event.sender;
callback(arg);
});
}
public unsubscribeAll<MessageType>(event: Event<MessageType>) {
console.log('unsubscribeAll', event.topic);
this.ipc.removeAllListeners(event.topic);
}
public unsubscribe<MessageType>(event: Event<MessageType>, callback: any) {
throw new Error('Not implemented'); // Todo: implement
}
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
if (!this.client.isDestroyed()) {
this.client.send(event.topic, msg);
}
}
}

View File

@@ -1,26 +1,32 @@
import { CallbackStore, EventBusInterface } from './EventBus' import { CallbackStore } from "./CallbackStore"
import { Event } from './Events' import { EventBusInterface } from "./EventBusInterface"
import { Event } from '../Events'
import { IpcRenderer } from 'electron' import { IpcRenderer } from 'electron'
export class IpcRendererEventBus implements EventBusInterface { export class IpcRendererEventBus implements EventBusInterface {
private ipc: IpcRenderer private ipc: IpcRenderer
private callbacks: Array<CallbackStore> = [] private callbacks: Array<CallbackStore> = []
constructor(ipc: IpcRenderer) { constructor(ipc: IpcRenderer) {
this.ipc = ipc this.ipc = ipc
} }
public subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void) { public subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void) {
const wrappedCallback = (_: any, arg: any) => { const wrappedCallback = (_: any, arg: any) => {
callback(arg) callback(arg)
} }
console.log("subscribing", event.topic)
this.ipc.on(event.topic, wrappedCallback) this.ipc.on(event.topic, wrappedCallback)
this.callbacks.push({ this.callbacks.push({
callback, callback,
wrappedCallback, wrappedCallback,
}) })
} }
public unsubscribeAll<MessageType>(event: Event<MessageType>) { public unsubscribeAll<MessageType>(event: Event<MessageType>) {
this.ipc.removeAllListeners(event.topic) this.ipc.removeAllListeners(event.topic)
} }
public unsubscribe<MessageType>(event: Event<MessageType>, callback: any) { public unsubscribe<MessageType>(event: Event<MessageType>, callback: any) {
const item = this.callbacks.find(store => store.callback === callback) const item = this.callbacks.find(store => store.callback === callback)
if (!item) { if (!item) {
@@ -29,6 +35,7 @@ export class IpcRendererEventBus implements EventBusInterface {
this.ipc.removeListener(event.topic, item.wrappedCallback) this.ipc.removeListener(event.topic, item.wrappedCallback)
this.callbacks = this.callbacks.filter(a => a !== item) this.callbacks = this.callbacks.filter(a => a !== item)
} }
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) { public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
this.ipc.send(event.topic, msg) this.ipc.send(event.topic, msg)
} }

54
events/EventSystem/Rpc.ts Normal file
View File

@@ -0,0 +1,54 @@
import { Event } from '../Events';
import { EventBusInterface } from './EventBusInterface'
import { v4 } from 'uuid';
export type RpcEvent<RequstType, ResponseType> = {
topic: string;
};
export class Rpc {
constructor(private participant: EventBusInterface) { }
async call<RpcRequest, RpcResponse>(event: RpcEvent<RpcRequest, RpcResponse>, request: RpcRequest, timeout: number = 0): Promise<RpcResponse> {
return new Promise((resolve, reject) => {
let id = v4();
let responseEvent: Event<any> = { topic: `${event.topic}/response` };
let requestEvent: Event<any> = { topic: `${event.topic}/request` };
let callback = (result: { id: string; payload: RpcResponse; error: unknown }) => {
this.participant.unsubscribe(responseEvent as any, callback);
if (result.id === id) {
if (result.error) {
reject(result.error)
} else {
resolve(result.payload);
}
}
console.log("received", result)
};
this.participant.subscribe(responseEvent, callback);
this.participant.emit(requestEvent, { id, payload: request });
if (timeout > 0) {
setTimeout(() => {
reject(new Error(`Did not respond to ${event.topic} within ${timeout}ms`))
this.participant.unsubscribe(responseEvent as any, callback);
}, 10000)
}
});
}
async on<RpcRequest, RpcResponse>(event: RpcEvent<RpcRequest, RpcResponse>, handler: (request: RpcRequest) => Promise<RpcResponse>) {
this.participant.subscribe<RpcRequest>({ topic: `${event.topic}/request` } as RpcEvent<any, any>, async (request) => {
let payload;
let error;
try {
payload = await handler((request as any).payload);
} catch (e) {
error = e
}
console.log("Responding with", payload, error)
this.participant.emit({ topic: `${event.topic}/response` }, { id: (request as any).id, payload, error });
});
}
}

View File

@@ -1,7 +1,7 @@
import { Base64Message } from '../backend/src/Model/Base64Message' import { Base64Message } from '../backend/src/Model/Base64Message'
import { DataSourceState, MqttOptions } from '../backend/src/DataSource' import { DataSourceState, MqttOptions } from '../backend/src/DataSource'
import { UpdateInfo } from 'builder-util-runtime' import { UpdateInfo } from 'builder-util-runtime'
import { OpenDialogOptions, OpenDialogReturnValue } from 'electron' import { RpcEvent } from './EventSystem/Rpc';
export type Event<MessageType> = { export type Event<MessageType> = {
topic: string topic: string
@@ -51,25 +51,6 @@ export function makeConnectionMessageEvent(connectionId: string): Event<MqttMess
} }
} }
export interface OpenDialogRequest { export const getAppVersion: RpcEvent<void, string> = {
identifier: string topic: `getAppVersion`
options: OpenDialogOptions
} }
export function requestOpenDialog(): Event<OpenDialogRequest> {
return {
topic: `requestOpenDialog`,
}
}
export interface OpenDialogResponse {
identifier: string
result: OpenDialogReturnValue
}
export function openDialogResponse(): Event<OpenDialogResponse> {
return {
topic: `openDialogResponse`,
}
}

View File

@@ -0,0 +1,8 @@
import { OpenDialogOptions, OpenDialogReturnValue } from 'electron';
import { RpcEvent } from './EventSystem/Rpc';
export function makeOpenDialogRpc(): RpcEvent<OpenDialogOptions, OpenDialogReturnValue> {
return {
topic: `openDialog`
};
}

View File

View File

@@ -1,39 +1,22 @@
import { Event } from './' import { RpcEvent } from './EventSystem/Rpc'
interface StorageEvent { export interface StoreCommand {
transactionId: string store: string
data: any
} }
export interface StoreCommand extends StorageEvent { export interface LoadCommand {
store?: string
data?: any
error?: any
}
export interface LoadCommand extends StorageEvent {
store: string store: string
} }
export const storageStoreEvent: Event<StoreCommand> = { export const storageStoreEvent: RpcEvent<StoreCommand, void> = {
topic: 'storage/store', topic: 'storage/store',
} }
export const storageLoadEvent: Event<LoadCommand> = { export const storageLoadEvent: RpcEvent<LoadCommand, StoreCommand> = {
topic: 'storage/load', topic: 'storage/load',
} }
export function makeStorageAcknowledgementEvent(transactionId: string): Event<StoreCommand> { export const storageClearEvent: RpcEvent<void, void> = {
return {
topic: `storage/ack/${transactionId}`,
}
}
export function makeStorageResponseEvent(transactionId: string): Event<StoreCommand> {
return {
topic: `storage/response/${transactionId}`,
}
}
export const storageClearEvent: Event<StorageEvent> = {
topic: 'storage/clear', topic: 'storage/clear',
} }

View File

@@ -1,3 +1,4 @@
export * from './Events' export * from './Events'
export * from './EventDispatcher' export * from './EventSystem/EventDispatcher'
export * from './EventBus' export * from './EventSystem/EventBus'
export * from './EventSystem/EventBusInterface'

82
package-lock.json generated
View File

@@ -22,6 +22,7 @@
"mime": "^2.4.4", "mime": "^2.4.4",
"mqtt": "^3.0.0", "mqtt": "^3.0.0",
"sha1": "^1.1.1", "sha1": "^1.1.1",
"uuid": "^8.3.2",
"yarn-run-all": "^3.1.1" "yarn-run-all": "^3.1.1"
}, },
"devDependencies": { "devDependencies": {
@@ -35,6 +36,7 @@
"@types/node": "^12.6.8", "@types/node": "^12.6.8",
"@types/semver": "7", "@types/semver": "7",
"@types/sha1": "^1.1.1", "@types/sha1": "^1.1.1",
"@types/uuid": "^8.3.4",
"builder-util-runtime": "^9", "builder-util-runtime": "^9",
"chai": "^4.2.0", "chai": "^4.2.0",
"cspell": "^4.0.28", "cspell": "^4.0.28",
@@ -52,6 +54,9 @@
"ts-node": "^10.5.0", "ts-node": "^10.5.0",
"typescript": "^4.5.5", "typescript": "^4.5.5",
"webdriverio": "7.16" "webdriverio": "7.16"
},
"engines": {
"node": "16"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@@ -1133,6 +1138,12 @@
"integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==",
"dev": true "dev": true
}, },
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"node_modules/@types/verror": { "node_modules/@types/verror": {
"version": "1.10.5", "version": "1.10.5",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.5.tgz", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.5.tgz",
@@ -3026,15 +3037,6 @@
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
"dev": true "dev": true
}, },
"node_modules/devtools/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/diff": { "node_modules/diff": {
"version": "3.5.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
@@ -5113,6 +5115,16 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/istanbul-lib-processinfo/node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"dev": true,
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/istanbul-lib-report": { "node_modules/istanbul-lib-report": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
@@ -8243,15 +8255,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/spectron/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/spectron/node_modules/webdriver": { "node_modules/spectron/node_modules/webdriver": {
"version": "7.16.13", "version": "7.16.13",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.13.tgz", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.13.tgz",
@@ -8979,13 +8982,11 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "3.4.0", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"dev": true,
"bin": { "bin": {
"uuid": "bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/v8-compile-cache-lib": { "node_modules/v8-compile-cache-lib": {
@@ -10899,6 +10900,12 @@
"integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==",
"dev": true "dev": true
}, },
"@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"@types/verror": { "@types/verror": {
"version": "1.10.5", "version": "1.10.5",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.5.tgz", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.5.tgz",
@@ -12384,12 +12391,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
"dev": true "dev": true
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
} }
} }
}, },
@@ -13966,6 +13967,14 @@
"p-map": "^3.0.0", "p-map": "^3.0.0",
"rimraf": "^3.0.0", "rimraf": "^3.0.0",
"uuid": "^3.3.3" "uuid": "^3.3.3"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
} }
}, },
"istanbul-lib-report": { "istanbul-lib-report": {
@@ -16435,12 +16444,6 @@
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true "dev": true
}, },
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
},
"webdriver": { "webdriver": {
"version": "7.16.13", "version": "7.16.13",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.13.tgz", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.13.tgz",
@@ -17019,10 +17022,9 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
}, },
"uuid": { "uuid": {
"version": "3.4.0", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"dev": true
}, },
"v8-compile-cache-lib": { "v8-compile-cache-lib": {
"version": "3.0.0", "version": "3.0.0",

View File

@@ -4,7 +4,7 @@
"description": "Explore your message queues", "description": "Explore your message queues",
"main": "dist/src/electron.js", "main": "dist/src/electron.js",
"engines": { "engines": {
"npm": "16" "node": "16"
}, },
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
@@ -83,6 +83,7 @@
"@types/node": "^12.6.8", "@types/node": "^12.6.8",
"@types/semver": "7", "@types/semver": "7",
"@types/sha1": "^1.1.1", "@types/sha1": "^1.1.1",
"@types/uuid": "^8.3.4",
"builder-util-runtime": "^9", "builder-util-runtime": "^9",
"chai": "^4.2.0", "chai": "^4.2.0",
"cspell": "^4.0.28", "cspell": "^4.0.28",
@@ -114,6 +115,7 @@
"mime": "^2.4.4", "mime": "^2.4.4",
"mqtt": "^3.0.0", "mqtt": "^3.0.0",
"sha1": "^1.1.1", "sha1": "^1.1.1",
"uuid": "^8.3.2",
"yarn-run-all": "^3.1.1" "yarn-run-all": "^3.1.1"
} }
} }

View File

@@ -1,7 +1,7 @@
import * as log from 'electron-log' import * as log from 'electron-log'
import * as path from 'path' import * as path from 'path'
import ConfigStorage from '../backend/src/ConfigStorage' import ConfigStorage from '../backend/src/ConfigStorage'
import { ipcMain, app, BrowserWindow, Menu, dialog } from 'electron' import { app, BrowserWindow, Menu, dialog } from 'electron'
import { autoUpdater } from 'electron-updater' import { autoUpdater } from 'electron-updater'
import { ConnectionManager } from '../backend/src/index' import { ConnectionManager } from '../backend/src/index'
// import { electronTelemetryFactory } from 'electron-telemetry' // import { electronTelemetryFactory } from 'electron-telemetry'
@@ -10,7 +10,8 @@ import buildOptions from './buildOptions'
import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development' import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development'
import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater' import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater'
import { registerCrashReporter } from './registerCrashReporter' import { registerCrashReporter } from './registerCrashReporter'
import { backendEvents, EventDispatcher, OpenDialogRequest, openDialogResponse, OpenDialogResponse, requestOpenDialog } from '../events' import { makeOpenDialogRpc } from '../events/OpenDialogRequest'
import { backendRpc, getAppVersion } from '../events'
registerCrashReporter() registerCrashReporter()
@@ -20,14 +21,10 @@ registerCrashReporter()
app.commandLine.appendSwitch('--no-sandbox') app.commandLine.appendSwitch('--no-sandbox')
app.whenReady().then(() => { app.whenReady().then(() => {
backendEvents.subscribe(requestOpenDialog(), async (request) => { backendRpc.on(makeOpenDialogRpc(), async (request) => {
let result = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request.options) return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
backendEvents.emit(openDialogResponse(), {
identifier: request.identifier,
result: result
})
}) })
backendRpc.on(getAppVersion, async () => app.getVersion())
}) })
autoUpdater.logger = log autoUpdater.logger = log

View File

@@ -661,6 +661,11 @@
"resolved" "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz" "resolved" "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz"
"version" "0.7.36" "version" "0.7.36"
"@types/uuid@^8.3.4":
"integrity" "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
"resolved" "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz"
"version" "8.3.4"
"@types/verror@^1.10.3": "@types/verror@^1.10.3":
"integrity" "sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw==" "integrity" "sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw=="
"resolved" "https://registry.npmjs.org/@types/verror/-/verror-1.10.5.tgz" "resolved" "https://registry.npmjs.org/@types/verror/-/verror-1.10.5.tgz"
@@ -5295,7 +5300,7 @@
"resolved" "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" "resolved" "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"
"version" "3.4.0" "version" "3.4.0"
"uuid@^8.0.0": "uuid@^8.0.0", "uuid@^8.3.2":
"integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" "integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" "resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
"version" "8.3.2" "version" "8.3.2"