clickgo-native 2.0.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +110 -21
  2. 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
- // --- 成功运行一个 task 后再次添加 init ---
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 (!methods[name]) {
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 r = methods[name].handler(...param);
518
- if (methods[name].once) {
541
+ const method = methods[name];
542
+ if (method.once) {
519
543
  delete methods[name];
520
544
  }
521
- return r;
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
- if (t !== token) {
529
- return false;
530
- }
531
- return true;
567
+ return (typeof t === 'string') && (token !== '') && (t === token);
532
568
  }
533
569
  /**
534
570
  * --- 内部调用用来创建实体窗体的函数 ---
@@ -554,6 +590,52 @@ function createForm(p, opt = {}) {
554
590
  'transparent': opt.transparent,
555
591
  };
556
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
+ });
557
639
  form.webContents.userAgent = 'electron/' + electron.app.getVersion() + ' ' + platform + '/' + process.arch + ' frame/' + (hasFrame ? '1' : '0') + ' chrome/' + process.versions.chrome;
558
640
  form.once('ready-to-show', function () {
559
641
  if (!form) {
@@ -570,6 +652,9 @@ function createForm(p, opt = {}) {
570
652
  if (p.startsWith('https://') || p.startsWith('http://')) {
571
653
  // --- 加载网页 ---
572
654
  form.loadURL(p).catch(function (e) {
655
+ if (redirectBlocked) {
656
+ return;
657
+ }
573
658
  throw e;
574
659
  });
575
660
  }
@@ -586,8 +671,12 @@ function createForm(p, opt = {}) {
586
671
  throw e;
587
672
  });
588
673
  }
589
- form.on('close', function () {
674
+ form.on('closed', function () {
590
675
  form = undefined;
676
+ token = '';
677
+ mainPage = '';
678
+ mainNavigating = false;
679
+ delete methods['cg-init'];
591
680
  });
592
681
  // --- 最大化事件 ---
593
682
  form.on('maximize', function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clickgo-native",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "The software developed with ClickGo will run in Windows, Mac OS, Linux.",
5
5
  "type": "module",
6
6
  "keywords": [