nexfep 0.6.0 → 0.7.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/README-CN.md CHANGED
@@ -63,19 +63,22 @@ const app = new Application();
63
63
  const app = new Application({ WindowsWebview2UserDataFolder: "C:\\custom\\webview2-data" });
64
64
  // 或指定日志文件路径
65
65
  const app = new Application({ LogFilePath: "./app.log" });
66
+ // 或注册自定义协议代理
67
+ const app = new Application({ localProxys: [{ protocolName: "app", localPath: "./public" }] });
66
68
  ```
67
69
 
68
70
  **构造函数参数**
69
71
 
70
- | 选项 | 类型 | 默认值 | 说明 |
71
- | ------------------------------- | ---------------- | ------------------------------------------------ | ----------------------------------- |
72
- | `WindowsWebview2UserDataFolder` | `string`(可选) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 用户数据目录(仅 Windows) |
73
- | `LogFilePath` | `string`(可选) | 无 | 日志文件输出路径 |
72
+ | 选项 | 类型 | 默认值 | 说明 |
73
+ | ------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- |
74
+ | `WindowsWebview2UserDataFolder` | `string`(可选) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 用户数据目录(仅 Windows) |
75
+ | `LogFilePath` | `string`(可选) | 无 | 日志文件输出路径 |
76
+ | `localProxys` | `Array<{ protocolName: string; localPath: string }>`(可选) | 无 | 自定义协议代理,用于提供本地文件服务 |
74
77
 
75
78
  **属性**
76
79
 
77
80
  - `windows` — `WindowPool` 实例,用于管理浏览器窗口
78
- - `utils` — 工具方法(如桌面通知)
81
+ - `utils` — 工具方法(如桌面通知、文件选择弹窗)
79
82
  - `logger` — `Logger` 实例,用于日志记录
80
83
 
81
84
  **方法**
@@ -84,7 +87,69 @@ const app = new Application({ LogFilePath: "./app.log" });
84
87
  - `createLocker(appName)` — 创建应用实例锁,防止多个实例运行
85
88
  - `exit()` — 退出应用
86
89
 
87
- ### Icon
90
+ ### 基础类型
91
+
92
+ Nexfep 提供了几个贯穿框架的基础类型。
93
+
94
+ #### Size
95
+
96
+ `Size` 表示窗口尺寸,包含宽度和高度,支持逻辑像素和物理像素。
97
+
98
+ ```typescript
99
+ import { Size } from "nexfep";
100
+
101
+ const size = new Size(800, 600);
102
+ const size = new Size(800, 600, true); // 逻辑像素(默认)
103
+ ```
104
+
105
+ **属性**
106
+
107
+ | 属性 | 类型 | 说明 |
108
+ | -------- | --------- | ----------------------------- |
109
+ | `width` | `number` | 宽度(像素) |
110
+ | `height` | `number` | 高度(像素) |
111
+ | `logical`| `boolean` | 是否使用逻辑像素,默认 `true` |
112
+
113
+ #### Position
114
+
115
+ `Position` 表示窗口在屏幕上的位置,支持逻辑像素和物理像素。
116
+
117
+ ```typescript
118
+ import { Position } from "nexfep";
119
+
120
+ const pos = new Position(100, 100);
121
+ const pos = new Position(100, 100, false); // 物理像素(默认)
122
+ ```
123
+
124
+ **属性**
125
+
126
+ | 属性 | 类型 | 说明 |
127
+ | -------- | --------- | ------------------------------ |
128
+ | `x` | `number` | X 坐标(像素) |
129
+ | `y` | `number` | Y 坐标(像素) |
130
+ | `logical`| `boolean` | 是否使用逻辑像素,默认 `false` |
131
+
132
+ #### WindowLevel
133
+
134
+ `WindowLevel` 是一个枚举,表示窗口的 Z 轴层级。
135
+
136
+ ```typescript
137
+ import { WindowLevel } from "nexfep";
138
+
139
+ window.setLevel(WindowLevel.Bottommost);
140
+ window.setLevel(WindowLevel.Normal);
141
+ window.setLevel(WindowLevel.Topmost);
142
+ ```
143
+
144
+ **值**
145
+
146
+ | 值 | 数值 | 说明 |
147
+ | -------------------------- | ---- | -------- |
148
+ | `WindowLevel.Bottommost` | `-1` | 置底 |
149
+ | `WindowLevel.Normal` | `0` | 正常层级 |
150
+ | `WindowLevel.Topmost` | `1` | 置顶 |
151
+
152
+ #### Icon
88
153
 
89
154
  `Icon` 用于表示图片资源,可被用作窗口图标、托盘图标等。
90
155
 
@@ -148,15 +213,22 @@ const locker = app.createLocker("my-app");
148
213
  try {
149
214
  // 尝试获取应用实例锁
150
215
  await locker.lock();
216
+ // 也可以带参数获取锁,例如:
217
+ // await locker.lock(process.argv[2]);
151
218
  } catch {
152
219
  // 应用实例已存在,退出应用
153
220
  app.exit();
154
221
  }
155
- // 在其它实例抢锁时聚焦当前实例
156
- locker.whenLost(() => {
222
+ // 你可以在其它实例抢锁时聚焦当前实例
223
+ locker.whenLost((data) => {
157
224
  if (!win.isFocused()) {
158
225
  win.focus();
159
226
  }
227
+ // 如果对方实例在获取锁时传递了参数,你可以使用它来执行特定操作
228
+ // 若没有传递,data 字段将会是 null
229
+ if (data) {
230
+ console.log("传递了参数:", data);
231
+ }
160
232
  });
161
233
  // 释放锁
162
234
  locker.unlock();
@@ -166,7 +238,7 @@ locker.unlock();
166
238
 
167
239
  | 方法 | 描述 |
168
240
  | -------------------- | ------------------------ |
169
- | `lock()` | 尝试获取应用实例锁 |
241
+ | `lock(data?)` | 尝试获取应用实例锁 |
170
242
  | `whenLost(callback)` | 当其它实例抢锁时执行回调 |
171
243
  | `unlock()` | 释放锁 |
172
244
 
@@ -226,6 +298,55 @@ const notification = app.utils.notify("标题", { body: "通知内容" });
226
298
  - `options`(可选)— 配置对象
227
299
  - `body` — 通知正文
228
300
 
301
+ ### 选择文件弹窗
302
+
303
+ 通过 `app.utils.openFileDialog()` 打开选择文件弹窗。
304
+
305
+ ```typescript
306
+ const files = await app.utils.openFileDialog({
307
+ multiple: true,
308
+ title: "选择文件",
309
+ filters: [
310
+ { name: "所有文件", extensions: ["*"] },
311
+ { name: "图片文件", extensions: ["jpg", "jpeg", "png"] },
312
+ ],
313
+ });
314
+ ```
315
+
316
+ **参数**
317
+
318
+ - `multiple`(可选)— 是否允许选择多个文件
319
+ - `title`(可选)— 弹窗标题
320
+ - `filters`(可选)— 文件类型过滤器数组,每个元素为一个对象,包含 `name`(过滤器名称)和 `extensions`(支持的文件扩展名数组)
321
+
322
+ **返回值**
323
+
324
+ - `Array<string>`: 选择的文件路径数组
325
+
326
+ ### 本地协议代理
327
+
328
+ Nexfep 支持注册自定义协议处理器,用于提供本地文件服务,从而可以通过自定义协议(如 `app://`)加载本地资源。
329
+
330
+ ```typescript
331
+ const app = new Application({
332
+ localProxys: [
333
+ { protocolName: "app", localPath: "./public" },
334
+ ],
335
+ });
336
+ ```
337
+
338
+ 注册后,即可使用自定义协议加载页面:
339
+
340
+ ```typescript
341
+ await window.loadURL("app://index.html");
342
+ ```
343
+
344
+ 对 `app://` 的请求会被拦截,并从 `./public` 目录中提供对应的文件。MIME 类型会根据文件扩展名自动检测。
345
+
346
+ **安全**
347
+
348
+ 协议代理使用路径解析来防止目录遍历攻击。如果请求试图访问配置的 `localPath` 之外的文件,将返回 `403 Forbidden` 响应。
349
+
229
350
  ### 窗口池
