From 809b59ebb15047650554b151ddc5f4a25b6fa7e6 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 4 Feb 2023 13:10:12 +1100 Subject: [PATCH] chore: update onboarding disclaimer, unify telemetry handling, detect firefox vs chromium --- scripts/build-browser-extension.sh | 2 +- src/api/browser.js | 4 +- src/api/mods.js | 20 ++-- src/core/client.mjs | 165 +++++++++++++-------------- src/core/menu/islands/Onboarding.mjs | 27 +++-- src/core/menu/islands/Sidebar.mjs | 2 +- src/core/menu/islands/Telemetry.mjs | 40 +++---- src/core/telemetry.mjs | 27 +++++ src/core/update.mjs | 7 +- src/init.js | 7 +- src/load.mjs | 16 +-- src/manifest.json | 1 - 12 files changed, 174 insertions(+), 144 deletions(-) create mode 100644 src/core/telemetry.mjs diff --git a/scripts/build-browser-extension.sh b/scripts/build-browser-extension.sh index 0150883..ddd1ea0 100755 --- a/scripts/build-browser-extension.sh +++ b/scripts/build-browser-extension.sh @@ -5,4 +5,4 @@ version=$(node -p "require('./package.json').version") cd src mkdir -p ../dist rm -f "../dist/notion-enhancer-$version.zip" -zip -r9 "../dist/notion-enhancer-$version.zip" . -x electron/\* \ No newline at end of file +zip -r9 "../dist/notion-enhancer-$version.zip" . \ No newline at end of file diff --git a/src/api/browser.js b/src/api/browser.js index 99f3e80..1331c14 100644 --- a/src/api/browser.js +++ b/src/api/browser.js @@ -6,7 +6,9 @@ "use strict"; -const platform = "browser", +const platform = navigator.userAgent.includes("Firefox") + ? "firefox" + : "chromium", version = chrome.runtime.getManifest().version, enhancerUrl = (target) => chrome.runtime.getURL(target); diff --git a/src/api/mods.js b/src/api/mods.js index 6f383fb..5efa2cf 100644 --- a/src/api/mods.js +++ b/src/api/mods.js @@ -25,18 +25,17 @@ const _isManifestValid = (modManifest) => { }; let _mods; -const getMods = async (category) => { +const getMods = async (asyncFilter) => { const { readJson } = globalThis.__enhancerApi; // prettier-ignore _mods ??= (await Promise.all((await readJson("registry.json")).map(async (_src) => { const modManifest = { ...(await readJson(`${_src}/mod.json`)), _src }; return _isManifestValid(modManifest) ? modManifest : undefined; }))).filter((mod) => mod); - return category - ? _mods.filter(({ _src }) => { - return _src === category || _src.startsWith(`${category}/`); - }) - : _mods; + // prettier-ignore + return (await Promise.all(_mods.map(async (mod) => { + return !asyncFilter || (await asyncFilter(mod)) ? mod : undefined; + }))).filter((mod) => mod); }, getProfile = async () => { const db = globalThis.__enhancerApi.initDatabase(); @@ -49,10 +48,10 @@ const isEnabled = async (id) => { const { version, initDatabase } = globalThis.__enhancerApi, mod = (await getMods()).find((mod) => mod.id === id); if (mod._src === "core") return true; - // prettier-ignore const agreedToTerms = await initDatabase().get("agreedToTerms"), enabledInProfile = await initDatabase([ - await getProfile(), "enabledMods", + await getProfile(), + "enabledMods", ]).get(id); return agreedToTerms === version && enabledInProfile; }, @@ -63,10 +62,9 @@ const isEnabled = async (id) => { }; const modDatabase = async (id) => { - // prettier-ignore const optionDefaults = (await getMods()) - .find((mod) => mod.id === id)?.options - .map((opt) => [opt.key, opt.value ?? opt.values?.[0]]) + .find((mod) => mod.id === id) + ?.options.map((opt) => [opt.key, opt.value ?? opt.values?.[0]]) .filter(([, value]) => typeof value !== "undefined"); return globalThis.__enhancerApi.initDatabase( [await getProfile(), id], diff --git a/src/core/client.mjs b/src/core/client.mjs index 9780a03..6ff0257 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -5,21 +5,20 @@ */ import { checkForUpdate } from "./update.mjs"; +import { sendTelemetryPing } from "./telemetry.mjs"; import { Frame, Modal, Button } from "./components.mjs"; -// prettier-ignore -const asyncFilter = async (arr, predicate) => Promise.all(arr.map(predicate)) - .then((results) => arr.filter((_v, index) => results[index])); - const doThemeOverride = async (db) => { const { getMods, isEnabled } = globalThis.__enhancerApi, - enabledFilter = (theme) => isEnabled(theme.id), - overrideThemes = await db.get("loadThemeOverrides"), - enabledThemes = await asyncFilter(await getMods("themes"), enabledFilter); - return ( - overrideThemes === "Enabled" || - (overrideThemes === "Auto" && enabledThemes.length) - ); + loadThemeOverrides = await db.get("loadThemeOverrides"); + if (loadThemeOverrides === "Enabled") return true; + if (loadThemeOverrides === "Disabled") return false; + // prettier-ignore + return (await getMods(async (mod) => { + // loadThemeOverrides === "Auto" + if (!mod._src.startsWith("themes/")) return false; + return await isEnabled(mod.id); + })).length; }, overrideThemes = async (db) => { const { html, enhancerUrl } = globalThis.__enhancerApi; @@ -39,86 +38,78 @@ const doThemeOverride = async (db) => { }; const insertMenu = async (db) => { - const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`, - { html, addKeyListener, addMutationListener } = globalThis.__enhancerApi, - { platform, enhancerUrl, onMessage } = globalThis.__enhancerApi, - menuButtonIconStyle = await db.get("menuButtonIconStyle"), - openMenuHotkey = await db.get("openMenuHotkey"), - renderPing = { - namespace: "notion-enhancer", - hotkey: openMenuHotkey, - icon: menuButtonIconStyle, - }; + const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`, + { html, addKeyListener, addMutationListener } = globalThis.__enhancerApi, + { platform, enhancerUrl, onMessage } = globalThis.__enhancerApi, + menuButtonIconStyle = await db.get("menuButtonIconStyle"), + openMenuHotkey = await db.get("openMenuHotkey"), + renderPing = { + namespace: "notion-enhancer", + hotkey: openMenuHotkey, + icon: menuButtonIconStyle, + }; - let _contentWindow; - const sendThemePing = () => { - const darkMode = document.body.classList.contains("dark"), - notionTheme = darkMode ? "dark" : "light"; - if (renderPing.theme === notionTheme) return; - renderPing.theme = notionTheme; - _contentWindow?.postMessage?.(renderPing, "*"); - }, - sendRenderPing = (contentWindow) => { - _contentWindow ??= contentWindow; - if (!$modal.hasAttribute("open")) return; - delete renderPing.theme; - _contentWindow?.focus?.(); - sendThemePing(); - }; + let _contentWindow; + const sendThemePing = () => { + const darkMode = document.body.classList.contains("dark"), + notionTheme = darkMode ? "dark" : "light"; + if (renderPing.theme === notionTheme) return; + renderPing.theme = notionTheme; + _contentWindow?.postMessage?.(renderPing, "*"); + }, + sendRenderPing = (contentWindow) => { + _contentWindow ??= contentWindow; + if (!$modal.hasAttribute("open")) return; + delete renderPing.theme; + _contentWindow?.focus?.(); + sendThemePing(); + }; - const $modal = html`<${Modal} onopen=${sendRenderPing}> - <${Frame} - title="notion-enhancer menu" - src="${enhancerUrl("core/menu/index.html")}" - onload=${function () { - // pass notion-enhancer api to electron menu process - if (platform !== "browser") { - const apiKey = "__enhancerApi"; - this.contentWindow[apiKey] = globalThis[apiKey]; - } - sendRenderPing(this.contentWindow); - }} - /> - `, - $button = html`<${Button} - onclick=${$modal.open} - notifications=${(await checkForUpdate()) ? 1 : 0} - themeOverridesLoaded=${await doThemeOverride(db)} - icon="notion-enhancer${menuButtonIconStyle === "Monochrome" - ? "?mask" - : " text-[16px]"}" - >notion-enhancer - `; - document.body.append($modal); - addMutationListener(notionSidebar, () => { - if (document.contains($button)) return; - document.querySelector(notionSidebar)?.append($button); - }); + const $modal = html`<${Modal} onopen=${sendRenderPing}> + <${Frame} + title="notion-enhancer menu" + src="${enhancerUrl("core/menu/index.html")}" + onload=${function () { + // pass notion-enhancer api to electron menu process + if (["darwin", "win32", "linux"].includes(platform)) { + const apiKey = "__enhancerApi"; + this.contentWindow[apiKey] = globalThis[apiKey]; + } + sendRenderPing(this.contentWindow); + }} + /> + `, + $button = html`<${Button} + onclick=${$modal.open} + notifications=${(await checkForUpdate()) ? 1 : 0} + themeOverridesLoaded=${await doThemeOverride(db)} + icon="notion-enhancer${menuButtonIconStyle === "Monochrome" + ? "?mask" + : " text-[16px]"}" + >notion-enhancer + `; + document.body.append($modal); + addMutationListener(notionSidebar, () => { + if (document.contains($button)) return; document.querySelector(notionSidebar)?.append($button); - addMutationListener("body", sendThemePing); - window.addEventListener("focus", sendRenderPing); + }); + document.querySelector(notionSidebar)?.append($button); + addMutationListener("body", sendThemePing); + window.addEventListener("focus", sendRenderPing); - addKeyListener(openMenuHotkey, (event) => { - event.preventDefault(); - $modal.open(); - }); - window.addEventListener("message", (event) => { - if (event.data?.namespace !== "notion-enhancer") return; - if (event.data?.action === "close-menu") $modal.close(); - if (event.data?.action === "open-menu") $modal.open(); - }); - onMessage("notion-enhancer", (message) => { - if (message === "open-menu") $modal.open(); - }); - }, - sendTelemetryPing = async () => { - const { version } = globalThis.__enhancerApi, - db = globalThis.__enhancerApi.initDatabase(), - agreedToTerms = await db.get("agreedToTerms"), - telemetryEnabled = await db.get("telemetryEnabled"); - if (!telemetryEnabled || agreedToTerms !== version) return; - // telemetry - }; + addKeyListener(openMenuHotkey, (event) => { + event.preventDefault(); + $modal.open(); + }); + window.addEventListener("message", (event) => { + if (event.data?.namespace !== "notion-enhancer") return; + if (event.data?.action === "close-menu") $modal.close(); + if (event.data?.action === "open-menu") $modal.open(); + }); + onMessage("notion-enhancer", (message) => { + if (message === "open-menu") $modal.open(); + }); +}; export default async (api, db) => { await Promise.all([ diff --git a/src/core/menu/islands/Onboarding.mjs b/src/core/menu/islands/Onboarding.mjs index fdf2574..83cd105 100644 --- a/src/core/menu/islands/Onboarding.mjs +++ b/src/core/menu/islands/Onboarding.mjs @@ -23,16 +23,23 @@ function Onboarding() { >Continue `, $agreeToTerms = html`
+ <${Heading} class="mb-[8px]"> + Thanks for installing the notion-enhancer! + <${Description}> - Thanks for installing the notion-enhancer! It's been absolutely - incredible to see how the notion-enhancer has grown from small - beginnings to something used today by over 11,000 people around the - world, now including you. Before you begin, please read the privacy - policy to learn how the notion-enhancer uses your data and the terms & - conditions to understand what the notion-enhancer does and does not - offer. Ticking the box below and pressing Continue will - unlock the notion-enhancer's full functionality, accessible through the - sidebar. + In order for the notion-enhancer to function, it may access, collect, + process and/or store data on your device (including workspace content, + device metadata, and notion-enhancer configuration) according to its + privacy policy. Unless otherwise stated for telemetry purposes, the + notion-enhancer will never transmit any of your data from your device. + Telemetry can be disabled at any time through the menu. +
+
+ The notion-enhancer is free and open-source software distributed under + the MIT License without warranty of any + kind. In no event shall the authors be liable for any consequences of + the software's use. Before continuing, you must read and agree to the + notion-enhancer's privacy policy and terms & conditions.
<${Checkbox} @@ -77,7 +84,7 @@ function Onboarding() {
`, $featuredSponsors = html`
- <${Heading} class="mt-[32px] mb-[8px]">Featured Sponsors + <${Heading} class="mb-[8px]">Featured Sponsors <${Description}> A few awesome companies out there have teamed up with me to provide you with the notion-enhancer, free forever. Check them out! diff --git a/src/core/menu/islands/Sidebar.mjs b/src/core/menu/islands/Sidebar.mjs index 06b2d77..e99b1fb 100644 --- a/src/core/menu/islands/Sidebar.mjs +++ b/src/core/menu/islands/Sidebar.mjs @@ -53,7 +53,7 @@ function Sidebar({ items, categories }) { const { html, version } = globalThis.__enhancerApi, { initDatabase, isEnabled } = globalThis.__enhancerApi, $agreeToUnlock = html`To unlock the notion-enhancer's full functionality, agree to the privacy policy and terms & conditions on the welcome page. diff --git a/src/core/menu/islands/Telemetry.mjs b/src/core/menu/islands/Telemetry.mjs index fa01c1e..531b603 100644 --- a/src/core/menu/islands/Telemetry.mjs +++ b/src/core/menu/islands/Telemetry.mjs @@ -4,26 +4,14 @@ * (https://notion-enhancer.github.io/) under the MIT license */ +import { collectTelemetryData } from "../../telemetry.mjs"; import { useState, setState } from "../state.mjs"; import { Option } from "./Options.mjs"; const privacyPolicy = "https://notion-enhancer.github.io/about/privacy-policy/"; function Telemetry() { - const { html, platform, version } = globalThis.__enhancerApi, - { getMods, isEnabled, initDatabase } = globalThis.__enhancerApi, - timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - - const $enabledMods = html``; - useState(["rerender"], async () => { - let enabledMods = []; - for (const mod of await getMods()) { - if (mod._src === "core") continue; - if (await isEnabled(mod.id)) enabledMods.push(mod.id); - } - $enabledMods.innerText = JSON.stringify(enabledMods); - }); - - const _get = async () => { + const { html, initDatabase } = globalThis.__enhancerApi, + _get = async () => { // defaults to true, must be explicitly set to false to disable return initDatabase().get("telemetryEnabled") ?? true; }, @@ -32,6 +20,19 @@ function Telemetry() { setState({ rerender: true }); }; + const $ = { + platform: html``, + version: html``, + timezone: html``, + enabledMods: html``, + }; + useState(["rerender"], async () => { + const telemetryData = await collectTelemetryData(); + for (const key in telemetryData) { + $[key].innerText = JSON.stringify(telemetryData[key]); + } + }); + // todo: actually collect telemetry return html`<${Option} type="toggle" @@ -39,11 +40,10 @@ function Telemetry() { description=${html`If telemetry is enabled, usage data will be collected once a week from your device in order to better understand how and where the notion-enhancer is used. This data is anonymous and includes only your - platform ("${platform}"), timezone - ("${timezone}"), notion-enhancer version - ("${version}"), and enabled mods (${$enabledMods}). You can - opt in or out of telemetry at any time. This setting syncs across - configuration profiles. For more information, read the notion-enhancer's + platform (${$.platform}), notion-enhancer version (${$.version}), timezone + (${$.timezone}), and enabled mods (${$.enabledMods}). You can opt in or + out of telemetry at any time. This setting syncs across configuration + profiles. For more information, read the notion-enhancer's privacy policy.`} ...${{ _get, _set }} />`; diff --git a/src/core/telemetry.mjs b/src/core/telemetry.mjs new file mode 100644 index 0000000..c743965 --- /dev/null +++ b/src/core/telemetry.mjs @@ -0,0 +1,27 @@ +/** + * notion-enhancer + * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +const collectTelemetryData = async () => { + const { platform, version } = globalThis.__enhancerApi, + { getMods, isEnabled } = globalThis.__enhancerApi, + timezone = Intl.DateTimeFormat().resolvedOptions().timeZone, + // prettier-ignore + enabledMods = (await getMods(async (mod) => { + if (mod._src === "core") return false; + return await isEnabled(mod.id); + })).map(mod => mod.id); + return { platform, version, timezone, enabledMods }; + }, + sendTelemetryPing = async () => { + const db = globalThis.__enhancerApi.initDatabase(), + agreedToTerms = await db.get("agreedToTerms"), + telemetryEnabled = await db.get("telemetryEnabled"); + if (!telemetryEnabled || agreedToTerms !== version) return; + // telemetry + const telemetryData = await collectTelemetryData(); + }; + +export { collectTelemetryData, sendTelemetryPing }; diff --git a/src/core/update.mjs b/src/core/update.mjs index 9293207..e8d7e84 100644 --- a/src/core/update.mjs +++ b/src/core/update.mjs @@ -8,8 +8,11 @@ let _release; const repo = "notion-enhancer/notion-enhancer", endpoint = `https://api.github.com/repos/${repo}/releases/latest`, getRelease = async () => { - const { readJson } = globalThis.__enhancerApi; - _release ??= (await readJson(endpoint))?.tag_name.replace(/^v/, ""); + const { version, readJson } = globalThis.__enhancerApi; + try { + _release ??= (await readJson(endpoint))?.tag_name.replace(/^v/, ""); + } catch {} + _release ??= version; return _release; }; diff --git a/src/init.js b/src/init.js index ab659e0..71ac398 100644 --- a/src/init.js +++ b/src/init.js @@ -45,7 +45,10 @@ if (isElectron()) { } } }; - +} else { // clientStyles // clientScripts -} else import("./api/browser.js").then(() => import("./load.mjs")); + import(chrome.runtime.getURL("/api/browser.js")).then(() => { + import(chrome.runtime.getURL("/load.mjs")); + }); +} diff --git a/src/load.mjs b/src/load.mjs index 3a48c4f..303e979 100644 --- a/src/load.mjs +++ b/src/load.mjs @@ -9,21 +9,21 @@ export default (async () => { // prettier-ignore const { enhancerUrl } = globalThis.__enhancerApi, - isMenu = location.href.startsWith(enhancerUrl("/core/menu/index.html")), + isMenu = location.href.startsWith(enhancerUrl("core/menu/index.html")), pageLoaded = /(^\/$)|((-|\/)[0-9a-f]{32}((\?.+)|$))/.test(location.pathname), signedIn = localStorage["LRU:KeyValueStore2:current-user-id"]; if (!isMenu && (!signedIn || !pageLoaded)) return; if (!isMenu) console.log("notion-enhancer: loading..."); await Promise.all([ - import("./assets/icons.svg.js"), - import("./vendor/twind.min.js"), - import("./vendor/lucide.min.js"), - import("./vendor/htm.min.js"), - import("./api/events.js"), - import("./api/mods.js"), + import(enhancerUrl("assets/icons.svg.js")), + import(enhancerUrl("vendor/twind.min.js")), + import(enhancerUrl("vendor/lucide.min.js")), + import(enhancerUrl("vendor/htm.min.js")), + import(enhancerUrl("api/events.js")), + import(enhancerUrl("api/mods.js")), ]); - await import("./api/interface.js"); + await import(enhancerUrl("api/interface.js")); const { getMods, isEnabled, modDatabase } = globalThis.__enhancerApi; for (const mod of await getMods()) { diff --git a/src/manifest.json b/src/manifest.json index ade0312..b6875ae 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -7,7 +7,6 @@ "homepage_url": "https://notion-enhancer.github.io", "content_scripts": [{ "matches": ["*://*.notion.so/*"], "js": ["/init.js"] }], "background": { "service_worker": "/worker.js" }, - "options_page": "/core/menu/index.html", "action": {}, "icons": { "16": "/assets/colour-x16.png",