@cordy/electro 1.0.8 → 1.1.0
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 +145 -94
- package/dist/index.mjs +247 -203
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import { BaseWindow } from "electron";
|
|
1
|
+
import { BaseWindow, WebContentsView } from "electron";
|
|
2
2
|
import { UserConfig } from "vite";
|
|
3
3
|
|
|
4
4
|
//#region src/config/types.d.ts
|
|
5
5
|
declare const RUNTIME_BRAND: unique symbol;
|
|
6
|
-
declare const
|
|
6
|
+
declare const VIEW_BRAND: unique symbol;
|
|
7
7
|
declare const CONFIG_BRAND: unique symbol;
|
|
8
|
-
type WindowLifecycle = "singleton" | "multi";
|
|
9
|
-
type WindowCloseBehavior = "hide" | "destroy";
|
|
10
|
-
type WindowType = "base-window" | "browser-window";
|
|
11
|
-
interface WindowBehavior {
|
|
12
|
-
close: WindowCloseBehavior;
|
|
13
|
-
}
|
|
14
8
|
interface RuntimeDefinition {
|
|
15
9
|
readonly [RUNTIME_BRAND]: true;
|
|
16
10
|
readonly entry: string;
|
|
@@ -18,45 +12,37 @@ interface RuntimeDefinition {
|
|
|
18
12
|
/** @internal Caller path captured by defineRuntime(). */
|
|
19
13
|
readonly __source: string;
|
|
20
14
|
}
|
|
21
|
-
interface
|
|
22
|
-
readonly [
|
|
15
|
+
interface ViewDefinition {
|
|
16
|
+
readonly [VIEW_BRAND]: true;
|
|
23
17
|
readonly name: string;
|
|
24
18
|
readonly entry: string;
|
|
25
|
-
readonly type?: WindowType;
|
|
26
19
|
readonly features?: readonly string[];
|
|
27
20
|
readonly vite?: UserConfig;
|
|
28
21
|
readonly preload?: string;
|
|
29
|
-
readonly
|
|
30
|
-
|
|
31
|
-
readonly behavior?: WindowBehavior;
|
|
32
|
-
readonly window?: Record<string, unknown>;
|
|
33
|
-
/** @internal Caller path captured by defineWindow(). */
|
|
22
|
+
readonly webPreferences?: Record<string, unknown>;
|
|
23
|
+
/** @internal Caller path captured by defineView(). */
|
|
34
24
|
readonly __source: string;
|
|
35
25
|
}
|
|
36
26
|
interface ElectroConfig {
|
|
37
27
|
readonly [CONFIG_BRAND]: true;
|
|
38
28
|
readonly runtime: RuntimeDefinition;
|
|
39
|
-
readonly
|
|
29
|
+
readonly views?: readonly ViewDefinition[];
|
|
40
30
|
}
|
|
41
31
|
interface DefineRuntimeInput {
|
|
42
32
|
entry: string;
|
|
43
33
|
vite?: UserConfig;
|
|
44
34
|
}
|
|
45
|
-
interface
|
|
35
|
+
interface DefineViewInput {
|
|
46
36
|
name: string;
|
|
47
37
|
entry: string;
|
|
48
|
-
type?: WindowType;
|
|
49
38
|
features?: readonly string[];
|
|
50
39
|
vite?: UserConfig;
|
|
51
40
|
preload?: string;
|
|
52
|
-
|
|
53
|
-
autoShow?: boolean;
|
|
54
|
-
behavior?: WindowBehavior;
|
|
55
|
-
window?: Record<string, unknown>;
|
|
41
|
+
webPreferences?: Record<string, unknown>;
|
|
56
42
|
}
|
|
57
43
|
interface DefineConfigInput {
|
|
58
44
|
runtime: RuntimeDefinition;
|
|
59
|
-
|
|
45
|
+
views?: readonly ViewDefinition[];
|
|
60
46
|
}
|
|
61
47
|
//#endregion
|
|
62
48
|
//#region src/config/define-config.d.ts
|
|
@@ -65,8 +51,8 @@ declare function defineConfig(input: DefineConfigInput): ElectroConfig;
|
|
|
65
51
|
//#region src/config/define-runtime.d.ts
|
|
66
52
|
declare function defineRuntime(input: DefineRuntimeInput): RuntimeDefinition;
|
|
67
53
|
//#endregion
|
|
68
|
-
//#region src/config/define-
|
|
69
|
-
declare function
|
|
54
|
+
//#region src/config/define-view.d.ts
|
|
55
|
+
declare function defineView(input: DefineViewInput): ViewDefinition;
|
|
70
56
|
//#endregion
|
|
71
57
|
//#region src/core/event-bus/types.d.ts
|
|
72
58
|
type EventHandler = (payload: unknown) => void;
|
|
@@ -146,60 +132,96 @@ declare enum FeatureStatus {
|
|
|
146
132
|
ERROR = "error"
|
|
147
133
|
}
|
|
148
134
|
//#endregion
|
|
149
|
-
//#region src/
|
|
150
|
-
|
|
151
|
-
|
|
135
|
+
//#region src/core/view/types.d.ts
|
|
136
|
+
type ViewId = string;
|
|
137
|
+
/** Config for renderer-linked views (built by Vite). */
|
|
138
|
+
interface RendererViewConfig {
|
|
139
|
+
id: ViewId;
|
|
140
|
+
/** Link to a defineView() name. `true` = use `id` as the renderer name. */
|
|
141
|
+
renderer: true | string;
|
|
142
|
+
webPreferences?: Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
/** Config for dynamic views (no renderer entry, programmatic). */
|
|
145
|
+
interface DynamicViewConfig {
|
|
146
|
+
id: ViewId;
|
|
147
|
+
webPreferences?: Record<string, unknown>;
|
|
148
|
+
}
|
|
149
|
+
type ViewConfig = RendererViewConfig | DynamicViewConfig;
|
|
150
|
+
/** A WebContentsView augmented with `load()` for renderer-linked views. */
|
|
151
|
+
type ElectroView = WebContentsView & {
|
|
152
152
|
load(): Promise<void>;
|
|
153
153
|
};
|
|
154
|
-
/**
|
|
155
|
-
interface
|
|
156
|
-
|
|
154
|
+
/** Public interface — hides the View class. */
|
|
155
|
+
interface CreatedView {
|
|
156
|
+
readonly id: ViewId;
|
|
157
|
+
/** Create the WebContentsView. Idempotent: returns existing if alive. */
|
|
158
|
+
create(): ElectroView;
|
|
159
|
+
/** Return the live ElectroView, or null if not yet created / destroyed. */
|
|
160
|
+
view(): ElectroView | null;
|
|
161
|
+
/** Destroy the WebContentsView and clear refs. */
|
|
162
|
+
destroy(): void;
|
|
157
163
|
}
|
|
158
|
-
/**
|
|
159
|
-
interface
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
/** Type-erased interface for managers. */
|
|
165
|
+
interface ViewInstance {
|
|
166
|
+
readonly id: ViewId;
|
|
167
|
+
create(): ElectroView;
|
|
168
|
+
view(): ElectroView | null;
|
|
169
|
+
destroy(): void;
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/core/view/manager.d.ts
|
|
173
|
+
/**
|
|
174
|
+
* ViewManager — global registry for view instances.
|
|
175
|
+
*
|
|
176
|
+
* Views are registered at runtime startup and accessible from any feature.
|
|
177
|
+
* Views are idempotent: `get(id).create()` returns the same instance if alive.
|
|
178
|
+
*/
|
|
179
|
+
declare class ViewManager {
|
|
180
|
+
private readonly instances;
|
|
181
|
+
register(view: ViewInstance): void;
|
|
182
|
+
get(id: ViewId): ViewInstance | null;
|
|
183
|
+
destroyAll(): void;
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/core/window/types.d.ts
|
|
187
|
+
type WindowId = string;
|
|
188
|
+
interface WindowConfig<TApi = void> {
|
|
189
|
+
id: WindowId;
|
|
190
|
+
options?: Record<string, unknown>;
|
|
191
|
+
api?: (window: BaseWindow) => TApi;
|
|
192
|
+
}
|
|
193
|
+
/** Public interface — hides the Window class. */
|
|
194
|
+
interface CreatedWindow<TApi = void> {
|
|
195
|
+
readonly id: WindowId;
|
|
196
|
+
/** Create the BaseWindow. Idempotent: returns existing if alive. */
|
|
197
|
+
create(): BaseWindow;
|
|
198
|
+
/** The live BaseWindow, or null if not yet created / destroyed. */
|
|
199
|
+
readonly window: BaseWindow | null;
|
|
200
|
+
/** The typed API, or null if not yet created / destroyed. */
|
|
201
|
+
readonly api: TApi | null;
|
|
202
|
+
/** Destroy the BaseWindow and clear refs. */
|
|
203
|
+
destroy(): void;
|
|
204
|
+
}
|
|
205
|
+
/** Type-erased interface for managers. */
|
|
206
|
+
interface WindowInstance {
|
|
207
|
+
readonly id: WindowId;
|
|
208
|
+
create(): BaseWindow;
|
|
209
|
+
readonly window: BaseWindow | null;
|
|
210
|
+
readonly api: unknown;
|
|
211
|
+
destroy(): void;
|
|
163
212
|
}
|
|
164
213
|
//#endregion
|
|
165
|
-
//#region src/window/manager.d.ts
|
|
214
|
+
//#region src/core/window/manager.d.ts
|
|
166
215
|
/**
|
|
167
|
-
* WindowManager — registry
|
|
216
|
+
* WindowManager — global registry for window instances.
|
|
168
217
|
*
|
|
169
|
-
*
|
|
170
|
-
* abstraction. Enforces singleton/multi lifecycle semantics and provides
|
|
171
|
-
* querying and bulk destruction capabilities.
|
|
218
|
+
* Windows are registered at runtime startup and accessible from any feature.
|
|
172
219
|
*/
|
|
173
220
|
declare class WindowManager {
|
|
174
|
-
private readonly factory;
|
|
175
|
-
private readonly definitions;
|
|
176
221
|
private readonly instances;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
registerDefinition(definition: WindowDefinition): void;
|
|
180
|
-
/** Check whether a definition is registered for the given name. */
|
|
181
|
-
hasDefinition(name: string): boolean;
|
|
182
|
-
/**
|
|
183
|
-
* Create a window from a registered definition.
|
|
184
|
-
*
|
|
185
|
-
* - Throws if no definition exists for `name`.
|
|
186
|
-
* - For singleton lifecycle (the default), throws if a live instance already exists.
|
|
187
|
-
* - For multi lifecycle, always creates a new instance.
|
|
188
|
-
*/
|
|
189
|
-
createWindow(name: string): ElectroWindow;
|
|
190
|
-
/**
|
|
191
|
-
* Get the first alive window for the given name, or null.
|
|
192
|
-
* Cleans up destroyed instances as a side effect.
|
|
193
|
-
*/
|
|
194
|
-
getWindow(name: string): ElectroWindow | null;
|
|
195
|
-
/** Destroy all instances for the given name. No-op if name is unknown. */
|
|
196
|
-
destroyWindow(name: string): void;
|
|
197
|
-
/** Destroy all tracked windows across all names. */
|
|
222
|
+
register(window: WindowInstance): void;
|
|
223
|
+
get(id: WindowId): WindowInstance | null;
|
|
198
224
|
destroyAll(): void;
|
|
199
|
-
/** Returns a snapshot of all tracked windows (including destroyed ones). */
|
|
200
|
-
list(): WindowInfo[];
|
|
201
|
-
/** Return alive (non-destroyed) instances for a given name. */
|
|
202
|
-
private getAliveInstances;
|
|
203
225
|
}
|
|
204
226
|
//#endregion
|
|
205
227
|
//#region src/core/task/enums.d.ts
|
|
@@ -331,14 +353,17 @@ interface FeatureMap {}
|
|
|
331
353
|
interface ServiceOwnerMap {}
|
|
332
354
|
/** Maps task ID → owning feature ID. Populated by codegen. */
|
|
333
355
|
interface TaskOwnerMap {}
|
|
334
|
-
/** Declaration-merging registry for
|
|
335
|
-
interface
|
|
356
|
+
/** Declaration-merging registry for view types. Codegen populates this. */
|
|
357
|
+
interface ViewMap {}
|
|
358
|
+
/** Declaration-merging registry for window API types. Codegen populates this. */
|
|
359
|
+
interface WindowApiMap {}
|
|
336
360
|
/** Resolve the owning feature ID for a service. Falls back to `string` (→ BaseContext). */
|
|
337
361
|
type _ServiceOwner<TId extends string> = TId extends keyof ServiceOwnerMap ? ServiceOwnerMap[TId] : string;
|
|
338
362
|
/** Resolve the owning feature ID for a task. Falls back to `string` (→ BaseContext). */
|
|
339
363
|
type _TaskOwner<TId extends string> = TId extends keyof TaskOwnerMap ? TaskOwnerMap[TId] : string;
|
|
340
|
-
type
|
|
341
|
-
type
|
|
364
|
+
type _SuggestViewKeys = (keyof ViewMap & string) | (string & {});
|
|
365
|
+
type _SuggestWindowKeys = (keyof WindowApiMap & string) | (string & {});
|
|
366
|
+
type _ResolveWindowApi<K extends string> = K extends keyof WindowApiMap ? WindowApiMap[K] : unknown;
|
|
342
367
|
/** Extract the dependency IDs union for a feature. */
|
|
343
368
|
type _DepIds<FId> = FId extends keyof FeatureMap ? FeatureMap[FId]["dependencies"] : never;
|
|
344
369
|
/** Own service keys for a feature. */
|
|
@@ -395,8 +420,9 @@ type TypedContext<FId extends keyof FeatureMap, ExcludeSvc extends string = neve
|
|
|
395
420
|
publish<K extends _OwnEventKeys<FId>>(event: K, ...args: undefined extends _ResolveOwnEvent<FId, K> ? [payload?: _ResolveOwnEvent<FId, K>] : [payload: _ResolveOwnEvent<FId, K>]): void;
|
|
396
421
|
on<K extends _OwnEventKeys<FId> | _DepEventKeys<FId>>(event: K, handler: (payload: _ResolveEvent<FId, K>) => void): () => void;
|
|
397
422
|
};
|
|
398
|
-
|
|
399
|
-
|
|
423
|
+
getWindow: <K extends _SuggestWindowKeys>(id: K) => CreatedWindow<_ResolveWindowApi<K & string>> | null;
|
|
424
|
+
createView: <K extends _SuggestViewKeys>(id: K) => ElectroView;
|
|
425
|
+
getView: <K extends _SuggestViewKeys>(id: K) => ElectroView | null;
|
|
400
426
|
};
|
|
401
427
|
/** Unscoped context — flat suggestions from all features. */
|
|
402
428
|
type BaseContext = {
|
|
@@ -409,8 +435,9 @@ type BaseContext = {
|
|
|
409
435
|
publish(event: _SuggestAllEvent, payload?: unknown): void;
|
|
410
436
|
on(event: string, handler: (payload: unknown) => void): () => void;
|
|
411
437
|
};
|
|
412
|
-
|
|
413
|
-
|
|
438
|
+
getWindow: <K extends _SuggestWindowKeys>(id: K) => CreatedWindow<_ResolveWindowApi<K & string>> | null;
|
|
439
|
+
createView: <K extends _SuggestViewKeys>(id: K) => ElectroView;
|
|
440
|
+
getView: <K extends _SuggestViewKeys>(id: K) => ElectroView | null;
|
|
414
441
|
};
|
|
415
442
|
interface LoggerContext {
|
|
416
443
|
debug(code: string, message: string, details?: Record<string, unknown>): void;
|
|
@@ -580,9 +607,12 @@ declare class FeatureManager {
|
|
|
580
607
|
/** Global task ownership: taskId → featureId. */
|
|
581
608
|
private readonly taskOwners;
|
|
582
609
|
private windowManager;
|
|
610
|
+
private viewManager;
|
|
583
611
|
constructor(logger: LoggerContext, eventBus?: EventBus | undefined);
|
|
584
|
-
/** @internal Set the window manager (called by
|
|
612
|
+
/** @internal Set the window manager (called by Runtime before bootstrap). */
|
|
585
613
|
setWindowManager(windowManager: WindowManager): void;
|
|
614
|
+
/** @internal Set the view manager (called by Runtime before bootstrap). */
|
|
615
|
+
setViewManager(viewManager: ViewManager): void;
|
|
586
616
|
/**
|
|
587
617
|
* Register a feature or a list of features.
|
|
588
618
|
* If a feature with the same ID is already registered, a warning is logged and the feature is skipped.
|
|
@@ -641,7 +671,7 @@ declare class Feature<FId extends FeatureId> {
|
|
|
641
671
|
* @throws If the transition is not allowed from the current state.
|
|
642
672
|
*/
|
|
643
673
|
transition(target: FeatureStatus): void;
|
|
644
|
-
initialize(features: Feature<FeatureId>[], manager: FeatureManager, eventBus?: EventBus, windowManager?: WindowManager): Promise<void>;
|
|
674
|
+
initialize(features: Feature<FeatureId>[], manager: FeatureManager, eventBus?: EventBus, windowManager?: WindowManager, viewManager?: ViewManager): Promise<void>;
|
|
645
675
|
activate(): Promise<void>;
|
|
646
676
|
deactivate(): Promise<void>;
|
|
647
677
|
destroy(): Promise<void>;
|
|
@@ -710,6 +740,8 @@ declare class Logger implements LoggerContext {
|
|
|
710
740
|
//#region src/core/runtime/types.d.ts
|
|
711
741
|
type RuntimeConfig = {
|
|
712
742
|
features?: FeatureConfig<any>[];
|
|
743
|
+
windows?: WindowInstance[];
|
|
744
|
+
views?: ViewInstance[];
|
|
713
745
|
logger?: {
|
|
714
746
|
handlers?: LogHandler[];
|
|
715
747
|
};
|
|
@@ -721,10 +753,9 @@ declare class Runtime {
|
|
|
721
753
|
readonly logger: Logger;
|
|
722
754
|
private readonly featureManager;
|
|
723
755
|
private readonly eventBus;
|
|
724
|
-
private windowManager;
|
|
756
|
+
private readonly windowManager;
|
|
757
|
+
private readonly viewManager;
|
|
725
758
|
constructor(config?: RuntimeConfig);
|
|
726
|
-
/** @internal Inject window manager (called by Electron layer before start). */
|
|
727
|
-
_injectWindowManager(windowManager: WindowManager): void;
|
|
728
759
|
register(features: FeatureConfig<any> | FeatureConfig<any>[]): void;
|
|
729
760
|
start(): Promise<void>;
|
|
730
761
|
shutdown(): Promise<void>;
|
|
@@ -750,25 +781,45 @@ declare function createService<Scope extends ServiceScope, TApi, TId extends Ser
|
|
|
750
781
|
/** Create a {@link Task} from a config object. Throws if `id` is empty. */
|
|
751
782
|
declare function createTask<TId extends TaskId, TPayload = void>(config: TaskConfig<TId, TPayload>): CreatedTask<TId, TPayload>;
|
|
752
783
|
//#endregion
|
|
784
|
+
//#region src/core/window/helpers.d.ts
|
|
785
|
+
/**
|
|
786
|
+
* Creates a {@link Window} instance from a configuration object.
|
|
787
|
+
*
|
|
788
|
+
* @param config - Window configuration with `id`, optional `options`, and optional `api`.
|
|
789
|
+
* @returns A new `Window` instance ready for registration.
|
|
790
|
+
* @throws If `config.id` is empty.
|
|
791
|
+
*/
|
|
792
|
+
declare function createWindow<TApi = void>(config: WindowConfig<TApi>): CreatedWindow<TApi>;
|
|
793
|
+
//#endregion
|
|
794
|
+
//#region src/core/view/helpers.d.ts
|
|
795
|
+
/**
|
|
796
|
+
* Creates a {@link View} instance from a configuration object.
|
|
797
|
+
*
|
|
798
|
+
* @param config - View configuration with `id`, and either `renderer` (for Vite-built views) or `webPreferences` (for dynamic views).
|
|
799
|
+
* @returns A new `View` instance ready for registration.
|
|
800
|
+
* @throws If `config.id` is empty.
|
|
801
|
+
*/
|
|
802
|
+
declare function createView(config: ViewConfig): CreatedView;
|
|
803
|
+
//#endregion
|
|
753
804
|
//#region src/policy/types.d.ts
|
|
754
805
|
/** Outcome of a policy check. */
|
|
755
806
|
declare enum PolicyDecision {
|
|
756
807
|
ALLOWED = "ALLOWED",
|
|
757
808
|
ACCESS_DENIED = "ACCESS_DENIED",
|
|
758
|
-
|
|
809
|
+
VIEW_NOT_FOUND = "VIEW_NOT_FOUND"
|
|
759
810
|
}
|
|
760
811
|
/** Result of a policy check with context. */
|
|
761
812
|
interface PolicyResult {
|
|
762
813
|
readonly decision: PolicyDecision;
|
|
763
|
-
readonly
|
|
814
|
+
readonly viewName: string;
|
|
764
815
|
readonly featureId: string;
|
|
765
816
|
}
|
|
766
817
|
//#endregion
|
|
767
818
|
//#region src/policy/engine.d.ts
|
|
768
819
|
/**
|
|
769
|
-
* Deny-by-default policy engine for
|
|
820
|
+
* Deny-by-default policy engine for view–feature access control.
|
|
770
821
|
*
|
|
771
|
-
* A
|
|
822
|
+
* A view's renderer can only access exposed services of features
|
|
772
823
|
* listed in its `features: []` config. Everything else is denied.
|
|
773
824
|
*
|
|
774
825
|
* Used at:
|
|
@@ -777,15 +828,15 @@ interface PolicyResult {
|
|
|
777
828
|
*/
|
|
778
829
|
declare class PolicyEngine {
|
|
779
830
|
private readonly policies;
|
|
780
|
-
constructor(
|
|
831
|
+
constructor(views: readonly ViewDefinition[]);
|
|
781
832
|
/** Full policy check with decision code and context. */
|
|
782
|
-
check(
|
|
833
|
+
check(viewName: string, featureId: string): PolicyResult;
|
|
783
834
|
/** Convenience: returns true only when access is ALLOWED. */
|
|
784
|
-
canAccess(
|
|
785
|
-
/** Returns the allowed feature IDs for a
|
|
786
|
-
getAllowedFeatures(
|
|
787
|
-
/** Returns all registered
|
|
788
|
-
|
|
835
|
+
canAccess(viewName: string, featureId: string): boolean;
|
|
836
|
+
/** Returns the allowed feature IDs for a view. Throws if view unknown. */
|
|
837
|
+
getAllowedFeatures(viewName: string): readonly string[];
|
|
838
|
+
/** Returns all registered view names. */
|
|
839
|
+
getViewNames(): string[];
|
|
789
840
|
}
|
|
790
841
|
//#endregion
|
|
791
|
-
export { type BaseContext, type CreatedEvent, type CreatedService, type CreatedTask, type DefineConfigInput, type DefineRuntimeInput, type
|
|
842
|
+
export { type BaseContext, type CreatedEvent, type CreatedService, type CreatedTask, type CreatedView, type CreatedWindow, type DefineConfigInput, type DefineRuntimeInput, type DefineViewInput, type ElectroConfig, type ElectroView, EventAccessor, type EventHandler, type EventId, type EventInstance, type EventSubscription, type FeatureConfig, type FeatureContext, type FeatureHandle, type FeatureId, type FeatureMap, FeatureStatus, type LogEntry, type LogHandler, type LoggerContext, PolicyDecision, PolicyEngine, type PolicyResult, Runtime, type RuntimeConfig, type RuntimeDefinition, RuntimeState, type ServiceConfig, type ServiceId, type ServiceInfo, type ServiceOwnerMap, ServiceScope, ServiceStatus, type StopMode, type TaskConfig, type TaskExecutionContext, type TaskHandle, type TaskId, TaskOverlapStrategy, type TaskOwnerMap, TaskRetryStrategy, TaskStatus, type TaskStatusInfo, type TypedContext, type ViewConfig, type ViewDefinition, type ViewId, type ViewInstance, type ViewMap, type WindowApiMap, type WindowConfig, type WindowId, type WindowInstance, createEvent, createFeature, createRuntime, createService, createTask, createView, createWindow, defineConfig, defineRuntime, defineView };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { join } from "node:path";
|
|
3
2
|
import { Cron } from "croner";
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
6
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
@@ -8,15 +8,15 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/config/define-config.ts
|
|
10
10
|
function defineConfig(input) {
|
|
11
|
-
const
|
|
11
|
+
const views = input.views ?? [];
|
|
12
12
|
const seen = /* @__PURE__ */ new Set();
|
|
13
|
-
for (const
|
|
14
|
-
if (seen.has(
|
|
15
|
-
seen.add(
|
|
13
|
+
for (const view of views) {
|
|
14
|
+
if (seen.has(view.name)) throw new Error(`[electro] defineConfig: duplicate view name "${view.name}"`);
|
|
15
|
+
seen.add(view.name);
|
|
16
16
|
}
|
|
17
17
|
return {
|
|
18
18
|
runtime: input.runtime,
|
|
19
|
-
|
|
19
|
+
views
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -53,26 +53,19 @@ function defineRuntime(input) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
//#endregion
|
|
56
|
-
//#region src/config/define-
|
|
57
|
-
const
|
|
58
|
-
function
|
|
59
|
-
if (!input.name || input.name.trim().length === 0) throw new Error("[electro]
|
|
60
|
-
if (!
|
|
61
|
-
if (!input.entry || input.entry.trim().length === 0) throw new Error("[electro]
|
|
62
|
-
const lifecycle = input.lifecycle ?? "singleton";
|
|
63
|
-
const close = input.behavior?.close ?? (lifecycle === "multi" ? "destroy" : "hide");
|
|
64
|
-
if (lifecycle === "multi" && close === "hide") throw new Error("[electro] defineWindow: behavior.close \"hide\" is only allowed with lifecycle \"singleton\". Multi-instance windows must use \"destroy\".");
|
|
56
|
+
//#region src/config/define-view.ts
|
|
57
|
+
const VIEW_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
58
|
+
function defineView(input) {
|
|
59
|
+
if (!input.name || input.name.trim().length === 0) throw new Error("[electro] defineView: name must be a non-empty string");
|
|
60
|
+
if (!VIEW_NAME_PATTERN.test(input.name)) throw new Error(`[electro] defineView: name "${input.name}" is invalid. Must match ${VIEW_NAME_PATTERN.toString()}`);
|
|
61
|
+
if (!input.entry || input.entry.trim().length === 0) throw new Error("[electro] defineView: entry must be a non-empty string");
|
|
65
62
|
return {
|
|
66
63
|
name: input.name,
|
|
67
64
|
entry: input.entry,
|
|
68
|
-
type: input.type,
|
|
69
65
|
features: input.features,
|
|
70
66
|
vite: input.vite,
|
|
71
67
|
preload: input.preload,
|
|
72
|
-
|
|
73
|
-
autoShow: input.autoShow ?? false,
|
|
74
|
-
behavior: { close },
|
|
75
|
-
window: input.window,
|
|
68
|
+
webPreferences: input.webPreferences,
|
|
76
69
|
__source: getCallerPath() ?? ""
|
|
77
70
|
};
|
|
78
71
|
}
|
|
@@ -158,125 +151,6 @@ let RuntimeState = /* @__PURE__ */ function(RuntimeState) {
|
|
|
158
151
|
return RuntimeState;
|
|
159
152
|
}({});
|
|
160
153
|
|
|
161
|
-
//#endregion
|
|
162
|
-
//#region src/window/default-factory.ts
|
|
163
|
-
/**
|
|
164
|
-
* Default WindowFactory — creates Electron BrowserWindow or BaseWindow
|
|
165
|
-
* from a WindowDefinition at runtime.
|
|
166
|
-
*/
|
|
167
|
-
function createDefaultWindowFactory() {
|
|
168
|
-
return { create(definition) {
|
|
169
|
-
if (definition.type === "browser-window") {
|
|
170
|
-
const { BrowserWindow } = __require("electron");
|
|
171
|
-
return new BrowserWindow({
|
|
172
|
-
show: definition.autoShow ?? false,
|
|
173
|
-
...definition.window ?? {}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
const { BaseWindow: BW } = __require("electron");
|
|
177
|
-
return new BW({
|
|
178
|
-
show: definition.autoShow ?? false,
|
|
179
|
-
...definition.window ?? {}
|
|
180
|
-
});
|
|
181
|
-
} };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
//#endregion
|
|
185
|
-
//#region src/window/manager.ts
|
|
186
|
-
/**
|
|
187
|
-
* WindowManager — registry, creation, and lifecycle coordinator for windows.
|
|
188
|
-
*
|
|
189
|
-
* Manages window definitions and their instances through a WindowFactory
|
|
190
|
-
* abstraction. Enforces singleton/multi lifecycle semantics and provides
|
|
191
|
-
* querying and bulk destruction capabilities.
|
|
192
|
-
*/
|
|
193
|
-
var WindowManager = class {
|
|
194
|
-
factory;
|
|
195
|
-
definitions = /* @__PURE__ */ new Map();
|
|
196
|
-
instances = /* @__PURE__ */ new Map();
|
|
197
|
-
constructor(factory) {
|
|
198
|
-
this.factory = factory;
|
|
199
|
-
}
|
|
200
|
-
/** Register a window definition. Throws on duplicate name. */
|
|
201
|
-
registerDefinition(definition) {
|
|
202
|
-
if (this.definitions.has(definition.name)) throw new Error(`Window definition duplicate: "${definition.name}" is already registered`);
|
|
203
|
-
this.definitions.set(definition.name, definition);
|
|
204
|
-
}
|
|
205
|
-
/** Check whether a definition is registered for the given name. */
|
|
206
|
-
hasDefinition(name) {
|
|
207
|
-
return this.definitions.has(name);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Create a window from a registered definition.
|
|
211
|
-
*
|
|
212
|
-
* - Throws if no definition exists for `name`.
|
|
213
|
-
* - For singleton lifecycle (the default), throws if a live instance already exists.
|
|
214
|
-
* - For multi lifecycle, always creates a new instance.
|
|
215
|
-
*/
|
|
216
|
-
createWindow(name) {
|
|
217
|
-
const definition = this.definitions.get(name);
|
|
218
|
-
if (!definition) throw new Error(`Cannot create window: no definition found for "${name}"`);
|
|
219
|
-
if ((definition.lifecycle ?? "singleton") === "singleton") {
|
|
220
|
-
if (this.getAliveInstances(name).length > 0) throw new Error(`Cannot create window "${name}": singleton instance already exists`);
|
|
221
|
-
}
|
|
222
|
-
const window = this.factory.create(definition);
|
|
223
|
-
const electrified = window;
|
|
224
|
-
electrified.load = async () => {
|
|
225
|
-
if (typeof window.loadURL !== "function") throw new Error(`Cannot load window "${name}": BaseWindow does not support loadURL/loadFile. Set type: "browser-window" in your window definition.`);
|
|
226
|
-
const bw = window;
|
|
227
|
-
const devUrl = process.env[`ELECTRO_DEV_URL_${name}`];
|
|
228
|
-
if (devUrl) {
|
|
229
|
-
await bw.loadURL(devUrl);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const { app } = await import("electron");
|
|
233
|
-
await bw.loadFile(join(app.getAppPath(), ".electro", "out", "renderer", name, "index.html"));
|
|
234
|
-
};
|
|
235
|
-
const list = this.instances.get(name);
|
|
236
|
-
if (list) list.push(electrified);
|
|
237
|
-
else this.instances.set(name, [electrified]);
|
|
238
|
-
return electrified;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Get the first alive window for the given name, or null.
|
|
242
|
-
* Cleans up destroyed instances as a side effect.
|
|
243
|
-
*/
|
|
244
|
-
getWindow(name) {
|
|
245
|
-
const list = this.instances.get(name);
|
|
246
|
-
if (!list) return null;
|
|
247
|
-
const alive = list.filter((w) => !w.isDestroyed());
|
|
248
|
-
this.instances.set(name, alive);
|
|
249
|
-
return alive.length > 0 ? alive[0] : null;
|
|
250
|
-
}
|
|
251
|
-
/** Destroy all instances for the given name. No-op if name is unknown. */
|
|
252
|
-
destroyWindow(name) {
|
|
253
|
-
const list = this.instances.get(name);
|
|
254
|
-
if (!list) return;
|
|
255
|
-
for (const window of list) if (!window.isDestroyed()) window.destroy();
|
|
256
|
-
this.instances.set(name, []);
|
|
257
|
-
}
|
|
258
|
-
/** Destroy all tracked windows across all names. */
|
|
259
|
-
destroyAll() {
|
|
260
|
-
for (const [name] of this.instances) this.destroyWindow(name);
|
|
261
|
-
}
|
|
262
|
-
/** Returns a snapshot of all tracked windows (including destroyed ones). */
|
|
263
|
-
list() {
|
|
264
|
-
const result = [];
|
|
265
|
-
for (const [name, windows] of this.instances) for (const window of windows) result.push({
|
|
266
|
-
name,
|
|
267
|
-
windowId: window.id,
|
|
268
|
-
destroyed: window.isDestroyed()
|
|
269
|
-
});
|
|
270
|
-
return result;
|
|
271
|
-
}
|
|
272
|
-
/** Return alive (non-destroyed) instances for a given name. */
|
|
273
|
-
getAliveInstances(name) {
|
|
274
|
-
const list = this.instances.get(name);
|
|
275
|
-
if (!list) return [];
|
|
276
|
-
return list.filter((w) => !w.isDestroyed());
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
|
|
280
154
|
//#endregion
|
|
281
155
|
//#region src/core/event-bus/event-bus.ts
|
|
282
156
|
var EventBus = class {
|
|
@@ -311,26 +185,6 @@ var EventBus = class {
|
|
|
311
185
|
}
|
|
312
186
|
};
|
|
313
187
|
|
|
314
|
-
//#endregion
|
|
315
|
-
//#region src/window/accessor.ts
|
|
316
|
-
/**
|
|
317
|
-
* Thin accessor bound to FeatureContext.
|
|
318
|
-
*
|
|
319
|
-
* Provides `createWindow(name)` and `getWindow(name)` for features.
|
|
320
|
-
* Delegates all work to WindowManager.
|
|
321
|
-
*/
|
|
322
|
-
var WindowAccessor = class {
|
|
323
|
-
constructor(manager) {
|
|
324
|
-
this.manager = manager;
|
|
325
|
-
}
|
|
326
|
-
createWindow(name) {
|
|
327
|
-
return this.manager.createWindow(name);
|
|
328
|
-
}
|
|
329
|
-
getWindow(name) {
|
|
330
|
-
return this.manager.getWindow(name);
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
|
|
334
188
|
//#endregion
|
|
335
189
|
//#region src/core/service/enums.ts
|
|
336
190
|
let ServiceScope = /* @__PURE__ */ function(ServiceScope) {
|
|
@@ -676,12 +530,15 @@ var Feature = class {
|
|
|
676
530
|
throw new Error("Events not yet initialized");
|
|
677
531
|
}
|
|
678
532
|
},
|
|
679
|
-
createWindow: () => {
|
|
680
|
-
throw new Error("Window manager not available");
|
|
681
|
-
},
|
|
682
533
|
getWindow: () => {
|
|
683
534
|
throw new Error("Window manager not available");
|
|
684
535
|
},
|
|
536
|
+
createView: () => {
|
|
537
|
+
throw new Error("View manager not available");
|
|
538
|
+
},
|
|
539
|
+
getView: () => {
|
|
540
|
+
throw new Error("View manager not available");
|
|
541
|
+
},
|
|
685
542
|
logger: this.logger,
|
|
686
543
|
signal: this.controller.signal
|
|
687
544
|
};
|
|
@@ -699,8 +556,8 @@ var Feature = class {
|
|
|
699
556
|
transition(target) {
|
|
700
557
|
this.state.transition(target);
|
|
701
558
|
}
|
|
702
|
-
async initialize(features, manager, eventBus, windowManager) {
|
|
703
|
-
this.buildContext(features, manager, eventBus, windowManager);
|
|
559
|
+
async initialize(features, manager, eventBus, windowManager, viewManager) {
|
|
560
|
+
this.buildContext(features, manager, eventBus, windowManager, viewManager);
|
|
704
561
|
await this.config.onInitialize?.(this.context);
|
|
705
562
|
}
|
|
706
563
|
async activate() {
|
|
@@ -716,7 +573,7 @@ var Feature = class {
|
|
|
716
573
|
async destroy() {
|
|
717
574
|
await this.config.onDestroy?.(this.context);
|
|
718
575
|
}
|
|
719
|
-
buildContext(features, manager, eventBus, windowManager) {
|
|
576
|
+
buildContext(features, manager, eventBus, windowManager, viewManager) {
|
|
720
577
|
this.context.signal = this.controller.signal;
|
|
721
578
|
this.context.logger = this.logger;
|
|
722
579
|
this.serviceManager = new ServiceManager(this.context);
|
|
@@ -753,10 +610,20 @@ var Feature = class {
|
|
|
753
610
|
}
|
|
754
611
|
};
|
|
755
612
|
}
|
|
756
|
-
if (windowManager) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
613
|
+
if (windowManager) this.context.getWindow = (id) => {
|
|
614
|
+
return windowManager.get(id);
|
|
615
|
+
};
|
|
616
|
+
if (viewManager) {
|
|
617
|
+
this.context.createView = (id) => {
|
|
618
|
+
const inst = viewManager.get(id);
|
|
619
|
+
if (!inst) throw new Error(`View "${id}" not registered`);
|
|
620
|
+
return inst.create();
|
|
621
|
+
};
|
|
622
|
+
this.context.getView = (id) => {
|
|
623
|
+
const inst = viewManager.get(id);
|
|
624
|
+
if (!inst) return null;
|
|
625
|
+
return inst.view();
|
|
626
|
+
};
|
|
760
627
|
}
|
|
761
628
|
}
|
|
762
629
|
};
|
|
@@ -770,14 +637,19 @@ var FeatureManager = class {
|
|
|
770
637
|
/** Global task ownership: taskId → featureId. */
|
|
771
638
|
taskOwners = /* @__PURE__ */ new Map();
|
|
772
639
|
windowManager = null;
|
|
640
|
+
viewManager = null;
|
|
773
641
|
constructor(logger, eventBus = void 0) {
|
|
774
642
|
this.logger = logger;
|
|
775
643
|
this.eventBus = eventBus;
|
|
776
644
|
}
|
|
777
|
-
/** @internal Set the window manager (called by
|
|
645
|
+
/** @internal Set the window manager (called by Runtime before bootstrap). */
|
|
778
646
|
setWindowManager(windowManager) {
|
|
779
647
|
this.windowManager = windowManager;
|
|
780
648
|
}
|
|
649
|
+
/** @internal Set the view manager (called by Runtime before bootstrap). */
|
|
650
|
+
setViewManager(viewManager) {
|
|
651
|
+
this.viewManager = viewManager;
|
|
652
|
+
}
|
|
781
653
|
/**
|
|
782
654
|
* Register a feature or a list of features.
|
|
783
655
|
* If a feature with the same ID is already registered, a warning is logged and the feature is skipped.
|
|
@@ -837,7 +709,7 @@ var FeatureManager = class {
|
|
|
837
709
|
const features = [];
|
|
838
710
|
const deps = feature.config.dependencies ?? [];
|
|
839
711
|
for (const dep of deps) features.push(this.registry.get(dep));
|
|
840
|
-
await feature.initialize(features, this, this.eventBus, this.windowManager ?? void 0);
|
|
712
|
+
await feature.initialize(features, this, this.eventBus, this.windowManager ?? void 0, this.viewManager ?? void 0);
|
|
841
713
|
feature.transition(FeatureStatus.READY);
|
|
842
714
|
} catch (err) {
|
|
843
715
|
feature.transition(FeatureStatus.ERROR);
|
|
@@ -1006,6 +878,49 @@ var Logger = class {
|
|
|
1006
878
|
}
|
|
1007
879
|
};
|
|
1008
880
|
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/core/view/manager.ts
|
|
883
|
+
/**
|
|
884
|
+
* ViewManager — global registry for view instances.
|
|
885
|
+
*
|
|
886
|
+
* Views are registered at runtime startup and accessible from any feature.
|
|
887
|
+
* Views are idempotent: `get(id).create()` returns the same instance if alive.
|
|
888
|
+
*/
|
|
889
|
+
var ViewManager = class {
|
|
890
|
+
instances = /* @__PURE__ */ new Map();
|
|
891
|
+
register(view) {
|
|
892
|
+
if (this.instances.has(view.id)) throw new Error(`View "${view.id}" is already registered`);
|
|
893
|
+
this.instances.set(view.id, view);
|
|
894
|
+
}
|
|
895
|
+
get(id) {
|
|
896
|
+
return this.instances.get(id) ?? null;
|
|
897
|
+
}
|
|
898
|
+
destroyAll() {
|
|
899
|
+
for (const inst of this.instances.values()) inst.destroy();
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
//#endregion
|
|
904
|
+
//#region src/core/window/manager.ts
|
|
905
|
+
/**
|
|
906
|
+
* WindowManager — global registry for window instances.
|
|
907
|
+
*
|
|
908
|
+
* Windows are registered at runtime startup and accessible from any feature.
|
|
909
|
+
*/
|
|
910
|
+
var WindowManager = class {
|
|
911
|
+
instances = /* @__PURE__ */ new Map();
|
|
912
|
+
register(window) {
|
|
913
|
+
if (this.instances.has(window.id)) throw new Error(`Window "${window.id}" is already registered`);
|
|
914
|
+
this.instances.set(window.id, window);
|
|
915
|
+
}
|
|
916
|
+
get(id) {
|
|
917
|
+
return this.instances.get(id) ?? null;
|
|
918
|
+
}
|
|
919
|
+
destroyAll() {
|
|
920
|
+
for (const inst of this.instances.values()) inst.destroy();
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
1009
924
|
//#endregion
|
|
1010
925
|
//#region src/core/runtime/runtime.ts
|
|
1011
926
|
const RUNTIME_TRANSITIONS = {
|
|
@@ -1021,7 +936,8 @@ var Runtime = class {
|
|
|
1021
936
|
logger;
|
|
1022
937
|
featureManager;
|
|
1023
938
|
eventBus;
|
|
1024
|
-
windowManager
|
|
939
|
+
windowManager;
|
|
940
|
+
viewManager;
|
|
1025
941
|
constructor(config) {
|
|
1026
942
|
this.state = new StateMachine({
|
|
1027
943
|
transitions: RUNTIME_TRANSITIONS,
|
|
@@ -1033,24 +949,19 @@ var Runtime = class {
|
|
|
1033
949
|
this.featureManager = new FeatureManager(this.logger, this.eventBus);
|
|
1034
950
|
this.logger.addHandler(createConsoleHandler());
|
|
1035
951
|
if (config?.logger?.handlers) for (const handler of config.logger.handlers) this.logger.addHandler(handler);
|
|
952
|
+
this.windowManager = new WindowManager();
|
|
953
|
+
for (const win of config?.windows ?? []) this.windowManager.register(win);
|
|
954
|
+
this.viewManager = new ViewManager();
|
|
955
|
+
for (const view of config?.views ?? []) this.viewManager.register(view);
|
|
956
|
+
this.featureManager.setWindowManager(this.windowManager);
|
|
957
|
+
this.featureManager.setViewManager(this.viewManager);
|
|
1036
958
|
if (config?.features) this.featureManager.register(config.features);
|
|
1037
959
|
}
|
|
1038
|
-
/** @internal Inject window manager (called by Electron layer before start). */
|
|
1039
|
-
_injectWindowManager(windowManager) {
|
|
1040
|
-
this.state.assertState(RuntimeState.CREATED);
|
|
1041
|
-
this.windowManager = windowManager;
|
|
1042
|
-
this.featureManager.setWindowManager(windowManager);
|
|
1043
|
-
}
|
|
1044
960
|
register(features) {
|
|
1045
961
|
this.state.assertState(RuntimeState.CREATED);
|
|
1046
962
|
this.featureManager.register(features);
|
|
1047
963
|
}
|
|
1048
964
|
async start() {
|
|
1049
|
-
if (!this.windowManager && typeof __ELECTRO_WINDOW_DEFINITIONS__ !== "undefined") {
|
|
1050
|
-
const wm = new WindowManager(createDefaultWindowFactory());
|
|
1051
|
-
for (const def of __ELECTRO_WINDOW_DEFINITIONS__) wm.registerDefinition(def);
|
|
1052
|
-
this._injectWindowManager(wm);
|
|
1053
|
-
}
|
|
1054
965
|
this.state.transition(RuntimeState.STARTING);
|
|
1055
966
|
try {
|
|
1056
967
|
await this.featureManager.bootstrap();
|
|
@@ -1064,7 +975,8 @@ var Runtime = class {
|
|
|
1064
975
|
this.state.assertState(RuntimeState.RUNNING);
|
|
1065
976
|
this.state.transition(RuntimeState.STOPPING);
|
|
1066
977
|
await this.featureManager.shutdown();
|
|
1067
|
-
this.
|
|
978
|
+
this.viewManager.destroyAll();
|
|
979
|
+
this.windowManager.destroyAll();
|
|
1068
980
|
this.state.transition(RuntimeState.STOPPED);
|
|
1069
981
|
}
|
|
1070
982
|
async enable(id) {
|
|
@@ -1531,22 +1443,154 @@ function createTask(config) {
|
|
|
1531
1443
|
return new Task(config);
|
|
1532
1444
|
}
|
|
1533
1445
|
|
|
1446
|
+
//#endregion
|
|
1447
|
+
//#region src/core/window/window.ts
|
|
1448
|
+
/**
|
|
1449
|
+
* Window — manages a single BaseWindow instance with an optional typed API.
|
|
1450
|
+
*
|
|
1451
|
+
* `create()` is idempotent: calling it while an alive window exists returns
|
|
1452
|
+
* the existing instance. After the window is destroyed (user close, etc.),
|
|
1453
|
+
* calling `create()` again spawns a fresh one.
|
|
1454
|
+
*/
|
|
1455
|
+
var Window = class {
|
|
1456
|
+
_window = null;
|
|
1457
|
+
_api = null;
|
|
1458
|
+
constructor(config) {
|
|
1459
|
+
this.config = config;
|
|
1460
|
+
}
|
|
1461
|
+
get id() {
|
|
1462
|
+
return this.config.id;
|
|
1463
|
+
}
|
|
1464
|
+
create() {
|
|
1465
|
+
if (this._window && !this._window.isDestroyed()) return this._window;
|
|
1466
|
+
const { BaseWindow: BW } = __require("electron");
|
|
1467
|
+
this._window = new BW({
|
|
1468
|
+
show: false,
|
|
1469
|
+
...this.config.options ?? {}
|
|
1470
|
+
});
|
|
1471
|
+
if (this.config.api) this._api = this.config.api(this._window);
|
|
1472
|
+
return this._window;
|
|
1473
|
+
}
|
|
1474
|
+
get window() {
|
|
1475
|
+
if (this._window?.isDestroyed()) {
|
|
1476
|
+
this._window = null;
|
|
1477
|
+
this._api = null;
|
|
1478
|
+
}
|
|
1479
|
+
return this._window;
|
|
1480
|
+
}
|
|
1481
|
+
get api() {
|
|
1482
|
+
if (this._window?.isDestroyed()) {
|
|
1483
|
+
this._window = null;
|
|
1484
|
+
this._api = null;
|
|
1485
|
+
}
|
|
1486
|
+
return this._api;
|
|
1487
|
+
}
|
|
1488
|
+
destroy() {
|
|
1489
|
+
if (this._window && !this._window.isDestroyed()) this._window.destroy();
|
|
1490
|
+
this._window = null;
|
|
1491
|
+
this._api = null;
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
//#endregion
|
|
1496
|
+
//#region src/core/window/helpers.ts
|
|
1497
|
+
/**
|
|
1498
|
+
* Creates a {@link Window} instance from a configuration object.
|
|
1499
|
+
*
|
|
1500
|
+
* @param config - Window configuration with `id`, optional `options`, and optional `api`.
|
|
1501
|
+
* @returns A new `Window` instance ready for registration.
|
|
1502
|
+
* @throws If `config.id` is empty.
|
|
1503
|
+
*/
|
|
1504
|
+
function createWindow(config) {
|
|
1505
|
+
if (!config.id) throw new Error("Window must have an id");
|
|
1506
|
+
return new Window(config);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
//#endregion
|
|
1510
|
+
//#region src/core/view/view.ts
|
|
1511
|
+
function isRendererConfig(config) {
|
|
1512
|
+
return "renderer" in config;
|
|
1513
|
+
}
|
|
1514
|
+
function resolveRendererName(config) {
|
|
1515
|
+
return config.renderer === true ? config.id : config.renderer;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* View — manages a single WebContentsView instance.
|
|
1519
|
+
*
|
|
1520
|
+
* `create()` is idempotent: calling it while an alive view exists returns
|
|
1521
|
+
* the existing instance. After the view is destroyed, calling `create()`
|
|
1522
|
+
* spawns a fresh one.
|
|
1523
|
+
*
|
|
1524
|
+
* For renderer-linked views, `load()` resolves the dev URL or production
|
|
1525
|
+
* file path automatically.
|
|
1526
|
+
*/
|
|
1527
|
+
var View = class {
|
|
1528
|
+
_view = null;
|
|
1529
|
+
constructor(config) {
|
|
1530
|
+
this.config = config;
|
|
1531
|
+
}
|
|
1532
|
+
get id() {
|
|
1533
|
+
return this.config.id;
|
|
1534
|
+
}
|
|
1535
|
+
create() {
|
|
1536
|
+
if (this._view && !this._view.webContents.isDestroyed()) return this._view;
|
|
1537
|
+
const { WebContentsView: WCV } = __require("electron");
|
|
1538
|
+
const view = new WCV({ webPreferences: { ...this.config.webPreferences ?? {} } });
|
|
1539
|
+
if (isRendererConfig(this.config)) {
|
|
1540
|
+
const rendererName = resolveRendererName(this.config);
|
|
1541
|
+
view.load = async () => {
|
|
1542
|
+
const devUrl = process.env[`ELECTRO_DEV_URL_${rendererName}`];
|
|
1543
|
+
if (devUrl) {
|
|
1544
|
+
await view.webContents.loadURL(devUrl);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const { app } = await import("electron");
|
|
1548
|
+
await view.webContents.loadFile(join(app.getAppPath(), ".electro", "out", "renderer", rendererName, "index.html"));
|
|
1549
|
+
};
|
|
1550
|
+
} else view.load = async () => {};
|
|
1551
|
+
this._view = view;
|
|
1552
|
+
return view;
|
|
1553
|
+
}
|
|
1554
|
+
view() {
|
|
1555
|
+
if (this._view?.webContents.isDestroyed()) this._view = null;
|
|
1556
|
+
return this._view;
|
|
1557
|
+
}
|
|
1558
|
+
destroy() {
|
|
1559
|
+
if (this._view && !this._view.webContents.isDestroyed()) this._view.webContents.close();
|
|
1560
|
+
this._view = null;
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
//#endregion
|
|
1565
|
+
//#region src/core/view/helpers.ts
|
|
1566
|
+
/**
|
|
1567
|
+
* Creates a {@link View} instance from a configuration object.
|
|
1568
|
+
*
|
|
1569
|
+
* @param config - View configuration with `id`, and either `renderer` (for Vite-built views) or `webPreferences` (for dynamic views).
|
|
1570
|
+
* @returns A new `View` instance ready for registration.
|
|
1571
|
+
* @throws If `config.id` is empty.
|
|
1572
|
+
*/
|
|
1573
|
+
function createView(config) {
|
|
1574
|
+
if (!config.id) throw new Error("View must have an id");
|
|
1575
|
+
return new View(config);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1534
1578
|
//#endregion
|
|
1535
1579
|
//#region src/policy/types.ts
|
|
1536
1580
|
/** Outcome of a policy check. */
|
|
1537
1581
|
let PolicyDecision = /* @__PURE__ */ function(PolicyDecision) {
|
|
1538
1582
|
PolicyDecision["ALLOWED"] = "ALLOWED";
|
|
1539
1583
|
PolicyDecision["ACCESS_DENIED"] = "ACCESS_DENIED";
|
|
1540
|
-
PolicyDecision["
|
|
1584
|
+
PolicyDecision["VIEW_NOT_FOUND"] = "VIEW_NOT_FOUND";
|
|
1541
1585
|
return PolicyDecision;
|
|
1542
1586
|
}({});
|
|
1543
1587
|
|
|
1544
1588
|
//#endregion
|
|
1545
1589
|
//#region src/policy/engine.ts
|
|
1546
1590
|
/**
|
|
1547
|
-
* Deny-by-default policy engine for
|
|
1591
|
+
* Deny-by-default policy engine for view–feature access control.
|
|
1548
1592
|
*
|
|
1549
|
-
* A
|
|
1593
|
+
* A view's renderer can only access exposed services of features
|
|
1550
1594
|
* listed in its `features: []` config. Everything else is denied.
|
|
1551
1595
|
*
|
|
1552
1596
|
* Used at:
|
|
@@ -1555,38 +1599,38 @@ let PolicyDecision = /* @__PURE__ */ function(PolicyDecision) {
|
|
|
1555
1599
|
*/
|
|
1556
1600
|
var PolicyEngine = class {
|
|
1557
1601
|
policies = /* @__PURE__ */ new Map();
|
|
1558
|
-
constructor(
|
|
1559
|
-
for (const
|
|
1602
|
+
constructor(views) {
|
|
1603
|
+
for (const view of views) this.policies.set(view.name, new Set(view.features ?? []));
|
|
1560
1604
|
}
|
|
1561
1605
|
/** Full policy check with decision code and context. */
|
|
1562
|
-
check(
|
|
1563
|
-
const allowed = this.policies.get(
|
|
1606
|
+
check(viewName, featureId) {
|
|
1607
|
+
const allowed = this.policies.get(viewName);
|
|
1564
1608
|
if (!allowed) return {
|
|
1565
|
-
decision: PolicyDecision.
|
|
1566
|
-
|
|
1609
|
+
decision: PolicyDecision.VIEW_NOT_FOUND,
|
|
1610
|
+
viewName,
|
|
1567
1611
|
featureId
|
|
1568
1612
|
};
|
|
1569
1613
|
return {
|
|
1570
1614
|
decision: allowed.has(featureId) ? PolicyDecision.ALLOWED : PolicyDecision.ACCESS_DENIED,
|
|
1571
|
-
|
|
1615
|
+
viewName,
|
|
1572
1616
|
featureId
|
|
1573
1617
|
};
|
|
1574
1618
|
}
|
|
1575
1619
|
/** Convenience: returns true only when access is ALLOWED. */
|
|
1576
|
-
canAccess(
|
|
1577
|
-
return this.check(
|
|
1620
|
+
canAccess(viewName, featureId) {
|
|
1621
|
+
return this.check(viewName, featureId).decision === PolicyDecision.ALLOWED;
|
|
1578
1622
|
}
|
|
1579
|
-
/** Returns the allowed feature IDs for a
|
|
1580
|
-
getAllowedFeatures(
|
|
1581
|
-
const allowed = this.policies.get(
|
|
1582
|
-
if (!allowed) throw new Error(`
|
|
1623
|
+
/** Returns the allowed feature IDs for a view. Throws if view unknown. */
|
|
1624
|
+
getAllowedFeatures(viewName) {
|
|
1625
|
+
const allowed = this.policies.get(viewName);
|
|
1626
|
+
if (!allowed) throw new Error(`View "${viewName}" is not registered in the policy engine`);
|
|
1583
1627
|
return [...allowed];
|
|
1584
1628
|
}
|
|
1585
|
-
/** Returns all registered
|
|
1586
|
-
|
|
1629
|
+
/** Returns all registered view names. */
|
|
1630
|
+
getViewNames() {
|
|
1587
1631
|
return [...this.policies.keys()];
|
|
1588
1632
|
}
|
|
1589
1633
|
};
|
|
1590
1634
|
|
|
1591
1635
|
//#endregion
|
|
1592
|
-
export { EventAccessor, FeatureStatus, PolicyDecision, PolicyEngine, Runtime, RuntimeState, ServiceScope, ServiceStatus, TaskOverlapStrategy, TaskRetryStrategy, TaskStatus, createEvent, createFeature, createRuntime, createService, createTask, defineConfig, defineRuntime,
|
|
1636
|
+
export { EventAccessor, FeatureStatus, PolicyDecision, PolicyEngine, Runtime, RuntimeState, ServiceScope, ServiceStatus, TaskOverlapStrategy, TaskRetryStrategy, TaskStatus, createEvent, createFeature, createRuntime, createService, createTask, createView, createWindow, defineConfig, defineRuntime, defineView };
|