230
351
 
231
352
  `WindowPool` 是框架的核心管理类,负责窗口的创建和回收。
@@ -253,22 +374,22 @@ const win = await pool.createWindow({
253
374
  title: "我的应用", // 窗口标题,默认 "Nexfep Window"
254
375
  icon: iconInstance, // 窗口图标,Icon 实例,可选
255
376
  resizable: true, // 是否可调整大小,默认 true
256
- width: 800, // 窗口宽度,默认 800
257
- height: 600, // 窗口高度,默认 600
377
+ size: new Size(800, 600), // 窗口尺寸,可选
378
+ position: new Position(100, 100), // 窗口位置,可选
258
379
  });
259
380
  ```
260
381
 
261
382
  **参数说明**
262
383
 
263
- | 选项 | 类型 | 默认值 | 说明 |
264
- | ------------ | --------- | ----------------- | ------------------------------------------------------------------- |
265
- | `visible` | `boolean` | `true` | 是否立即显示窗口 |
266
- | `decoration` | `boolean` | `true` | 是否使用系统窗口装饰。设为 `false` 时,窗口无边框,需要自定义标题栏 |
267
- | `title` | `string` | `"Nexfep Window"` | 窗口标题 |
268
- | `icon` | `Icon` | 无 | 窗口图标 |
269
- | `resizable` | `boolean` | `true` | 窗口是否可调整大小 |
270
- | `width` | `number` | `800` | 窗口宽度(像素) |
271
- | `height` | `number` | `600` | 窗口高度(像素) |
384
+ | 选项 | 类型 | 默认值 | 说明 |
385
+ | ------------ | ----------- | ----------------- | ------------------------------------------------------------------- |
386
+ | `visible` | `boolean` | `true` | 是否立即显示窗口 |
387
+ | `decoration` | `boolean` | `true` | 是否使用系统窗口装饰。设为 `false` 时,窗口无边框,需要自定义标题栏 |
388
+ | `title` | `string` | `"Nexfep Window"` | 窗口标题 |
389
+ | `icon` | `Icon` | 无 | 窗口图标 |
390
+ | `resizable` | `boolean` | `true` | 窗口是否可调整大小 |
391
+ | `size` | `Size` | 无 | 窗口尺寸,可选 |
392
+ | `position` | `Position` | 无 | 窗口位置,可选 |
272
393
 
273
394
  ### 窗口操作
274
395
 
@@ -280,7 +401,8 @@ window.minimize();
280
401
  window.close();
281
402
  window.focus();
282
403
  window.setTitle("新标题");
283
- window.setSize(800, 600);
404
+ window.setSize(new Size(800, 600));
405
+ window.setPosition(new Position(100, 100));
284
406
  window.openDevTools();
285
407
  ```
286
408
 
@@ -404,6 +526,12 @@ window.addEventListener("user-login", (event) => {
404
526
  });
405
527
  ```
406
528
 
529
+ 特别地,主进程可以通过 `pool.broadcast` 向所有打开的窗口发送事件,参数与页面中的 `window.broadcast` 相同:
530
+
531
+ ```typescript
532
+ pool.broadcast("user-login", { userId: 123 });
533
+ ```
534
+
407
535
  #### 定向发送
408
536
 
409
537
  通过 `window.tell` 向指定 ID 的窗口发送消息:
@@ -432,6 +560,12 @@ window.addEventListener("custom-message", (event) => {
432
560
  console.log("当前窗口 ID:", window.id);
433
561
  ```
434
562
 
563
+ 主进程也可通过 `win.tell` 向该窗口发送消息:
564
+
565
+ ```typescript
566
+ win.tell("custom-message", { text: `你好,窗口 ${win.id}` });
567
+ ```
568
+
435
569
  ### 自定义消息
436
570
 
437
571
  #### 发送消息
@@ -621,11 +755,12 @@ nexfep build -u 7
621
755
 
622
756
  | 方法/属性 | 参数 | 返回值 | 说明 |
623
757
  | ----------------------- | -------------------------------------------------- | ----------- | ---------------- |
624
- | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath? }` | Application | 创建应用实例 |
758
+ | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath?, localProxys? }` | Application | 创建应用实例 |
625
759
  | `windows` | / | WindowPool | 窗口池实例 |
626
- | `utils` | / | \_\_Utils | 工具方法(通知) |
760
+ | `utils` | / | \_\_Utils | 工具方法(通知、文件选择弹窗) |
627
761
  | `logger` | / | Logger | 日志实例 |
628
762
  | `createTray(options)` | 见 Tray 章节 | Tray | 创建系统托盘图标 |
763
+ | `createLocker(appName)` | `appName`: string | Locker | 创建应用实例锁 |
629
764
  | `exit()` | 无 | void | 退出应用 |
630
765
 
631
766
  ### WindowPool
