@owear/core 0.1.2 → 0.1.4

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.
Files changed (58) hide show
  1. package/dist/app.d.ts +128 -0
  2. package/dist/app.js +285 -0
  3. package/dist/appicon.d.ts +4 -0
  4. package/dist/appicon.js +16 -0
  5. package/dist/bridge.d.ts +79 -0
  6. package/dist/bridge.js +55 -0
  7. package/dist/browserwindow.d.ts +115 -0
  8. package/dist/browserwindow.js +237 -0
  9. package/dist/channel.d.ts +22 -0
  10. package/dist/channel.js +114 -0
  11. package/dist/dialog.d.ts +47 -0
  12. package/dist/dialog.js +22 -0
  13. package/dist/index.d.ts +83 -96
  14. package/dist/index.js +26 -253
  15. package/dist/installer.d.ts +126 -0
  16. package/dist/installer.js +84 -0
  17. package/dist/ipc/node.d.ts +7 -0
  18. package/dist/ipc/node.js +25 -0
  19. package/dist/menu.d.ts +56 -0
  20. package/dist/menu.js +208 -0
  21. package/dist/modules.d.ts +16 -0
  22. package/dist/modules.js +13 -0
  23. package/dist/nativeimage.d.ts +41 -0
  24. package/dist/nativeimage.js +376 -0
  25. package/dist/node/worker-runner.d.ts +1 -0
  26. package/dist/node/worker-runner.js +35 -0
  27. package/dist/node/worker.d.ts +29 -0
  28. package/dist/node/worker.js +61 -0
  29. package/dist/port.d.ts +28 -0
  30. package/dist/port.js +42 -0
  31. package/dist/power.d.ts +33 -0
  32. package/dist/power.js +99 -0
  33. package/dist/protocol.d.ts +14 -0
  34. package/dist/protocol.js +46 -0
  35. package/dist/safestorage.d.ts +16 -0
  36. package/dist/safestorage.js +21 -0
  37. package/dist/screen.d.ts +92 -0
  38. package/dist/screen.js +121 -0
  39. package/dist/session.d.ts +24 -0
  40. package/dist/session.js +42 -0
  41. package/dist/theme.d.ts +30 -0
  42. package/dist/theme.js +34 -0
  43. package/dist/tray.d.ts +30 -0
  44. package/dist/tray.js +69 -0
  45. package/dist/updater/boot.d.ts +20 -0
  46. package/dist/updater/boot.js +49 -0
  47. package/dist/updater/delta.d.ts +30 -0
  48. package/dist/updater/delta.js +96 -0
  49. package/dist/updater/yaml.d.ts +6 -0
  50. package/dist/updater/yaml.js +160 -0
  51. package/dist/updater.d.ts +119 -0
  52. package/dist/updater.js +429 -0
  53. package/dist/webcontents.d.ts +44 -0
  54. package/dist/webcontents.js +109 -0
  55. package/dist/webrequest.d.ts +23 -0
  56. package/dist/webrequest.js +76 -0
  57. package/package.json +7 -6
  58. package/LICENSE +0 -201
