feat: implement desktop logic in a seperate folder

This commit is contained in:
thecodrr
2021-07-07 00:56:34 +05:00
parent 3560e0fdbf
commit 070f5e83ea
22 changed files with 2913 additions and 0 deletions

2
apps/web/desktop/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
build

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

View File

@@ -0,0 +1,29 @@
const { autoUpdater } = require("electron-updater");
const { EVENTS } = require("./events");
const { sendMessageToRenderer } = require("./ipc");
async function configureAutoUpdater() {
autoUpdater.autoDownload = false;
autoUpdater.allowDowngrade = false;
autoUpdater.allowPrerelease = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.addListener("checking-for-update", () => {
sendMessageToRenderer(EVENTS.checkingForUpdate);
});
autoUpdater.addListener("update-available", (info) => {
sendMessageToRenderer(EVENTS.updateAvailable, info);
});
autoUpdater.addListener("download-progress", (progress) => {
console.log("Downloading", progress);
sendMessageToRenderer(EVENTS.updateDownloadProgress, progress);
});
autoUpdater.addListener("update-downloaded", (info) => {
sendMessageToRenderer(EVENTS.updateDownloadCompleted, info);
});
autoUpdater.addListener("update-not-available", (info) => {
sendMessageToRenderer(EVENTS.updateNotAvailable, info);
});
await autoUpdater.checkForUpdates();
}
module.exports = { configureAutoUpdater };

View File

@@ -0,0 +1,3 @@
owner: streetwriters
repo: notesnook
provider: github

View File

@@ -0,0 +1,68 @@
const { app, BrowserWindow } = require("electron");
const path = require("path");
const os = require("os");
const { isDevelopment } = require("./utils");
const { registerProtocol, URL } = require("./protocol");
const { configureAutoUpdater } = require("./autoupdate");
require("./ipc/index.js");
try {
require("electron-reloader")(module);
} catch (_) {}
let mainWindow;
async function createWindow() {
mainWindow = new BrowserWindow({
autoHideMenuBar: true,
icon: path.join(
__dirname,
os.platform() === "win32" ? "app.ico" : "favicon-72x72.png"
),
webPreferences: {
devTools: true, // isDev,
nodeIntegration: false, //true,
enableRemoteModule: false,
contextIsolation: true,
sandbox: true,
preload: __dirname + "/preload.js",
},
});
mainWindow.on("show", () => {
console.log("SHOWIGN!");
});
if (isDevelopment())
mainWindow.webContents.openDevTools({ mode: "right", activate: true });
mainWindow.maximize();
// await loadURL(mainWindow);
mainWindow.loadURL(isDevelopment() ? process.env.ELECTRON_START_URL : URL);
mainWindow.on("closed", () => {
mainWindow = null;
});
global.win = mainWindow;
}
app.commandLine.appendSwitch("lang", "en-US");
app.on("ready", async () => {
registerProtocol();
await createWindow();
configureAutoUpdater();
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (mainWindow === null) {
createWindow();
}
});

View File

@@ -0,0 +1,7 @@
module.exports.EVENTS = {
checkingForUpdate: "checkingForUpdate",
updateAvailable: "updateAvailable",
updateDownloadProgress: "updateDownloadProgress",
updateDownloadCompleted: "updateDownloadCompleted",
updateNotAvailable: "updateNotAvailable",
};

View File

@@ -0,0 +1,13 @@
const { CancellationToken } = require("builder-util-runtime");
const { autoUpdater } = require("electron-updater");
const { sendMessageToRenderer } = require("..");
const { EVENTS } = require("../../events");
module.exports = {
type: "downloadUpdate",
action: () => {
sendMessageToRenderer(EVENTS.updateDownloadProgress, { progress: 0 });
autoUpdater.cancellationToken = new CancellationToken();
autoUpdater.downloadUpdate(autoUpdater.cancellationToken);
},
};

View File

@@ -0,0 +1,10 @@
const actions = {};
module.exports.getAction = function getAction(actionName) {
try {
if (!actions[actionName]) actions[actionName] = require(`./${actionName}`);
} catch (e) {
console.error(e);
}
return actions[actionName];
};

View File

@@ -0,0 +1,8 @@
const { autoUpdater } = require("electron-updater");
module.exports = {
type: "installUpdate",
action: () => {
autoUpdater.quitAndInstall();
},
};