@@ -637,7 +772,8 @@ nexfep build -u 7
637
772
  | `unhandle(event, callback)` | `event`: string, `callback`: (data: any) => any | 无 | 取消监听指定事件 |
638
773
  | `global` | / | Map\<string, any> | 全局变量 Map |
639
774
  | `closeWindow(window)` | `window`: Window | Promise\<void> | 关闭指定窗口并回收至池中 |
640
- | `onCustomMessage` | `(window: Window, data: string) => void` | 无 | 自定义消息回调函数 |
775
+ | `onCustomMessage` | `(window: Window, message: string, data: any) => void` | 无 | 自定义消息回调函数 |
776
+ | `broadcast(event, data)` | `event`: string, `data`: any | 无 | 向所有打开的窗口发送事件 |
641
777
 
642
778
  ### Window
643
779
 
@@ -655,19 +791,20 @@ nexfep build -u 7
655
791
  | `setTitle(title)` | `title`: string | void | 设置窗口标题 |
656
792
  | `setDecorated(isDecorated)` | `isDecorated`: boolean | void | 设置窗口是否带边框和标题栏 |
657
793
  | `setResizable(resizable)` | `resizable`: boolean | void | 设置窗口是否可调整大小 |
658
- | `setLevel(level)` | `level`: `-1` \| `0` \| `1` | void | 设置窗口层级:-1=置底,0=正常,1=置顶 |
794
+ | `setLevel(level)` | `level`: `WindowLevel` | void | 设置窗口层级:Bottommost、Normal、Topmost |
659
795
  | `setFullScreen(isFullScreen, options?)` | `isFullScreen`: `boolean`, `options?`: `{ borderless?: boolean }` | void | 设置全屏模式。`borderless: true` 为无边框全屏,否则为独占全屏 |
660
796
  | `setIcon(icon)` | `icon`: `Icon` | void | 设置窗口图标 |
661
- | `setSize(width, height)` | `width`: number, `height`: number | void | 设置窗口尺寸(像素) |
662
- | `getSize()` | 无 | { width: number, height: number } | 获取窗口尺寸(像素) |
663
- | `setPosition(x, y)` | `x`: number, `y`: number | void | 设置窗口位置(像素) |
664
- | `getPosition()` | 无 | { x: number, y: number } | 获取窗口位置(像素) |
797
+ | `setSize(size)` | `size`: `Size` | void | 设置窗口尺寸 |
798
+ | `getSize(logical?)` | `logical?`: `boolean` | { width: number, height: number } | 获取窗口尺寸(像素) |
799
+ | `setPosition(position)` | `position`: `Position` | void | 设置窗口位置 |
800
+ | `getPosition(logical?)` | `logical?`: `boolean` | { x: number, y: number } | 获取窗口位置(像素) |
665
801
  | `focus()` | 无 | void | 窗口获取焦点 |
666
802
  | `isFocused()` | 无 | boolean | 是否有焦点 |
667
803
  | `isMaximized()` | 无 | boolean | 是否最大化 |
668
804
  | `isMinimized()` | 无 | boolean | 是否最小化 |
669
805
  | `toggleMaximize()` | 无 | void | 切换最大化状态 |
670
806
  | `toggleMinimize()` | 无 | void | 切换最小化状态 |
807
+ | `tell(message, data)` | `message`: string, `data`: any | void | 从主进程向该窗口发送消息 |
671
808
  | `openDevTools()` | 无 | void | 打开开发者工具 |
672
809
  | `closeDevTools()` | 无 | void | 关闭开发者工具 |
673
810
  | `id` | 无 | number | 窗口唯一标识,自增编号 |
package/README.md CHANGED
@@ -63,19 +63,22 @@ const app = new Application();
63
63
  const app = new Application({ WindowsWebview2UserDataFolder: "C:\\custom\\webview2-data" });
64
64
  // or with log file path
65
65
  const app = new Application({ LogFilePath: "./app.log" });
