web: fix service worker to allow Notesnook to behave as a PWA again (#2905)

* web: make service worker work again

* web: only enable sourcemap during dev
This commit is contained in:
Abdullah Atta
2023-07-12 06:51:14 +05:00
committed by GitHub
parent df5cbacc8a
commit 5854a3eef5
37 changed files with 1343 additions and 355 deletions

View File

@@ -1,47 +0,0 @@
const { execSync } = require("child_process");
const { cpus, networkInterfaces } = require("os");
const { version } = require("./package.json");
const ip = require("ip");
const NUM_CPUS = cpus().length;
const IS_CI = process.env.CI;
const gitHash = execSync("git rev-parse --short HEAD").toString().trim();
const APP_VERSION = version.replaceAll(".", "");
console.log("App version:", APP_VERSION);
console.log("Ip address:", ip.address());
module.exports = {
beta: {
REACT_APP_BETA: true
},
test: {
TEST_ALL: true
},
all: {
UV_THREADPOOL_SIZE: IS_CI ? NUM_CPUS : 2,
GENERATE_SOURCEMAP: process.env.NODE_ENV === "development",
// INLINE_RUNTIME_CHUNK: false,
// DISABLE_ESLINT_PLUGIN: true,
REACT_APP_GIT_HASH: gitHash,
REACT_APP_VERSION: APP_VERSION
},
dev: {
REACT_APP_LOCALHOST: ip.address()
},
web: {
REACT_APP_PLATFORM: "web"
},
debug: {
PWDEBUG: 1,
DEBUG: "pw:api"
},
silent: {
REACT_APP_TEST: true
// DISABLE_ESLINT_PLUGIN: "true"
// FAST_REFRESH: "false",
// BROWSER: "none"
},
desktop: {
// BROWSER: "none",
REACT_APP_PLATFORM: "desktop"
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -82,8 +82,8 @@
"autoprefixer": "^10.4.14",
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"cross-env": "^7.0.3",
"dotenv": "^10.0.0",
"env-cmd": "^10.1.0",
"file-loader": "^6.2.0",
"find-process": "^1.4.4",
"happy-dom": "^8.9.0",
@@ -111,13 +111,13 @@
"@types/react-dom@>17": "17.0.2"
},
"scripts": {
"start": "env-cmd -e all,dev,web vite",
"start:desktop": "env-cmd -e all,desktop vite",
"start": "cross-env PLATFORM=web vite",
"start:desktop": "cross-env PLATFORM=desktop vite",
"start:test": "serve -s build/ -p 3000",
"build": "env-cmd -e all,web vite build",
"build:test": "env-cmd -e all,dev,web,silent vite build",
"build:beta": "env-cmd -e all,web,beta vite build",
"build:desktop": "env-cmd -e all,desktop vite build",
"build": "cross-env PLATFORM=web vite build",
"build:test": "cross-env PLATFORM=web TEST=true vite build",
"build:beta": "cross-env PLATFORM=web BETA=true vite build",
"build:desktop": "cross-env PLATFORM=desktop vite build",
"test": "playwright test -u"
},
"browserslist": {

View File

@@ -39,7 +39,7 @@ import {
showOnboardingDialog
} from "./common/dialog-controller";
import useSystemTheme from "./hooks/use-system-theme";
import { isTesting } from "./utils/platform";
import { updateStatus, removeStatus, getStatus } from "./hooks/use-status";
import { showToast } from "./utils/toast";
import { interruptedOnboarding } from "./dialogs/onboarding-dialog";
@@ -199,7 +199,7 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
}, []);
useEffect(() => {
if (!dialogAnnouncements.length || isTesting()) return;
if (!dialogAnnouncements.length || IS_TESTING) return;
(async () => {
await showAnnouncementDialog(dialogAnnouncements[0]);
})();

View File

@@ -111,11 +111,7 @@ function SuspenseLoader<TComponent extends React.JSXElementConstructor<any>>({
props: any
) => React.ReactComponentElement<any, any>;
return (
<Suspense
fallback={
import.meta.env.REACT_APP_PLATFORM === "desktop" ? null : fallback
}
>
<Suspense fallback={IS_DESKTOP_APP ? null : fallback}>
<Component {...props} />
</Suspense>
);

View File

@@ -47,7 +47,7 @@ async function initializeDatabase(persistence: DatabasePersistence) {
FS,
new Compressor()
);
// if (isTesting()) {
// if (IS_TESTING) {
// } else {
// db.host({

View File

@@ -27,7 +27,7 @@ import Config from "../utils/config";
import { hashNavigate, getCurrentHash } from "../navigation";
import { db } from "./db";
import { sanitizeFilename } from "@notesnook/common";
import { isDesktop, isTesting } from "../utils/platform";
import { store as userstore } from "../stores/user-store";
import FileSaver from "file-saver";
import { showToast } from "../utils/toast";
@@ -66,7 +66,7 @@ export const CREATE_BUTTON_MAP = {
export async function introduceFeatures() {
const hash = getCurrentHash().replace("#", "");
if (!!hash || isTesting()) return;
if (!!hash || IS_TESTING) return;
const features = [];
for (let feature of features) {
if (!Config.get(`feature:${feature}`)) {
@@ -95,7 +95,7 @@ export async function createBackup() {
const filename = sanitizeFilename(`notesnook-backup-${getFormattedDate()}`);
const ext = "nnbackup";
if (isDesktop()) {
if (IS_DESKTOP_APP) {
const directory = Config.get(
"backupStorageLocation",
PATHS.backupsDirectory
@@ -189,7 +189,7 @@ export function totalSubscriptionConsumed(user) {
}
export async function showUpgradeReminderDialogs() {
if (isTesting()) return;
if (IS_TESTING) return;
const user = userstore.get().user;
if (!user || !user.subscription || user.subscription?.expiry === 0) return;

View File

@@ -25,7 +25,7 @@ import { Backup, User, Email, Warn, Icon } from "../components/icons";
import dayjs from "dayjs";
import { showBuyDialog, showRecoveryKeyDialog } from "./dialog-controller";
import { hardNavigate, hashNavigate } from "../navigation";
import { isDesktop, isTesting } from "../utils/platform";
import { isUserPremium } from "../hooks/use-is-user-premium";
import { showToast } from "../utils/toast";
import { TaskScheduler } from "../utils/task-scheduler";
@@ -176,9 +176,9 @@ function isIgnored(key: keyof typeof NoticesData) {
var openedToast: { hide: () => void } | null = null;
async function saveBackup() {
if (isDesktop()) {
if (IS_DESKTOP_APP) {
await createBackup();
} else if (isUserPremium() && !isTesting()) {
} else if (isUserPremium() && !IS_TESTING) {
if (openedToast !== null) return;
openedToast = showToast(
"success",

View File

@@ -85,7 +85,7 @@ type TipTapProps = {
fontFamily: string;
};
const SAVE_INTERVAL = import.meta.env.REACT_APP_TEST ? 100 : 300;
const SAVE_INTERVAL = IS_TESTING ? 100 : 300;
function save(
sessionId: number,

View File

@@ -48,7 +48,6 @@ import { useStore as useUserStore } from "../../stores/user-store";
import { useStore as useThemeStore } from "../../stores/theme-store";
import useLocation from "../../hooks/use-location";
import { FlexScrollContainer } from "../scroll-container";
import { isDesktop } from "../../utils/platform";
type Route = {
title: string;
@@ -192,7 +191,7 @@ function NavigationMenu(props: NavigationMenuProps) {
))}
{colors.map((color, index) => (
<NavigationItem
animate={!isDesktop()}
animate={!IS_DESKTOP_APP}
index={index}
isTablet={isTablet}
key={color.id}
@@ -221,7 +220,7 @@ function NavigationMenu(props: NavigationMenuProps) {
/>
{shortcuts.map((item, index) => (
<NavigationItem
animate={!isDesktop()}
animate={!IS_DESKTOP_APP}
index={colors.length - 1 + index}
isTablet={isTablet}
key={item.id}

View File

@@ -31,18 +31,15 @@ import {
PricingInfo
} from "./types";
// const isDev = false; // import.meta.env.NODE_ENV === "development";
// const isDev = false; // import.meta.env.DEV;
// const VENDOR_ID = isDev ? 1506 : 128190;
const PADDLE_ORIGIN =
import.meta.env.NODE_ENV === "development"
const PADDLE_ORIGIN = import.meta.env.DEV
? "https://sandbox-buy.paddle.com"
: "https://buy.paddle.com";
const CHECKOUT_CREATE_ORIGIN =
import.meta.env.NODE_ENV === "development"
const CHECKOUT_CREATE_ORIGIN = import.meta.env.DEV
? "https://sandbox-create-checkout.paddle.com"
: "https://create-checkout.paddle.com";
const CHECKOUT_SERVICE_ORIGIN =
import.meta.env.NODE_ENV === "development"
const CHECKOUT_SERVICE_ORIGIN = import.meta.env.DEV
? "https://sandbox-checkout-service.paddle.com"
: "https://checkout-service.paddle.com";

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useState } from "react";
import { isTesting } from "../../utils/platform";
import { Period, Plan } from "./types";
type PlanMetadata = {
@@ -32,7 +32,7 @@ export const DEFAULT_PLANS: Plan[] = [
country: "PK",
currency: "USD",
discount: 0,
id: import.meta.env.NODE_ENV === "development" ? "9822" : "648884",
id: import.meta.env.DEV ? "9822" : "648884",
price: { gross: 4.49, net: 0, tax: 0 }
},
{
@@ -40,7 +40,7 @@ export const DEFAULT_PLANS: Plan[] = [
country: "PK",
currency: "USD",
discount: 0,
id: import.meta.env.NODE_ENV === "development" ? "50305" : "658759",
id: import.meta.env.DEV ? "50305" : "658759",
price: { gross: 49.99, net: 0, tax: 0 }
}
];
@@ -52,8 +52,7 @@ export const PLAN_METADATA: Record<Period, PlanMetadata> = {
let CACHED_PLANS: Plan[];
export async function getPlans(): Promise<Plan[] | null> {
if (isTesting() || import.meta.env.NODE_ENV === "development")
return DEFAULT_PLANS;
if (IS_TESTING || import.meta.env.DEV) return DEFAULT_PLANS;
if (CACHED_PLANS) return CACHED_PLANS;
const url = `https://notesnook.com/api/v1/prices/products/web`;

View File

@@ -22,7 +22,7 @@ import Dialog from "../components/dialog";
import { getHomeRoute, hardNavigate } from "../navigation";
import { appVersion } from "../utils/version";
import Config from "../utils/config";
import { isTesting } from "../utils/platform";
import { useEffect } from "react";
import { ArrowRight, Checkmark, Icon, Warn } from "../components/icons";
@@ -106,7 +106,7 @@ const features: Record<FeatureKeys, Feature> = {
appVersion.isBeta || Config.has((k) => k.endsWith(":highlights"));
if (!hasShownAny) Config.set(key, true);
return hasShownAny && !isTesting() && !hasShownBefore;
return hasShownAny && !IS_TESTING && !hasShownBefore;
}
}
};

View File

@@ -24,7 +24,7 @@ import { createBackup } from "../common";
import { db } from "../common/db";
import { Perform } from "../common/dialog-controller";
import { TaskManager } from "../common/task-manager";
import { isDesktop } from "../utils/platform";
import Dialog from "../components/dialog";
type MigrationProgressEvent = {
@@ -74,7 +74,7 @@ export default function MigrationDialog(props: MigrationDialogProps) {
}, [props]);
useEffect(() => {
if (isDesktop()) {
if (IS_DESKTOP_APP) {
(async () => {
await startMigration();
})();
@@ -115,7 +115,7 @@ export default function MigrationDialog(props: MigrationDialogProps) {
);
}
if (isDesktop() || isProcessing) return null;
if (IS_DESKTOP_APP || isProcessing) return null;
return (
<Dialog

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { SettingsGroup } from "./types";
import { useStore as useSettingStore } from "../../stores/setting-store";
import { useStore as useThemeStore } from "../../stores/theme-store";
import { isDesktop } from "../../utils/platform";
import { AccentColors } from "./components/accent-colors";
export const AppearanceSettings: SettingsGroup[] = [
@@ -72,7 +72,7 @@ export const AppearanceSettings: SettingsGroup[] = [
key: "zoom-factor",
title: "Zoom factor",
description: "Zoom in or out the app content.",
isHidden: () => !isDesktop(),
isHidden: () => !IS_DESKTOP_APP,
onStateChange: (listener) =>
useThemeStore.subscribe(
(s) => [s.theme, s.followSystemTheme],

View File

@@ -25,7 +25,7 @@ import { isUserPremium } from "../../hooks/use-is-user-premium";
import { createBackup, importBackup, verifyAccount } from "../../common";
import { db } from "../../common/db";
import { exportNotes } from "../../common/export";
import { isDesktop, isTesting } from "../../utils/platform";
import { desktop } from "../../common/desktop-bridge";
import { PATHS } from "@notesnook/desktop";
@@ -56,7 +56,7 @@ export const BackupExportSettings: SettingsGroup[] = [
key: "restore-backup",
title: "Restore backup",
description: "Restore a backup file from your disk drive.",
isHidden: () => !useUserStore.getState().isLoggedIn && !isTesting(),
isHidden: () => !useUserStore.getState().isLoggedIn && !IS_TESTING,
components: [
{
type: "button",
@@ -71,8 +71,8 @@ export const BackupExportSettings: SettingsGroup[] = [
},
{
key: "auto-backup",
title: isDesktop() ? "Automatic backups" : "Backup reminders",
description: isDesktop()
title: IS_DESKTOP_APP ? "Automatic backups" : "Backup reminders",
description: IS_DESKTOP_APP
? "Backup all your data automatically at a set interval."
: "You will be shown regular reminders to backup your data.",
isHidden: () => !isUserPremium(),
@@ -119,7 +119,7 @@ export const BackupExportSettings: SettingsGroup[] = [
key: "backup-directory",
title: "Backups directory",
description: "Select directory to store all backup files.",
isHidden: () => !isDesktop(),
isHidden: () => !IS_DESKTOP_APP,
components: [
{
type: "button",

View File

@@ -27,7 +27,7 @@ import { useStore as useSettingStore } from "../../stores/setting-store";
import { getFonts } from "@notesnook/editor";
import { useSpellChecker } from "../../hooks/use-spell-checker";
import { SpellCheckerLanguages } from "./components/spell-checker-languages";
import { isDesktop } from "../../utils/platform";
import { CustomizeToolbar } from "./components/customize-toolbar";
export const EditorSettings: SettingsGroup[] = [
@@ -118,7 +118,7 @@ symbols (e.g. 202305261253)`,
key: "spell-check",
section: "editor",
header: "Spell check",
isHidden: () => !isDesktop(),
isHidden: () => !IS_DESKTOP_APP,
onRender: () => {
useSpellChecker.getState().refresh();
},

View File

@@ -51,7 +51,7 @@ import { SyncSettings } from "./sync-settings";
import { BehaviourSettings } from "./behaviour-settings";
import { DesktopIntegrationSettings } from "./desktop-integration-settings";
import { NotificationsSettings } from "./notifications-settings";
import { isDesktop } from "../../utils/platform";
import { BackupExportSettings } from "./backup-export-settings";
import { ImporterSettings } from "./importer-settings";
import { VaultSettings } from "./vault-settings";
@@ -105,7 +105,7 @@ const sectionGroups: SectionGroup[] = [
key: "desktop",
title: "Desktop integration",
icon: Desktop,
isHidden: () => !isDesktop()
isHidden: () => !IS_DESKTOP_APP
},
{ key: "notifications", title: "Notifications", icon: Notification }
]

View File

@@ -81,7 +81,7 @@ What data is collected & when?`,
"Prevent Notesnook app from being captured by any screen capturing software like TeamViewer & AnyDesk.",
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.privacyMode, listener),
isHidden: () => !isDesktop() || getPlatform() === "linux",
isHidden: () => !IS_DESKTOP_APP || getPlatform() === "linux",
components: [
{
type: "toggle",

View File

@@ -16,11 +16,20 @@ 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 <http://www.gnu.org/licenses/>.
*/
/* eslint-disable no-var */
import "vite/client";
import "vite-plugin-svgr/client";
declare global {
var PUBLIC_URL: string;
var APP_VERSION: string;
var GIT_HASH: string;
var IS_DESKTOP_APP: boolean;
var IS_TESTING: boolean;
var PLATFORM: "web" | "desktop";
var IS_BETA: boolean;
interface Window {
os?: () => NodeJS.Platform | "mas";
NativeNNCrypto?: new () => import("@notesnook/crypto").NNCrypto;

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useState } from "react";
import { isDesktop } from "../utils/platform";
import { checkForUpdate } from "../utils/updater";
import { AppEventManager, AppEvents } from "../common/app-events";
@@ -57,7 +57,7 @@ export function useAutoUpdater() {
}
function updateNotAvailable() {
if (isDesktop()) changeStatus({ type: "updated" });
if (IS_DESKTOP_APP) changeStatus({ type: "updated" });
else changeStatus();
}

View File

@@ -23,9 +23,9 @@ import "allotment/dist/style.css";
import "../utils/analytics";
import "../app.css";
if (import.meta.env.NODE_ENV === "production") {
console.log = () => {};
}
// if (import.meta.env.PROD) {
// console.log = () => {};
// }
const memory = {
isDatabaseLoaded: false

View File

@@ -22,7 +22,6 @@ import {
useStore as useUserStore,
store as userstore
} from "../stores/user-store";
import { isTesting } from "../utils/platform";
export function useIsUserPremium() {
const user = useUserStore((store) => store.user);
@@ -30,7 +29,7 @@ export function useIsUserPremium() {
}
export function isUserPremium(user?: User) {
if (isTesting()) return true;
if (IS_TESTING) return true;
if (!user) user = userstore.get().user;
const subStatus = user?.subscription?.type;

View File

@@ -23,7 +23,7 @@ import { AppEventManager, AppEvents } from "./common/app-events";
import { render } from "react-dom";
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
import Config from "./utils/config";
import { isTesting } from "./utils/platform";
import { initalizeLogger, logger } from "./utils/logger";
import { AuthProps } from "./views/auth";
import { loadDatabase } from "./hooks/use-database";
@@ -125,7 +125,7 @@ function fallbackRoute(): RouteWithPath {
}
function redirectToRegistration(path: Routes): RouteWithPath<AuthProps> | null {
if (!isTesting() && !shouldSkipInitiation() && !routes[path]) {
if (!IS_TESTING && !shouldSkipInitiation() && !routes[path]) {
window.history.replaceState({}, "", makeURL("/signup", getCurrentHash()));
return { route: routes["/signup"], path: "/signup" };
}
@@ -156,8 +156,7 @@ async function renderApp() {
} = getRoute();
if (serviceWorkerWhitelist.includes(path)) await initializeServiceWorker();
if (import.meta.env.REACT_APP_PLATFORM === "desktop")
await loadDatabase("db");
if (IS_DESKTOP_APP) await loadDatabase("db");
logger.measure("app render");
@@ -174,7 +173,7 @@ async function renderApp() {
}
async function initializeServiceWorker() {
if (import.meta.env.REACT_APP_PLATFORM !== "desktop") {
if (!IS_DESKTOP_APP) {
logger.info("Initializing service worker...");
const serviceWorker = await import("./service-worker-registration");

View File

@@ -19,11 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { INNCrypto } from "@notesnook/crypto/dist/src/interfaces";
import CryptoWorker from "@notesnook/crypto-worker/dist/src/worker.js?worker";
import { isDesktop } from "../utils/platform";
async function loadNNCrypto() {
const hasWorker = "Worker" in window || "Worker" in global;
if (isDesktop() && window.NativeNNCrypto) {
if (IS_DESKTOP_APP && window.NativeNNCrypto) {
return window.NativeNNCrypto;
} else if (hasWorker) {
const { NNCryptoWorker } = await import("@notesnook/crypto-worker");

View File

@@ -29,6 +29,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
type ServiceWorkerRegistrationConfig = {
onUpdate?: (registration: ServiceWorkerRegistration) => void;
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onError?: (error: Error) => void;
};
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
@@ -39,13 +45,10 @@ const isLocalhost = Boolean(
)
);
export function register(config) {
if (
import.meta.env.NODE_ENV === "production" &&
"serviceWorker" in navigator
) {
export function register(config: ServiceWorkerRegistrationConfig) {
if (import.meta.env.PROD && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(import.meta.env.PUBLIC_URL, window.location.href);
const publicUrl = new URL(PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
@@ -54,7 +57,7 @@ export function register(config) {
}
window.addEventListener("load", () => {
const swUrl = `${import.meta.env.PUBLIC_URL}/service-worker.js`;
const swUrl = `${PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
@@ -76,7 +79,10 @@ export function register(config) {
}
}
function registerValidSW(swUrl, config) {
function registerValidSW(
swUrl: string,
config: ServiceWorkerRegistrationConfig
) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
@@ -120,7 +126,10 @@ function registerValidSW(swUrl, config) {
});
}
function checkValidServiceWorker(swUrl, config) {
function checkValidServiceWorker(
swUrl: string,
config: ServiceWorkerRegistrationConfig
) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { "Service-Worker": "script" }

View File

@@ -16,14 +16,8 @@ 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 <http://www.gnu.org/licenses/>.
*/
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
/* eslint-disable no-var */
/// <reference lib="webworker" />
import { clientsClaim } from "workbox-core";
import { ExpirationPlugin } from "workbox-expiration";
@@ -31,20 +25,17 @@ import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { StaleWhileRevalidate } from "workbox-strategies";
declare var self: ServiceWorkerGlobalScope & typeof globalThis;
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
/**
* @type {import("workbox-precaching/_types").PrecacheEntry[]}
*/
var precacheRoutes = self.__WB_MANIFEST;
const precacheRoutes = self.__WB_MANIFEST;
const filters = [/KaTeX/i, /hack/i, /code-lang-/i];
precacheAndRoute(
precacheRoutes.filter((route) => {
return filters.every((filter) => !filter.test(route.url));
return filters.every(
(filter) => !filter.test(typeof route === "string" ? route : route.url)
);
})
);
@@ -70,7 +61,7 @@ registerRoute(
return true;
},
createHandlerBoundToURL(import.meta.env.PUBLIC_URL + "/index.html")
createHandlerBoundToURL(PUBLIC_URL + "/index.html")
);
// An example runtime caching route for requests that aren't handled by the
@@ -101,12 +92,11 @@ self.addEventListener("message", (event) => {
break;
case "GET_VERSION":
{
const VERSION = import.meta.env.REACT_APP_VERSION;
const HASH = import.meta.env.REACT_APP_GIT_HASH;
if (!event.source) return;
event.source.postMessage({
type: data.type,
version: VERSION,
hash: HASH
version: APP_VERSION,
hash: GIT_HASH
});
}
break;

View File

@@ -25,7 +25,6 @@ import { isUserPremium } from "../hooks/use-is-user-premium";
import { SUBSCRIPTION_STATUS } from "../common/constants";
import { appVersion } from "../utils/version";
import { findItemAndDelete } from "@notesnook/core/utils/array";
import { isTesting } from "../utils/platform";
/**
* @extends {BaseStore<AnnouncementStore>}
@@ -35,7 +34,7 @@ class AnnouncementStore extends BaseStore {
dialogAnnouncements = [];
refresh = async () => {
if (isTesting()) return;
if (IS_TESTING) return;
try {
const inlineAnnouncements = [];
@@ -77,7 +76,7 @@ export { useStore, store };
export const allowedPlatforms = [
"all",
import.meta.env.REACT_APP_PLATFORM,
PLATFORM,
...(window.os ? [window.os()] : [])
];

View File

@@ -26,7 +26,7 @@ import { showReminderPreviewDialog } from "../common/dialog-controller";
import dayjs from "dayjs";
import Config from "../utils/config";
import { store as notestore } from "./note-store";
import { isDesktop, isTesting } from "../utils/platform";
import { desktop } from "../common/desktop-bridge";
/**
@@ -57,7 +57,7 @@ async function resetReminders(reminders) {
await TaskScheduler.stopAllWithPrefix("reminder:");
if (
!isTesting() &&
!IS_TESTING &&
(!("Notification" in window) || Notification.permission !== "granted")
)
return;
@@ -93,12 +93,12 @@ function scheduleReminder(id, reminder, cron) {
return TaskScheduler.register(`reminder:${id}`, cron, async () => {
if (!Config.get("reminderNotifications", true)) return;
if (isTesting()) {
if (IS_TESTING) {
window.confirm("Reminder activated!");
return;
}
if (isDesktop()) {
if (IS_DESKTOP_APP) {
const tag = await desktop?.integration.showNotification.query({
title: reminder.title,
body: reminder.description,

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import CompressorWorker from "./compressor.worker.ts?worker";
import type { Compressor as CompressorWorkerType } from "./compressor.worker";
import { wrap, Remote } from "comlink";
import { isDesktop } from "./platform";
import { desktop } from "../common/desktop-bridge";
export class Compressor {
@@ -28,21 +28,21 @@ export class Compressor {
private compressor!: Remote<CompressorWorkerType>;
constructor() {
if (!isDesktop()) {
if (!IS_DESKTOP_APP) {
this.worker = new CompressorWorker();
this.compressor = wrap<CompressorWorkerType>(this.worker);
}
}
async compress(data: string) {
if (isDesktop())
if (IS_DESKTOP_APP)
return await desktop?.compress.gzip.query({ data, level: 6 });
return await this.compressor.gzip({ data, level: 6 });
}
async decompress(data: string) {
if (isDesktop()) return await desktop?.compress.gunzip.query(data);
if (IS_DESKTOP_APP) return await desktop?.compress.gunzip.query(data);
return await this.compressor.gunzip({ data });
}

View File

@@ -122,10 +122,6 @@ export function getDownloadLink(platform: string) {
}
}
export function isDesktop() {
return "os" in window || import.meta.env.REACT_APP_PLATFORM === "desktop";
}
export function isMac() {
return (
getPlatform() === "macOS" || getPlatform() === "darwin" || isMacStoreApp()
@@ -135,7 +131,3 @@ export function isMac() {
export function isMacStoreApp() {
return window.os && window.os() === "mas";
}
export function isTesting() {
return !!import.meta.env.REACT_APP_TEST;
}

View File

@@ -21,7 +21,7 @@ import Config from "./config";
export function isTelemetryEnabled() {
// telemetry is always disabled in DEBUG/TEST mode
if (import.meta.env.NODE_ENV !== "production") return false;
if (import.meta.env.DEV) return false;
return Config.get("telemetry", false);
}

View File

@@ -19,11 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { AppEventManager, AppEvents } from "../common/app-events";
import { desktop } from "../common/desktop-bridge";
import { isDesktop } from "./platform";
import { appVersion, getServiceWorkerVersion } from "./version";
export async function checkForUpdate() {
if (isDesktop()) await desktop?.updater.check.query();
if (IS_DESKTOP_APP) await desktop?.updater.check.query();
else {
AppEventManager.publish(AppEvents.checkingForUpdate);
@@ -35,7 +34,11 @@ export async function checkForUpdate() {
const workerVersion = await getServiceWorkerVersion(
registration.waiting
);
if (!workerVersion || workerVersion.numerical <= appVersion.numerical) {
if (
!workerVersion ||
workerVersion.numerical <= appVersion.numerical ||
workerVersion.hash === appVersion.hash
) {
registration.waiting.postMessage({ type: "SKIP_WAITING" });
continue;
}
@@ -52,17 +55,21 @@ export async function checkForUpdate() {
}
export async function downloadUpdate() {
if (isDesktop()) await desktop?.updater.download.query();
if (IS_DESKTOP_APP) await desktop?.updater.download.query();
else {
console.log("Force updating");
try {
if (!("serviceWorker" in navigator)) return;
const registration = await navigator.serviceWorker.ready;
await registration.update();
} catch (e) {
console.error(e);
}
}
}
export async function installUpdate() {
if (isDesktop()) await desktop?.updater.install.query();
if (IS_DESKTOP_APP) await desktop?.updater.install.query();
else {
const registrations =
(await navigator.serviceWorker?.getRegistrations()) || [];

View File

@@ -20,15 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
export type Platforms = "web" | "desktop";
export type AppVersion = typeof appVersion;
export const appVersion = {
formatted: format(
import.meta.env.REACT_APP_VERSION,
import.meta.env.REACT_APP_GIT_HASH,
import.meta.env.REACT_APP_PLATFORM as Platforms,
import.meta.env.REACT_APP_BETA === "true"
),
clean: formatVersion(import.meta.env.REACT_APP_VERSION),
numerical: parseInt(import.meta.env.REACT_APP_VERSION || "0"),
isBeta: import.meta.env.REACT_APP_BETA === "true"
formatted: format(APP_VERSION, GIT_HASH, PLATFORM, IS_BETA),
clean: formatVersion(APP_VERSION),
numerical: parseInt(APP_VERSION || "0"),
hash: GIT_HASH,
isBeta: IS_BETA
};
function format(
@@ -59,11 +55,12 @@ export function getServiceWorkerVersion(
if (type !== "GET_VERSION") return;
clearTimeout(timeout);
const { version } = ev.data;
const { version, hash } = ev.data;
resolve({
formatted: formatVersion(version),
numerical: parseInt(version),
clean: formatVersion(version),
hash,
isBeta: appVersion.isBeta
});
});

View File

@@ -38,7 +38,7 @@ import useDatabase from "../hooks/use-database";
import Loader from "../components/loader";
import { showToast } from "../utils/toast";
import AuthContainer from "../components/auth-container";
import { isTesting } from "../utils/platform";
import { useTimer } from "../hooks/use-timer";
import { AuthenticatorType } from "../dialogs/mfa/types";
import {
@@ -521,7 +521,7 @@ function AccountRecovery(props: BaseAuthComponentProps<"recover">) {
const url = await db.user?.recoverAccount(form.email.toLowerCase());
console.log(url);
if (isTesting()) {
if (IS_TESTING) {
window.open(url, "_self");
return;
}

View File

@@ -21,128 +21,23 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import svgrPlugin from "vite-plugin-svgr";
import envCompatible from "vite-plugin-env-compatible";
import { VitePWA, ManifestOptions } from "vite-plugin-pwa";
import { VitePWA } from "vite-plugin-pwa";
import autoprefixer from "autoprefixer";
import { WEB_MANIFEST } from "./web-manifest";
import { execSync } from "child_process";
import { version } from "./package.json";
const WEB_MANIFEST: Partial<ManifestOptions> = {
name: "Notesnook",
description:
"A fully open source & end-to-end encrypted note taking alternative to Evernote.",
short_name: "Notesnook",
shortcuts: [
{
name: "New note",
url: "/#/notes/create",
description: "Create a new note",
icons: [
{
src: "/android-chrome-192x192.png",
sizes: "192x192",
type: "image/png"
const gitHash = (() => {
try {
return execSync("git rev-parse --short HEAD").toString().trim();
} catch (e) {
return process.env.GIT_HASH || "gitless";
}
]
},
{
name: "New notebook",
url: "/#/notebooks/create",
description: "Create a new notebook",
icons: [
{
src: "/android-chrome-192x192.png",
sizes: "192x192",
type: "image/png"
}
]
}
],
icons: [
{
src: "favicon-16x16.png",
sizes: "16x16",
type: "image/png"
},
{
src: "favicon-32x32.png",
sizes: "32x32",
type: "image/png"
},
{
src: "favicon-72x72.png",
sizes: "72x72",
type: "image/png"
},
{
src: "/android-chrome-192x192.png",
sizes: "192x192",
type: "image/png"
},
{
src: "/android-chrome-512x512.png",
sizes: "512x512",
type: "image/png"
}
],
screenshots: [
{
src: "/screenshots/screenshot-1.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-2.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-3.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-4.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-5.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-6.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-7.jpg",
sizes: "1080x1920",
type: "image/jpeg"
}
],
related_applications: [
{
platform: "play",
url: "https://play.google.com/store/apps/details?id=com.streetwriters.notesnook",
id: "com.streetwriters.notesnook"
},
{
platform: "itunes",
url: "https://apps.apple.com/us/app/notesnook-private-notes-app/id1544027013"
}
],
prefer_related_applications: true,
orientation: "any",
start_url: ".",
theme_color: "#01c352",
background_color: "#ffffff",
display: "standalone",
categories: ["productivity", "lifestyle", "education", "books"]
};
})();
const appVersion = version.replaceAll(".", "");
const isTesting =
process.env.REACT_APP_TEST === "true" ||
process.env.NODE_ENV === "development";
const isDesktop = process.env.REACT_APP_PLATFORM === "desktop";
process.env.TEST === "true" || process.env.NODE_ENV === "development";
const isDesktop = process.env.PLATFORM === "desktop";
export default defineConfig({
envPrefix: "REACT_APP_",
@@ -152,14 +47,23 @@ export default defineConfig({
minify: "esbuild",
cssMinify: true,
emptyOutDir: true,
sourcemap: process.env.GENERATE_SOURCEMAP === "true",
sourcemap: isTesting,
rollupOptions: {
output: {
assetFileNames: "assets/[name]-[hash:12][extname]",
chunkFileNames: "[name]-[hash:12].js"
chunkFileNames: "assets/[name]-[hash:12].js"
}
}
},
define: {
GIT_HASH: `"${gitHash}"`,
APP_VERSION: `"${appVersion}"`,
PUBLIC_URL: `"${process.env.PUBLIC_URL || ""}"`,
IS_DESKTOP_APP: isDesktop,
PLATFORM: `"${process.env.PLATFORM}"`,
IS_TESTING: process.env.TEST === "true",
IS_BETA: process.env.BETA === "true"
},
logLevel: process.env.NODE_ENV === "production" ? "warn" : "info",
resolve: {
dedupe: ["react", "react-dom", "@mdi/js", "@mdi/react", "@emotion/react"],
@@ -194,7 +98,7 @@ export default defineConfig({
manifest: WEB_MANIFEST,
injectRegister: null,
srcDir: "src",
filename: "service-worker.js"
filename: "service-worker.ts"
})
]),
react({

135
apps/web/web-manifest.ts Normal file
View File

@@ -0,0 +1,135 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
*/
import { ManifestOptions } from "vite-plugin-pwa";
export const WEB_MANIFEST: Partial<ManifestOptions> = {
name: "Notesnook",
description:
"A fully open source & end-to-end encrypted note taking alternative to Evernote.",
short_name: "Notesnook",
shortcuts: [
{
name: "New note",
url: "/#/notes/create",
description: "Create a new note",
icons: [
{
src: "/android-chrome-192x192.png",
sizes: "192x192",
type: "image/png"
}
]
},
{
name: "New notebook",
url: "/#/notebooks/create",
description: "Create a new notebook",
icons: [
{
src: "/android-chrome-192x192.png",
sizes: "192x192",
type: "image/png"
}
]
}
],
icons: [
{
src: "favicon-16x16.png",
sizes: "16x16",
type: "image/png"
},
{
src: "favicon-32x32.png",
sizes: "32x32",
type: "image/png"
},
{
src: "favicon-72x72.png",
sizes: "72x72",
type: "image/png"
},
{
src: "/android-chrome-192x192.png",
sizes: "192x192",
type: "image/png"
},
{
src: "/android-chrome-512x512.png",
sizes: "512x512",
type: "image/png"
}
],
screenshots: [
{
src: "/screenshots/screenshot-1.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-2.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-3.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-4.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-5.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-6.jpg",
sizes: "1080x1920",
type: "image/jpeg"
},
{
src: "/screenshots/screenshot-7.jpg",
sizes: "1080x1920",
type: "image/jpeg"
}
],
related_applications: [
{
platform: "play",
url: "https://play.google.com/store/apps/details?id=com.streetwriters.notesnook",
id: "com.streetwriters.notesnook"
},
{
platform: "itunes",
url: "https://apps.apple.com/us/app/notesnook-private-notes-app/id1544027013"
}
],
prefer_related_applications: true,
orientation: "any",
start_url: ".",
theme_color: "#01c352",
background_color: "#ffffff",
display: "standalone",
categories: ["productivity", "lifestyle", "education", "books"]
};