View File

@@ -0,0 +1,8 @@
const { shell } = require("electron");
module.exports = {
type: "openLink",
action: (args) => {
const { link } = args;
return shell.openExternal(link);
},
};

View File

@@ -0,0 +1,23 @@
const { app } = require("electron");
const fs = require("fs");
const path = require("path");
module.exports = {
type: "saveFile",
action: (args) => {
const { data, filePath } = args;
if (!data || !filePath) return;
const resolvedPath = path.join(
...filePath.split("/").map((segment) => {
let resolved = segment;
try {
resolved = app.getPath(resolved);
} finally {
return resolved;
}
})
);
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
fs.writeFileSync(resolvedPath, data);
},
};

View File

@@ -0,0 +1,16 @@
const { ipcMain, BrowserWindow } = require("electron");
const { getAction } = require("./actions");
ipcMain.on("fromRenderer", async (event, args) => {
const { type } = args;
const action = getAction(type);
if (!action) return;
await action.action(args);
});
module.exports.sendMessageToRenderer = function (type, payload = {}) {
global.win.webContents.send("fromMain", {
type,
...payload,
});
};

View File

@@ -0,0 +1,130 @@
{
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "1.4.1",
"private": true,
"main": "./electron.js",
"homepage": "https://notesnook.com/",
"repository": "https://github.com/streetwriters/notesnook",
"dependencies": {
"electron-serve": "^1.1.0",
"electron-updater": "^4.3.8",
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@types/node-fetch": "^2.5.10",
"copyfiles": "^2.4.1",
"electron": "^12.0.0",
"electron-builder": "^22.11.7",
"electron-builder-notarize": "^1.2.0",
"electron-reloader": "^1.2.1",
"esbuild": "^0.12.15"
},
"scripts": {
"bundle": "esbuild ./electron.js ./preload.js --minify --external:electron --bundle --outdir=./build --platform=node",
"release": "./scripts/publish.sh"
},
"author": {
"name": "Streetwriters (Private) Ltd.",
"email": "support@streetwriters.co",
"url": "https://streetwriters.co"
},
"build": {
"appId": "com.streetwriters.notesnook",
"productName": "Notesnook",
"copyright": "Copyright © 2021 Streetwriters (Private) Ltd.",
"artifactName": "notesnook.${ext}",
"files": [
"!*.chunk.js.map",
"!*.chunk.js.LICENSE.txt",
"build/",
"!node_modules",
"node_modules/node-fetch",
"node_modules/electron-updater",
"node_modules/lazy-val",
"node_modules/builder-util-runtime",
"node_modules/fs-extra",
"node_modules/js-yaml",
"node_modules/lodash.isequal",
"node_modules/semver",
"node_modules/debug",
"node_modules/ms",
"node_modules/lru-cache",
"node_modules/yallist",
"node_modules/universalify",
"node_modules/graceful-fs",
"node_modules/at-least-node",
"node_modules/jsonfile",
"node_modules/sax"
],
"afterSign": "electron-builder-notarize",
"afterPack": "./scripts/removeLocales.js",
"mac": {
"target": [
"dmg",
"zip"
],
"electronLanguages": [
"en-US"
],
"category": "public.app-category.productivity",
"darkModeSupport": true,
"type": "distribution",
"hardenedRuntime": true,
"entitlements": "assets/entitlements.mac.plist",
"entitlementsInherit": "assets/entitlements.mac.plist",
"gatekeeperAssess": false,
"icon": "assets/icons/app.icns"
},
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
],
"icon": "assets/icons/app.icns",
"title": "Install Notesnook"
},
"win": {
"target": [
"nsis"
],
"icon": "assets/icons/app.ico"
},
"nsis": {
"oneClick": true,
"createDesktopShortcut": "always"
},
"linux": {
"target": [
"AppImage",
"deb",
"rpm"
],
"category": "Office",
"icon": "assets/icons/app.icns",
"description": "Your private note taking space",
"executableName": "Notesnook"
},
"extraResources": [
"./assets/**"
],
"directories": {
"buildResources": "assets",
"output": "./dist/"
},
"publish": {
"provider": "github",
"repo": "notesnook",
"owner": "streetwriters"
}
}
}

View File