66
+ // or with custom protocol proxy
67
+ const app = new Application({ localProxys: [{ protocolName: "app", localPath: "./public" }] });
66
68
  ```
67
69
 
68
70
  **Constructor Options**
69
71
 
70
- | Option | Type | Default | Description |
71
- | ------------------------------- | ------------------- | ------------------------------------------------ | ------------------------------------------- |
72
- | `WindowsWebview2UserDataFolder` | `string` (optional) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 user data directory (Windows only) |
73
- | `LogFilePath` | `string` (optional) | none | File path for log output |
72
+ | Option | Type | Default | Description |
73
+ | ------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------- |
74
+ | `WindowsWebview2UserDataFolder` | `string` (optional) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 user data directory (Windows only) |
75
+ | `LogFilePath` | `string` (optional) | none | File path for log output |
76
+ | `localProxys` | `Array<{ protocolName: string; localPath: string }>` (optional) | none | Custom protocol proxies for serving local files |
74
77
 
75
78
  **Properties**
76
79
 
77
80
  - `windows` — The `WindowPool` instance for managing browser windows
78
- - `utils` — Utility methods (e.g., desktop notifications)
81
+ - `utils` — Utility methods (e.g., desktop notifications, file dialogs)
79
82
  - `logger` — The `Logger` instance for logging
80
83
 
81
84
  **Methods**
@@ -84,7 +87,69 @@ const app = new Application({ LogFilePath: "./app.log" });
84
87
  - `createLocker(appName)` — Create an application instance lock to prevent multiple instances
85
88
  - `exit()` — Exit the application
86
89
 
87
- ### Icon
90
+ ### Basic Types
91
+
92
+ Nexfep provides several basic types that are used throughout the framework.
93
+
94
+ #### Size
95
+
96
+ `Size` represents a window size with width and height, supporting both logical and physical pixels.
97
+
98
+ ```typescript
99
+ import { Size } from "nexfep";
100
+
101
+ const size = new Size(800, 600);
102
+ const size = new Size(800, 600, true); // logical pixels (default)
103
+ ```
104
+
105
+ **Properties**
106
+
107
+ | Property | Type | Description |
108
+ | -------- | -------- | ------------------------------------------------------- |
109
+ | `width` | `number` | Width in pixels |
110
+ | `height` | `number` | Height in pixels |
111
+ | `logical`| `boolean`| Whether to use logical pixels (DPI-aware), default `true` |
112
+
113
+ #### Position
114
+
115
+ `Position` represents a window position on screen, supporting both logical and physical pixels.
116
+
117
+ ```typescript
118
+ import { Position } from "nexfep";
119
+
120
+ const pos = new Position(100, 100);
121
+ const pos = new Position(100, 100, false); // physical pixels (default)
122
+ ```
123
+
124
+ **Properties**
125
+
126
+ | Property | Type | Description |
127
+ | -------- | -------- | -------------------------------------------------------- |
128
+ | `x` | `number` | X position in pixels |
129
+ | `y` | `number` | Y position in pixels |
130
+ | `logical`| `boolean`| Whether to use logical pixels (DPI-aware), default `false` |
131
+
132
+ #### WindowLevel
133
+
134
+ `WindowLevel` is an enum representing the window z-order level.
135
+
136
+ ```typescript
137
+ import { WindowLevel } from "nexfep";
138
+
139
+ window.setLevel(WindowLevel.Bottommost);
140
+ window.setLevel(WindowLevel.Normal);
141
+ window.setLevel(WindowLevel.Topmost);
142
+ ```
143
+
144
+ **Values**
145
+
146
+ | Value | Numeric | Description |
147
+ | --------------------------- | ------- | ----------------- |
148
+ | `WindowLevel.Bottommost` | `-1` | Always on bottom |
149
+ | `WindowLevel.Normal` | `0` | Normal level |
150
+ | `WindowLevel.Topmost` | `1` | Always on top |
151
+
152
+ #### Icon
88
153
 
89
154
  `Icon` represents an image resource that can be used as a window icon, tray icon, etc.
90
155
 
@@ -148,15 +213,22 @@ const locker = app.createLocker("my-app");
148
213
  try {
149
214
  // Try to acquire the lock
150
215
  await locker.lock();
216
+ // You can also pass data to the other instance when acquiring the lock
217
+ // await locker.lock(process.argv[2]);
151
218
  } catch {
152
219
  // Instance already exists, exit the application
153
220
  app.exit();
154
221
  }
155
222
  // Focus the current instance when other instances acquire the lock
156
- locker.whenLost(() => {
223
+ locker.whenLost((data) => {
157
224
  if (!win.isFocused()) {
158
225
  win.focus();
159
226
  }
227
+ // If the other instance passed data, you can use it to perform specific actions
228
+ // If no data was passed, data will be null
229
+ if (data) {
230
+ console.log("Other instance passed data:", data);
231
+ }
160
232
  });
161
233
  // Release the lock
162
234
  locker.unlock();
@@ -164,7 +236,7 @@ locker.unlock();
164
236
 
165
237
  **Methods**
166
238
 
167
- - `lock()` — Try to acquire the lock
239
+ - `lock(data?)` — Try to acquire the lock
168
240
  - `whenLost(callback)` — Register a callback to be called when other instances acquire the lock
169
241
  - `unlock()` — Release the lock
170
242
 
@@ -224,6 +296,55 @@ const notification = app.utils.notify("Title", { body: "Notification body" });
224
296
  - `options` (optional) — Configuration object
225
297
  - `body` — Notification body text
226
298
 
299
+ ### File Selection Dialog
300
+
301
+ Open the file selection dialog using `app.utils.openFileDialog()`.
302
+
303
+ ```typescript
304
+ const files = await app.utils.openFileDialog({
305
+ multiple: true,
306
+ title: "Select Files",
307
+ filters: [
308
+ { name: "All Files", extensions: ["*"] },
309
+ { name: "Image Files", extensions: ["jpg", "jpeg", "png"] },
310
+ ],
311
+ });
312
+ ```
313
+
314
+ **Parameters**
315
+
316
+ - `multiple` (Optional) — Allow selection of multiple files
317
+ - `title` (Optional) — Title of the dialog
318
+ - `filters` (Optional) — Array of file type filters, each object has `name` (filter name) and `extensions` (supported file extensions array)
319
+
320
+ **Return Value**
321
+
322
+ - `Array<string>`: Array of selected file paths
323
+
324
+ ### Local Protocol Proxy
325
+
326
+ Nexfep supports registering custom protocol handlers to serve local files, allowing you to load local resources using custom protocols like `app://`.
327
+
328
+ ```typescript
329
+ const app = new Application({
330
+ localProxys: [
331
+ { protocolName: "app", localPath: "./public" },
332
+ ],
333
+ });
334
+ ```
335
+
336
+ After registration, you can load pages using the custom protocol:
337
+
338
+ ```typescript
339
+ await window.loadURL("app://index.html");
340
+ ```
341
+
342
+ Requests to `app://` will be intercepted and the corresponding files from the `./public` directory will be served. The MIME type is automatically detected based on the file extension.
343
+
344
+ **Security**
345
+
346
+ The proxy uses path resolution to prevent directory traversal attacks. If a request tries to access files outside the configured `localPath`, it will return a `403 Forbidden` response.
347
+
227
348
  ### Window Pool
228
349
 
229
350
  `WindowPool` is the core management class of the framework, responsible for window creation and recycling.
@@ -251,22 +372,22 @@ const win = await pool.createWindow({
251
372
  title: "My App", // Window title, default "Nexfep Window"
252
373
  icon: iconInstance, // Window icon, Icon instance, optional
253
374
  resizable: true, // Whether the window is resizable, default true
254
- width: 800, // Window width, default 800
255
- height: 600, // Window height, default 600
375
+ size: new Size(800, 600), // Window size, optional
376
+ position: new Position(100, 100), // Window position, optional
256
377
  });
257
378
  ```
258
379
 
259
380
  **Parameters**
260
381
 
261
- | Option | Type | Default | Description |
262
- | ------------ | --------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
263
- | `visible` | `boolean` | `true` | Whether to immediately show the window |
264
- | `decoration` | `boolean` | `true` | Whether to use system window decorations. When `false`, the window has no border and requires a custom title bar |
265
- | `title` | `string` | `"Nexfep Window"` | Window title |
266
- | `icon` | `Icon` | none | Window icon |
267
- | `resizable` | `boolean` | `true` | Whether the window is resizable |
268
- | `width` | `number` | `800` | Window width in pixels |
269
- | `height` | `number` | `600` | Window height in pixels |
382
+ | Option | Type | Default | Description |
383
+ | ------------ | ----------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
384
+ | `visible` | `boolean` | `true` | Whether to immediately show the window |
385
+ | `decoration` | `boolean` | `true` | Whether to use system window decorations. When `false`, the window has no border and requires a custom title bar |
386
+ | `title` | `string` | `"Nexfep Window"` | Window title |
387
+ | `icon` | `Icon` | none | Window icon |
388
+ | `resizable` | `boolean` | `true` | Whether the window is resizable |
389
+ | `size` | `Size` | none | Window size, optional |
390
+ | `position` | `Position` | none | Window position, optional |
270
391
 
271
392
  ### Window Operations
272
393
 
@@ -278,7 +399,8 @@ window.minimize();
278
399
  window.close();
279
400
  window.focus();
280
401
  window.setTitle("New Title");
281
- window.setSize(800, 600);
402
+ window.setSize(new Size(800, 600));
403
+ window.setPosition(new Position(100, 100));
282
404
  window.openDevTools();
283
405
  ```
