From 664fa8f3d3633b65eff6ddeb14e102fb4a63dd4f Mon Sep 17 00:00:00 2001 From: BelKed <66956532+BelKed@users.noreply.github.com> Date: Thu, 6 Feb 2025 02:36:55 +0100 Subject: [PATCH 1/5] Working code, needs polishing --- src/background/client.ts | 7 ++- src/background/heartbeat.ts | 4 +- src/background/helpers.ts | 15 ++++++- src/manifest.json | 4 ++ src/popup/index.html | 10 +++++ src/popup/main.ts | 15 +++++++ src/popup/style.css | 8 ++++ src/settings/index.html | 40 +++++++++++++++++ src/settings/main.ts | 87 +++++++++++++++++++++++++++++++++++++ src/settings/style.css | 41 +++++++++++++++++ src/storage.ts | 6 +++ vite.config.ts | 7 ++- 12 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 src/settings/index.html create mode 100644 src/settings/main.ts create mode 100644 src/settings/style.css diff --git a/src/background/client.ts b/src/background/client.ts index 32fdfaa..7f12ed2 100644 --- a/src/background/client.ts +++ b/src/background/client.ts @@ -2,7 +2,7 @@ import config from '../config' import { AWClient, IEvent } from 'aw-client' import retry from 'p-retry' -import { emitNotification, getBrowserName, logHttpError } from './helpers' +import { emitNotification, getBrowser, logHttpError } from './helpers' import { getSyncStatus, setSyncStatus } from '../storage' export const getClient = () => @@ -65,4 +65,7 @@ export async function sendHeartbeat( }) } -export const getBucketId = () => `aw-watcher-web-${getBrowserName()}` +export const getBucketId = async (): Promise => { + const browser = await getBrowser() + return `aw-watcher-web-${browser}` +} diff --git a/src/background/heartbeat.ts b/src/background/heartbeat.ts index 1da190d..7ea70d9 100644 --- a/src/background/heartbeat.ts +++ b/src/background/heartbeat.ts @@ -37,7 +37,7 @@ async function heartbeat( console.debug('Sending heartbeat for previous data', previousData) await sendHeartbeat( client, - getBucketId(), + await getBucketId(), new Date(now.getTime() - 1), previousData, config.heartbeat.intervalInSeconds + 20, @@ -46,7 +46,7 @@ async function heartbeat( console.debug('Sending heartbeat', data) await sendHeartbeat( client, - getBucketId(), + await getBucketId(), now, data, config.heartbeat.intervalInSeconds + 20, diff --git a/src/background/helpers.ts b/src/background/helpers.ts index 600dcd3..6b2ddd7 100644 --- a/src/background/helpers.ts +++ b/src/background/helpers.ts @@ -1,5 +1,6 @@ import browser from 'webextension-polyfill' import { FetchError } from 'aw-client' +import { getBrowserName, setBrowserName } from '../storage' export const getTab = (id: number) => browser.tabs.get(id) export const getTabs = (query: browser.Tabs.QueryQueryInfoType = {}) => @@ -23,8 +24,20 @@ export function emitNotification(title: string, message: string) { }) } +export const getBrowser = async (): Promise => { + const storedName = await getBrowserName() + if (storedName) { + return storedName + } + + const browserName = detectBrowser() + + await setBrowserName(browserName) + return browserName +} + // FIXME: Detect Vivaldi? It seems to be intentionally impossible -export const getBrowserName = () => { +export const detectBrowser = () => { if ((navigator as any).brave?.isBrave()) { return 'brave' } else if ( diff --git a/src/manifest.json b/src/manifest.json index e8bc849..34c239f 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -24,6 +24,10 @@ "{{firefox}}.persistent": true }, + "options_ui": { + "page": "src/settings/index.html" + }, + "{{firefox}}.permissions": [ "tabs", "alarms", diff --git a/src/popup/index.html b/src/popup/index.html index 21366c6..77faf93 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -56,6 +56,16 @@ + + + Browser: + + + + + + +
diff --git a/src/popup/main.ts b/src/popup/main.ts index 8970a21..e5c85cf 100644 --- a/src/popup/main.ts +++ b/src/popup/main.ts @@ -8,6 +8,7 @@ import { setEnabled, watchSyncDate, watchSyncSuccess, + getBrowserName, } from '../storage' function setConnected(connected: boolean | undefined) { @@ -31,6 +32,7 @@ async function renderStatus() { const enabled = await getEnabled() const syncStatus = await getSyncStatus() const consentStatus = await getConsentStatus() + const browserName = await getBrowserName() // Enabled checkbox const enabledCheckbox = document.getElementById('status-enabled-checkbox') @@ -72,6 +74,11 @@ async function renderStatus() { if (!(webuiLink instanceof HTMLAnchorElement)) throw Error('Web UI link is not an anchor') webuiLink.href = baseUrl ?? '#' + + const browserNameElement = document.getElementById('status-browser') + if (!(browserNameElement instanceof HTMLElement)) + throw Error('Browser name element is not defined') + browserNameElement.innerText = browserName } function domListeners() { @@ -82,6 +89,7 @@ function domListeners() { const enabled = enabledCheckbox.checked setEnabled(enabled) }) + const consentButton = document.getElementById('status-consent-btn')! consentButton.addEventListener('click', () => { browser.tabs.create({ @@ -89,6 +97,13 @@ function domListeners() { url: browser.runtime.getURL('src/consent/index.html'), }) }) + + const browserButton = document.getElementById('edit-btn') + if (!(browserButton instanceof HTMLButtonElement)) + throw Error('Edit button is not a button') + browserButton.addEventListener('click', () => { + browser.runtime.openOptionsPage() + }) } renderStatus() diff --git a/src/popup/style.css b/src/popup/style.css index f44868c..7f5cd50 100644 --- a/src/popup/style.css +++ b/src/popup/style.css @@ -29,3 +29,11 @@ button { #status-consent-btn { display: none; } + +form { + margin: 0 0.5em; +} + +label { + font-weight: bold; +} diff --git a/src/settings/index.html b/src/settings/index.html new file mode 100644 index 0000000..1bc011c --- /dev/null +++ b/src/settings/index.html @@ -0,0 +1,40 @@ + + + + + ActivityWatch settings + + + + +
+
+ + + + +
+
+ + + diff --git a/src/settings/main.ts b/src/settings/main.ts new file mode 100644 index 0000000..c971fa9 --- /dev/null +++ b/src/settings/main.ts @@ -0,0 +1,87 @@ +import browser from 'webextension-polyfill' +import { getBrowserName, setBrowserName } from '../storage' + +interface HTMLElementEvent extends Event { + target: T +} + +async function reloadExtension(): Promise { + browser.runtime.reload() +} + +async function saveOptions(e: SubmitEvent): Promise { + e.preventDefault() + + const browserSelect = document.querySelector('#browser') + const customBrowserInput = + document.querySelector('#customBrowser') + if (!browserSelect) return + + let selectedBrowser = browserSelect.value + if (selectedBrowser === 'other' && customBrowserInput?.value) { + selectedBrowser = customBrowserInput.value.toLowerCase() + } + + const form = e.target as HTMLFormElement + const button = form.querySelector('button') + if (!button) return + + button.textContent = 'Saving...' + button.classList.remove('accept') + + try { + await setBrowserName(selectedBrowser) + await reloadExtension() + button.textContent = 'Save' + button.classList.add('accept') + } catch (error) { + console.error('Failed to save options:', error) + button.textContent = 'Error' + button.classList.add('error') + } +} + +function toggleCustomBrowserInput(): void { + const browserSelect = document.querySelector('#browser') + const customInput = document.querySelector('#customBrowser') + const otherBrowserInput = + document.querySelector('#otherBrowserInput') + + if (browserSelect && customInput && otherBrowserInput) { + const isOther = browserSelect.value === 'other' + otherBrowserInput.style.display = isOther ? 'block' : 'none' + customInput.required = isOther + } +} + +async function restoreOptions(): Promise { + try { + const browserName = await getBrowserName() + const browserSelect = document.querySelector('#browser') + const otherBrowserInput = document.querySelector('#otherBrowserInput') + const customInput = document.querySelector('#customBrowser') + + if (!browserSelect || !otherBrowserInput || !customInput || !browserName) return + + const standardBrowsers = Array.from(browserSelect.options).map(opt => opt.value) + if (!standardBrowsers.includes(browserName)) { + browserSelect.value = 'other' + otherBrowserInput.style.display = 'block' + customInput.value = browserName + customInput.required = true + } else { + browserSelect.value = browserName + } + } catch (error) { + console.error('Failed to restore options:', error) + } +} + +document.addEventListener('DOMContentLoaded', restoreOptions) +document + .querySelector('#browser') + ?.addEventListener('change', toggleCustomBrowserInput) +const form = document.querySelector('form') +if (form) { + form.addEventListener('submit', saveOptions as EventListener) +} diff --git a/src/settings/style.css b/src/settings/style.css new file mode 100644 index 0000000..ad990b4 --- /dev/null +++ b/src/settings/style.css @@ -0,0 +1,41 @@ +body { + font-family: 'Segoe UI', 'Lucida Grande', Tahoma, sans-serif; + font-size: 100%; + width: 25em; +} + +hr { + border: 1px solid #ddd; +} + +a { + text-decoration: none; +} + +button { + cursor: pointer; +} + +.button { + display: inline-block; + color: #333333; + padding: 0.5em; + margin: 0.5em 0 0.5em 0.5em; + border-radius: 0.2em; + background-color: #eee; + border: 1px solid #ddd; +} + +#status-consent-btn { + display: none; +} + +#otherBrowserInput { + margin-top: 10px; +} + +#customBrowser { + width: 100%; + padding: 5px; + box-sizing: border-box; +} diff --git a/src/storage.ts b/src/storage.ts index 73a5096..fea6dd9 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -84,3 +84,9 @@ export const getHeartbeatData = (): Promise => .then((_) => _.heartbeatData as HeartbeatData | undefined) export const setHeartbeatData = (heartbeatData: HeartbeatData) => browser.storage.local.set({ heartbeatData }) + +type BrowserName = string +export const getBrowserName = (): Promise => + browser.storage.local.get('browserName').then((_) => _.browserName) +export const setBrowserName = (browserName: BrowserName) => + browser.storage.local.set({ browserName }) diff --git a/vite.config.ts b/vite.config.ts index c7dde5b..123316b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,7 +20,12 @@ export default defineConfig({ plugins: [ webExtension({ manifest: generateManifest, - additionalInputs: ['src/consent/index.html', 'src/consent/main.ts'], + additionalInputs: [ + 'src/consent/index.html', + 'src/consent/main.ts', + 'src/settings/index.html', + 'src/settings/main.ts', + ], browser: process.env.VITE_TARGET_BROWSER, }), ], From de50dbc60b0849df8d751e6b6dbbe641856d19b6 Mon Sep 17 00:00:00 2001 From: BelKed <66956532+BelKed@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:21:34 +0100 Subject: [PATCH 2/5] Cleanup and simplify --- src/settings/index.html | 54 +++++++++--------- src/settings/main.ts | 118 +++++++++++++++++++++------------------- src/settings/style.css | 39 ++----------- 3 files changed, 96 insertions(+), 115 deletions(-) diff --git a/src/settings/index.html b/src/settings/index.html index 1bc011c..25114e6 100644 --- a/src/settings/index.html +++ b/src/settings/index.html @@ -8,33 +8,35 @@
-
- - - - -
+ + + + + + +
+ diff --git a/src/settings/main.ts b/src/settings/main.ts index c971fa9..823e347 100644 --- a/src/settings/main.ts +++ b/src/settings/main.ts @@ -2,86 +2,92 @@ import browser from 'webextension-polyfill' import { getBrowserName, setBrowserName } from '../storage' interface HTMLElementEvent extends Event { - target: T + target: T } async function reloadExtension(): Promise { - browser.runtime.reload() + browser.runtime.reload() } async function saveOptions(e: SubmitEvent): Promise { - e.preventDefault() + e.preventDefault() - const browserSelect = document.querySelector('#browser') - const customBrowserInput = - document.querySelector('#customBrowser') - if (!browserSelect) return + const browserSelect = document.querySelector('#browser') + const customBrowserInput = + document.querySelector('#customBrowser') + if (!browserSelect) return - let selectedBrowser = browserSelect.value - if (selectedBrowser === 'other' && customBrowserInput?.value) { - selectedBrowser = customBrowserInput.value.toLowerCase() - } + let selectedBrowser = browserSelect.value + if (selectedBrowser === 'other' && customBrowserInput?.value) { + selectedBrowser = customBrowserInput.value.toLowerCase() + } - const form = e.target as HTMLFormElement - const button = form.querySelector('button') - if (!button) return + const form = e.target as HTMLFormElement + const button = form.querySelector('button') + if (!button) return - button.textContent = 'Saving...' - button.classList.remove('accept') + button.textContent = 'Saving...' + button.classList.remove('accept') - try { - await setBrowserName(selectedBrowser) - await reloadExtension() - button.textContent = 'Save' - button.classList.add('accept') - } catch (error) { - console.error('Failed to save options:', error) - button.textContent = 'Error' - button.classList.add('error') - } + try { + await setBrowserName(selectedBrowser) + await reloadExtension() + button.textContent = 'Save' + button.classList.add('accept') + } catch (error) { + console.error('Failed to save options:', error) + button.textContent = 'Error' + button.classList.add('error') + } } function toggleCustomBrowserInput(): void { - const browserSelect = document.querySelector('#browser') - const customInput = document.querySelector('#customBrowser') - const otherBrowserInput = - document.querySelector('#otherBrowserInput') + const browserSelect = document.querySelector('#browser') + const customInput = document.querySelector('#customBrowser') - if (browserSelect && customInput && otherBrowserInput) { - const isOther = browserSelect.value === 'other' - otherBrowserInput.style.display = isOther ? 'block' : 'none' - customInput.required = isOther - } + if (browserSelect && customInput) { + const isOther = browserSelect.value === 'other' + customInput.style.display = isOther ? 'block' : 'none' + customInput.required = isOther + } } async function restoreOptions(): Promise { - try { - const browserName = await getBrowserName() - const browserSelect = document.querySelector('#browser') - const otherBrowserInput = document.querySelector('#otherBrowserInput') - const customInput = document.querySelector('#customBrowser') + try { + const browserName = await getBrowserName() + const browserSelect = document.querySelector('#browser') + const customInput = + document.querySelector('#customBrowser') - if (!browserSelect || !otherBrowserInput || !customInput || !browserName) return + if (!browserSelect || !customInput || !browserName) return - const standardBrowsers = Array.from(browserSelect.options).map(opt => opt.value) - if (!standardBrowsers.includes(browserName)) { - browserSelect.value = 'other' - otherBrowserInput.style.display = 'block' - customInput.value = browserName - customInput.required = true - } else { - browserSelect.value = browserName - } - } catch (error) { - console.error('Failed to restore options:', error) + const standardBrowsers = Array.from(browserSelect.options).map( + (opt) => opt.value, + ) + if (!standardBrowsers.includes(browserName)) { + browserSelect.value = 'other' + customInput.style.display = 'block' + customInput.value = browserName + customInput.required = true + } else { + browserSelect.value = browserName + customInput.style.display = 'none' + customInput.required = false } + } catch (error) { + console.error('Failed to restore options:', error) + } } -document.addEventListener('DOMContentLoaded', restoreOptions) +document.addEventListener('DOMContentLoaded', () => { + restoreOptions() + toggleCustomBrowserInput() +}) + document - .querySelector('#browser') - ?.addEventListener('change', toggleCustomBrowserInput) + .querySelector('#browser') + ?.addEventListener('change', toggleCustomBrowserInput) const form = document.querySelector('form') if (form) { - form.addEventListener('submit', saveOptions as EventListener) + form.addEventListener('submit', saveOptions as EventListener) } diff --git a/src/settings/style.css b/src/settings/style.css index ad990b4..acb9885 100644 --- a/src/settings/style.css +++ b/src/settings/style.css @@ -1,41 +1,14 @@ body { font-family: 'Segoe UI', 'Lucida Grande', Tahoma, sans-serif; - font-size: 100%; - width: 25em; } -hr { - border: 1px solid #ddd; -} - -a { - text-decoration: none; -} - -button { - cursor: pointer; -} - -.button { - display: inline-block; - color: #333333; - padding: 0.5em; - margin: 0.5em 0 0.5em 0.5em; - border-radius: 0.2em; - background-color: #eee; - border: 1px solid #ddd; -} - -#status-consent-btn { - display: none; -} - -#otherBrowserInput { - margin-top: 10px; +form { + display: flex; + gap: 10px; + align-items: center; + margin: 0.5em; } #customBrowser { - width: 100%; - padding: 5px; - box-sizing: border-box; + display: none; } From dc530a171208ab202751b4044140f5ebcf09f2fa Mon Sep 17 00:00:00 2001 From: BelKed <66956532+BelKed@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:24:50 +0100 Subject: [PATCH 3/5] Remove unnecessary files from `additionalInputs` --- vite.config.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 123316b..c7dde5b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,12 +20,7 @@ export default defineConfig({ plugins: [ webExtension({ manifest: generateManifest, - additionalInputs: [ - 'src/consent/index.html', - 'src/consent/main.ts', - 'src/settings/index.html', - 'src/settings/main.ts', - ], + additionalInputs: ['src/consent/index.html', 'src/consent/main.ts'], browser: process.env.VITE_TARGET_BROWSER, }), ], From 9f6d2e944586a0f05be7648c5cd635bdad58db9a Mon Sep 17 00:00:00 2001 From: BelKed <66956532+BelKed@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:40:32 +0100 Subject: [PATCH 4/5] Remove 'required' attribute --- src/settings/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/settings/index.html b/src/settings/index.html index 25114e6..f433709 100644 --- a/src/settings/index.html +++ b/src/settings/index.html @@ -31,7 +31,6 @@ name="customBrowser" pattern="[a-z]*" placeholder="Custom browser name" - required /> From 9e9d30fe59f14c60b85ff0a85148dc43af232888 Mon Sep 17 00:00:00 2001 From: BelKed <66956532+BelKed@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:56:01 +0100 Subject: [PATCH 5/5] Add Zen browser --- src/settings/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/settings/index.html b/src/settings/index.html index f433709..8a59bf5 100644 --- a/src/settings/index.html +++ b/src/settings/index.html @@ -22,6 +22,7 @@ +