@@ -0,0 +1,29 @@
1
+ export interface WorkerHandle {
2
+ /** Envía un mensaje al worker (structured clone de Node). */
3
+ postMessage(message: unknown): void;
4
+ /** Escucha los mensajes del worker o su salida. */
5
+ on(event: 'message', listener: (message: unknown) => void): void;
6
+ on(event: 'exit', listener: (code: number) => void): void;
7
+ /** Termina el worker. */
8
+ kill(signal?: NodeJS.Signals): void;
9
+ /** PID del worker (undefined hasta que el SO lo asigna). */
10
+ readonly pid: number | undefined;
11
+ }
12
+ export interface ForkWorkerOptions {
13
+ args?: string[];
14
+ cwd?: string;
15
+ env?: NodeJS.ProcessEnv;
16
+ /** execArgv para el hijo (por defecto, el del proceso actual). */
17
+ execArgv?: string[];
18
+ }
19
+ /**
20
+ * Resuelve el entry de un worker. Si es relativo sin `./`, se busca en
21
+ * `OW_APP_WORKERS` (lo define `ow dev`/`ow build`); si no, contra el cwd.
22
+ * Las rutas absolutas y las que empiezan por `.` se respetan tal cual.
23
+ */
24
+ export declare function resolveWorkerEntry(entry: string): string;
25
+ /**
26
+ * Lanza un worker Node con canal de mensajes. Devuelve un handle síncrono.
27
+ * Los errores de arranque se reportan por el evento `exit` del worker.
28
+ */
29
+ export declare function forkWorker(entry: string, options?: ForkWorkerOptions): WorkerHandle;
@@ -0,0 +1,61 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // packages/core/src/node/worker.ts — worker Node con canal de mensajes.
5
+ //
6
+ // Reemplazo Owear de `utilityProcess.fork` de Electron. El proceso principal
7
+ // de Owear YA es Node real, así que un worker es un hijo con canal IPC
8
+ // (`child_process.fork`). Los workers portados desde Electron que usan
9
+ // `process.parentPort` funcionan sin cambios: worker-runner.ts lo emula.
10
+ //
11
+ // const w = app.forkWorker('tree-sitter-worker.js')
12
+ // w.on('message', (m) => …)
13
+ // w.postMessage({ … })
14
+ import { fork } from 'node:child_process';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
17
+ const runnerPath = join(dirname(fileURLToPath(import.meta.url)), 'worker-runner.js');
18
+ /**
19
+ * Resuelve el entry de un worker. Si es relativo sin `./`, se busca en
20
+ * `OW_APP_WORKERS` (lo define `ow dev`/`ow build`); si no, contra el cwd.
21
+ * Las rutas absolutas y las que empiezan por `.` se respetan tal cual.
22
+ */
23
+ export function resolveWorkerEntry(entry) {
24
+ if (isAbsolute(entry))
25
+ return entry;
26
+ if (entry.startsWith('.'))
27
+ return resolve(entry);
28
+ const dir = process.env.OW_APP_WORKERS;
29
+ return dir ? join(dir, entry) : resolve(entry);
30
+ }
31
+ /**
32
+ * Lanza un worker Node con canal de mensajes. Devuelve un handle síncrono.
33
+ * Los errores de arranque se reportan por el evento `exit` del worker.
34
+ */
35
+ export function forkWorker(entry, options = {}) {
36
+ const resolved = resolveWorkerEntry(entry);
37
+ const child = fork(runnerPath, [resolved, ...(options.args ?? [])], {
38
+ cwd: options.cwd,
39
+ env: options.env,
40
+ execArgv: options.execArgv ?? process.execArgv,
41
+ stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
42
+ });
43
+ const handle = {
44
+ postMessage(message) {
45
+ child.send(message);
46
+ },
47
+ on(event, listener) {
48
+ if (event === 'message')
49
+ child.on('message', (m) => listener(m));
50
+ else
51
+ child.on('exit', (code) => listener((code ?? 0)));
52
+ },
53
+ kill(signal) {
54
+ child.kill(signal);
55
+ },
56
+ get pid() {
57
+ return child.pid;
58
+ },
59
+ };
60
+ return handle;
61
+ }
package/dist/port.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export interface MessagePortMain {
3
+ /** Id global del endpoint (para transferirlo a un renderer). */
4
+ readonly portId: number;
5
+ /** Envía un mensaje al otro extremo. */
6
+ postMessage(message: unknown): void;
7
+ on(event: 'message', listener: (message: unknown) => void): this;
8
+ start(): void;
9
+ close(): void;
10
+ }
11
+ type PortSide = {
12
+ kind: 'main';
13
+ } | {
14
+ kind: 'renderer';
15
+ windowId: number;
16
+ };
17
+ interface PortRecord {
18
+ id: number;
19
+ peer: number;
20
+ side: PortSide;
21
+ emitter: EventEmitter;
22
+ }
23
+ export declare const portRecords: Map<number, PortRecord>;
24
+ export declare let nextPortId: number;
25
+ /** Reserva un id de puerto nuevo (evita asignar el import fuera del módulo). */
26
+ export declare function allocPortId(): number;
27
+ export declare function makeMainPort(id: number): MessagePortMain;
28
+ export {};
package/dist/port.js ADDED
@@ -0,0 +1,42 @@
1
+ import { channel } from './channel.js';
2
+ import { nodeHandlers } from './ipc/node.js';
3
+ export const portRecords = new Map();
4
+ export let nextPortId = 1;
5
+ /** Reserva un id de puerto nuevo (evita asignar el import fuera del módulo). */
6
+ export function allocPortId() {
7
+ return nextPortId++;
8
+ }
9
+ function deliverPort(fromId, data) {
10
+ const rec = portRecords.get(fromId);
11
+ if (!rec)
12
+ return;
13
+ const peer = portRecords.get(rec.peer);
14
+ if (!peer)
15
+ return;
16
+ if (peer.side.kind === 'main') {
17
+ peer.emitter.emit('message', data);
18
+ }
19
+ else {
20
+ void channel.call('node.emit', {
21
+ name: '__ow_port:msg',
22
+ payload: { id: peer.id, data },
23
+ windowId: peer.side.windowId,
24
+ });
25
+ }
26
+ }
27
+ export function makeMainPort(id) {
28
+ const rec = portRecords.get(id);
29
+ return Object.assign(rec.emitter, {
30
+ portId: id,
31
+ postMessage: (data) => deliverPort(id, data),
32
+ start: () => undefined,
33
+ close: () => portRecords.delete(id),
34
+ });
35
+ }
36
+ // Handler reservado: el renderer publica en un puerto con
37
+ // `ow.invoke('node','call',{ fn:'__ow_port_post', args:[{ id, data }] })`.
38
+ nodeHandlers.set('__ow_port_post', (msg) => {
39
+ if (msg && typeof msg.id === 'number')
40
+ deliverPort(msg.id, msg.data);
41
+ return null;
42
+ });
@@ -0,0 +1,33 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export type IdleState = 'active' | 'idle' | 'locked' | 'unknown';
3
+ declare class PowerMonitor extends EventEmitter {
4
+ private _watching;
5
+ private _onBattery;
6
+ private _ensure;
7
+ /** Empieza a escuchar eventos de energía (auto al usar el módulo o un evento). */
8
+ watch(): Promise<void>;
9
+ on(event: string, listener: (...args: any[]) => void): this;
10
+ once(event: string, listener: (...args: any[]) => void): this;
11
+ getIdleTime(): Promise<number>;
12
+ getIdleState(threshold: number): Promise<IdleState>;
13
+ isOnBatteryPower(): Promise<boolean>;
14
+ /** Último valor conocido (se actualiza con los eventos ac/battery). */
15
+ get onBatteryPower(): boolean;
16
+ /** @internal */
17
+ _setOnBattery(v: boolean): void;
18
+ getSystemIdleTime: () => Promise<number>;
19
+ getSystemIdleState: (threshold: number) => Promise<IdleState>;
20
+ }
21
+ /** Eventos de energía: `suspend`, `resume`, `ac`, `battery`, `shutdown`, `lock`, `unlock`. */
22
+ export declare const powerMonitor: PowerMonitor;
23
+ export type PowerSaveBlockerType = 'prevent-app-suspension' | 'prevent-display-sleep';
24
+ /**
25
+ * Evita que el sistema entre en suspensión. `prevent-display-sleep` tiene
26
+ * precedencia sobre `prevent-app-suspension`.
27
+ */
28
+ export declare const powerSaveBlocker: {
29
+ start(type?: PowerSaveBlockerType): number;
30
+ stop(id: number): boolean;
31
+ isStarted(id: number): boolean;
32
+ };
33
+ export {};
package/dist/power.js ADDED
@@ -0,0 +1,99 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // @owear/core/src/power.ts — powerMonitor / powerSaveBlocker.
5
+ import { EventEmitter } from 'node:events';
6
+ import { invokeNative } from './channel.js';
7
+ import { app } from './index.js';
8
+ class PowerMonitor extends EventEmitter {
9
+ _watching = false;
10
+ _onBattery = false;
11
+ async _ensure() {
12
+ if (this._watching)
13
+ return;
14
+ this._watching = true;
15
+ try {
16
+ await app.whenReady();
17
+ await invokeNative('power', 'monitorStart');
18
+ this._onBattery = await invokeNative('power', 'isOnBattery');
19
+ }
20
+ catch {
21
+ /* sin kernel: módulo no disponible */
22
+ }
23
+ }
24
+ /** Empieza a escuchar eventos de energía (auto al usar el módulo o un evento). */
25
+ watch() {
26
+ return this._ensure();
27
+ }
28
+ on(event, listener) {
29
+ void this._ensure();
30
+ return super.on(event, listener);
31
+ }
32
+ once(event, listener) {
33
+ void this._ensure();
34
+ return super.once(event, listener);
35
+ }
36
+ getIdleTime() {
37
+ return invokeNative('power', 'idleTime');
38
+ }
39
+ getIdleState(threshold) {
40
+ return invokeNative('power', 'idleState', threshold);
41
+ }
42
+ isOnBatteryPower() {
43
+ return invokeNative('power', 'isOnBattery').then((v) => {
44
+ this._onBattery = v;
45
+ return v;
46
+ });
47
+ }
48
+ /** Último valor conocido (se actualiza con los eventos ac/battery). */
49
+ get onBatteryPower() {
50
+ return this._onBattery;
51
+ }
52
+ /** @internal */
53
+ _setOnBattery(v) {
54
+ this._onBattery = v;
55
+ }
56
+ getSystemIdleTime = this.getIdleTime.bind(this);
57
+ getSystemIdleState = this.getIdleState.bind(this);
58
+ }
59
+ /** Eventos de energía: `suspend`, `resume`, `ac`, `battery`, `shutdown`, `lock`, `unlock`. */
60
+ export const powerMonitor = new PowerMonitor();
61
+ const activeBlockers = new Map();
62
+ let nextBlockerId = 1;
63
+ /**
64
+ * Evita que el sistema entre en suspensión. `prevent-display-sleep` tiene
65
+ * precedencia sobre `prevent-app-suspension`.
66
+ */
67
+ export const powerSaveBlocker = {
68
+ start(type = 'prevent-display-sleep') {
69
+ const id = nextBlockerId++;
70
+ activeBlockers.set(id, { type, native: null });
71
+ void (async () => {
72
+ try {
73
+ await app.whenReady();
74
+ const native = await invokeNative('power', 'inhibitStart', type);
75
+ const b = activeBlockers.get(id);
76
+ if (b)
77
+ b.native = native;
78
+ else
79
+ await invokeNative('power', 'inhibitStop', native);
80
+ }
81
+ catch {
82
+ /* sin kernel: el bloqueador queda solo en el registro JS */
83
+ }
84
+ })();
85
+ return id;
86
+ },
87
+ stop(id) {
88
+ const b = activeBlockers.get(id);
89
+ if (!b)
90
+ return false;
91
+ activeBlockers.delete(id);
92
+ if (b.native != null)
93
+ void invokeNative('power', 'inhibitStop', b.native).catch(() => undefined);
94
+ return true;
95
+ },
96
+ isStarted(id) {
97
+ return activeBlockers.has(id);
98
+ },
99
+ };
@@ -0,0 +1,14 @@
1
+ export interface ProtocolRequest {
2
+ url: string;
3
+ method: string;
4
+ headers: Record<string, string>;
5
+ /** Cuerpo de la request en base64 (formato del bridge). */
6
+ body: string;
7
+ }
8
+ export type ProtocolHandlerResult = Response | {
9
+ status?: number;
10
+ headers?: Record<string, string>;
11
+ body?: string | Uint8Array;
12
+ } | string | null | undefined;
13
+ export type ProtocolHandler = (req: ProtocolRequest) => ProtocolHandlerResult | Promise<ProtocolHandlerResult>;
14
+ export declare const protocolHandlers: Map<string, ProtocolHandler>;
@@ -0,0 +1,46 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // @owear/core/src/protocol.ts — esquemas personalizados (protocol).
5
+ import { channel } from './channel.js';
6
+ export const protocolHandlers = new Map();
7
+ async function normalizeProtocolResponse(res) {
8
+ if (res == null)
9
+ return { status: 404, headers: {}, body: '' };
10
+ if (typeof res === 'string')
11
+ return { status: 200, headers: { 'content-type': 'text/html' }, body: toB64(res) };
12
+ if (typeof Response !== 'undefined' && res instanceof Response) {
13
+ const headers = {};
14
+ res.headers.forEach((v, k) => {
15
+ headers[k] = v;
16
+ });
17
+ const body = Buffer.from(await res.arrayBuffer()).toString('base64');
18
+ return { status: res.status, headers, body };
19
+ }
20
+ const r = res;
21
+ let body = '';
22
+ if (r.body instanceof Uint8Array)
23
+ body = Buffer.from(r.body).toString('base64');
24
+ else if (typeof r.body === 'string')
25
+ body = toB64(r.body);
26
+ return { status: r.status ?? 200, headers: r.headers ?? {}, body };
27
+ }
28
+ function toB64(text) {
29
+ return Buffer.from(text, 'utf-8').toString('base64');
30
+ }
31
+ channel.on('protocol.request', (params) => {
32
+ const { reqId, scheme, url, method, headers, body } = params ?? {};
33
+ const handler = protocolHandlers.get(scheme);
34
+ Promise.resolve()
35
+ .then(() => handler
36
+ ? handler({ url, method, headers: headers ?? {}, body: body ?? '' })
37
+ : null)
38
+ .then((res) => normalizeProtocolResponse(res ?? null))
39
+ .then((out) => channel.call('protocol.respond', {
40
+ reqId,
41
+ status: out.status,
42
+ headers: out.headers,
43
+ body: out.body,
44
+ }))
45
+ .catch(() => channel.call('protocol.respond', { reqId, status: 500, headers: {}, body: '' }));
46
+ });
@@ -0,0 +1,16 @@
1
+ export interface SafeStorageResult {
2
+ /** El dato cifrado (o el texto tal cual si no hay backend). */
3
+ data: string;
4
+ /** true si se cifró de verdad (DPAPI en Windows, AES-GCM en Linux). */
5
+ encrypted: boolean;
6
+ }
7
+ /**
8
+ * Almacenamiento cifrado por el SO (módulo `safestorage`).
9
+ * Windows: DPAPI · Linux: AES-256-GCM con clave local (0600).
10
+ * `decrypt` acepta tanto blobs cifrados como texto plano.
11
+ */
12
+ export declare const safeStorage: {
13
+ isAvailable(): Promise<boolean>;
14
+ encrypt(text: string): Promise<SafeStorageResult>;
15
+ decrypt(data: string): Promise<string>;
16
+ };
@@ -0,0 +1,21 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // @owear/core/src/safestorage.ts — almacenamiento cifrado por el SO.
5
+ import { invokeNative } from './channel.js';
6
+ /**
7
+ * Almacenamiento cifrado por el SO (módulo `safestorage`).
8
+ * Windows: DPAPI · Linux: AES-256-GCM con clave local (0600).
9
+ * `decrypt` acepta tanto blobs cifrados como texto plano.
10
+ */
11
+ export const safeStorage = {
12
+ isAvailable() {
13
+ return invokeNative('safestorage', 'isAvailable');
14
+ },
15
+ encrypt(text) {
16
+ return invokeNative('safestorage', 'encrypt', text);
17
+ },
18
+ decrypt(data) {
19
+ return invokeNative('safestorage', 'decrypt', data);
20
+ },
21
+ };
@@ -0,0 +1,92 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export interface DisplayBounds {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface DisplaySize {
9
+ width: number;
10
+ height: number;
11
+ }
12
+ export interface Display {
13
+ id: number;
14
+ primary: boolean;
15
+ /** Etiqueta legible (modelo/monitor); puede venir vacía. */
16
+ label: string;
17
+ /** En píxeles NATIVOS de pantalla (coincide con las coords del kernel). */
18
+ bounds: DisplayBounds;
19
+ size: DisplaySize;
20
+ workArea: DisplayBounds;
21
+ workAreaSize: DisplaySize;
22
+ scaleFactor: number;
23
+ rotation: number;
24
+ internal: boolean;
25
+ detected: boolean;
26
+ displayFrequency: number;
27
+ colorDepth: number;
28
+ depthPerComponent: number;
29
+ colorSpace: string;
30
+ monochrome: boolean;
31
+ touchSupport: 'available' | 'unavailable' | 'unknown';
32
+ accelerometerSupport: 'available' | 'unavailable' | 'unknown';
33
+ nativeOrigin: {
34
+ x: number;
35
+ y: number;
36
+ };
37
+ }
38
+ export type DisplayMetric = 'bounds' | 'workArea' | 'scaleFactor' | 'rotation';
39
+ declare class Screen extends EventEmitter {
40
+ private _watching;
41
+ private _ensure;
42
+ /** Conecta el watcher nativo (se llama solo al usar el módulo o un evento). */
43
+ watch(): Promise<void>;
44
+ on(event: string, listener: (...args: any[]) => void): this;
45
+ once(event: string, listener: (...args: any[]) => void): this;
46
+ getAllDisplays(): Promise<Display[]>;
47
+ getPrimaryDisplay(): Promise<Display | null>;
48
+ getCursorScreenPoint(): Promise<{
49
+ x: number;
50
+ y: number;
51
+ }>;
52
+ /** Display más cercano a un punto (calculado en el SDK sobre los bounds). */
53
+ getDisplayNearestPoint(point: {
54
+ x: number;
55
+ y: number;
56
+ }): Promise<Display | null>;
57
+ /** Display que más se solapa con un rect (o el más cercano a su centro). */
58
+ getDisplayMatching(rect: DisplayBounds): Promise<Display | null>;
59
+ private _displayAt;
60
+ /**
61
+ * Punto físico de pantalla → DIP, relativo al display que lo contiene
62
+ * (aproximación por escala del display; coincide exactamente con escala 1).
63
+ */
64
+ screenToDipPoint(point: {
65
+ x: number;
66
+ y: number;
67
+ }): Promise<{
68
+ x: number;
69
+ y: number;
70
+ }>;
71
+ dipToScreenPoint(point: {
72
+ x: number;
73
+ y: number;
74
+ }): Promise<{
75
+ x: number;
76
+ y: number;
77
+ }>;
78
+ getDisplays: () => Promise<Display[]>;
79
+ getPrimary: () => Promise<Display | null>;
80
+ getCursor: () => Promise<{
81
+ x: number;
82
+ y: number;
83
+ }>;
84
+ nearestDisplay: (point: {
85
+ x: number;
86
+ y: number;
87
+ }) => Promise<Display | null>;
88
+ displayMatching: (rect: DisplayBounds) => Promise<Display | null>;
89
+ }
90
+ /** Pantallas del sistema + eventos de cambio (multi-monitor). */
91
+ export declare const screen: Screen;
92
+ export {};
package/dist/screen.js ADDED
@@ -0,0 +1,121 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // @owear/core/src/screen.ts — pantallas (multi-monitor) y eventos.
5
+ import { EventEmitter } from 'node:events';
6
+ import { invokeNative } from './channel.js';
7
+ import { app } from './index.js';
8
+ function distanceToRect(p, r) {
9
+ const dx = Math.max(r.x - p.x, 0, p.x - (r.x + r.width));
10
+ const dy = Math.max(r.y - p.y, 0, p.y - (r.y + r.height));
11
+ return dx * dx + dy * dy;
12
+ }
13
+ function intersectionArea(a, b) {
14
+ const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
15
+ const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
16
+ return w > 0 && h > 0 ? w * h : 0;
17
+ }
18
+ class Screen extends EventEmitter {
19
+ _watching = false;
20
+ async _ensure() {
21
+ if (this._watching)
22
+ return;
23
+ this._watching = true;
24
+ try {
25
+ await app.whenReady();
26
+ await invokeNative('screen', 'watch');
27
+ }
28
+ catch {
29
+ /* sin kernel: módulo no disponible */
30
+ }
31
+ }
32
+ /** Conecta el watcher nativo (se llama solo al usar el módulo o un evento). */
33
+ watch() {
34
+ return this._ensure();
35
+ }
36
+ on(event, listener) {
37
+ void this._ensure();
38
+ return super.on(event, listener);
39
+ }
40
+ once(event, listener) {
41
+ void this._ensure();
42
+ return super.once(event, listener);
43
+ }
44
+ async getAllDisplays() {
45
+ await this._ensure();
46
+ return invokeNative('screen', 'getAllDisplays');
47
+ }
48
+ async getPrimaryDisplay() {
49
+ await this._ensure();
50
+ return invokeNative('screen', 'getPrimaryDisplay');
51
+ }
52
+ async getCursorScreenPoint() {
53
+ return invokeNative('screen', 'getCursorScreenPoint');
54
+ }
55
+ /** Display más cercano a un punto (calculado en el SDK sobre los bounds). */
56
+ async getDisplayNearestPoint(point) {
57
+ const all = await this.getAllDisplays();
58
+ let best = null;
59
+ let bestD = Infinity;
60
+ for (const d of all) {
61
+ const dist = distanceToRect(point, d.bounds);
62
+ if (dist < bestD) {
63
+ bestD = dist;
64
+ best = d;
65
+ }
66
+ }
67
+ return best;
68
+ }
69
+ /** Display que más se solapa con un rect (o el más cercano a su centro). */
70
+ async getDisplayMatching(rect) {
71
+ const all = await this.getAllDisplays();
72
+ let best = null;
73
+ let bestA = 0;
74
+ for (const d of all) {
75
+ const area = intersectionArea(rect, d.bounds);
76
+ if (area > bestA) {
77
+ bestA = area;
78
+ best = d;
79
+ }
80
+ }
81
+ if (best)
82
+ return best;
83
+ const center = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
84
+ return this.getDisplayNearestPoint(center);
85
+ }
86
+ async _displayAt(point) {
87
+ return this.getDisplayNearestPoint(point);
88
+ }
89
+ /**
90
+ * Punto físico de pantalla → DIP, relativo al display que lo contiene
91
+ * (aproximación por escala del display; coincide exactamente con escala 1).
92
+ */
93
+ async screenToDipPoint(point) {
94
+ const d = await this._displayAt(point);
95
+ const s = d?.scaleFactor ?? 1;
96
+ if (!d || s <= 1)
97
+ return point;
98
+ return {
99
+ x: d.bounds.x + Math.round((point.x - d.bounds.x) / s),
100
+ y: d.bounds.y + Math.round((point.y - d.bounds.y) / s),
101
+ };
102
+ }
103
+ async dipToScreenPoint(point) {
104
+ const d = await this._displayAt(point);
105
+ const s = d?.scaleFactor ?? 1;
106
+ if (!d || s <= 1)
107
+ return point;
108
+ return {
109
+ x: d.bounds.x + Math.round((point.x - d.bounds.x) * s),
110
+ y: d.bounds.y + Math.round((point.y - d.bounds.y) * s),
111
+ };
112
+ }
113
+ // ── alias estilo Electron (portabilidad) ─────────────────────────────────
114
+ getDisplays = this.getAllDisplays.bind(this);
115
+ getPrimary = this.getPrimaryDisplay.bind(this);
116
+ getCursor = this.getCursorScreenPoint.bind(this);
117
+ nearestDisplay = this.getDisplayNearestPoint.bind(this);
118
+ displayMatching = this.getDisplayMatching.bind(this);
119
+ }
120
+ /** Pantallas del sistema + eventos de cambio (multi-monitor). */
121
+ export const screen = new Screen();
@@ -0,0 +1,24 @@
1
+ export interface PermissionRequest {
2
+ id: number;
3
+ /** geolocation | notifications | media | microphone | camera | clipboardRead | … */
4
+ permission: string;
5
+ origin: string;
6
+ }
7
+ export type PermissionHandler = (req: PermissionRequest) => boolean | Promise<boolean>;
8
+ /**
9
+ * Permisos del WebView (geolocalización, notificaciones, cámara, micrófono…).
10
+ * El handler decide allow/deny; si no hay handler registrado, se deniega.
11
+ * session.onPermissionRequest(({ permission, origin }) => permission === 'notifications')
12
+ */
13
+ export declare const session: {
14
+ /**
15
+ * Referencia a una partición de sesión. Pásala al crear la ventana:
16
+ * const s = session.fromPartition('persist:cuenta-2')
17
+ * new BrowserWindow({ session: s.partition, url })
18
+ * Aísla cookies/storage/cache por perfil (WebKit: data dir; WebView2: perfil).
19
+ */
20
+ fromPartition(partition: string): {
21
+ partition: string;
22
+ };
23
+ onPermissionRequest(handler: PermissionHandler): () => void;
24
+ };
@@ -0,0 +1,42 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // @owear/core/src/session.ts — permisos del WebView y particiones.
5
+ import { channel } from './channel.js';
6
+ import { app } from './index.js';
7
+ const permissionHandlers = [];
8
+ channel.on('session.permissionRequest', (params) => {
9
+ const { id, permission, origin } = params ?? {};
10
+ Promise.all(permissionHandlers.map((h) => Promise.resolve(h({ id, permission, origin }))))
11
+ .then((results) => channel.call('session.respondPermission', { id, allow: results.some(Boolean) }))
12
+ .catch(() => channel.call('session.respondPermission', { id, allow: false }));
13
+ });
14
+ /**
15
+ * Permisos del WebView (geolocalización, notificaciones, cámara, micrófono…).
16
+ * El handler decide allow/deny; si no hay handler registrado, se deniega.
17
+ * session.onPermissionRequest(({ permission, origin }) => permission === 'notifications')
18
+ */
19
+ export const session = {
20
+ /**
21
+ * Referencia a una partición de sesión. Pásala al crear la ventana:
22
+ * const s = session.fromPartition('persist:cuenta-2')
23
+ * new BrowserWindow({ session: s.partition, url })
24
+ * Aísla cookies/storage/cache por perfil (WebKit: data dir; WebView2: perfil).
25
+ */
26
+ fromPartition(partition) {
27
+ return { partition };
28
+ },
29
+ onPermissionRequest(handler) {
30
+ permissionHandlers.push(handler);
31
+ // El handler se registra ya; avisar al kernel en cuanto haya canal.
32
+ app
33
+ .whenReady()
34
+ .then(() => channel.call('session.setPermissionHandler', { enabled: true }))
35
+ .catch(() => undefined);
36
+ return () => {
37
+ const i = permissionHandlers.indexOf(handler);
38
+ if (i >= 0)
39
+ permissionHandlers.splice(i, 1);
40
+ };
41
+ },
42
+ };