284
406
 
@@ -402,6 +524,12 @@ window.addEventListener("user-login", (event) => {
402
524
  });
403
525
  ```
404
526
 
527
+ The main process can send events to all open windows via `pool.broadcast`, with parameters identical to those of `window.broadcast` in the page:
528
+
529
+ ```typescript
530
+ pool.broadcast("user-login", { userId: 123 });
531
+ ```
532
+
405
533
  #### Tell
406
534
 
407
535
  Send a message to a specific window by its ID via `window.tell`:
@@ -430,6 +558,12 @@ Each window's ID can be accessed via `window.id`:
430
558
  console.log("This window ID:", window.id);
431
559
  ```
432
560
 
561
+ The main process can also send messages to this window through `win.tell`:
562
+
563
+ ```typescript
564
+ win.tell("custom-message", { text: `Hello Window ${win.id}` });
565
+ ```
566
+
433
567
  ### Custom Messages
434
568
 
435
569
  #### Send Messages
@@ -619,11 +753,12 @@ Please do not include the outer `metadata` field, just the internal fields. Like
619
753
 
620
754
  | Method/Property | Parameters | Return Value | Description |
621
755
  | ----------------------- | -------------------------------------------------- | ------------ | -------------------------------- |
622
- | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath? }` | Application | Creates the application instance |
756
+ | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath?, localProxys? }` | Application | Creates the application instance |
623
757
  | `windows` | / | WindowPool | The window pool instance |
624
- | `utils` | / | \_\_Utils | Utility methods (notifications) |
758
+ | `utils` | / | \_\_Utils | Utility methods (notifications, file dialogs) |
625
759
  | `logger` | / | Logger | The logger instance |
626
760
  | `createTray(options)` | see Tray section | Tray | Creates a system tray icon |
761
+ | `createLocker(appName)` | `appName`: string | Locker | Creates an application lock |
627
762
  | `exit()` | None | void | Exits the application |
628
763
 
629
764
  ### WindowPool
@@ -635,7 +770,8 @@ Please do not include the outer `metadata` field, just the internal fields. Like
635
770
  | `unhandle(event, callback)` | `event`: string, `callback`: (data: any) => any | None | Removes the specified event listener |
636
771
  | `global` | / | Map\<string, any> | A global variable map |
637
772
  | `closeWindow(window)` | `window`: Window | Promise\<void> | Closes the specified window and returns it to the pool |
638
- | `onCustomMessage` | `(window: Window, data: string) => void` | None | Custom message callback |
773
+ | `onCustomMessage` | `(window: Window, message: string, data: any) => void` | None | Custom message callback |
774
+ | `broadcast(event, data)` | `event`: string, `data`: any | None | Sends an event to all open windows |
639
775
 
640
776
  ### Window
641
777
 
@@ -653,19 +789,20 @@ Please do not include the outer `metadata` field, just the internal fields. Like
653
789
  | `setTitle(title)` | `title`: string | void | Sets the window title |
654
790
  | `setDecorated(isDecorated)` | `isDecorated`: boolean | void | Sets whether the window has borders and title bar |
655
791
  | `setResizable(resizable)` | `resizable`: boolean | void | Sets whether the window is resizable |
656
- | `setLevel(level)` | `level`: `-1` \| `0` \| `1` | void | Sets window level: -1=bottom, 0=normal, 1=top |
792
+ | `setLevel(level)` | `level`: `WindowLevel` | void | Sets window level: Bottommost, Normal, Topmost |
657
793
  | `setFullScreen(isFullScreen, options?)` | `isFullScreen`: `boolean`, `options?`: `{ borderless?: boolean }` | void | Sets fullscreen mode. `borderless: true` for borderless fullscreen, otherwise exclusive fullscreen |
658
794
  | `setIcon(icon)` | `icon`: `Icon` | void | Sets the window icon |
659
- | `setSize(width, height)` | `width`: number, `height`: number | void | Sets the window size in pixels |
660
- | `getSize()` | None | { width: number, height: number } | Gets the window size in pixels |
661
- | `setPosition(x, y)` | `x`: number, `y`: number | void | Sets the window position in pixels |
662
- | `getPosition()` | None | { x: number, y: number } | Gets the window position in pixels |
795
+ | `setSize(size)` | `size`: `Size` | void | Sets the window size |
796
+ | `getSize(logical?)` | `logical?`: `boolean` | { width: number, height: number } | Gets the window size in pixels |
797
+ | `setPosition(position)` | `position`: `Position` | void | Sets the window position |
798
+ | `getPosition(logical?)` | `logical?`: `boolean` | { x: number, y: number } | Gets the window position in pixels |
663
799
  | `focus()` | None | void | Focuses the window |
664
800
  | `isFocused()` | None | boolean | Whether the window has focus |
665
801
  | `isMaximized()` | None | boolean | Whether the window is maximized |
666
802
  | `isMinimized()` | None | boolean | Whether the window is minimized |
667
803
  | `toggleMaximize()` | None | void | Toggles the window maximized state |
668
804
  | `toggleMinimize()` | None | void | Toggles the window minimized state |
805
+ | `tell(message, data)` | `message`: string, `data`: any | void | Sends a message to the window from the main process |
669
806
  | `openDevTools()` | None | void | Opens developer tools |
670
807
  | `closeDevTools()` | None | void | Closes developer tools |
671
808
  | `id` | None | number | Unique window identifier, auto-incrementing |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexfep",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "A desktop application framework based on @webviewjs/webview",
5
5
  "keywords": [
6
6
  "@webviewjs/webview",
@@ -41,8 +41,8 @@
41
41
  "src/Logger.d.ts",
42
42
  "src/SingleInstance.js",
43
43
  "src/SingleInstance.d.ts",
44
- "src/Icon.js",
45
- "src/Icon.d.ts",
44
+ "src/Basics.js",
45
+ "src/Basics.d.ts",
46
46
  "frontend/index.d.ts"
47
47
  ],
48
48
  "type": "module",
