From 1ee576bfb270fd7acc73f645b609b70b7707614f Mon Sep 17 00:00:00 2001
From: Samuele de Tomasi
Date: Thu, 15 Jul 2021 18:36:35 +0200
Subject: [PATCH] Added IPC (for IpcRenderer and IpcMain) and a (very simple)
loading screen
---
electron/IPC/General/IPC.ts | 32 ++++++++
electron/IPC/General/channelsInterface.ts | 9 +++
electron/IPC/General/contextBridge.ts | 54 +++++++++++++
electron/IPC/systemInfo.ts | 38 +++++++++
electron/IPC/updaterInfo.ts | 94 ++++++++++++++++++++++
electron/configureDev.ts | 54 +++++++++++++
electron/index.ts | 74 ++++-------------
electron/mainWindow.ts | 93 +++++++++++++++++++++
electron/preload.ts | 6 ++
package.json | 2 +-
svelte/src/components/InfoElectron.svelte | 27 +++++++
svelte/src/components/Version.svelte | 94 ++++++++++++++++++++++
svelte/src/routes/help/index.svelte | 5 +-
svelte/src/routes/help/tab/command.svelte | 4 +-
svelte/src/routes/help/tab/renderer.svelte | 10 +++
svelte/src/routes/help/tab/start.svelte | 9 +++
svelte/static/loading.html | 1 +
17 files changed, 545 insertions(+), 61 deletions(-)
create mode 100644 electron/IPC/General/IPC.ts
create mode 100644 electron/IPC/General/channelsInterface.ts
create mode 100644 electron/IPC/General/contextBridge.ts
create mode 100644 electron/IPC/systemInfo.ts
create mode 100644 electron/IPC/updaterInfo.ts
create mode 100644 electron/configureDev.ts
create mode 100644 electron/mainWindow.ts
create mode 100644 electron/preload.ts
create mode 100644 svelte/src/components/InfoElectron.svelte
create mode 100644 svelte/src/components/Version.svelte
create mode 100644 svelte/src/routes/help/tab/renderer.svelte
create mode 100644 svelte/static/loading.html
diff --git a/electron/IPC/General/IPC.ts b/electron/IPC/General/IPC.ts
new file mode 100644
index 0000000..6e48710
--- /dev/null
+++ b/electron/IPC/General/IPC.ts
@@ -0,0 +1,32 @@
+import { BrowserWindow, IpcMain } from "electron";
+import { APIChannels, SendChannels } from "./channelsInterface";
+
+export default class IPC {
+ nameAPI: string = "api";
+ validSendChannel: SendChannels = {};
+ validReceiveChannel: string[] = [];
+
+ constructor(channels: APIChannels) {
+ this.nameAPI = channels.nameAPI;
+ this.validSendChannel = channels.validSendChannel;
+ this.validReceiveChannel = channels.validReceiveChannel;
+ }
+
+ get channels():APIChannels {
+ return {
+ nameAPI: this.nameAPI,
+ validSendChannel: this.validSendChannel,
+ validReceiveChannel: this.validReceiveChannel
+ }
+ }
+
+ initIpcMain(ipcMain:IpcMain, mainWindow: BrowserWindow) {
+ if (mainWindow) {
+ Object.keys(this.validSendChannel).forEach(key => {
+ ipcMain.on(key, async( event, message) => {
+ this.validSendChannel[key](mainWindow, event, message);
+ });
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/electron/IPC/General/channelsInterface.ts b/electron/IPC/General/channelsInterface.ts
new file mode 100644
index 0000000..d9cff2b
--- /dev/null
+++ b/electron/IPC/General/channelsInterface.ts
@@ -0,0 +1,9 @@
+export interface APIChannels {
+ nameAPI: string,
+ validSendChannel: SendChannels,
+ validReceiveChannel: string[]
+}
+
+export interface SendChannels {
+ [key: string]: Function
+}
\ No newline at end of file
diff --git a/electron/IPC/General/contextBridge.ts b/electron/IPC/General/contextBridge.ts
new file mode 100644
index 0000000..bf80faa
--- /dev/null
+++ b/electron/IPC/General/contextBridge.ts
@@ -0,0 +1,54 @@
+import { contextBridge, ipcRenderer } from "electron";
+import { APIChannels } from "./channelsInterface";
+import IPC from "./IPC";
+
+interface APIContextBridge {
+ send: (channel: string, data: any) => void;
+ receive: (channel: string, func: (arg0: any) => void) => void;
+}
+
+export function generateContextBridge(listIPC: IPC[]) {
+
+ let listChannels: APIChannels[] = [];
+ listIPC.forEach(el => {
+ listChannels.push(el.channels);
+ });
+
+ let listAPI: {[key: string]: APIContextBridge} = {};
+
+ listChannels.forEach(el => {
+ const api = getContextBridge(el);
+ const name = el.nameAPI;
+ listAPI[name] = {...api};
+ });
+
+ contextBridge.exposeInMainWorld("api", {
+ ...listAPI
+ });
+}
+
+function getContextBridge(obj: APIChannels): APIContextBridge {
+ const { validReceiveChannel } = { ...obj };
+ const validSendChannel = getArrayOfValidSendChannel(obj);
+
+ return {
+ send: (channel: string, data: any) => {
+ // whitelist channels
+ if (validSendChannel.includes(channel)) {
+ ipcRenderer.send(channel, data);
+ }
+ },
+ receive: (channel: string, func: (arg0: any) => void) => {
+ if (validReceiveChannel.includes(channel)) {
+ // Deliberately strip event as it includes `sender`
+ ipcRenderer.on(channel, (event, ...args: [any]) => {func(...args);});
+ }
+ }
+ }
+};
+
+function getArrayOfValidSendChannel(obj: APIChannels): string[] {
+ const { validSendChannel } = { ...obj };
+ let result: string[] = Object.keys(validSendChannel);
+ return result;
+}
diff --git a/electron/IPC/systemInfo.ts b/electron/IPC/systemInfo.ts
new file mode 100644
index 0000000..638c2fb
--- /dev/null
+++ b/electron/IPC/systemInfo.ts
@@ -0,0 +1,38 @@
+import { SendChannels } from "./General/channelsInterface";
+import IPC from "./General/IPC";
+import { BrowserWindow } from "electron";
+
+const nameAPI = "systemInfo";
+
+// to Main
+const validSendChannel: SendChannels = {
+ "requestSystemInfo": requestSystemInfo
+};
+
+// from Main
+const validReceiveChannel: string[] = [
+ "getSystemInfo",
+];
+
+const systemInfo = new IPC ({
+ nameAPI,
+ validSendChannel,
+ validReceiveChannel
+});
+
+export default systemInfo;
+
+// Enter here the functions for ElectronJS
+
+function requestSystemInfo(mainWindow: BrowserWindow, event: Electron.IpcMainEvent, message: any) {
+ const versionChrome = process.versions.chrome;
+ const versionNode = process.versions.node;
+ const versionElectron = process.versions.electron;
+ const result = {
+ chrome: versionChrome,
+ node: versionNode,
+ electron: versionElectron
+ }
+ mainWindow.webContents.send("getSystemInfo", result);
+}
+
diff --git a/electron/IPC/updaterInfo.ts b/electron/IPC/updaterInfo.ts
new file mode 100644
index 0000000..4d9b850
--- /dev/null
+++ b/electron/IPC/updaterInfo.ts
@@ -0,0 +1,94 @@
+import { BrowserWindow, app } from "electron";
+import { AppUpdater, autoUpdater } from "electron-updater";
+import { SendChannels } from "./General/channelsInterface";
+import IPC from "./General/IPC";
+
+const nameAPI = "updaterInfo";
+
+// to Main
+const validSendChannel: SendChannels = {
+ "requestVersionNumber": requestVersionNumber,
+ "checkForUpdate": checkForUpdate,
+ "startDownloadUpdate": startDownloadUpdate,
+ "quitAndInstall": quitAndInstall,
+};
+
+// from Main
+const validReceiveChannel: string[] = [
+ "getVersionNumber",
+ "checkingForUpdate",
+ "updateAvailable",
+ "updateNotAvailable",
+ "downloadProgress",
+ "updateDownloaded",
+];
+
+class UpdaterInfo extends IPC {
+ initAutoUpdater(autoUpdater: AppUpdater, mainWindow: BrowserWindow) {
+ initAutoUpdater(autoUpdater, mainWindow);
+ }
+}
+
+const updaterInfo = new UpdaterInfo ({
+ nameAPI,
+ validSendChannel,
+ validReceiveChannel
+});
+
+export default updaterInfo;
+
+// Enter here the functions for ElectronJS
+
+function initAutoUpdater(autoUpdater: AppUpdater, mainWindow: BrowserWindow) {
+ autoUpdater.on('checking-for-update', () => {
+ mainWindow.webContents.send("checkingForUpdate", null);
+ });
+
+ autoUpdater.on('error', (err) => { });
+
+ autoUpdater.on("update-available", (info: any) => {
+ mainWindow.webContents.send("updateAvailable", info);
+ });
+
+ autoUpdater.on('download-progress', (info: any) => {
+ mainWindow.webContents.send("downloadProgress", info);
+ });
+
+ autoUpdater.on("update-downloaded", (info: any) => {
+ mainWindow.webContents.send("updateDownloaded", info);
+ });
+
+ autoUpdater.on("update-not-available", (info: any) => {
+ mainWindow.webContents.send("updateNotAvailable", info);
+ });
+}
+
+function requestVersionNumber(mainWindow: BrowserWindow, event: Electron.IpcMainEvent, message: any) {
+ const version = app.getVersion();
+ const result = {version};
+ mainWindow.webContents.send("getVersionNumber", result);
+}
+
+function checkForUpdate(mainWindow: BrowserWindow, event: Electron.IpcMainEvent, message: any) {
+ autoUpdater.autoDownload = false;
+ autoUpdater.checkForUpdates();
+}
+
+function startDownloadUpdate(mainWindow: BrowserWindow, event: Electron.IpcMainEvent, message: any) {
+ autoUpdater.downloadUpdate();
+}
+
+function quitAndInstall(mainWindow: BrowserWindow, event: Electron.IpcMainEvent, message: any) {
+ autoUpdater.quitAndInstall();
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/electron/configureDev.ts b/electron/configureDev.ts
new file mode 100644
index 0000000..f1eba83
--- /dev/null
+++ b/electron/configureDev.ts
@@ -0,0 +1,54 @@
+import path from "path";
+import serve from "electron-serve";
+import { exec } from "child_process";
+
+export interface DeveloperOptions {
+ isInProduction: boolean;
+ serveSvelteDev: boolean;
+ buildSvelteDev: boolean;
+ watchSvelteBuild: boolean;
+}
+
+class ConfigureDev {
+ isInProduction: boolean;
+ serveSvelteDev: boolean;
+ buildSvelteDev: boolean;
+ watchSvelteBuild: boolean;
+ loadURL: any;
+
+ constructor(settings: DeveloperOptions) {
+ this.isInProduction = settings.isInProduction
+ this.serveSvelteDev = settings.serveSvelteDev
+ this.buildSvelteDev = settings.buildSvelteDev
+ this.watchSvelteBuild = settings.watchSvelteBuild
+ this.loadURL = null;
+
+ this._check_isInProduction();
+
+ if (!this.isInProduction && this.serveSvelteDev) this._dev_Svelte();
+ if (!this.isInProduction && this.buildSvelteDev) this._build_Dist();
+ if (!this.isInProduction && this.watchSvelteBuild) this._watch_Dist();
+ if (this.isInProduction || !this.serveSvelteDev) this._serve_Dist();
+ }
+
+ _check_isInProduction() {
+ if (! this.isInProduction){
+ this.isInProduction = process.env.NODE_ENV === "production" || !/[\\/]electron/.exec(process.execPath); // !process.execPath.match(/[\\/]electron/);
+ };
+ }
+ _dev_Svelte() {
+ exec("npm run svelte:dev");
+ require("electron-reload")(path.join(__dirname, "..", "svelte"));
+ }
+ _build_Dist() { exec("npm run svelte:build"); }
+ _watch_Dist() { require("electron-reload")(path.join(__dirname, "www")); }
+ _serve_Dist() {
+ this.loadURL = serve({ directory: "dist/www" });
+ }
+
+ isLocalHost() { return this.serveSvelteDev; }
+ isElectronServe() { return !this.serveSvelteDev; }
+
+}
+
+export default ConfigureDev;
\ No newline at end of file
diff --git a/electron/index.ts b/electron/index.ts
index e3908b9..e6ef963 100644
--- a/electron/index.ts
+++ b/electron/index.ts
@@ -1,70 +1,30 @@
-import { app, BrowserWindow } from "electron";
-import path from "path";
-import serve from "electron-serve";
-import { exec } from "child_process";
+import { ipcMain } from 'electron';
+import { autoUpdater } from "electron-updater";
+import Main from "./mainWindow";
+
+import systemInfo from './IPC/systemInfo';
+import updaterInfo from './IPC/updaterInfo';
const developerOptions = {
- isInProduction: false, // true if is in production
+ isInProduction: true, // true if is in production
serveSvelteDev: false, // true when you want to watch svelte
- buildSvelteDiv: false, // true when you want to build svelte
+ buildSvelteDev: false, // true when you want to build svelte
watchSvelteBuild: false, // true when you want to watch build svelte
};
-if (! developerOptions.isInProduction){
- developerOptions.isInProduction = process.env.NODE_ENV === "production" || !/[\\/]electron/.exec(process.execPath); // !process.execPath.match(/[\\/]electron/);
-}
-let loadURL:any = null;
-
-if (!developerOptions.isInProduction && developerOptions.serveSvelteDev) {
- console.log("npm run svelte:dev");
- exec("npm run svelte:dev");
- console.log("electron-reload svelte dev");
- require("electron-reload")(path.join(__dirname, "..", "svelte"));
-}
-
-if (!developerOptions.isInProduction && developerOptions.buildSvelteDiv) {
- console.log("npm run svelte:build");
- exec("npm run svelte:build");
+const windowSettings = {
+ title: "MEMENTO - SvelteKit, Electron, TypeScript",
+ width: 854,
+ height: 854
}
-if (!developerOptions.isInProduction && developerOptions.watchSvelteBuild) {
- console.log("electron-reload www");
- require("electron-reload")(path.join(__dirname, "www"));
-}
+let main = new Main(windowSettings, developerOptions);
+main.onEvent.on("window-created", async () => {
+ systemInfo.initIpcMain(ipcMain, main.window);
+ updaterInfo.initIpcMain(ipcMain, main.window);
-if (developerOptions.isInProduction || !developerOptions.serveSvelteDev) {
- console.log("serve dist/www");
- loadURL = serve({ directory: "dist/www" });
-}
+ updaterInfo.initAutoUpdater(autoUpdater, main.window);
-let mainWindow = null;
-
-const createWindow = async () => {
- mainWindow = new BrowserWindow({
- width: 854,
- height: 480,
- webPreferences: {
- nodeIntegration: true,
- contextIsolation: true,
- enableRemoteModule: true,
- },
- });
-
- if (developerOptions.serveSvelteDev) {
- mainWindow.loadURL("http://localhost:3000/");
- } else if (loadURL) {
- await loadURL(mainWindow);
- }
-};
-
-app.on("ready", async () => {
- app.name = "Svelte Template";
- await createWindow();
});
-app.on("window-all-closed", () => {
- if (process.platform !== "darwin") {
- app.quit();
- }
-});
diff --git a/electron/mainWindow.ts b/electron/mainWindow.ts
new file mode 100644
index 0000000..1319b45
--- /dev/null
+++ b/electron/mainWindow.ts
@@ -0,0 +1,93 @@
+import { app, BrowserWindow } from 'electron';
+import path from "path";
+import EventEmitter from 'events';
+import ConfigureDev from './configureDev';
+import { DeveloperOptions } from "./configureDev";
+
+const appName = "MEMENTO - SvelteKit, Electron, TypeScript";
+
+const defaultSettings = {
+ title: "MEMENTO - SvelteKit, Electron, TypeScript",
+ width: 854,
+ height: 480
+}
+
+const defaultSettingsDev:DeveloperOptions = {
+ isInProduction: true, // true if is in production
+ serveSvelteDev: false, // true when you want to watch svelte
+ buildSvelteDev: false, // true when you want to build svelte
+ watchSvelteBuild: false, // true when you want to watch build svelte
+}
+
+class Main {
+ window!: BrowserWindow;
+ settings: {[key: string]: any};
+ onEvent: EventEmitter = new EventEmitter();
+ settingsDev: DeveloperOptions;
+ configDev: ConfigureDev;
+
+ constructor(settings: {[key: string]: any} | null = null, settingsDev: DeveloperOptions | null = null) {
+ this.settings = settings ? {...settings} : {...defaultSettings};
+ this.settingsDev = settingsDev ? {...settingsDev} : {...defaultSettingsDev};
+
+ this.configDev = new ConfigureDev(this.settingsDev);
+
+ app.on('ready', () => {
+
+ let loading = new BrowserWindow({show: false, frame: false})
+
+ loading.once('show', async () => {
+ this.window = await this.createWindow();
+ this.onEvent.emit("window-created");
+ loading.hide()
+ loading.close()
+ })
+ loading.loadURL(path.join(__dirname, "www", "loading.html"));
+ loading.show();
+ })
+
+ app.on('window-all-closed', this.onWindowAllClosed);
+ app.on('activate', this.onActivate);
+
+ }
+
+ async createWindow() {
+ let settings = {...this.settings}
+ app.name = appName;
+ let window = new BrowserWindow({
+ ...settings,
+ show: false, // false
+ webPreferences: {
+ nodeIntegration: false,
+ contextIsolation: true,
+ enableRemoteModule: true,
+ preload: path.join(__dirname, "preload.js")
+ }
+ });
+
+ if (this.configDev.isLocalHost()) {
+ await window.loadURL("http://localhost:3000/");
+ } else if (this.configDev.isElectronServe()) {
+ await this.configDev.loadURL(window);
+ }
+
+ window.show();
+
+ return window;
+ }
+
+ onWindowAllClosed() {
+ if (process.platform !== 'darwin') {
+ app.quit();
+ }
+ }
+
+ onActivate() {
+ if (!this.window) {
+ this.createWindow();
+ }
+ }
+
+}
+
+export default Main;
diff --git a/electron/preload.ts b/electron/preload.ts
new file mode 100644
index 0000000..07c5214
--- /dev/null
+++ b/electron/preload.ts
@@ -0,0 +1,6 @@
+import { generateContextBridge } from "./IPC/General/contextBridge"
+
+import systemInfo from "./IPC/systemInfo";
+import updaterInfo from './IPC/updaterInfo';
+
+generateContextBridge([systemInfo, updaterInfo]);
diff --git a/package.json b/package.json
index 1d479f2..d93cb79 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "Memento: how to use Svelte with Electron and TypeScript",
"author": "Samuele de Tomasi ",
"license": "MIT",
- "version": "0.0.1",
+ "version": "0.0.2",
"main": "dist/index.js",
"scripts": {
"nodemon": "nodemon",
diff --git a/svelte/src/components/InfoElectron.svelte b/svelte/src/components/InfoElectron.svelte
new file mode 100644
index 0000000..cddf3a7
--- /dev/null
+++ b/svelte/src/components/InfoElectron.svelte
@@ -0,0 +1,27 @@
+
+
+
+
We are using
+
+ Node.js {node}
+ Chromium {chrome}
+ Electron {electron}
+
+
+
+
diff --git a/svelte/src/components/Version.svelte b/svelte/src/components/Version.svelte
new file mode 100644
index 0000000..224f3ae
--- /dev/null
+++ b/svelte/src/components/Version.svelte
@@ -0,0 +1,94 @@
+
+
+
+ App version {version} .
+
+
+
+ {#if !checkingForUpdate && !updateAvailable && !downloading && !quitAndInstall}
+
Check for Update
+ {/if}
+ {#if checkingForUpdate}
+
Checking for update...
+ {/if}
+ {#if updateAvailable}
+
Updates are available. Click to download.
+ {/if}
+ {#if updateNotAvailable}
+
Update not available
+ {/if}
+ {#if downloading}
+ {downloadMessage}
+ {/if}
+ {#if quitAndInstall}
+
The updates are ready. Click to quit and install.
+ {/if}
+
+
+
diff --git a/svelte/src/routes/help/index.svelte b/svelte/src/routes/help/index.svelte
index 2e152bc..d1534f5 100644
--- a/svelte/src/routes/help/index.svelte
+++ b/svelte/src/routes/help/index.svelte
@@ -5,6 +5,7 @@
import Start from './tab/start.svelte';
import Command from './tab/command.svelte';
import Note from './tab/note.svelte';
+ import Renderer from './tab/renderer.svelte';
const tabs: {
label: string;
@@ -12,7 +13,8 @@
}[] = [
{ label: 'Get Started', page: Start },
{ label: 'Command', page: Command },
- { label: 'Notes', page: Note }
+ { label: 'Notes', page: Note },
+ { label: 'Renderer', page: Renderer }
];
@@ -26,6 +28,7 @@
Template to create desktop application with SvelteKit, Electron and TypeScript
+
diff --git a/svelte/src/routes/help/tab/command.svelte b/svelte/src/routes/help/tab/command.svelte
index 5807e0c..008617d 100644
--- a/svelte/src/routes/help/tab/command.svelte
+++ b/svelte/src/routes/help/tab/command.svelte
@@ -9,12 +9,12 @@
- You can configure settings in index.ts
. Change developerOptions
:
+ You can configure settings inindex.ts
. Change developerOptions
:
isInProduction
: true if is in production
serveSvelteDev
: true when you want to watch svelte
- buildSvelteDiv
: true when you want to build svelte
+ buildSvelteDev
: true when you want to build svelte
watchSvelteBuild
: true when you want to watch build svelte
diff --git a/svelte/src/routes/help/tab/renderer.svelte b/svelte/src/routes/help/tab/renderer.svelte
new file mode 100644
index 0000000..7173cc3
--- /dev/null
+++ b/svelte/src/routes/help/tab/renderer.svelte
@@ -0,0 +1,10 @@
+
+
+
+
Renderer
+
+
+
diff --git a/svelte/src/routes/help/tab/start.svelte b/svelte/src/routes/help/tab/start.svelte
index ed17513..205fede 100644
--- a/svelte/src/routes/help/tab/start.svelte
+++ b/svelte/src/routes/help/tab/start.svelte
@@ -7,6 +7,8 @@
>.
+ This project is still under development: the todos
section doesn't work!
+
To create a new project based on this template using degit:
@@ -24,3 +26,10 @@
>
+
+
diff --git a/svelte/static/loading.html b/svelte/static/loading.html
new file mode 100644
index 0000000..3f68feb
--- /dev/null
+++ b/svelte/static/loading.html
@@ -0,0 +1 @@
+loading
\ No newline at end of file