From 8ae4ad61269eb7aaeeb51a4e14c08bb88b93f237 Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Fri, 11 Nov 2022 16:57:55 +0500 Subject: [PATCH] web: implement native compression --- apps/web/desktop/ipc/calls/gzip.js | 33 +++++++++++++++++ apps/web/desktop/ipc/calls/index.js | 5 ++- apps/web/desktop/ipc/index.js | 3 +- apps/web/desktop/preload.js | 13 +++++++ apps/web/package-lock.json | 2 +- apps/web/package.json | 2 +- apps/web/src/global.d.ts | 40 ++++++++++++++++++++ apps/web/src/interfaces/storage.ts | 1 + apps/web/src/utils/compressor.ts | 49 +++++++++++++++++++++++++ apps/web/src/utils/compressor.worker.ts | 38 +++++++++++++++++++ apps/web/tsconfig.json | 2 +- 11 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 apps/web/desktop/ipc/calls/gzip.js create mode 100644 apps/web/src/global.d.ts create mode 100644 apps/web/src/utils/compressor.ts create mode 100644 apps/web/src/utils/compressor.worker.ts diff --git a/apps/web/desktop/ipc/calls/gzip.js b/apps/web/desktop/ipc/calls/gzip.js new file mode 100644 index 000000000..f9c240b10 --- /dev/null +++ b/apps/web/desktop/ipc/calls/gzip.js @@ -0,0 +1,33 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2022 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import zlib from "node:zlib"; +import utils from "node:util"; + +const gzipAsync = utils.promisify(zlib.gzip); +const gunzipAsync = utils.promisify(zlib.gunzip); + +export async function gzip(args) { + const { data, level } = args; + return (await gzipAsync(data, { level })).toString("base64"); +} + +export async function gunzip(args) { + const { data } = args; + return (await gunzipAsync(Buffer.from(data, "base64"))).toString("utf-8"); +} diff --git a/apps/web/desktop/ipc/calls/index.js b/apps/web/desktop/ipc/calls/index.js index 4dc408377..4af981a88 100644 --- a/apps/web/desktop/ipc/calls/index.js +++ b/apps/web/desktop/ipc/calls/index.js @@ -19,10 +19,13 @@ along with this program. If not, see . import getZoomFactor from "./getZoomFactor"; import selectDirectory from "./selectDirectory"; +import { gunzip, gzip } from "./gzip"; const calls = { getZoomFactor, - selectDirectory + selectDirectory, + gunzip, + gzip }; export const getCall = function getAction(callName) { diff --git a/apps/web/desktop/ipc/index.js b/apps/web/desktop/ipc/index.js index 4f96ccf5f..309f6805a 100644 --- a/apps/web/desktop/ipc/index.js +++ b/apps/web/desktop/ipc/index.js @@ -32,9 +32,8 @@ ipcMain.on("fromRenderer", async (event, args) => { }); ipcMain.handle("fromRenderer", async (event, args) => { - logger.info("Call requested by renderer", args); - const { type } = args; + logger.info("Call requested by renderer", type); const call = getCall(type); if (!call) return; diff --git a/apps/web/desktop/preload.js b/apps/web/desktop/preload.js index 78cc0ccb5..60d75e30f 100644 --- a/apps/web/desktop/preload.js +++ b/apps/web/desktop/preload.js @@ -62,5 +62,18 @@ contextBridge.exposeInMainWorld("native", { buttonLabel, defaultPath }); + }, + gzip: ({ data, level }) => { + return ipcRenderer.invoke("fromRenderer", { + type: "gzip", + data, + level + }); + }, + gunzip: ({ data }) => { + return ipcRenderer.invoke("fromRenderer", { + type: "gunzip", + data + }); } }); diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index cca4f4db4..db451da39 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -28,7 +28,7 @@ "cronosjs": "^1.7.1", "dayjs": "^1.10.4", "event-source-polyfill": "^1.0.25", - "fflate": "^0.7.2", + "fflate": "^0.7.4", "file-saver": "^2.0.5", "framer-motion": "^4.1.17", "hash-wasm": "^4.9.0", diff --git a/apps/web/package.json b/apps/web/package.json index 7dac8048f..d1df9d61b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -34,7 +34,7 @@ "cronosjs": "^1.7.1", "dayjs": "^1.10.4", "event-source-polyfill": "^1.0.25", - "fflate": "^0.7.2", + "fflate": "^0.7.4", "file-saver": "^2.0.5", "framer-motion": "^4.1.17", "hash-wasm": "^4.9.0", diff --git a/apps/web/src/global.d.ts b/apps/web/src/global.d.ts new file mode 100644 index 000000000..d9833dac9 --- /dev/null +++ b/apps/web/src/global.d.ts @@ -0,0 +1,40 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2022 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +declare interface Window { + native: { + static gzip({ + data, + level + }: { + data: string; + level: number; + }): Promise; + static gunzip({ data }: { data: string }): Promise; + static selectDirectory({ + title, + buttonLabel, + defaultPath + }: { + title?: string; + buttonLabel?: string; + defaultPath?: string; + }): Promise; + }; +} diff --git a/apps/web/src/interfaces/storage.ts b/apps/web/src/interfaces/storage.ts index a64a27c34..c4ef98236 100644 --- a/apps/web/src/interfaces/storage.ts +++ b/apps/web/src/interfaces/storage.ts @@ -33,6 +33,7 @@ const APP_SALT = "oVzKtazBo7d8sb7TBvY9jw"; export class NNStorage { database: LocalForage; + constructor(name: string, persistence: DatabasePersistence = "db") { const drivers = persistence === "memory" diff --git a/apps/web/src/utils/compressor.ts b/apps/web/src/utils/compressor.ts new file mode 100644 index 000000000..992d73f7f --- /dev/null +++ b/apps/web/src/utils/compressor.ts @@ -0,0 +1,49 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2022 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import Worker from "worker-loader?filename=static/workers/compressor.worker.[hash].js!./compressor.worker"; +import type { Compressor as CompressorWorker } from "./compressor.worker"; +import { wrap, Remote } from "comlink"; +import { isDesktop } from "./platform"; + +export class Compressor { + private worker!: globalThis.Worker; + private compressor!: Remote; + + constructor() { + if (!isDesktop()) { + this.worker = new Worker(); + this.compressor = wrap(this.worker); + } + } + + async compress(data: string) { + if (isDesktop()) return await window.native.gzip({ data, level: 6 }); + + return await this.compressor.gzip({ data, level: 6 }); + } + + async decompress(data: string) { + if (isDesktop()) return await window.native.gunzip({ data }); + + return await this.compressor.gunzip({ data }); + } +} diff --git a/apps/web/src/utils/compressor.worker.ts b/apps/web/src/utils/compressor.worker.ts new file mode 100644 index 000000000..3ee5298fd --- /dev/null +++ b/apps/web/src/utils/compressor.worker.ts @@ -0,0 +1,38 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2022 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import { expose } from "comlink"; +import { gzipSync, gunzipSync } from "fflate"; +import { fromBase64, toBase64 } from "@aws-sdk/util-base64-browser"; + +const module = { + gzip: ({ data, level }: { data: string; level: number }) => { + return toBase64( + gzipSync(new TextEncoder().encode(data), { + level: level as any + }) + ); + }, + gunzip: ({ data }: { data: string }) => { + return new TextDecoder().decode(gunzipSync(fromBase64(data))); + } +}; + +expose(module); +export type Compressor = typeof module; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 01ddaf231..7c3fd9dca 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -7,5 +7,5 @@ "maxNodeModuleJsDepth": 5, "noEmit": true }, - "include": ["src"] + "include": ["src", "global.d.ts"] }