@@ -0,0 +1,3 @@
module.exports.PATHS = {
backupsDirectory: "documents/Notesnook/backups",
};

View File

@@ -0,0 +1,22 @@
const { contextBridge, ipcRenderer } = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld("api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["fromRenderer"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, args) => {
func(args);
});
}
},
});

View File

@@ -0,0 +1,65 @@
const { protocol } = require("electron");
const { isDevelopment, getPath } = require("./utils");
const fs = require("fs");
const path = require("path");
const fetch = require("node-fetch").default;
const FILE_NOT_FOUND = -6;
const BASE_PATH = isDevelopment() ? "../public" : "";
const HOSTNAME = `app.notesnook.com`;
const PROTOCOL = "https";
const extensionToMimeType = {
html: "text/html",
json: "application/json",
js: "application/javascript",
css: "text/css",
svg: "image/svg+xml",
png: "image/png",
};
function registerProtocol() {
protocol.interceptStreamProtocol(
PROTOCOL,
async (request, callback) => {
const url = new URL(request.url);
if (url.hostname === HOSTNAME) {
const absoluteFilePath = path.normalize(
`${__dirname}${
url.pathname === "/"
? `${BASE_PATH}/index.html`
: `${BASE_PATH}/${url.pathname}`
}`
);
const filePath = getPath(absoluteFilePath);
if (!filePath) {
callback({ error: FILE_NOT_FOUND });
return;
}
const fileExtension = path.extname(filePath).replace(".", "");
const data = fs.createReadStream(filePath);
callback({
data,
mimeType: extensionToMimeType[fileExtension],
});
} else {
const response = await fetch(request.url, {
...request,
body: !!request.uploadData ? request.uploadData[0].bytes : null,
headers: { ...request.headers, origin: `${PROTOCOL}://${HOSTNAME}/` },
});
callback({
data: response.body,
headers: response.headers,
mimeType: response.headers.get("Content-Type"),
statusCode: response.status,
});
}
},
(err) => {
if (err) console.error("Failed to register protocol");
}
);
}
module.exports = { registerProtocol, URL: `${PROTOCOL}://${HOSTNAME}/` };

View File

@@ -0,0 +1,30 @@
# Go to notes-web directory
cd ../
# Generate the web app build
if ! yarn build:desktop; then
echo "Failed to build web app."
exit
fi
# Change back to desktop directory
cd desktop
# Copy build files to desktop folder
# NOTE: we have to do this because electron-builder cannot work in parent directories.
if ! yarn copyfiles -a ../build ./build; then
echo "Could not copy build files."
exit
fi
# Generate electron specific bundle
if ! yarn bundle; then
echo "Could not generate electron bundle"
exit
fi
# Build and publish
if ! electron-builder -c.extraMetadata.main=./build/electron.js; then
echo "Failed to build and publish electron builds"
exit
fi

View File

@@ -0,0 +1,17 @@
//https://www.electron.build/configuration/configuration#afterpack
exports.default = async function (context) {
//console.log(context)
var fs = require("fs");
var localeDir = context.appOutDir + "/locales/";
fs.readdir(localeDir, function (err, files) {
//files is array of filenames (basename form)
if (!(files && files.length)) return;
for (var i = 0, len = files.length; i < len; i++) {
var match = files[i].match(/en-US\.pak/);
if (match === null) {
fs.unlinkSync(localeDir + files[i]);
}
}
});
};

29
apps/web/desktop/utils.js Normal file
View File

@@ -0,0 +1,29 @@
const { app } = require("electron");
const path = require("path");
const fs = require("fs");
function isDevelopment() {
if (typeof electron === "string") {
throw new TypeError("Not running in an Electron environment!");
}
const isEnvSet = "ELECTRON_IS_DEV" in process.env;
const getFromEnv = Number.parseInt(process.env.ELECTRON_IS_DEV, 10) === 1;
return isEnvSet ? getFromEnv : !app.isPackaged;
}
function getPath(filePath) {
try {
const result = fs.statSync(filePath);
if (result.isFile()) {
return filePath;
}
if (result.isDirectory()) {
return getPath(path.join(filePath, "index.html"));
}
} catch (_) {}
}
module.exports = { getPath, isDevelopment };

2420
apps/web/desktop/yarn.lock Normal file

File diff suppressed because it is too large Load Diff