@@ -65,8 +65,9 @@
65
65
  "lint:fix": "oxlint --fix"
66
66
  },
67
67
  "dependencies": {
68
- "@nexfteam/single-instance": "^0.0.7",
69
- "@webviewjs/webview": "0.4.1"
68
+ "@nexfteam/single-instance": "0.0.8",
69
+ "@webviewjs/webview": "0.4.3",
70
+ "mrmime": "^2.0.1"
70
71
  },
71
72
  "devDependencies": {
72
73
  "@types/node": "^22.0.0",
@@ -3,13 +3,21 @@ import { WindowPool } from "./WindowManager.js";
3
3
  import { Tray } from "./Tray.js";
4
4
  import { Logger } from "./Logger.js";
5
5
  import { Locker } from "./SingleInstance.js";
6
- import { Icon } from "./Icon.js";
6
+ import { Icon } from "./Basics.js";
7
7
  declare class __Utils {
8
- app: WebviewApplication;
9
- constructor(app: WebviewApplication);
8
+ app: Application;
9
+ constructor(app: Application);
10
10
  notify(title: string, options?: {
11
11
  body?: string;
12
12
  }): Notification;
13
+ openFileDialog(options?: {
14
+ multiple?: boolean;
15
+ title?: string;
16
+ filters?: Array<{
17
+ name: string;
18
+ extensions: Array<string>;
19
+ }>;
20
+ }): Promise<string[]>;
13
21
  }
14
22
  declare class Application {
15
23
  app: WebviewApplication;
@@ -19,6 +27,10 @@ declare class Application {
19
27
  constructor(options?: {
20
28
  WindowsWebview2UserDataFolder?: string;
21
29
  LogFilePath?: string;
30
+ localProxys?: Array<{
31
+ protocolName: string;
32
+ localPath: string;
33
+ }>;
22
34
  });
23
35
  createTray(options: {
24
36
  id: string;
@@ -32,4 +44,5 @@ declare class Application {
32
44
  createLocker(appName: string): Locker;
33
45
  exit(): void;
34
46
  }
35
- export { Application, Icon };
47
+ export { Application };
48
+ export * from "./Basics.js";
@@ -3,7 +3,6 @@ import { WindowPool } from "./WindowManager.js";
3
3
  import { Tray } from "./Tray.js";
4
4
  import { Logger } from "./Logger.js";
5
5
  import { Locker } from "./SingleInstance.js";
6
- import { Icon } from "./Icon.js";
7
6
  class __Utils {
8
7
  app;
9
8
  constructor(app) {
@@ -13,6 +12,12 @@ class __Utils {
13
12
  const notification = new Notification(title, { body: options?.body });
14
13
  return notification;
15
14
  }
15
+ async openFileDialog(options) {
16
+ const tempWin = await this.app.windows.createWindow({ visible: false });
17
+ const result = tempWin.window.openFileDialog(options);
18
+ tempWin.close();
19
+ return result;
20
+ }
16
21
  }
17
22
  class Application {
18
23
  app;
@@ -21,11 +26,12 @@ class Application {
21
26
  utils;
22
27
  constructor(options = {}) {
23
28
  this.app = new WebviewApplication();
24
- this.utils = new __Utils(this.app);
29
+ this.utils = new __Utils(this);
25
30
  this.logger = new Logger(options.LogFilePath);
26
31
  const poolOptions = {
27
32
  WindowsWebview2UserDataFolder: options.WindowsWebview2UserDataFolder,
28
33
  logger: this.logger,
34
+ localProxys: options.localProxys,
29
35
  };
30
36
  this.windows = new WindowPool(this.app, poolOptions);
31
37
  this.app.whenReady();
@@ -40,4 +46,5 @@ class Application {
40
46
  this.app.exit();
41
47
  }
42
48
  }
43
- export { Application, Icon };
49
+ export { Application };
50
+ export * from "./Basics.js";
@@ -16,4 +16,21 @@ declare class Icon {
16
16
  toTrayIconImage(): TrayIconImage;
17
17
  toBuffer(): Buffer;
18
18
  }
19
- export { Icon };
19
+ declare class Size {
20
+ width: number;
21
+ height: number;
22
+ logical: boolean;
23
+ constructor(width: number, height: number, logical?: boolean);
24
+ }
25
+ declare class Position {
26
+ x: number;
27
+ y: number;
28
+ logical: boolean;
29
+ constructor(x: number, y: number, logical?: boolean);
30
+ }
31
+ declare enum WindowLevel {
32
+ Bottommost = -1,
33
+ Normal = 0,
34
+ Topmost = 1
35
+ }
36
+ export { Icon, Size, Position, WindowLevel };
@@ -33,4 +33,30 @@ class Icon {
33
33
  return Buffer.from(this.data);
34
34
  }
35
35
  }
36
- export { Icon };
36
+ class Size {
37
+ width;
38
+ height;
39
+ logical;
40
+ constructor(width, height, logical) {
41
+ this.width = width;
42
+ this.height = height;
43
+ this.logical = logical ?? true;
44
+ }
45
+ }
46
+ class Position {
47
+ x;
48
+ y;
49
+ logical;
50
+ constructor(x, y, logical) {
51
+ this.x = x;
52
+ this.y = y;
53
+ this.logical = logical ?? false;
54
+ }
55
+ }
56
+ var WindowLevel;
57
+ (function (WindowLevel) {
58
+ WindowLevel[WindowLevel["Bottommost"] = -1] = "Bottommost";
59
+ WindowLevel[WindowLevel["Normal"] = 0] = "Normal";
60
+ WindowLevel[WindowLevel["Topmost"] = 1] = "Topmost";
61
+ })(WindowLevel || (WindowLevel = {}));
62
+ export { Icon, Size, Position, WindowLevel };
@@ -2,8 +2,8 @@ import { SingleInstance } from "@nexfteam/single-instance";
2
2
  declare class Locker {
3
3
  locker: SingleInstance;
4
4
  constructor(appName: string);
5
- lock(): Promise<void>;
5
+ lock(data?: string): Promise<void>;
6
6
  unlock(): Promise<void>;
7
- whenLost(callback: () => void): void;
7
+ whenLost(callback: (data?: string) => void): void;
8
8
  }
9
9
  export { Locker };
@@ -4,8 +4,8 @@ class Locker {
4
4
  constructor(appName) {
5
5
  this.locker = new SingleInstance(appName);
6
6
  }
7
- lock() {
8
- return this.locker.lock();
7
+ lock(data) {
8
+ return this.locker.lock(data);
9
9
  }
10
10
  unlock() {
11
11
  return this.locker.unlock();
package/src/Tray.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Application, TrayIcon } from "@webviewjs/webview";
2
- import { Icon } from "./Icon.js";
2
+ import { Icon } from "./Basics.js";
3
3
  declare class Tray {
4
4
  app: Application;
5
5
  tray: TrayIcon;
@@ -1,6 +1,6 @@
1
1
  import { Application, BrowserWindow, Webview } from "@webviewjs/webview";
2
2
  import { Logger } from "./Logger.js";
3
- import { Icon } from "./Icon.js";
3
+ import { Icon, Size, Position, WindowLevel } from "./Basics.js";
4
4
  declare class Window {
5
5
  window: BrowserWindow;
6
6
  webview: Webview;
@@ -10,7 +10,7 @@ declare class Window {
10
10
  id: number;
11
11
  private myPool;
12
12
  constructor(window: BrowserWindow, webview: Webview, id: number, myPool: WindowPool);
13
- setLevel(level: -1 | 0 | 1): void;
13
+ setLevel(level: WindowLevel): void;
14
14
  setFullScreen(isFullScreen: boolean, options?: {
15
15
  borderless?: boolean;
16
16
  }): void;
@@ -28,12 +28,13 @@ declare class Window {
28
28
  openDevTools(): void;
29
29
  closeDevTools(): void;
30
30
  setIcon(icon: Icon): void;
31
- setSize(width: number, height: number): void;
32
- getSize(): import("@webviewjs/webview").Dimensions;
33
- setPosition(x: number, y: number): void;
34
- getPosition(): import("@webviewjs/webview").Position;
31
+ setSize(size: Size): void;
32
+ getSize(logical?: boolean): import("@webviewjs/webview").Dimensions;
33
+ setPosition(position: Position): void;
34
+ getPosition(logical?: boolean): import("@webviewjs/webview").Position;
35
35
  isFocused(): boolean;
36
36
  focus(): void;
37
+ tell(message: string, data: any): void;
37
38
  hide(): void;
38
39
  show(): void;
39
40
  close(): void;
@@ -49,11 +50,17 @@ declare class WindowPool {
49
50
  private injectCount;
50
51
  private windowCount;
51
52
  private freeWindowCount;
53
+ private localProxys;
52
54
  global: Map<string, any>;
53
55
  constructor(app: Application, options: {
54
56
  WindowsWebview2UserDataFolder?: string;
55
57
  logger?: Logger;
58
+ localProxys?: Array<{
59
+ protocolName: string;
60
+ localPath: string;
61
+ }>;
56
62
  });
63
+ __registryLocalProxy(window: BrowserWindow, protocolName: string, localPath: string): void;
57
64
  __injectCode(window: Window, code: string): Promise<void>;
58
65
  __injectControlFunctions(windowObj: Window): Promise<void>;
59
66
  __createNewWindowObj(): Promise<Window>;
@@ -63,11 +70,12 @@ declare class WindowPool {
63
70
  title?: string;
64
71
  icon?: Icon;
65
72
  resizable?: boolean;
66
- width?: number;
67
- height?: number;
73
+ size?: Size;
74
+ position?: Position;
68
75
  }): Promise<Window>;
69
76
  closeWindow(window: Window): Promise<void>;
70
77
  handle(event: string, callback: (data: any) => any): Promise<void>;
71
78
  unhandle(event: string, callback: (data: any) => any): Promise<void>;
79
+ broadcast(event: string, data: any): Promise<void>;
72
80
  }
73
81
  export { WindowPool, Window };
@@ -1,7 +1,9 @@
1
1
  import { Logger } from "./Logger.js";
2
+ import { WindowLevel } from "./Basics.js";
2
3
  import os from "os";
3
4
  import path from "path";
4
5
  import fs from "fs";
6
+ import { lookup } from "mrmime";
5
7
  class Window {
6
8
  window;
7
9
  webview;
@@ -20,17 +22,17 @@ class Window {
20
22
  this.id = id;
21
23
  }
22
24
  setLevel(level) {
23
- if (level == -1) {
25
+ if (level == WindowLevel.Bottommost) {
24
26
  this.window.setAlwaysOnTop(false);
25
27
  this.window.setAlwaysOnBottom(true);
26
28
  return;
27
29
  }
28
- else if (level == 0) {
30
+ else if (level == WindowLevel.Normal) {
29
31
  this.window.setAlwaysOnTop(false);
30
32
  this.window.setAlwaysOnBottom(false);
31
33
  return;
32
34
  }
33
- else if (level == 1) {
35
+ else if (level == WindowLevel.Topmost) {
34
36
  this.window.setAlwaysOnTop(true);
35
37
  this.window.setAlwaysOnBottom(false);
36
38
  return;
@@ -102,17 +104,17 @@ class Window {
102
104
  setIcon(icon) {
103
105
  this.window.setWindowIcon(icon.data, icon.width, icon.height);
104
106
  }
105
- setSize(width, height) {
106
- this.window.setSize(width, height);
107
+ setSize(size) {
108
+ this.window.setSize(size.width, size.height, size.logical);
107
109
  }
108
- getSize() {
109
- return this.window.getInnerSize();
110
+ getSize(logical) {
111
+ return this.window.getInnerSize(logical);
110
112
  }
111
- setPosition(x, y) {
112
- this.window.setPosition(x, y);
113
+ setPosition(position) {
114
+ this.window.setPosition(position.x, position.y, position.logical);
113
115
  }
114
- getPosition() {
115
- return this.window.getPosition();
116
+ getPosition(logical) {
117
+ return this.window.getPosition(logical);
116
118
  }
117
119
  isFocused() {
118
120
  return this.window.isFocused();
@@ -121,6 +123,9 @@ class Window {
121
123
  this.unMinimize();
122
124
  this.window.focus();
123
125
  }
126
+ tell(message, data) {
127
+ this.webview.evaluateScript(`window.dispatchEvent(new CustomEvent('${message}', { detail: ${JSON.stringify(data)} }));`);
128
+ }
124
129
  hide() {
125
130
  this.window.hide();
126
131
  this.isShow = false;
@@ -150,6 +155,7 @@ class WindowPool {
150
155
  injectCount;
151
156
  windowCount;
152
157
  freeWindowCount;
158
+ localProxys;
153
159
  global;
154
160
  constructor(app, options) {
155
161
  if (os.platform() === "win32") {
@@ -171,6 +177,36 @@ class WindowPool {
171
177
  this.freeWindowCount = 0;
172
178
  this.global = new Map();
173
179
  this.logger = options.logger || new Logger();
180
+ this.localProxys = options.localProxys || [];
181
+ }
182
+ __registryLocalProxy(window, protocolName, localPath) {
183
+ const basePath = path.resolve(localPath);
184
+ console.log(window.registerProtocol);
185
+ window.registerProtocol(protocolName, async (request) => {
186
+ try {
187
+ const url = new URL(request.url);
188
+ let relativePath = url.pathname;
189
+ if (relativePath === "/" || relativePath === "") {
190
+ relativePath = "/index.html";
191
+ }
192
+ const absolutePath = path.join(basePath, relativePath);
193
+ const resolvedPath = path.resolve(absolutePath);
194
+ if (!resolvedPath.startsWith(basePath)) {
195
+ return new Response("Forbidden", { status: 403 });
196
+ }
197
+ const content = fs.readFileSync(resolvedPath);
198
+ const mimeType = lookup(resolvedPath) || "application/octet-stream";
199
+ return new Response(content, {
200
+ status: 200,
201
+ headers: {
202
+ "Content-Type": mimeType,
203
+ },
204
+ });
205
+ }
206
+ catch {
207
+ return new Response("Not Found", { status: 404 });
208
+ }
209
+ });
174
210
  }
175
211
  async __injectCode(window, code) {
176
212
  await window.webview.evaluateScript(code);
@@ -362,6 +398,9 @@ class WindowPool {
362
398
  visible: false,
363
399
  focused: false,
364
400
  });
401
+ this.localProxys.forEach((proxy) => {
402
+ this.__registryLocalProxy(window, proxy.protocolName, proxy.localPath);
403
+ });
365
404
  this.freeWindowCount++;
366
405
  const webview = window.createWebview();
367
406
  const windowObj = new Window(window, webview, ++this.windowCount, this);
@@ -370,13 +409,14 @@ class WindowPool {
370
409
  const dataText = data.body.toString();
371
410
  const dataObj = JSON.parse(dataText);
372
411
  if (dataObj.type == "NexfepBeforeUnload") {
373
- webview.evaluateScript(`if(window?.isNexfepLoadDone){
374
- const MessageBody = { type: 'NexfepBeforeUnload' }
375
- window.ipc.postMessage(JSON.stringify(MessageBody));
376
- }else{
377
- const MessageBody = { type: 'NexfepLoadFalse' }
378
- window.ipc.postMessage(JSON.stringify(MessageBody));
379
- }`);
412
+ webview.evaluateScript(`
413
+ if(window?.isNexfepLoadDone){
414
+ const MessageBody = { type: 'NexfepBeforeUnload' }
415
+ window.ipc.postMessage(JSON.stringify(MessageBody));
416
+ }else{
417
+ const MessageBody = { type: 'NexfepLoadFalse' }
418
+ window.ipc.postMessage(JSON.stringify(MessageBody));
419
+ }`);
380
420
  }
381
421
  else if (dataObj.type == "NexfepLoadFalse") {
382
422
  this.__injectControlFunctions(windowObj);
@@ -416,14 +456,10 @@ class WindowPool {
416
456
  handlers.forEach(async (handler) => {
417
457
  const result = await handler(dataObj.data);
418
458
  if (result) {
419
- webview.evaluateScript(`
420
- window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(result)} }));
421
- `);
459
+ webview.evaluateScript(`window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(result)} }));`);
422
460
  }
423
461
  else {
424
- webview.evaluateScript(`
425
- window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: undefined }));
426
- `);
462
+ webview.evaluateScript(`window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: undefined }));`);
427
463
  }
428
464
  });
429
465
  }
@@ -432,16 +468,12 @@ class WindowPool {
432
468
  }
433
469
  else if (dataObj.type == "NexfepGetGlobal") {
434
470
  const value = this.global.get(dataObj.name);
435
- webview.evaluateScript(`
436
- window.dispatchEvent(new CustomEvent('nexfep-get-global-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(value)} }));
437
- `);
471
+ webview.evaluateScript(`window.dispatchEvent(new CustomEvent('nexfep-get-global-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(value)} }));`);
438
472
  }
439
473
  else if (dataObj.type == "NexfepBroadcast") {
440
474
  this.windows.forEach(async (w) => {
441
475
  if (w != windowObj && w.isOpen) {
442
- w.webview.evaluateScript(`
443
- window.dispatchEvent(new CustomEvent('${dataObj.name}', { detail: ${JSON.stringify(dataObj.data)} }));
444
- `);
476
+ w.webview.evaluateScript(`window.dispatchEvent(new CustomEvent('${dataObj.name}', { detail: ${JSON.stringify(dataObj.data)} }));`);
445
477
  }
446
478
  });
447
479
  }
@@ -451,9 +483,7 @@ class WindowPool {
451
483
  }
452
484
  this.windows.forEach(async (w) => {
453
485
  if (w.id == dataObj.to) {
454
- w.webview.evaluateScript(`
455
- window.dispatchEvent(new CustomEvent('${dataObj.message}', { detail: ${JSON.stringify(dataObj.data)} }));
456
- `);
486
+ w.webview.evaluateScript(`window.dispatchEvent(new CustomEvent('${dataObj.message}', { detail: ${JSON.stringify(dataObj.data)} }));`);
457
487
  }
458
488
  });
459
489
  }
@@ -492,7 +522,12 @@ class WindowPool {
492
522
  window.setIcon(options?.icon);
493
523
  }
494
524
  window.setResizable(options?.resizable ?? true);
495
- window.setSize(options?.width ?? 800, options?.height ?? 600);
525
+ if (options?.size) {
526
+ window.setSize(options?.size);
527
+ }
528
+ if (options?.position) {
529
+ window.setPosition(options?.position);
530
+ }
496
531
  }
497
532
  if (this.freeWindowCount == 0) {
498
533
  this.__createNewWindowObj();
@@ -518,5 +553,12 @@ class WindowPool {
518
553
  async unhandle(event, callback) {
519
554
  this.handlers.set(event, (this.handlers.get(event) || []).filter((c) => c !== callback));
520
555
  }
556
+ async broadcast(event, data) {
557
+ this.windows.forEach(async (w) => {
558
+ if (w.isOpen) {
559
+ w.webview.evaluateScript(`window.dispatchEvent(new CustomEvent('${event}', { detail: ${JSON.stringify(data)} }));`);
560
+ }
561
+ });
562
+ }
521
563
  }
522
564
  export { WindowPool, Window };