@owear/core 0.1.0 → 0.1.3
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/app.d.ts +122 -0
- package/dist/app.js +276 -0
- package/dist/bridge.d.ts +79 -0
- package/dist/bridge.js +55 -0
- package/dist/browserwindow.d.ts +113 -0
- package/dist/browserwindow.js +226 -0
- package/dist/channel.d.ts +22 -0
- package/dist/channel.js +114 -0
- package/dist/dialog.d.ts +47 -0
- package/dist/dialog.js +22 -0
- package/dist/index.d.ts +76 -96
- package/dist/index.js +24 -253
- package/dist/installer.d.ts +112 -0
- package/dist/installer.js +72 -0
- package/dist/ipc/node.d.ts +7 -0
- package/dist/ipc/node.js +25 -0
- package/dist/menu.d.ts +56 -0
- package/dist/menu.js +208 -0
- package/dist/modules.d.ts +16 -0
- package/dist/modules.js +13 -0
- package/dist/nativeimage.d.ts +41 -0
- package/dist/nativeimage.js +376 -0
- package/dist/node/worker-runner.d.ts +1 -0
- package/dist/node/worker-runner.js +35 -0
- package/dist/node/worker.d.ts +29 -0
- package/dist/node/worker.js +61 -0
- package/dist/port.d.ts +28 -0
- package/dist/port.js +42 -0
- package/dist/power.d.ts +33 -0
- package/dist/power.js +99 -0
- package/dist/protocol.d.ts +14 -0
- package/dist/protocol.js +46 -0
- package/dist/safestorage.d.ts +16 -0
- package/dist/safestorage.js +21 -0
- package/dist/screen.d.ts +92 -0
- package/dist/screen.js +121 -0
- package/dist/session.d.ts +24 -0
- package/dist/session.js +42 -0
- package/dist/theme.d.ts +30 -0
- package/dist/theme.js +34 -0
- package/dist/tray.d.ts +30 -0
- package/dist/tray.js +69 -0
- package/dist/webcontents.d.ts +44 -0
- package/dist/webcontents.js +109 -0
- package/dist/webrequest.d.ts +23 -0
- package/dist/webrequest.js +76 -0
- package/package.json +4 -3
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { type ForkWorkerOptions, type WorkerHandle } from './node/worker.js';
|
|
2
|
+
import type { MessagePortMain } from './port.js';
|
|
3
|
+
import type { ProtocolHandler } from './protocol.js';
|
|
4
|
+
import type { NodeHandler, ContextNodeHandler } from './ipc/node.js';
|
|
5
|
+
/** Resuelto por app.whenReady(). Compartido con BrowserWindow. */
|
|
6
|
+
export declare let readyPromise: Promise<void> | null;
|
|
7
|
+
export declare const app: {
|
|
8
|
+
/** Conecta con el kernel. Resuelve cuando el canal de control está listo. */
|
|
9
|
+
whenReady(): Promise<void>;
|
|
10
|
+
quit(exitCode?: number): Promise<void>;
|
|
11
|
+
info(): Promise<{
|
|
12
|
+
pid: number;
|
|
13
|
+
version: string;
|
|
14
|
+
socket: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Ruta estándar del sistema (home, userData, temp, logs, downloads, exe…). */
|
|
17
|
+
getPath(name: string): string;
|
|
18
|
+
setPath(name: string, value: string): void;
|
|
19
|
+
getName(): string;
|
|
20
|
+
setName(name: string): void;
|
|
21
|
+
/** Versión de la app (package.json); si no, la del kernel. */
|
|
22
|
+
getVersion(): string;
|
|
23
|
+
isPackaged(): boolean;
|
|
24
|
+
getAppPath(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Opciones de línea de comandos del WebView. `appendSwitch` se aplica al
|
|
27
|
+
* crear la ventana (Windows: AdditionalBrowserArguments; Linux: best-effort).
|
|
28
|
+
*/
|
|
29
|
+
commandLine: {
|
|
30
|
+
_switches: Map<string, string | undefined>;
|
|
31
|
+
appendSwitch(key: string, value?: string): void;
|
|
32
|
+
appendArgument(arg: string): void;
|
|
33
|
+
getSwitchValue(key: string): string;
|
|
34
|
+
hasSwitch(key: string): boolean;
|
|
35
|
+
};
|
|
36
|
+
on(event: "before-quit" | "will-quit" | "window-all-closed" | "second-instance" | "activate" | "child-process-gone" | string, listener: (...args: any[]) => void): void;
|
|
37
|
+
off(event: string, listener: (...args: any[]) => void): void;
|
|
38
|
+
once(event: string, listener: (...args: any[]) => void): void;
|
|
39
|
+
/**
|
|
40
|
+
* Localiza (o descarga) un runtime Node. Prioridad: OW_NODE_BIN → Node del
|
|
41
|
+
* sistema → caché de Owear → descarga oficial. `source` indica de dónde
|
|
42
|
+
* salió ("env" | "system" | "cache" | "downloaded").
|
|
43
|
+
*/
|
|
44
|
+
ensureNodeRuntime(range?: string): Promise<{
|
|
45
|
+
path: string;
|
|
46
|
+
version: string;
|
|
47
|
+
source: string;
|
|
48
|
+
}>;
|
|
49
|
+
/** Acceso crudo al canal (para módulos custom del SDK). */
|
|
50
|
+
__channel: import("./channel.js").ControlChannel;
|
|
51
|
+
/**
|
|
52
|
+
* Registra un handler invocable desde el renderer con
|
|
53
|
+
* `ow.invoke('node', 'call', { fn, args })`. Devuelve un unsubscribe.
|
|
54
|
+
* Es la vía para exponer Node a la UI (extension host, libs Node-only).
|
|
55
|
+
*/
|
|
56
|
+
handle(fn: string, handler: NodeHandler): () => void;
|
|
57
|
+
/**
|
|
58
|
+
* Como `handle`, pero el handler recibe la ventana de origen:
|
|
59
|
+
* app.handleContext('x', (ctx, payload) => ctx.windowId)
|
|
60
|
+
* `ctx.windowId` es 0 si la llamada no vino de una ventana concreta.
|
|
61
|
+
*/
|
|
62
|
+
handleContext(fn: string, handler: ContextNodeHandler): () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Empuja un evento a los renderers, recibible con `ow.on(name, cb)`.
|
|
65
|
+
* `windowId` opcional para dirigirlo a una ventana concreta.
|
|
66
|
+
*/
|
|
67
|
+
send(name: string, payload?: unknown, windowId?: number): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Lanza un worker Node con canal de mensajes (reemplazo de
|
|
70
|
+
* `utilityProcess.fork`). El entry puede ser absoluto, relativo a
|
|
71
|
+
* `app.workersDir()`, o con `./` relativo al cwd.
|
|
72
|
+
*
|
|
73
|
+
* const w = app.forkWorker('tree-sitter-worker.js')
|
|
74
|
+
* w.postMessage({ op: 'tokenize', text })
|
|
75
|
+
* w.on('message', (m) => …)
|
|
76
|
+
*/
|
|
77
|
+
forkWorker(entry: string, options?: ForkWorkerOptions): WorkerHandle;
|
|
78
|
+
/** Directorio de workers compilados (`OW_APP_WORKERS`), si `ow dev`/`ow build` lo definió. */
|
|
79
|
+
workersDir(): string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Crea un canal bidireccional de dos puertos. Ambos nacen en el main;
|
|
82
|
+
* transfiere uno a un renderer con `app.sendPort(windowId, name, port)`.
|
|
83
|
+
* El renderer lo recibe con `ow.on(name, ({ port }) => ow.port(port))`.
|
|
84
|
+
*/
|
|
85
|
+
createChannel(): {
|
|
86
|
+
port1: MessagePortMain;
|
|
87
|
+
port2: MessagePortMain;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Transfiere un puerto a una ventana. El renderer lo recibe como
|
|
91
|
+
* `ow.on(name, ({ port }) => { const p = ow.port(port); … })`.
|
|
92
|
+
*/
|
|
93
|
+
sendPort(windowId: number, name: string, port: MessagePortMain): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Registra un esquema personalizado (protocol API, estilo intermedio):
|
|
96
|
+
* app.protocol('scrakk-ext', { privileged: { secure: true, cors: true },
|
|
97
|
+
* handler: async (req) => new Response('<h1>hola</h1>', { headers: {...} }) })
|
|
98
|
+
* app.protocol('assets', { serve: '/ruta/al/dir' }) // el kernel sirve el dir
|
|
99
|
+
* El handler corre en el main (Node); puede devolver un `Response`, un objeto
|
|
100
|
+
* `{ status, headers, body }`, un string o `null`.
|
|
101
|
+
*/
|
|
102
|
+
protocol(name: string, options?: {
|
|
103
|
+
privileged?: {
|
|
104
|
+
secure?: boolean;
|
|
105
|
+
cors?: boolean;
|
|
106
|
+
stream?: boolean;
|
|
107
|
+
standard?: boolean;
|
|
108
|
+
fetch?: boolean;
|
|
109
|
+
};
|
|
110
|
+
serve?: string;
|
|
111
|
+
handler?: ProtocolHandler;
|
|
112
|
+
}): Promise<void>;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Invoca una función de un módulo nativo del kernel desde el proceso
|
|
116
|
+
* principal (fs, process, net, clipboard…). Es el equivalente de `ow.invoke()`
|
|
117
|
+
* del renderer, pero sin depender de una ventana: es la pieza que permite
|
|
118
|
+
* portar APIs de Electron al main process.
|
|
119
|
+
*
|
|
120
|
+
* const txt = await invokeNative<string>('fs', 'readText', '/etc/hostname')
|
|
121
|
+
*/
|
|
122
|
+
export { invokeNative } from './channel.js';
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// Copyright 2026 Owear Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// @owear/core/src/app.ts — ciclo de vida, rutas, identidad y commandLine.
|
|
5
|
+
import { EventEmitter } from 'node:events';
|
|
6
|
+
import * as os from 'node:os';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import { channel, socketPathFromEnv } from './channel.js';
|
|
10
|
+
import { forkWorker } from './node/worker.js';
|
|
11
|
+
import { nodeHandlers, nodeContextHandlers } from './ipc/node.js';
|
|
12
|
+
import { portRecords, makeMainPort, allocPortId } from './port.js';
|
|
13
|
+
import { protocolHandlers } from './protocol.js';
|
|
14
|
+
/** Resuelto por app.whenReady(). Compartido con BrowserWindow. */
|
|
15
|
+
export let readyPromise = null;
|
|
16
|
+
// ── app: rutas, identidad, commandLine y eventos de ciclo de vida ──────────
|
|
17
|
+
const appEmitter = new EventEmitter();
|
|
18
|
+
let appNameOverride = null;
|
|
19
|
+
let appInfoCache = null;
|
|
20
|
+
const pathOverrides = new Map();
|
|
21
|
+
channel.on('app.event', (params) => {
|
|
22
|
+
appEmitter.emit(params?.name, params?.payload);
|
|
23
|
+
});
|
|
24
|
+
function readPackageJson() {
|
|
25
|
+
const dirs = [
|
|
26
|
+
process.cwd(),
|
|
27
|
+
process.env.OW_APP_MAIN ? path.dirname(process.env.OW_APP_MAIN) : '',
|
|
28
|
+
process.env.OW_ASSETS_DIR ?? '',
|
|
29
|
+
];
|
|
30
|
+
for (const dir of dirs) {
|
|
31
|
+
if (!dir)
|
|
32
|
+
continue;
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* siguiente */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const appPkg = readPackageJson();
|
|
43
|
+
function appId() {
|
|
44
|
+
return process.env.OW_APP_ID || process.env.OW_APP_NAME || appPkg.name || 'app';
|
|
45
|
+
}
|
|
46
|
+
/** Rutas síncronas estilo Electron (mismas convenciones que el módulo `path`). */
|
|
47
|
+
function computePath(name) {
|
|
48
|
+
const home = os.homedir();
|
|
49
|
+
const win = process.platform === 'win32';
|
|
50
|
+
const appData = win
|
|
51
|
+
? process.env.APPDATA ?? path.join(home, 'AppData', 'Roaming')
|
|
52
|
+
: process.env.XDG_DATA_HOME ?? path.join(home, '.local', 'share');
|
|
53
|
+
const localData = win
|
|
54
|
+
? process.env.LOCALAPPDATA ?? path.join(home, 'AppData', 'Local')
|
|
55
|
+
: process.env.XDG_DATA_HOME ?? path.join(home, '.local', 'share');
|
|
56
|
+
switch (name) {
|
|
57
|
+
case 'home':
|
|
58
|
+
return home;
|
|
59
|
+
case 'appData':
|
|
60
|
+
return appData;
|
|
61
|
+
case 'userData':
|
|
62
|
+
return win ? path.join(localData, appId()) : path.join(process.env.XDG_CONFIG_HOME ?? path.join(home, '.config'), appId());
|
|
63
|
+
case 'sessionData':
|
|
64
|
+
return appInfoCache?.appPath ? path.join(appInfoCache.appPath, 'session') : path.join(localData, appId(), 'session');
|
|
65
|
+
case 'cache':
|
|
66
|
+
return win ? path.join(localData, 'owear', 'cache') : process.env.XDG_CACHE_HOME ?? path.join(home, '.cache');
|
|
67
|
+
case 'temp':
|
|
68
|
+
return os.tmpdir();
|
|
69
|
+
case 'logs':
|
|
70
|
+
return path.join(computePath('userData'), 'logs');
|
|
71
|
+
case 'downloads':
|
|
72
|
+
return win ? path.join(home, 'Downloads') : (process.env.XDG_DOWNLOAD_DIR ?? path.join(home, 'Downloads'));
|
|
73
|
+
case 'documents':
|
|
74
|
+
return path.join(home, 'Documents');
|
|
75
|
+
case 'desktop':
|
|
76
|
+
return path.join(home, 'Desktop');
|
|
77
|
+
case 'pictures':
|
|
78
|
+
return path.join(home, 'Pictures');
|
|
79
|
+
case 'music':
|
|
80
|
+
return path.join(home, 'Music');
|
|
81
|
+
case 'videos':
|
|
82
|
+
return path.join(home, 'Videos');
|
|
83
|
+
case 'exe':
|
|
84
|
+
return appInfoCache?.exePath ?? path.dirname(process.execPath);
|
|
85
|
+
case 'appPath':
|
|
86
|
+
return appInfoCache?.appPath ?? process.cwd();
|
|
87
|
+
default:
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export const app = {
|
|
92
|
+
/** Conecta con el kernel. Resuelve cuando el canal de control está listo. */
|
|
93
|
+
whenReady() {
|
|
94
|
+
if (!readyPromise) {
|
|
95
|
+
readyPromise = channel.connect(socketPathFromEnv()).then(async () => {
|
|
96
|
+
try {
|
|
97
|
+
appInfoCache = await channel.call('app.info');
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* sin kernel: rutas locales */
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return readyPromise;
|
|
105
|
+
},
|
|
106
|
+
quit(exitCode = 0) {
|
|
107
|
+
return channel.call('app.quit', { exitCode }).catch(() => undefined);
|
|
108
|
+
},
|
|
109
|
+
info() {
|
|
110
|
+
return channel.call('app.info');
|
|
111
|
+
},
|
|
112
|
+
// ── rutas / identidad (estilo Electron) ────────────────────────────────
|
|
113
|
+
/** Ruta estándar del sistema (home, userData, temp, logs, downloads, exe…). */
|
|
114
|
+
getPath(name) {
|
|
115
|
+
return pathOverrides.get(name) ?? computePath(name);
|
|
116
|
+
},
|
|
117
|
+
setPath(name, value) {
|
|
118
|
+
pathOverrides.set(name, value);
|
|
119
|
+
},
|
|
120
|
+
getName() {
|
|
121
|
+
return appNameOverride ?? appInfoCache?.name ?? process.env.OW_APP_NAME ?? appPkg.name ?? 'Owear App';
|
|
122
|
+
},
|
|
123
|
+
setName(name) {
|
|
124
|
+
appNameOverride = name;
|
|
125
|
+
void channel.call('app.setName', { name }).catch(() => undefined);
|
|
126
|
+
},
|
|
127
|
+
/** Versión de la app (package.json); si no, la del kernel. */
|
|
128
|
+
getVersion() {
|
|
129
|
+
return appPkg.version ?? appInfoCache?.version ?? '0.0.0';
|
|
130
|
+
},
|
|
131
|
+
isPackaged() {
|
|
132
|
+
return !!appInfoCache?.packaged;
|
|
133
|
+
},
|
|
134
|
+
getAppPath() {
|
|
135
|
+
return appInfoCache?.appPath ?? process.cwd();
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Opciones de línea de comandos del WebView. `appendSwitch` se aplica al
|
|
139
|
+
* crear la ventana (Windows: AdditionalBrowserArguments; Linux: best-effort).
|
|
140
|
+
*/
|
|
141
|
+
commandLine: {
|
|
142
|
+
_switches: new Map(),
|
|
143
|
+
appendSwitch(key, value) {
|
|
144
|
+
this._switches.set(key, value);
|
|
145
|
+
void channel.call('app.commandLine.appendSwitch', { key, value }).catch(() => undefined);
|
|
146
|
+
},
|
|
147
|
+
appendArgument(arg) {
|
|
148
|
+
void channel.call('app.commandLine.appendArgument', { arg }).catch(() => undefined);
|
|
149
|
+
},
|
|
150
|
+
getSwitchValue(key) {
|
|
151
|
+
return this._switches.get(key) ?? '';
|
|
152
|
+
},
|
|
153
|
+
hasSwitch(key) {
|
|
154
|
+
return this._switches.has(key);
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
// ── eventos de ciclo de vida (EventEmitter) ────────────────────────────
|
|
158
|
+
on(event, listener) {
|
|
159
|
+
appEmitter.on(event, listener);
|
|
160
|
+
},
|
|
161
|
+
off(event, listener) {
|
|
162
|
+
appEmitter.off(event, listener);
|
|
163
|
+
},
|
|
164
|
+
once(event, listener) {
|
|
165
|
+
appEmitter.once(event, listener);
|
|
166
|
+
},
|
|
167
|
+
/**
|
|
168
|
+
* Localiza (o descarga) un runtime Node. Prioridad: OW_NODE_BIN → Node del
|
|
169
|
+
* sistema → caché de Owear → descarga oficial. `source` indica de dónde
|
|
170
|
+
* salió ("env" | "system" | "cache" | "downloaded").
|
|
171
|
+
*/
|
|
172
|
+
ensureNodeRuntime(range = 'latest') {
|
|
173
|
+
return channel.call('node.ensure', { range });
|
|
174
|
+
},
|
|
175
|
+
/** Acceso crudo al canal (para módulos custom del SDK). */
|
|
176
|
+
__channel: channel,
|
|
177
|
+
/**
|
|
178
|
+
* Registra un handler invocable desde el renderer con
|
|
179
|
+
* `ow.invoke('node', 'call', { fn, args })`. Devuelve un unsubscribe.
|
|
180
|
+
* Es la vía para exponer Node a la UI (extension host, libs Node-only).
|
|
181
|
+
*/
|
|
182
|
+
handle(fn, handler) {
|
|
183
|
+
nodeHandlers.set(fn, handler);
|
|
184
|
+
return () => {
|
|
185
|
+
if (nodeHandlers.get(fn) === handler)
|
|
186
|
+
nodeHandlers.delete(fn);
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* Como `handle`, pero el handler recibe la ventana de origen:
|
|
191
|
+
* app.handleContext('x', (ctx, payload) => ctx.windowId)
|
|
192
|
+
* `ctx.windowId` es 0 si la llamada no vino de una ventana concreta.
|
|
193
|
+
*/
|
|
194
|
+
handleContext(fn, handler) {
|
|
195
|
+
nodeContextHandlers.set(fn, handler);
|
|
196
|
+
return () => {
|
|
197
|
+
if (nodeContextHandlers.get(fn) === handler)
|
|
198
|
+
nodeContextHandlers.delete(fn);
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
/**
|
|
202
|
+
* Empuja un evento a los renderers, recibible con `ow.on(name, cb)`.
|
|
203
|
+
* `windowId` opcional para dirigirlo a una ventana concreta.
|
|
204
|
+
*/
|
|
205
|
+
send(name, payload, windowId) {
|
|
206
|
+
return channel.call('node.emit', { name, payload, windowId });
|
|
207
|
+
},
|
|
208
|
+
/**
|
|
209
|
+
* Lanza un worker Node con canal de mensajes (reemplazo de
|
|
210
|
+
* `utilityProcess.fork`). El entry puede ser absoluto, relativo a
|
|
211
|
+
* `app.workersDir()`, o con `./` relativo al cwd.
|
|
212
|
+
*
|
|
213
|
+
* const w = app.forkWorker('tree-sitter-worker.js')
|
|
214
|
+
* w.postMessage({ op: 'tokenize', text })
|
|
215
|
+
* w.on('message', (m) => …)
|
|
216
|
+
*/
|
|
217
|
+
forkWorker(entry, options) {
|
|
218
|
+
return forkWorker(entry, options);
|
|
219
|
+
},
|
|
220
|
+
/** Directorio de workers compilados (`OW_APP_WORKERS`), si `ow dev`/`ow build` lo definió. */
|
|
221
|
+
workersDir() {
|
|
222
|
+
return process.env.OW_APP_WORKERS;
|
|
223
|
+
},
|
|
224
|
+
/**
|
|
225
|
+
* Crea un canal bidireccional de dos puertos. Ambos nacen en el main;
|
|
226
|
+
* transfiere uno a un renderer con `app.sendPort(windowId, name, port)`.
|
|
227
|
+
* El renderer lo recibe con `ow.on(name, ({ port }) => ow.port(port))`.
|
|
228
|
+
*/
|
|
229
|
+
createChannel() {
|
|
230
|
+
const a = allocPortId();
|
|
231
|
+
const b = allocPortId();
|
|
232
|
+
portRecords.set(a, { id: a, peer: b, side: { kind: 'main' }, emitter: new EventEmitter() });
|
|
233
|
+
portRecords.set(b, { id: b, peer: a, side: { kind: 'main' }, emitter: new EventEmitter() });
|
|
234
|
+
return { port1: makeMainPort(a), port2: makeMainPort(b) };
|
|
235
|
+
},
|
|
236
|
+
/**
|
|
237
|
+
* Transfiere un puerto a una ventana. El renderer lo recibe como
|
|
238
|
+
* `ow.on(name, ({ port }) => { const p = ow.port(port); … })`.
|
|
239
|
+
*/
|
|
240
|
+
sendPort(windowId, name, port) {
|
|
241
|
+
const rec = portRecords.get(port.portId);
|
|
242
|
+
if (rec)
|
|
243
|
+
rec.side = { kind: 'renderer', windowId };
|
|
244
|
+
return channel.call('node.emit', {
|
|
245
|
+
name,
|
|
246
|
+
payload: { port: port.portId },
|
|
247
|
+
windowId,
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
/**
|
|
251
|
+
* Registra un esquema personalizado (protocol API, estilo intermedio):
|
|
252
|
+
* app.protocol('scrakk-ext', { privileged: { secure: true, cors: true },
|
|
253
|
+
* handler: async (req) => new Response('<h1>hola</h1>', { headers: {...} }) })
|
|
254
|
+
* app.protocol('assets', { serve: '/ruta/al/dir' }) // el kernel sirve el dir
|
|
255
|
+
* El handler corre en el main (Node); puede devolver un `Response`, un objeto
|
|
256
|
+
* `{ status, headers, body }`, un string o `null`.
|
|
257
|
+
*/
|
|
258
|
+
protocol(name, options = {}) {
|
|
259
|
+
protocolHandlers.set(name, options.handler);
|
|
260
|
+
return channel.call('protocol.register', {
|
|
261
|
+
scheme: name,
|
|
262
|
+
privileged: options.privileged,
|
|
263
|
+
dir: options.serve,
|
|
264
|
+
handler: typeof options.handler === 'function',
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Invoca una función de un módulo nativo del kernel desde el proceso
|
|
270
|
+
* principal (fs, process, net, clipboard…). Es el equivalente de `ow.invoke()`
|
|
271
|
+
* del renderer, pero sin depender de una ventana: es la pieza que permite
|
|
272
|
+
* portar APIs de Electron al main process.
|
|
273
|
+
*
|
|
274
|
+
* const txt = await invokeNative<string>('fs', 'readText', '/etc/hostname')
|
|
275
|
+
*/
|
|
276
|
+
export { invokeNative } from './channel.js';
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type BridgePlatform = 'linux' | 'win' | 'mac' | (string & {});
|
|
2
|
+
/** Identidad de la app que se instala. */
|
|
3
|
+
export interface BridgeApp {
|
|
4
|
+
/** Id estable/pkg-name (reverse-DNS recomendado): com.acme.miapp. */
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
publisher?: string;
|
|
9
|
+
/** Icono relativo al payload (png/svg/ico). */
|
|
10
|
+
icon?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Formato de salida del payload que el instalador coloca. */
|
|
13
|
+
export type BridgeFormat = 'binary' | 'deb' | 'appimage' | 'exe' | 'msi';
|
|
14
|
+
/** Layout del payload: `minimal` = 1 binario; `layout` = árbol organizado. */
|
|
15
|
+
export type BridgeLayout = 'minimal' | 'layout';
|
|
16
|
+
export type BridgePreset = 'electron-like' | 'tauri-like' | 'flat' | 'custom';
|
|
17
|
+
/** Cómo resolver el runtime Node de la app instalada. */
|
|
18
|
+
export interface BridgeNode {
|
|
19
|
+
mode?: 'system' | 'download' | 'embed';
|
|
20
|
+
}
|
|
21
|
+
/** Qué se compila/embebe y en qué orden (grupos lógicos). */
|
|
22
|
+
export type BridgeGroup = 'kernel' | 'node' | 'modules:stock' | 'modules:app' | 'app:main' | 'app:workers' | 'app:assets' | 'resources';
|
|
23
|
+
/** Protección por grupo dentro del binario/payload. */
|
|
24
|
+
export interface BridgeProtect {
|
|
25
|
+
integrity?: boolean;
|
|
26
|
+
readonly?: boolean;
|
|
27
|
+
hidden?: boolean;
|
|
28
|
+
signed?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Configuración por plataforma. */
|
|
31
|
+
export interface BridgeTarget {
|
|
32
|
+
format?: BridgeFormat[];
|
|
33
|
+
layout?: BridgeLayout;
|
|
34
|
+
/** Preset de estructura para `layout` (o 'custom'). */
|
|
35
|
+
preset?: BridgePreset;
|
|
36
|
+
/** Directorio de instalación por defecto (per-user recomendado). */
|
|
37
|
+
dir?: string;
|
|
38
|
+
shortcuts?: Array<'desktop' | 'menu' | 'startup' | 'startMenu'>;
|
|
39
|
+
/** Scope de instalación por defecto. */
|
|
40
|
+
scope?: 'user' | 'system';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Pasos que el instalador ejecuta. Son NOMBRES de hooks implementados en el
|
|
44
|
+
* instalador (su `app/main.ts`), no funciones: el bridge debe ser serializable.
|
|
45
|
+
*/
|
|
46
|
+
export interface BridgeHooks {
|
|
47
|
+
preInstall?: string;
|
|
48
|
+
postInstall?: string;
|
|
49
|
+
preUninstall?: string;
|
|
50
|
+
postUninstall?: string;
|
|
51
|
+
}
|
|
52
|
+
/** Contrato completo app ↔ installer. */
|
|
53
|
+
export interface OwearBridge {
|
|
54
|
+
/** Versión del formato del bridge (para compatibilidad). */
|
|
55
|
+
bridgeVersion?: number;
|
|
56
|
+
app: BridgeApp;
|
|
57
|
+
targets: Partial<Record<BridgePlatform, BridgeTarget>>;
|
|
58
|
+
/** Orden de grupos al empaquetar/colocar (sobreescribe el preset). */
|
|
59
|
+
order?: BridgeGroup[];
|
|
60
|
+
/** Protección por grupo (integridad/flags). */
|
|
61
|
+
protect?: Partial<Record<BridgeGroup, BridgeProtect>>;
|
|
62
|
+
node?: BridgeNode;
|
|
63
|
+
hooks?: BridgeHooks;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Define el bridge con tipado y validación.
|
|
67
|
+
*
|
|
68
|
+
* // owear.bridge.ts
|
|
69
|
+
* import { defineBridge } from '@owear/core'
|
|
70
|
+
* export default defineBridge({
|
|
71
|
+
* app: { id: 'com.acme.miapp', name: 'MiApp', version: '1.0.0' },
|
|
72
|
+
* targets: {
|
|
73
|
+
* linux: { layout: 'minimal', format: ['binary'], shortcuts: ['desktop'] },
|
|
74
|
+
* win: { layout: 'layout', format: ['exe'], shortcuts: ['startMenu'] },
|
|
75
|
+
* },
|
|
76
|
+
* order: ['kernel','modules:stock','app:main','app:assets'],
|
|
77
|
+
* })
|
|
78
|
+
*/
|
|
79
|
+
export declare function defineBridge(b: OwearBridge): OwearBridge;
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Copyright 2026 Owear Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// @owear/core/src/bridge.ts — contrato (bridge) app ↔ installer ↔ uninstaller.
|
|
5
|
+
//
|
|
6
|
+
// El bridge es un fichero `owear.bridge.ts` (o `.js`) SIEMPRE presente en la
|
|
7
|
+
// app. `ow build installer` lo compila y lo embebe en el binario instalador
|
|
8
|
+
// como `bridge.json`, de modo que el instalador conoce la app sin adivinarla.
|
|
9
|
+
//
|
|
10
|
+
// El bridge es DATO puro (serializable a JSON): no metas funciones aquí. Los
|
|
11
|
+
// pasos que quieras ejecutar (hooks) se declaran por nombre y los implementa el
|
|
12
|
+
// propio instalador en su `app/main.ts`.
|
|
13
|
+
/** Comprueba que el bridge es utilizable (falla pronto, en build). */
|
|
14
|
+
function validateBridge(b) {
|
|
15
|
+
const err = (m) => {
|
|
16
|
+
throw new Error(`[owear.bridge] ${m}`);
|
|
17
|
+
};
|
|
18
|
+
if (!b || typeof b !== 'object')
|
|
19
|
+
err('el bridge debe ser un objeto (export default)');
|
|
20
|
+
if (!b.app?.id)
|
|
21
|
+
err('app.id requerido');
|
|
22
|
+
if (!b.app?.version)
|
|
23
|
+
err('app.version requerido');
|
|
24
|
+
if (!b.targets || typeof b.targets !== 'object')
|
|
25
|
+
err('targets requerido');
|
|
26
|
+
for (const [plat, t] of Object.entries(b.targets)) {
|
|
27
|
+
if (t?.layout && t.layout !== 'minimal' && t.layout !== 'layout')
|
|
28
|
+
err(`targets.${plat}.layout inválido: ${t.layout}`);
|
|
29
|
+
}
|
|
30
|
+
// Serializabilidad: el bridge se embebe como JSON (nada de funciones).
|
|
31
|
+
try {
|
|
32
|
+
JSON.stringify(b);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
err('el bridge no es serializable (¿metiste una función?)');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Define el bridge con tipado y validación.
|
|
40
|
+
*
|
|
41
|
+
* // owear.bridge.ts
|
|
42
|
+
* import { defineBridge } from '@owear/core'
|
|
43
|
+
* export default defineBridge({
|
|
44
|
+
* app: { id: 'com.acme.miapp', name: 'MiApp', version: '1.0.0' },
|
|
45
|
+
* targets: {
|
|
46
|
+
* linux: { layout: 'minimal', format: ['binary'], shortcuts: ['desktop'] },
|
|
47
|
+
* win: { layout: 'layout', format: ['exe'], shortcuts: ['startMenu'] },
|
|
48
|
+
* },
|
|
49
|
+
* order: ['kernel','modules:stock','app:main','app:assets'],
|
|
50
|
+
* })
|
|
51
|
+
*/
|
|
52
|
+
export function defineBridge(b) {
|
|
53
|
+
validateBridge(b);
|
|
54
|
+
return { bridgeVersion: 1, ...b };
|
|
55
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { WindowOptions, Bounds } from './index.js';
|
|
3
|
+
import { WebContents } from './webcontents.js';
|
|
4
|
+
/** Registro de ventanas por id (para resolver clicks de menú). */
|
|
5
|
+
export declare const windowsById: Map<number, BrowserWindow>;
|
|
6
|
+
export declare class BrowserWindow extends EventEmitter {
|
|
7
|
+
private _id;
|
|
8
|
+
private _options;
|
|
9
|
+
private _wc;
|
|
10
|
+
private _onWindowEvent;
|
|
11
|
+
private _onDisconnected;
|
|
12
|
+
constructor(options?: WindowOptions);
|
|
13
|
+
private _create;
|
|
14
|
+
private _wireEvents;
|
|
15
|
+
private _unwireEvents;
|
|
16
|
+
get id(): number | null;
|
|
17
|
+
private requireId;
|
|
18
|
+
loadURL(url: string): Promise<void>;
|
|
19
|
+
/** Evalúa JS; devuelve el resultado JSON-serializado. */
|
|
20
|
+
eval<T = unknown>(js: string): Promise<T>;
|
|
21
|
+
show(): Promise<void>;
|
|
22
|
+
hide(): Promise<void>;
|
|
23
|
+
focus(): Promise<void>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
destroy(): Promise<void>;
|
|
26
|
+
minimize(): Promise<void>;
|
|
27
|
+
maximize(): Promise<void>;
|
|
28
|
+
unmaximize(): Promise<void>;
|
|
29
|
+
setFullScreen(enabled: boolean): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Responde a un `closeRequested` (F3.4).
|
|
32
|
+
* `allow=false` cancela el cierre; `true` destruye la ventana.
|
|
33
|
+
* Si nadie responde en `OW_CLOSE_TIMEOUT_MS` (default 1000 ms), el kernel
|
|
34
|
+
* cierra igualmente.
|
|
35
|
+
*/
|
|
36
|
+
closeRespond(requestId: number, allow: boolean): Promise<void>;
|
|
37
|
+
isMaximized(): Promise<boolean>;
|
|
38
|
+
isMinimized(): Promise<boolean>;
|
|
39
|
+
getBounds(): Promise<Bounds>;
|
|
40
|
+
setBounds(b: Partial<Bounds>): Promise<void>;
|
|
41
|
+
setTitle(title: string): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Activa/desactiva o reconfigura los botones nativos de ventana
|
|
44
|
+
* (min/max/close) dentro de la titlebar custom (Linux: GTK `titlebutton`).
|
|
45
|
+
* `true` los activa con los valores por defecto.
|
|
46
|
+
*/
|
|
47
|
+
setTitleBarOverlay(overlay: boolean | {
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
color?: string;
|
|
50
|
+
symbolColor?: string;
|
|
51
|
+
buttonColor?: string;
|
|
52
|
+
height?: number;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
isVisible(): Promise<boolean>;
|
|
55
|
+
isFocused(): Promise<boolean>;
|
|
56
|
+
isResizable(): Promise<boolean>;
|
|
57
|
+
isMovable(): Promise<boolean>;
|
|
58
|
+
isMinimizable(): Promise<boolean>;
|
|
59
|
+
isMaximizable(): Promise<boolean>;
|
|
60
|
+
isClosable(): Promise<boolean>;
|
|
61
|
+
isAlwaysOnTop(): Promise<boolean>;
|
|
62
|
+
isKiosk(): Promise<boolean>;
|
|
63
|
+
isDestroyed(): Promise<boolean>;
|
|
64
|
+
isFullScreen(): Promise<boolean>;
|
|
65
|
+
setResizable(on?: boolean): Promise<void>;
|
|
66
|
+
setMovable(on?: boolean): Promise<void>;
|
|
67
|
+
setMinimizable(on?: boolean): Promise<void>;
|
|
68
|
+
setMaximizable(on?: boolean): Promise<void>;
|
|
69
|
+
setClosable(on?: boolean): Promise<void>;
|
|
70
|
+
setAlwaysOnTop(on?: boolean, level?: number): Promise<void>;
|
|
71
|
+
setSkipTaskbar(on?: boolean): Promise<void>;
|
|
72
|
+
setHasShadow(on?: boolean): Promise<void>;
|
|
73
|
+
setKiosk(on?: boolean): Promise<void>;
|
|
74
|
+
setIgnoreMouseEvents(ignore?: boolean, options?: {
|
|
75
|
+
forward?: boolean;
|
|
76
|
+
}): Promise<void>;
|
|
77
|
+
setProgressBar(value: number, options?: {
|
|
78
|
+
mode?: 'none' | 'normal' | 'indeterminate' | 'paused' | 'error';
|
|
79
|
+
}): Promise<void>;
|
|
80
|
+
setBackgroundColor(color: string): Promise<void>;
|
|
81
|
+
moveTop(): Promise<void>;
|
|
82
|
+
setAspectRatio(ratio: number, extraSize?: {
|
|
83
|
+
width: number;
|
|
84
|
+
height: number;
|
|
85
|
+
}): Promise<void>;
|
|
86
|
+
getContentBounds(): Promise<Bounds>;
|
|
87
|
+
setContentSize(width: number, height: number): Promise<void>;
|
|
88
|
+
getContentSize(): Promise<{
|
|
89
|
+
width: number;
|
|
90
|
+
height: number;
|
|
91
|
+
}>;
|
|
92
|
+
getMinimumSize(): Promise<{
|
|
93
|
+
width: number;
|
|
94
|
+
height: number;
|
|
95
|
+
}>;
|
|
96
|
+
getMaximumSize(): Promise<{
|
|
97
|
+
width: number;
|
|
98
|
+
height: number;
|
|
99
|
+
}>;
|
|
100
|
+
setMinimumSize(width: number, height: number): Promise<void>;
|
|
101
|
+
setMaximumSize(width: number, height: number): Promise<void>;
|
|
102
|
+
/** Ventanas creadas por esta app (orden de creación). Síncrono, como Electron. */
|
|
103
|
+
static getAllWindows(): BrowserWindow[];
|
|
104
|
+
/** Ventana con foco, o `null`. Síncrono, como Electron. */
|
|
105
|
+
static getFocusedWindow(): BrowserWindow | null;
|
|
106
|
+
static fromId(id: number): BrowserWindow | null;
|
|
107
|
+
/**
|
|
108
|
+
* `webContents` de esta ventana (estilo Electron): `send`, `capturePage`,
|
|
109
|
+
* eventos (`did-finish-load`, `before-input-event`, …) y
|
|
110
|
+
* `setWindowOpenHandler`.
|
|
111
|
+
*/
|
|
112
|
+
get webContents(): WebContents;
|
|
113
|
+
}
|