clickgo-native 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +114 -30
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as electron from 'electron';
|
|
2
2
|
import * as nodePath from 'path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
3
4
|
import * as lFs from './lib/fs.js';
|
|
4
5
|
import * as lTool from './lib/tool.js';
|
|
5
6
|
// npm publish --tag dev --access public
|
|
@@ -12,6 +13,10 @@ let isNoFormQuit = true;
|
|
|
12
13
|
let form;
|
|
13
14
|
/** --- 当前设定的通讯 token --- */
|
|
14
15
|
let token = '';
|
|
16
|
+
/** --- 主窗体唯一允许使用 Native 通讯的页面地址(不含 hash) --- */
|
|
17
|
+
let mainPage = '';
|
|
18
|
+
/** --- 主框架正在更换文档时暂停 Native 通讯 --- */
|
|
19
|
+
let mainNavigating = false;
|
|
15
20
|
/** --- 当前系统平台 --- */
|
|
16
21
|
const platform = process.platform;
|
|
17
22
|
// const platform: NodeJS.Platform = 'darwin';
|
|
@@ -42,6 +47,8 @@ const methods = {
|
|
|
42
47
|
}
|
|
43
48
|
form.setSize(width, height);
|
|
44
49
|
form.center();
|
|
50
|
+
// --- 首个应用窗体设置尺寸后再允许缩放,启动屏保持固定大小 ---
|
|
51
|
+
form.resizable = true;
|
|
45
52
|
}
|
|
46
53
|
},
|
|
47
54
|
// --- 设置窗体最大化、最小化、还原(从最大化还原) ---
|
|
@@ -471,18 +478,7 @@ export function showMainForm(path, opt = {}) {
|
|
|
471
478
|
// --- 有主窗体了就不能创建了 ---
|
|
472
479
|
return;
|
|
473
480
|
}
|
|
474
|
-
|
|
475
|
-
methods['cg-init'] = {
|
|
476
|
-
'once': true,
|
|
477
|
-
handler: function (t) {
|
|
478
|
-
// --- t 是网页传来的 token ---
|
|
479
|
-
if (!t || !form) {
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
form.resizable = true;
|
|
483
|
-
token = t;
|
|
484
|
-
},
|
|
485
|
-
};
|
|
481
|
+
resetMainSession();
|
|
486
482
|
const frm = createForm(path, {
|
|
487
483
|
'width': opt.width,
|
|
488
484
|
'height': opt.height,
|
|
@@ -495,6 +491,26 @@ export function showMainForm(path, opt = {}) {
|
|
|
495
491
|
frm.webContents.openDevTools();
|
|
496
492
|
}
|
|
497
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* --- 更换主页面文档时撤销旧 token,重新允许一次合法初始化 ---
|
|
496
|
+
* @returns 无
|
|
497
|
+
*/
|
|
498
|
+
function resetMainSession() {
|
|
499
|
+
token = '';
|
|
500
|
+
methods['cg-init'] = {
|
|
501
|
+
'once': true,
|
|
502
|
+
handler: function (t) {
|
|
503
|
+
// --- t 是网页传来的 token ---
|
|
504
|
+
if ((typeof t !== 'string') || !t || !form || token) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (hasFrame) {
|
|
508
|
+
form.resizable = true;
|
|
509
|
+
}
|
|
510
|
+
token = t;
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
498
514
|
/** --- 用户调用运行 boot 类 --- */
|
|
499
515
|
export function launcher(boot) {
|
|
500
516
|
(async function () {
|
|
@@ -511,24 +527,44 @@ export function launcher(boot) {
|
|
|
511
527
|
electron.Menu.setApplicationMenu(null);
|
|
512
528
|
// --- 实际用来监听网页传输过来的数据 ---
|
|
513
529
|
electron.ipcMain.handle('pre', function (e, name, ...param) {
|
|
514
|
-
if (!
|
|
530
|
+
if (!form || form.isDestroyed() || mainNavigating ||
|
|
531
|
+
(e.sender !== form.webContents) ||
|
|
532
|
+
!e.senderFrame || (e.senderFrame !== form.webContents.mainFrame) ||
|
|
533
|
+
!mainPage || (getPageUrl(e.senderFrame.url) !== mainPage) ||
|
|
534
|
+
(typeof name !== 'string') || !Object.hasOwn(methods, name)) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// --- 无效初始化不能消耗一次性监听 ---
|
|
538
|
+
if ((name === 'cg-init') && ((typeof param[0] !== 'string') || !param[0] || token)) {
|
|
515
539
|
return;
|
|
516
540
|
}
|
|
517
|
-
const
|
|
518
|
-
if (
|
|
541
|
+
const method = methods[name];
|
|
542
|
+
if (method.once) {
|
|
519
543
|
delete methods[name];
|
|
520
544
|
}
|
|
521
|
-
return
|
|
545
|
+
return method.handler(...param);
|
|
522
546
|
});
|
|
547
|
+
/**
|
|
548
|
+
* --- 精确匹配页面地址,允许同一页面改变 hash ---
|
|
549
|
+
* @param value 页面地址
|
|
550
|
+
* @returns 不含 hash 的地址,无效地址返回空字符串
|
|
551
|
+
*/
|
|
552
|
+
function getPageUrl(value) {
|
|
553
|
+
try {
|
|
554
|
+
const url = new URL(value);
|
|
555
|
+
url.hash = '';
|
|
556
|
+
return url.href;
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return '';
|
|
560
|
+
}
|
|
561
|
+
}
|
|
523
562
|
/**
|
|
524
563
|
* --- 验证 token 是否正确 ---
|
|
525
564
|
* @param t 要验证的 token
|
|
526
565
|
*/
|
|
527
566
|
export function verifyToken(t) {
|
|
528
|
-
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
return true;
|
|
567
|
+
return (typeof t === 'string') && (token !== '') && (t === token);
|
|
532
568
|
}
|
|
533
569
|
/**
|
|
534
570
|
* --- 内部调用用来创建实体窗体的函数 ---
|
|
@@ -537,8 +573,6 @@ export function verifyToken(t) {
|
|
|
537
573
|
function createForm(p, opt = {}) {
|
|
538
574
|
const url = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
|
|
539
575
|
let pre = nodePath.join(url.slice(0, url.lastIndexOf('/') + 1), './pre.js');
|
|
540
|
-
/** --- Linux 新版 Electron 无框窗口需要透明背景,避免 CSD 外边距显示为黑框 --- */
|
|
541
|
-
const transparent = opt.transparent ?? ((platform === 'linux') && !hasFrame);
|
|
542
576
|
const op = {
|
|
543
577
|
'webPreferences': {
|
|
544
578
|
'nodeIntegration': false,
|
|
@@ -546,26 +580,69 @@ function createForm(p, opt = {}) {
|
|
|
546
580
|
'preload': pre,
|
|
547
581
|
},
|
|
548
582
|
'width': opt.width ?? (hasFrame ? 800 : 600),
|
|
549
|
-
'height': opt.height ?? (hasFrame ? 700 :
|
|
583
|
+
'height': opt.height ?? (hasFrame ? 700 : 400),
|
|
550
584
|
'frame': hasFrame,
|
|
551
|
-
'thickFrame': platform === 'win32' ? hasFrame : undefined,
|
|
552
|
-
'roundedCorners': platform === 'linux' ? hasFrame : undefined,
|
|
553
|
-
'hasShadow': platform === 'linux' ? hasFrame : undefined,
|
|
554
585
|
'resizable': false,
|
|
555
586
|
'show': false,
|
|
556
587
|
'center': true,
|
|
557
588
|
'maximizable': opt.max ?? true,
|
|
558
|
-
'backgroundColor':
|
|
559
|
-
'transparent': transparent,
|
|
589
|
+
'backgroundColor': opt.background ?? 'rgba(0, 0, 0, 1)',
|
|
590
|
+
'transparent': opt.transparent,
|
|
560
591
|
};
|
|
561
592
|
form = new electron.BrowserWindow(op);
|
|
593
|
+
// --- 页面地址由本地主进程确定,不接受网页修改 ---
|
|
594
|
+
const lio = p.indexOf('?');
|
|
595
|
+
const pageUrl = (p.startsWith('https://') || p.startsWith('http://')) ?
|
|
596
|
+
new URL(p) : pathToFileURL(lio === -1 ? p : p.slice(0, lio));
|
|
597
|
+
if ((pageUrl.protocol === 'file:') && (lio !== -1)) {
|
|
598
|
+
pageUrl.search = p.slice(lio + 1);
|
|
599
|
+
}
|
|
600
|
+
mainPage = getPageUrl(pageUrl.href);
|
|
601
|
+
mainNavigating = true;
|
|
602
|
+
/** --- 主动拦截初始页面重定向产生的加载失败无需再次抛出 --- */
|
|
603
|
+
let redirectBlocked = false;
|
|
604
|
+
form.webContents.on('will-navigate', (event) => {
|
|
605
|
+
if (getPageUrl(event.url) !== mainPage) {
|
|
606
|
+
event.preventDefault();
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
form.webContents.on('will-redirect', (event) => {
|
|
610
|
+
if (event.isMainFrame && (getPageUrl(event.url) !== mainPage)) {
|
|
611
|
+
redirectBlocked = true;
|
|
612
|
+
event.preventDefault();
|
|
613
|
+
mainNavigating = false;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
form.webContents.setWindowOpenHandler(() => ({ 'action': 'deny' }));
|
|
617
|
+
form.webContents.on('did-start-navigation', (event) => {
|
|
618
|
+
if (event.isMainFrame && !event.isSameDocument && (getPageUrl(event.url) === mainPage)) {
|
|
619
|
+
mainNavigating = true;
|
|
620
|
+
resetMainSession();
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
form.webContents.on('did-navigate', () => {
|
|
624
|
+
mainNavigating = false;
|
|
625
|
+
if (form && (getPageUrl(form.webContents.getURL()) !== mainPage)) {
|
|
626
|
+
token = '';
|
|
627
|
+
delete methods['cg-init'];
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
form.webContents.on('did-fail-load', (_event, _code, _description, _url, isMainFrame) => {
|
|
631
|
+
if (isMainFrame) {
|
|
632
|
+
mainNavigating = false;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
form.webContents.on('render-process-gone', () => {
|
|
636
|
+
token = '';
|
|
637
|
+
mainNavigating = true;
|
|
638
|
+
});
|
|
562
639
|
form.webContents.userAgent = 'electron/' + electron.app.getVersion() + ' ' + platform + '/' + process.arch + ' frame/' + (hasFrame ? '1' : '0') + ' chrome/' + process.versions.chrome;
|
|
563
640
|
form.once('ready-to-show', function () {
|
|
564
641
|
if (!form) {
|
|
565
642
|
return;
|
|
566
643
|
}
|
|
567
644
|
if (opt.background) {
|
|
568
|
-
form.setBackgroundColor(transparent ? 'rgba(0, 0, 0, 0)' : 'rgba(0, 0, 0, 1)');
|
|
645
|
+
form.setBackgroundColor(opt.transparent ? 'rgba(0, 0, 0, 0)' : 'rgba(0, 0, 0, 1)');
|
|
569
646
|
}
|
|
570
647
|
form.show();
|
|
571
648
|
if (opt.stateMax) {
|
|
@@ -575,6 +652,9 @@ function createForm(p, opt = {}) {
|
|
|
575
652
|
if (p.startsWith('https://') || p.startsWith('http://')) {
|
|
576
653
|
// --- 加载网页 ---
|
|
577
654
|
form.loadURL(p).catch(function (e) {
|
|
655
|
+
if (redirectBlocked) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
578
658
|
throw e;
|
|
579
659
|
});
|
|
580
660
|
}
|
|
@@ -591,8 +671,12 @@ function createForm(p, opt = {}) {
|
|
|
591
671
|
throw e;
|
|
592
672
|
});
|
|
593
673
|
}
|
|
594
|
-
form.on('
|
|
674
|
+
form.on('closed', function () {
|
|
595
675
|
form = undefined;
|
|
676
|
+
token = '';
|
|
677
|
+
mainPage = '';
|
|
678
|
+
mainNavigating = false;
|
|
679
|
+
delete methods['cg-init'];
|
|
596
680
|
});
|
|
597
681
|
// --- 最大化事件 ---
|
|
598
682
|
form.on('maximize', function () {
|