diff --git a/app/electron/main.js b/app/electron/main.js index 13a504b37ac..47cb8cc6608 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -752,7 +752,7 @@ app.whenReady().then(() => { event.sender.send("siyuan-event", "leave-full-screen"); }); }); - ipcMain.on("siyuan-cmd", (event, data) => { + ipcMain.on("siyuan-cmd", async (event, data) => { let cmd = data; let webContentsId = event.sender.id; if (typeof data !== "string") { @@ -820,17 +820,29 @@ app.whenReady().then(() => { } break; case "setProxy": - event.sender.session.closeAllConnections().then(() => { + try { + let proxy, log; if (data.proxyURL.startsWith("://")) { - event.sender.session.setProxy({mode: "system"}).then(() => { - console.log("network proxy [system]"); - }); - return; + proxy = {mode: "system"}; + log = "network proxy [system]"; + } else { + proxy = {proxyRules: data.proxyURL}; + log = `network proxy [${data.proxyURL}]`; } - event.sender.session.setProxy({proxyRules: data.proxyURL}).then(() => { - console.log("network proxy [" + data.proxyURL + "]"); + await event.sender.session.closeAllConnections(); + await event.sender.session.setProxy(proxy); + console.log(log); + event.reply("siyuan-proxy-reply", { + state: "fulfilled", + proxy, }); - }); + } catch (error) { + event.reply("siyuan-proxy-reply", { + state: "rejected", + proxy, + error, + }); + } break; } }); diff --git a/app/src/boot/onGetConfig.ts b/app/src/boot/onGetConfig.ts index e3356b41fc4..4d90a0203db 100644 --- a/app/src/boot/onGetConfig.ts +++ b/app/src/boot/onGetConfig.ts @@ -22,7 +22,6 @@ import {showMessage} from "../dialog/message"; import {replaceLocalPath} from "../editor/rename"; import {setTabPosition} from "../window/setHeader"; import {initBar} from "../layout/topBar"; -import {setProxy} from "../config/util/about"; import {openChangelog} from "./openChangelog"; import {getIdFromSYProtocol, isSYProtocol} from "../util/pathName"; import {App} from "../index"; @@ -135,7 +134,6 @@ export const onGetConfig = (isStart: boolean, app: App) => { } }); initBar(app); - setProxy(); initStatus(); initWindow(app); appearance.onSetappearance(window.siyuan.config.appearance); diff --git a/app/src/config/util/about.ts b/app/src/config/util/about.ts index 65e7784f9a9..0d655f8af4e 100644 --- a/app/src/config/util/about.ts +++ b/app/src/config/util/about.ts @@ -8,9 +8,28 @@ import {Constants} from "../../constants"; export const setProxy = () => { /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_CMD, { - cmd: "setProxy", - proxyURL: `${window.siyuan.config.system.networkProxy.scheme}://${window.siyuan.config.system.networkProxy.host}:${window.siyuan.config.system.networkProxy.port}` + return new Promise((resolve, reject) => { + ipcRenderer.once(Constants.SIYUAN_PROXY_REPLY, (event, data: { + state: "fulfilled" | "rejected", + proxy: {mode: "system"} | {proxyRules: string}, + error?: any, + }) => { + switch (data.state) { + case "fulfilled": + resolve(data.proxy); + break; + case "rejected": + reject(data.error); + break; + default: + reject(new Error(`Unknown state: ${data.state}`)); + break; + } + }); + ipcRenderer.send(Constants.SIYUAN_CMD, { + cmd: "setProxy", + proxyURL: `${window.siyuan.config.system.networkProxy.scheme}://${window.siyuan.config.system.networkProxy.host}:${window.siyuan.config.system.networkProxy.port}` + }); }); /// #endif }; diff --git a/app/src/constants.ts b/app/src/constants.ts index 08beb0e29f6..bd08a8948e5 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -28,6 +28,8 @@ export abstract class Constants { public static readonly SIYUAN_GET: string = "siyuan-get"; public static readonly SIYUAN_EVENT: string = "siyuan-event"; + public static readonly SIYUAN_PROXY_REPLY: string = "siyuan-proxy-reply"; + public static readonly SIYUAN_CONFIG_TRAY: string = "siyuan-config-tray"; public static readonly SIYUAN_QUIT: string = "siyuan-quit"; public static readonly SIYUAN_HOTKEY: string = "siyuan-hotkey"; diff --git a/app/src/index.ts b/app/src/index.ts index 04892f01750..9a5e21fbd7c 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -4,11 +4,12 @@ import {Model} from "./layout/Model"; import {onGetConfig} from "./boot/onGetConfig"; import {initBlockPopover} from "./block/popover"; import {account} from "./config/account"; +import {setProxy} from "./config/util/about"; import {addScript, addScriptSync} from "./protyle/util/addScript"; import {genUUID} from "./util/genID"; import {fetchGet, fetchPost} from "./util/fetch"; import {addBaseURL, getIdFromSYProtocol, isSYProtocol, setNoteBook} from "./util/pathName"; -import {registerServiceWorker} from "./util/serviceWorker"; +import {registerServiceWorker, unregisterServiceWorker} from "./util/serviceWorker"; import {openFileById} from "./editor/util"; import { bootSync, @@ -33,11 +34,6 @@ export class App { public appId: string; constructor() { - /// #if BROWSER - registerServiceWorker(`${Constants.SERVICE_WORKER_PATH}?v=${Constants.SIYUAN_VERSION}`); - /// #endif - addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript"); - addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript"); addBaseURL(); this.appId = Constants.SIYUAN_APPID; @@ -161,6 +157,20 @@ export class App { data: response.data.conf.uiLayout.bottom }; } + + await setProxy(); + + /// #if BROWSER + await registerServiceWorker(); + /// #else + await unregisterServiceWorker(); + /// #endif + addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript"); + addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript"); + + setNoteBook(); + initBlockPopover(this); + await loadPlugins(this); getLocalStorage(() => { fetchGet(`/appearance/langs/${window.siyuan.config.appearance.lang}.json?v=${Constants.SIYUAN_VERSION}`, (lauguages) => { @@ -177,8 +187,6 @@ export class App { }); }); }); - setNoteBook(); - initBlockPopover(this); } } diff --git a/app/src/mobile/index.ts b/app/src/mobile/index.ts index d9eaa06d9cc..6792bdb7ff5 100644 --- a/app/src/mobile/index.ts +++ b/app/src/mobile/index.ts @@ -20,7 +20,7 @@ import {getCurrentEditor, openMobileFileById} from "./editor"; import {getSearch} from "../util/functions"; import {initRightMenu} from "./menu"; import {openChangelog} from "../boot/openChangelog"; -import {registerServiceWorker} from "../util/serviceWorker"; +import {registerServiceWorker, unregisterServiceWorker} from "../util/serviceWorker"; import {afterLoadPlugin, loadPlugins} from "../plugin/loader"; import {saveScroll} from "../protyle/scroll/saveScroll"; import {removeBlock} from "../protyle/wysiwyg/remove"; @@ -32,12 +32,8 @@ class App { public appId: string; constructor() { - if (!window.webkit?.messageHandlers && !window.JSAndroid) { - registerServiceWorker(`${Constants.SERVICE_WORKER_PATH}?v=${Constants.SIYUAN_VERSION}`); - } - addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript"); - addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript"); addBaseURL(); + this.appId = Constants.SIYUAN_APPID; window.siyuan = { zIndex: 10, @@ -60,6 +56,7 @@ class App { } }) }; + // 不能使用 touchstart,否则会被 event.stopImmediatePropagation() 阻塞 window.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => { if (!window.siyuan.menus.menu.element.contains(event.target) && !hasClosestByAttribute(event.target, "data-menu", "true")) { @@ -80,9 +77,19 @@ class App { window.addEventListener("pagehide", () => { saveScroll(window.siyuan.mobile.editor.protyle); }, false); + fetchPost("/api/system/getConf", {}, async (confResponse) => { confResponse.data.conf.keymap = Constants.SIYUAN_KEYMAP; window.siyuan.config = confResponse.data.conf; + + if (!window.webkit?.messageHandlers && !window.JSAndroid) { + await registerServiceWorker(); + } else { + await unregisterServiceWorker(); + } + addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript"); + addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript"); + await loadPlugins(this); getLocalStorage(() => { fetchGet(`/appearance/langs/${window.siyuan.config.appearance.lang}.json?v=${Constants.SIYUAN_VERSION}`, (lauguages) => { diff --git a/app/src/util/serviceWorker.ts b/app/src/util/serviceWorker.ts index e437249df4d..dd3983a2eb2 100644 --- a/app/src/util/serviceWorker.ts +++ b/app/src/util/serviceWorker.ts @@ -1,17 +1,94 @@ +import { Constants } from "../constants"; + +const pathname = `${Constants.SERVICE_WORKER_PATH}?v=${Constants.SIYUAN_VERSION}`; + // https://github.com/siyuan-note/siyuan/pull/8012 -export const registerServiceWorker = (scriptURL: string, scope = "/", workerType: WorkerType = "module") => { - if (!("serviceWorker" in navigator) || typeof (navigator.serviceWorker) === "undefined" || - !("caches" in window) || !("fetch" in window)) { - return; +export const registerServiceWorker = async ( + scirpt: string | URL = pathname, + scope: string = "/", + workerType: WorkerType = "module", +) => { + if (("serviceWorker" in window.navigator) && ("caches" in window) && ("fetch" in window)) { + try { + // REF https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration + const registration = await window.navigator.serviceWorker.register(scirpt, { + scope, + type: workerType, + }); + await registration.update(); + return true; + } catch (error) { + console.warn(`Registration service worker failed with ${error}`); + } } - // REF https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration - window.navigator.serviceWorker - .register(scriptURL, { - scope, - type: workerType, - }).then(registration => { - registration.update(); - }).catch(e => { - console.debug(`Registration failed with ${e}`); - }); + return false; }; + +export const unregisterServiceWorker = async ( + scirpt: string | URL = pathname, + scope: string = "/", +) => { + if ("serviceWorker" in window.navigator) { + try { + const scriptURL = new URL(scirpt, window.document.baseURI); + const scopeURL = new URL(scope, window.document.baseURI); + + const registration = await window.navigator.serviceWorker.getRegistration(scope); + if (isTargetRegistration(scriptURL, scopeURL, registration)) { + const result = await registration.unregister(); + console.debug(`Unregistration service worker ${result ? "succeeded" : "failed"}`); + return result; + } + } catch (error) { + console.warn(`Unregistration service worker failed with ${error}`); + } + } + return false; +} + +/* ❗加载同一个 worker 时不会触发 */ +export const disableServiceWorker = ( + scirpt: string | URL = pathname, + scope: string = "/", +) => { + if ("serviceWorker" in window.navigator) { + try { + const scriptURL = new URL(scirpt, window.document.baseURI); + const scopeURL = new URL(scope, window.document.baseURI); + + const listener = async (e: Event) => { + console.debug(e); + const container = e.target as ServiceWorkerContainer; + const controllerScriptURL = new URL(container.controller.scriptURL); + if (scriptURL.pathname === controllerScriptURL.pathname) { + const registration = await container.ready; + if (isTargetRegistration(scriptURL, scopeURL, registration)) { + const result = await registration.unregister(); + console.debug(`Auto unregistration service worker ${result ? "succeeded" : "failed"}`); + } + } + }; + window.navigator.serviceWorker.addEventListener("controllerchange", listener); + return () => window.navigator.serviceWorker.removeEventListener("controllerchange", listener); + } catch (error) { + } + } + return () => {}; +} + +const isTargetRegistration = ( + scriptURL: URL, + scopeURL: URL, + registration: ServiceWorkerRegistration, +) => { + try { + if (registration?.active?.scriptURL && registration?.scope) { + const registrationScriptURL = new URL(registration.active.scriptURL); + const registrationScopeURL = new URL(registration.scope); + return scriptURL.pathname === registrationScriptURL.pathname + && scopeURL.pathname === registrationScopeURL.pathname; + } + } catch (error) { + } + return false +}