@cordy/electro 1.2.16 → 1.2.18
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/index.d.mts +1 -0
- package/dist/index.mjs +95 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -774,6 +774,7 @@ declare class Runtime {
|
|
|
774
774
|
private readonly windowManager;
|
|
775
775
|
private readonly viewManager;
|
|
776
776
|
private readonly eventBridge;
|
|
777
|
+
private readonly ipcBridge;
|
|
777
778
|
constructor(config?: RuntimeConfig);
|
|
778
779
|
register(features: FeatureConfig<any> | FeatureConfig<any>[]): void;
|
|
779
780
|
start(): Promise<void>;
|
package/dist/index.mjs
CHANGED
|
@@ -864,6 +864,92 @@ var FeatureManager = class {
|
|
|
864
864
|
}
|
|
865
865
|
};
|
|
866
866
|
|
|
867
|
+
//#endregion
|
|
868
|
+
//#region src/core/ipc/bridge.ts
|
|
869
|
+
/**
|
|
870
|
+
* Registers invoke handlers for exposed service methods and enforces
|
|
871
|
+
* per-view feature access at runtime.
|
|
872
|
+
*/
|
|
873
|
+
var IpcBridge = class {
|
|
874
|
+
viewFeatures = /* @__PURE__ */ new Map();
|
|
875
|
+
ipcMainOverride;
|
|
876
|
+
ipcMain = null;
|
|
877
|
+
registeredChannels = /* @__PURE__ */ new Set();
|
|
878
|
+
constructor(featureManager, viewManager, viewRegistry, options = {}) {
|
|
879
|
+
this.featureManager = featureManager;
|
|
880
|
+
this.viewManager = viewManager;
|
|
881
|
+
this.ipcMainOverride = options.ipcMain ?? null;
|
|
882
|
+
for (const entry of viewRegistry) {
|
|
883
|
+
if (!entry.hasRenderer) continue;
|
|
884
|
+
this.viewFeatures.set(entry.id, new Set(entry.features ?? []));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
async start() {
|
|
888
|
+
if (this.ipcMain) return;
|
|
889
|
+
const ipcMain = this.ipcMainOverride ?? await this.resolveIpcMain();
|
|
890
|
+
if (!ipcMain) return;
|
|
891
|
+
this.ipcMain = ipcMain;
|
|
892
|
+
this.registerHandlers();
|
|
893
|
+
}
|
|
894
|
+
stop() {
|
|
895
|
+
if (!this.ipcMain) return;
|
|
896
|
+
for (const channel of this.registeredChannels) this.ipcMain.removeHandler(channel);
|
|
897
|
+
this.registeredChannels.clear();
|
|
898
|
+
this.ipcMain = null;
|
|
899
|
+
}
|
|
900
|
+
async resolveIpcMain() {
|
|
901
|
+
if (!process.versions.electron) return null;
|
|
902
|
+
try {
|
|
903
|
+
const candidate = (await import("electron")).ipcMain;
|
|
904
|
+
if (!candidate) return null;
|
|
905
|
+
if (typeof candidate.handle !== "function") return null;
|
|
906
|
+
if (typeof candidate.removeHandler !== "function") return null;
|
|
907
|
+
return candidate;
|
|
908
|
+
} catch {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
registerHandlers() {
|
|
913
|
+
if (!this.ipcMain) return;
|
|
914
|
+
for (const feature of this.featureManager.list()) for (const service of feature.config.services ?? []) {
|
|
915
|
+
if (service.scope !== ServiceScope.EXPOSED) continue;
|
|
916
|
+
const api = (feature.serviceManager?.get(service.id))?.api;
|
|
917
|
+
if (!api || typeof api !== "object") continue;
|
|
918
|
+
for (const [method, target] of Object.entries(api)) {
|
|
919
|
+
if (typeof target !== "function") continue;
|
|
920
|
+
const channel = `${feature.id}:${service.id}:${method}`;
|
|
921
|
+
if (this.registeredChannels.has(channel)) continue;
|
|
922
|
+
this.ipcMain.handle(channel, async (event, ...args) => {
|
|
923
|
+
return this.invoke(event, feature.id, service.id, method, args);
|
|
924
|
+
});
|
|
925
|
+
this.registeredChannels.add(channel);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
async invoke(event, featureId, serviceId, method, args) {
|
|
930
|
+
this.assertAccess(event.sender.id, featureId, serviceId, method);
|
|
931
|
+
const channel = `${featureId}:${serviceId}:${method}`;
|
|
932
|
+
const resolved = this.featureManager.get(featureId)?.serviceManager?.get(serviceId);
|
|
933
|
+
if (!resolved || resolved.scope !== ServiceScope.EXPOSED) throw new Error(`No handler registered for '${channel}'`);
|
|
934
|
+
const fn = resolved.api?.[method];
|
|
935
|
+
if (typeof fn !== "function") throw new Error(`No handler registered for '${channel}'`);
|
|
936
|
+
return await Promise.resolve(fn(...args));
|
|
937
|
+
}
|
|
938
|
+
assertAccess(senderWebContentsId, featureId, serviceId, method) {
|
|
939
|
+
const viewId = this.resolveSenderViewId(senderWebContentsId);
|
|
940
|
+
if (!viewId) throw new Error(`Access denied for '${featureId}:${serviceId}:${method}': unknown renderer sender`);
|
|
941
|
+
if (!this.viewFeatures.get(viewId)?.has(featureId)) throw new Error(`Access denied for '${featureId}:${serviceId}:${method}' from view '${viewId}'`);
|
|
942
|
+
}
|
|
943
|
+
resolveSenderViewId(senderWebContentsId) {
|
|
944
|
+
for (const viewId of this.viewFeatures.keys()) {
|
|
945
|
+
const view = this.viewManager.get(viewId)?.view();
|
|
946
|
+
if (!view || view.webContents.isDestroyed()) continue;
|
|
947
|
+
if (view.webContents.id === senderWebContentsId) return viewId;
|
|
948
|
+
}
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
|
|
867
953
|
//#endregion
|
|
868
954
|
//#region src/core/logger/console-handler.ts
|
|
869
955
|
const dim = "\x1B[90m";
|
|
@@ -1012,8 +1098,8 @@ var View = class {
|
|
|
1012
1098
|
}
|
|
1013
1099
|
};
|
|
1014
1100
|
const PRELOAD_EXTENSIONS = [
|
|
1015
|
-
"mjs",
|
|
1016
1101
|
"cjs",
|
|
1102
|
+
"mjs",
|
|
1017
1103
|
"js"
|
|
1018
1104
|
];
|
|
1019
1105
|
function resolveDefaultPreloadPath(viewId) {
|
|
@@ -1068,6 +1154,7 @@ var Runtime = class {
|
|
|
1068
1154
|
windowManager;
|
|
1069
1155
|
viewManager;
|
|
1070
1156
|
eventBridge = null;
|
|
1157
|
+
ipcBridge = null;
|
|
1071
1158
|
constructor(config) {
|
|
1072
1159
|
this.state = new StateMachine({
|
|
1073
1160
|
transitions: RUNTIME_TRANSITIONS,
|
|
@@ -1084,7 +1171,10 @@ var Runtime = class {
|
|
|
1084
1171
|
this.viewManager = new ViewManager();
|
|
1085
1172
|
const viewRegistry = typeof __ELECTRO_VIEW_REGISTRY__ !== "undefined" ? __ELECTRO_VIEW_REGISTRY__ : [];
|
|
1086
1173
|
for (const entry of viewRegistry) this.viewManager.register(new View(entry));
|
|
1087
|
-
if (viewRegistry.some((v) => v.features && v.features.length > 0))
|
|
1174
|
+
if (viewRegistry.some((v) => v.features && v.features.length > 0)) {
|
|
1175
|
+
this.eventBridge = new EventBridge(this.eventBus, this.viewManager, viewRegistry);
|
|
1176
|
+
this.ipcBridge = new IpcBridge(this.featureManager, this.viewManager, viewRegistry);
|
|
1177
|
+
}
|
|
1088
1178
|
this.featureManager.setWindowManager(this.windowManager);
|
|
1089
1179
|
this.featureManager.setViewManager(this.viewManager);
|
|
1090
1180
|
if (config?.features) this.featureManager.register(config.features);
|
|
@@ -1098,8 +1188,10 @@ var Runtime = class {
|
|
|
1098
1188
|
try {
|
|
1099
1189
|
this.eventBridge?.start();
|
|
1100
1190
|
await this.featureManager.bootstrap();
|
|
1191
|
+
await this.ipcBridge?.start();
|
|
1101
1192
|
this.state.transition(RuntimeState.RUNNING);
|
|
1102
1193
|
} catch (err) {
|
|
1194
|
+
this.ipcBridge?.stop();
|
|
1103
1195
|
this.eventBridge?.stop();
|
|
1104
1196
|
this.state.transition(RuntimeState.FAILED);
|
|
1105
1197
|
throw err;
|
|
@@ -1108,6 +1200,7 @@ var Runtime = class {
|
|
|
1108
1200
|
async shutdown() {
|
|
1109
1201
|
this.state.assertState(RuntimeState.RUNNING);
|
|
1110
1202
|
this.state.transition(RuntimeState.STOPPING);
|
|
1203
|
+
this.ipcBridge?.stop();
|
|
1111
1204
|
this.eventBridge?.stop();
|
|
1112
1205
|
await this.featureManager.shutdown();
|
|
1113
1206
|
this.viewManager.destroyAll();
|