import { ipcRenderer, contextBridge, type IpcRendererEvent } from "electron"; type UpdateCenterSnapshot = { items: Array<{ taskKey: string; packageName: string; displayName: string; currentVersion: string; newVersion: string; source: "aptss" | "apm"; ignored?: boolean; }>; tasks: Array<{ taskKey: string; packageName: string; source: "aptss" | "apm"; status: | "queued" | "downloading" | "installing" | "completed" | "failed" | "cancelled"; progress: number; logs: Array<{ time: number; message: string }>; errorMessage: string; }>; warnings: string[]; hasRunningTasks: boolean; }; type IpcRendererFacade = { on: typeof ipcRenderer.on; off: typeof ipcRenderer.off; send: typeof ipcRenderer.send; invoke: typeof ipcRenderer.invoke; }; type UpdateCenterStateListener = (snapshot: UpdateCenterSnapshot) => void; const updateCenterStateListeners = new Map< UpdateCenterStateListener, (_event: IpcRendererEvent, snapshot: UpdateCenterSnapshot) => void >(); // --------- Expose some API to the Renderer process --------- contextBridge.exposeInMainWorld("ipcRenderer", { on(...args: Parameters) { const [channel, listener] = args; return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args), ); }, off(...args: Parameters) { const [channel, ...omit] = args; return ipcRenderer.off(channel, ...omit); }, send(...args: Parameters) { const [channel, ...omit] = args; return ipcRenderer.send(channel, ...omit); }, invoke(...args: Parameters) { const [channel, ...omit] = args; return ipcRenderer.invoke(channel, ...omit); }, // You can expose other APTs you need here. // ... } satisfies IpcRendererFacade); contextBridge.exposeInMainWorld("apm_store", { arch: (() => { const arch = process.arch; if (arch === "x64") { return "amd64"; } else if (arch === "arm64") { return "arm64"; } else { return arch; } })(), }); contextBridge.exposeInMainWorld("updateCenter", { open: (): Promise => ipcRenderer.invoke("update-center-open"), refresh: (): Promise => ipcRenderer.invoke("update-center-refresh"), ignore: (payload: { packageName: string; newVersion: string; }): Promise => ipcRenderer.invoke("update-center-ignore", payload), unignore: (payload: { packageName: string; newVersion: string; }): Promise => ipcRenderer.invoke("update-center-unignore", payload), start: (taskKeys: string[]): Promise => ipcRenderer.invoke("update-center-start", taskKeys), cancel: (taskKey: string): Promise => ipcRenderer.invoke("update-center-cancel", taskKey), getState: (): Promise => ipcRenderer.invoke("update-center-get-state"), onState: (listener: UpdateCenterStateListener): void => { const wrapped = ( _event: IpcRendererEvent, snapshot: UpdateCenterSnapshot, ) => { listener(snapshot); }; updateCenterStateListeners.set(listener, wrapped); ipcRenderer.on("update-center-state", wrapped); }, offState: (listener: UpdateCenterStateListener): void => { const wrapped = updateCenterStateListeners.get(listener); if (!wrapped) { return; } ipcRenderer.off("update-center-state", wrapped); updateCenterStateListeners.delete(listener); }, }); // --------- Preload scripts loading --------- function domReady( condition: DocumentReadyState[] = ["complete", "interactive"], ) { return new Promise((resolve) => { if (condition.includes(document.readyState)) { resolve(true); } else { document.addEventListener("readystatechange", () => { if (condition.includes(document.readyState)) { resolve(true); } }); } }); } const safeDOM = { append(parent: HTMLElement, child: HTMLElement) { if (!Array.from(parent.children).find((e) => e === child)) { return parent.appendChild(child); } }, remove(parent: HTMLElement, child: HTMLElement) { if (Array.from(parent.children).find((e) => e === child)) { return parent.removeChild(child); } }, }; /** * https://tobiasahlin.com/spinkit * https://connoratherton.com/loaders * https://projects.lukehaas.me/css-loaders * https://matejkustec.github.io/SpinThatShit */ function useLoading() { const className = `loaders-css__square-spin`; const styleContent = ` @keyframes square-spin { 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); } 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); } 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); } 100% { transform: perspective(100px) rotateX(0) rotateY(0); } } .${className} > div { animation-fill-mode: both; width: 50px; height: 50px; background: #fff; animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; } .app-loading-wrap { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center; background: #282c34; z-index: 9; } `; const oStyle = document.createElement("style"); const oDiv = document.createElement("div"); oStyle.id = "app-loading-style"; oStyle.innerHTML = styleContent; oDiv.className = "app-loading-wrap"; oDiv.innerHTML = `
`; return { appendLoading() { safeDOM.append(document.head, oStyle); safeDOM.append(document.body, oDiv); }, removeLoading() { safeDOM.remove(document.head, oStyle); safeDOM.remove(document.body, oDiv); }, }; } // ---------------------------------------------------------------------- const { appendLoading, removeLoading } = useLoading(); domReady().then(appendLoading); window.onmessage = (ev) => { if (ev.data.payload === "removeLoading") removeLoading(); }; setTimeout(removeLoading, 4999);