@cityofzion/bs-electron 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.d.ts +3 -0
- package/dist/main.js +114 -0
- package/dist/package.json +32 -0
- package/dist/preload.d.ts +1 -0
- package/dist/preload.js +16 -0
- package/dist/renderer.d.ts +2 -0
- package/dist/renderer.js +65 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.js +71 -0
- package/package.json +32 -0
package/dist/main.d.ts
ADDED
package/dist/main.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.exposeApiToRenderer = exports.eraseApi = void 0;
|
|
16
|
+
const utils_1 = require("./utils");
|
|
17
|
+
const utils_2 = require("./utils");
|
|
18
|
+
const electron_1 = require("electron");
|
|
19
|
+
const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep"));
|
|
20
|
+
const exposedApis = new Map();
|
|
21
|
+
let initialized = false;
|
|
22
|
+
// Erase the api to expose all methods and properties to renderer. It also supports nested objects
|
|
23
|
+
function eraseApi(api, prefix) {
|
|
24
|
+
const { asyncMethods, syncMethods, properties } = (0, utils_1.getPropertiesAndMethods)(api);
|
|
25
|
+
// As it is a recursive function, we need to add the prefix to the methods and properties
|
|
26
|
+
const response = {
|
|
27
|
+
asyncMethods: asyncMethods.map(method => (prefix ? `${prefix}.${method}` : method)),
|
|
28
|
+
syncMethods: syncMethods.map(method => (prefix ? `${prefix}.${method}` : method)),
|
|
29
|
+
properties: properties.map(property => (prefix ? `${prefix}.${property}` : property)),
|
|
30
|
+
};
|
|
31
|
+
// Iterate for all properties to discover nested objects
|
|
32
|
+
properties.forEach(property => {
|
|
33
|
+
const propertyValue = api[property];
|
|
34
|
+
// If the property is not an object, we don't need to iterate over it. Array is also considered an object so we need to disconsider it
|
|
35
|
+
if (typeof propertyValue !== 'object' ||
|
|
36
|
+
Array.isArray(propertyValue) ||
|
|
37
|
+
propertyValue instanceof Map ||
|
|
38
|
+
propertyValue instanceof Set)
|
|
39
|
+
return;
|
|
40
|
+
const propertyWithPrefix = prefix ? `${prefix}.${property}` : property;
|
|
41
|
+
// Recursive call to discover nested properties and methods
|
|
42
|
+
const nestedPropertiesAndMethods = eraseApi(propertyValue, propertyWithPrefix);
|
|
43
|
+
// Add the nested properties and methods to the response
|
|
44
|
+
response.syncMethods.push(...nestedPropertiesAndMethods.syncMethods);
|
|
45
|
+
response.asyncMethods.push(...nestedPropertiesAndMethods.asyncMethods);
|
|
46
|
+
response.properties.push(...nestedPropertiesAndMethods.properties);
|
|
47
|
+
// Remove the actual property because it is a instance and can't be serialized by ipc
|
|
48
|
+
response.properties.splice(response.properties.indexOf(propertyWithPrefix), 1);
|
|
49
|
+
});
|
|
50
|
+
return response;
|
|
51
|
+
}
|
|
52
|
+
exports.eraseApi = eraseApi;
|
|
53
|
+
function exposeApiToRenderer(api) {
|
|
54
|
+
init();
|
|
55
|
+
// Class name
|
|
56
|
+
const apiName = api.constructor.name;
|
|
57
|
+
const apiIsAlreadyExposed = exposedApis.has(apiName);
|
|
58
|
+
if (apiIsAlreadyExposed) {
|
|
59
|
+
console.warn(`API ${apiName} is already exposed to renderer`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const apiClone = (0, lodash_clonedeep_1.default)(api);
|
|
63
|
+
const { asyncMethods, properties, syncMethods } = eraseApi(apiClone);
|
|
64
|
+
// For each property, we need to create a listener so renderer can request the property value
|
|
65
|
+
properties.forEach(property => {
|
|
66
|
+
electron_1.ipcMain.on((0, utils_2.buildIpcChannelName)(apiName, property), event => {
|
|
67
|
+
try {
|
|
68
|
+
const value = (0, utils_1.getValueFromPath)(api, property);
|
|
69
|
+
event.returnValue = { data: value };
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
event.returnValue = { error: error.message };
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
// For each syncMethods, we need to create a listener so renderer can request the property value. We need to bind the function to the api instance to keep the context, the getValueFromPath function will do this for us
|
|
77
|
+
syncMethods.forEach(method => {
|
|
78
|
+
electron_1.ipcMain.on((0, utils_2.buildIpcChannelName)(apiName, method), (event, ...args) => {
|
|
79
|
+
try {
|
|
80
|
+
const func = (0, utils_1.getValueFromPath)(api, method);
|
|
81
|
+
const data = func(...args);
|
|
82
|
+
event.returnValue = { data };
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
event.returnValue = { error: error.message };
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// For each asyncMethods, we need to create a listener so renderer can request the property value. We need to bind the function to the api instance to keep the context, the getValueFromPath function will do this for us
|
|
90
|
+
asyncMethods.forEach(method => {
|
|
91
|
+
electron_1.ipcMain.handle((0, utils_2.buildIpcChannelName)(apiName, method), (_event, ...args) => __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
try {
|
|
93
|
+
const func = (0, utils_1.getValueFromPath)(api, method);
|
|
94
|
+
const data = yield func(...args);
|
|
95
|
+
return { data };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return { error: error.message };
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
exposedApis.set(apiName, { properties, asyncMethods, syncMethods });
|
|
103
|
+
}
|
|
104
|
+
exports.exposeApiToRenderer = exposeApiToRenderer;
|
|
105
|
+
// This function is called only once to initialize the ipcMain listener so renderer can request the exposed api
|
|
106
|
+
function init() {
|
|
107
|
+
if (initialized)
|
|
108
|
+
return;
|
|
109
|
+
initialized = true;
|
|
110
|
+
electron_1.ipcMain.on(utils_1.GET_EXPOSED_API_CHANNEL, (event, apiName) => {
|
|
111
|
+
const exposedApi = exposedApis.get(apiName);
|
|
112
|
+
event.returnValue = exposedApi;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cityofzion/bs-electron",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"repository": "https://github.com/CityOfZion/blockchain-services",
|
|
5
|
+
"author": "Coz",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"/dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"format": "eslint --fix"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@cityofzion/blockchain-service": "workspace:*",
|
|
17
|
+
"lodash.clonedeep": "~4.5.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "~20.2.5",
|
|
21
|
+
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
|
22
|
+
"@typescript-eslint/parser": "^6.5.0",
|
|
23
|
+
"eslint": "^8.48.0",
|
|
24
|
+
"ts-node": "10.9.1",
|
|
25
|
+
"typescript": "4.9.5",
|
|
26
|
+
"@types/lodash.clonedeep": "~4.5.9"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"electron": "*",
|
|
30
|
+
"@electron-toolkit/preload": "*"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/preload.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const electron_1 = require("electron");
|
|
4
|
+
const preload_1 = require("@electron-toolkit/preload");
|
|
5
|
+
if (process.contextIsolated) {
|
|
6
|
+
try {
|
|
7
|
+
electron_1.contextBridge.exposeInMainWorld('ipcBsElectron', preload_1.electronAPI.ipcRenderer);
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
console.error(error);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
// @ts-ignore (define in dts)
|
|
15
|
+
window.ipcBsElectron = ipcRenderer;
|
|
16
|
+
}
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.bindApiFromMain = void 0;
|
|
13
|
+
const utils_1 = require("./utils");
|
|
14
|
+
const boundApis = new Map();
|
|
15
|
+
function bindApiFromMain(apiName) {
|
|
16
|
+
const boundApi = boundApis.get(apiName);
|
|
17
|
+
if (boundApi) {
|
|
18
|
+
return boundApi;
|
|
19
|
+
}
|
|
20
|
+
// Request the exposed api from main
|
|
21
|
+
const exposedApi = window.ipcBsElectron.sendSync(utils_1.GET_EXPOSED_API_CHANNEL, apiName);
|
|
22
|
+
if (!exposedApi) {
|
|
23
|
+
throw new Error(`API ${apiName} is not exposed to renderer`);
|
|
24
|
+
}
|
|
25
|
+
const api = {};
|
|
26
|
+
// For each property, we need to populate the api with a getter so the renderer can request the property value from main
|
|
27
|
+
exposedApi.properties.forEach(property => {
|
|
28
|
+
(0, utils_1.populateObjectFromPath)(api, String(property), {
|
|
29
|
+
get: () => {
|
|
30
|
+
const result = window.ipcBsElectron.sendSync((0, utils_1.buildIpcChannelName)(apiName, property));
|
|
31
|
+
if (result.error) {
|
|
32
|
+
throw new Error('Ipc only supports stringified data');
|
|
33
|
+
}
|
|
34
|
+
return result.data;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
// For each syncMethods, we need to populate the api with a function that will send a sync message to main to request the method execution result
|
|
39
|
+
exposedApi.syncMethods.forEach(method => {
|
|
40
|
+
(0, utils_1.populateObjectFromPath)(api, String(method), {
|
|
41
|
+
value: (...args) => {
|
|
42
|
+
const result = window.ipcBsElectron.sendSync((0, utils_1.buildIpcChannelName)(apiName, method), ...args);
|
|
43
|
+
if (result.error) {
|
|
44
|
+
throw new Error(result.error);
|
|
45
|
+
}
|
|
46
|
+
return result.data;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
// For each asyncMethods, we need to populate the api with a function that will send a sync message to main to request the method execution result
|
|
51
|
+
exposedApi.asyncMethods.forEach(method => {
|
|
52
|
+
(0, utils_1.populateObjectFromPath)(api, String(method), {
|
|
53
|
+
value: (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
const result = yield window.ipcBsElectron.invoke((0, utils_1.buildIpcChannelName)(apiName, method), ...args);
|
|
55
|
+
if (result.error) {
|
|
56
|
+
throw new Error(result.error);
|
|
57
|
+
}
|
|
58
|
+
return result.data;
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
boundApis.set(apiName, api);
|
|
63
|
+
return api;
|
|
64
|
+
}
|
|
65
|
+
exports.bindApiFromMain = bindApiFromMain;
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const CHANNEL_PREFIX = "bsElectron";
|
|
2
|
+
export declare const GET_EXPOSED_API_CHANNEL: string;
|
|
3
|
+
export type TApi = {
|
|
4
|
+
[K in keyof any]: any;
|
|
5
|
+
};
|
|
6
|
+
export type TExposedApi = {
|
|
7
|
+
properties: string[];
|
|
8
|
+
syncMethods: string[];
|
|
9
|
+
asyncMethods: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare function getPropertiesAndMethods(object: any): {
|
|
12
|
+
properties: string[];
|
|
13
|
+
syncMethods: string[];
|
|
14
|
+
asyncMethods: string[];
|
|
15
|
+
};
|
|
16
|
+
export declare function getValueFromPath(obj: any, path: string): any;
|
|
17
|
+
export declare function populateObjectFromPath(obj: any, path: string, value: PropertyDescriptor & ThisType<any>): any;
|
|
18
|
+
export declare function buildIpcChannelName<T>(apiName: string, methodOrProperty: keyof T): string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildIpcChannelName = exports.populateObjectFromPath = exports.getValueFromPath = exports.getPropertiesAndMethods = exports.GET_EXPOSED_API_CHANNEL = exports.CHANNEL_PREFIX = void 0;
|
|
4
|
+
exports.CHANNEL_PREFIX = 'bsElectron';
|
|
5
|
+
exports.GET_EXPOSED_API_CHANNEL = `${exports.CHANNEL_PREFIX}:getExposedApi`;
|
|
6
|
+
// It returns all properties and methods from an object and its prototype chain, that is, extended classes are also considered
|
|
7
|
+
function getPropertiesAndMethods(object) {
|
|
8
|
+
const syncMethods = new Set();
|
|
9
|
+
const asyncMethods = new Set();
|
|
10
|
+
const properties = new Set();
|
|
11
|
+
do {
|
|
12
|
+
for (const key of Reflect.ownKeys(object)) {
|
|
13
|
+
if (key === 'constructor')
|
|
14
|
+
continue;
|
|
15
|
+
const keyString = String(key);
|
|
16
|
+
if (typeof object[key] === 'function') {
|
|
17
|
+
try {
|
|
18
|
+
const funcResponse = object[key].call(object, {});
|
|
19
|
+
if (funcResponse instanceof Promise) {
|
|
20
|
+
funcResponse.catch(() => { }).then(() => { });
|
|
21
|
+
asyncMethods.add(keyString);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
syncMethods.add(keyString);
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
syncMethods.add(keyString);
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
properties.add(keyString);
|
|
32
|
+
}
|
|
33
|
+
} while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype);
|
|
34
|
+
return {
|
|
35
|
+
properties: Array.from(properties),
|
|
36
|
+
syncMethods: Array.from(syncMethods),
|
|
37
|
+
asyncMethods: Array.from(asyncMethods),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
exports.getPropertiesAndMethods = getPropertiesAndMethods;
|
|
41
|
+
function getValueFromPath(obj, path) {
|
|
42
|
+
return path.split('.').reduce((acc, key) => {
|
|
43
|
+
// If the key is a function, we need to bind it to the object to keep the context
|
|
44
|
+
if (typeof acc[key] === 'function') {
|
|
45
|
+
return acc[key].bind(acc);
|
|
46
|
+
}
|
|
47
|
+
return acc[key];
|
|
48
|
+
}, obj);
|
|
49
|
+
}
|
|
50
|
+
exports.getValueFromPath = getValueFromPath;
|
|
51
|
+
function populateObjectFromPath(obj, path, value) {
|
|
52
|
+
var _a;
|
|
53
|
+
const splittedPath = path.split('.');
|
|
54
|
+
let tempObj = obj;
|
|
55
|
+
for (let i = 0; i < splittedPath.length; i++) {
|
|
56
|
+
const property = splittedPath[i];
|
|
57
|
+
if (i === splittedPath.length - 1) {
|
|
58
|
+
Object.defineProperty(tempObj, property, value);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
tempObj[property] = (_a = tempObj[property]) !== null && _a !== void 0 ? _a : {};
|
|
62
|
+
tempObj = tempObj[property];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return obj;
|
|
66
|
+
}
|
|
67
|
+
exports.populateObjectFromPath = populateObjectFromPath;
|
|
68
|
+
function buildIpcChannelName(apiName, methodOrProperty) {
|
|
69
|
+
return `${exports.CHANNEL_PREFIX}:${apiName}:${String(methodOrProperty)}`;
|
|
70
|
+
}
|
|
71
|
+
exports.buildIpcChannelName = buildIpcChannelName;
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cityofzion/bs-electron",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"repository": "https://github.com/CityOfZion/blockchain-services",
|
|
5
|
+
"author": "Coz",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"/dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && cp package.json dist/",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"format": "eslint --fix"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@cityofzion/blockchain-service": "workspace:*",
|
|
17
|
+
"lodash.clonedeep": "~4.5.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "~20.2.5",
|
|
21
|
+
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
|
22
|
+
"@typescript-eslint/parser": "^6.5.0",
|
|
23
|
+
"eslint": "^8.48.0",
|
|
24
|
+
"ts-node": "10.9.1",
|
|
25
|
+
"typescript": "4.9.5",
|
|
26
|
+
"@types/lodash.clonedeep": "~4.5.9"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"electron": "*",
|
|
30
|
+
"@electron-toolkit/preload": "*"
|
|
31
|
+
}
|
|
32
|
+
}
|