diff --git a/src/background/heartbeat.ts b/src/background/heartbeat.ts index 8ad40a0..5922116 100644 --- a/src/background/heartbeat.ts +++ b/src/background/heartbeat.ts @@ -3,7 +3,7 @@ import { getActiveWindowTab, getTab, getTabs } from './helpers' import config from '../config' import { AWClient, IEvent } from 'aw-client' import { getBucketId, sendHeartbeat } from './client' -import { getEnabled, getHeartbeatData, setHeartbeatData } from '../storage' +import { getEnabled, getHeartbeatData, setHeartbeatData, getDisplayProfileName } from '../storage' import deepEqual from 'deep-equal' async function heartbeat( @@ -28,12 +28,15 @@ async function heartbeat( } const now = new Date() + const profileName = await getDisplayProfileName() + const data: IEvent['data'] = { url: tab.url, title: tab.title, audible: tab.audible ?? false, incognito: tab.incognito, tabCount: tabCount, + profileName: profileName, } const previousData = await getHeartbeatData() if (previousData && !deepEqual(previousData, data)) { diff --git a/src/background/main.ts b/src/background/main.ts index a1b092b..92a7879 100644 --- a/src/background/main.ts +++ b/src/background/main.ts @@ -14,6 +14,7 @@ import { setEnabled, setHostname, waitForEnabled, + generateProfileIdentifier, } from '../storage' async function getIsConsentRequired() { @@ -34,6 +35,11 @@ async function autodetectHostname() { } } +async function initializeProfileIdentifier() { + await generateProfileIdentifier() + console.debug('Profile identifier initialized') +} + /** Init */ console.info('Starting...') @@ -58,6 +64,7 @@ browser.runtime.onInstalled.addListener(async () => { } await autodetectHostname() + await initializeProfileIdentifier() }) console.debug('Creating alarms and tab listeners') diff --git a/src/popup/index.html b/src/popup/index.html index 9b13100..4459641 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -79,6 +79,15 @@ + + + Profile: + + + + + +
diff --git a/src/popup/main.ts b/src/popup/main.ts index fec25f2..beeb488 100644 --- a/src/popup/main.ts +++ b/src/popup/main.ts @@ -10,6 +10,7 @@ import { watchSyncSuccess, getBrowserName, getHostname, + getDisplayProfileName, } from '../storage' function setConnected(connected: boolean | undefined) { @@ -35,6 +36,7 @@ async function renderStatus() { const consentStatus = await getConsentStatus() const browserName = await getBrowserName() const hostname = await getHostname() + const profileName = await getDisplayProfileName() // Enabled checkbox const enabledCheckbox = document.getElementById('status-enabled-checkbox') @@ -88,6 +90,12 @@ async function renderStatus() { if (!(hostnameElement instanceof HTMLElement)) throw Error('Hostname element is not defined') hostnameElement.innerText = hostname ?? 'unknown' + + // Profile + const profileElement = document.getElementById('status-profile') + if (!(profileElement instanceof HTMLElement)) + throw Error('Profile element is not defined') + profileElement.innerText = profileName } function domListeners() { diff --git a/src/settings/index.html b/src/settings/index.html index eceb53c..916b813 100644 --- a/src/settings/index.html +++ b/src/settings/index.html @@ -50,6 +50,20 @@ /> +
+ + + +
+
diff --git a/src/settings/main.ts b/src/settings/main.ts index 176d6de..e1e39ff 100644 --- a/src/settings/main.ts +++ b/src/settings/main.ts @@ -4,6 +4,8 @@ import { setBrowserName, getHostname, setHostname, + getCustomProfileName, + setCustomProfileName, } from '../storage' import { detectBrowser } from '../background/helpers' @@ -34,6 +36,9 @@ async function saveOptions(e: SubmitEvent): Promise { const hostname = hostnameInput.value + const profileNameInput = document.querySelector('#profileName') + const profileName = profileNameInput?.value.trim() || '' + const form = e.target as HTMLFormElement const button = form.querySelector('button') if (!button) return @@ -44,6 +49,10 @@ async function saveOptions(e: SubmitEvent): Promise { try { await setBrowserName(selectedBrowser) await setHostname(hostname) + + // Save custom profile name (empty string clears it) + await setCustomProfileName(profileName) + await reloadExtension() button.textContent = 'Save' button.classList.add('accept') @@ -95,6 +104,13 @@ async function restoreOptions(): Promise { if (hostname !== undefined) { hostnameInput.value = hostname } + + // Restore custom profile name + const customProfileName = await getCustomProfileName() + const profileNameInput = document.querySelector('#profileName') + if (profileNameInput && customProfileName) { + profileNameInput.value = customProfileName + } } catch (error) { console.error('Failed to restore options:', error) } diff --git a/src/storage.ts b/src/storage.ts index 60cfdd3..b690667 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -101,3 +101,49 @@ export const getHostname = (): Promise => .then((data: StorageData) => data.hostname as string | undefined) export const setHostname = (hostname: Hostname) => browser.storage.local.set({ hostname }) + +type ProfileIdentifier = string +export const getProfileIdentifier = (): Promise => + browser.storage.local + .get('profileIdentifier') + .then((data: StorageData) => data.profileIdentifier as string | undefined) + +export const setProfileIdentifier = (profileIdentifier: ProfileIdentifier) => + browser.storage.local.set({ profileIdentifier }) + +export const generateProfileIdentifier = async (): Promise => { + const existing = await getProfileIdentifier() + if (existing) { + return existing + } + + // Generate a unique identifier for this profile/installation + const profileId = crypto.randomUUID() + await setProfileIdentifier(profileId) + return profileId +} + +type CustomProfileName = string +export const getCustomProfileName = (): Promise => + browser.storage.local + .get('customProfileName') + .then((data: StorageData) => data.customProfileName as string | undefined) + +export const setCustomProfileName = (customProfileName: CustomProfileName) => { + if (customProfileName.trim() === '') { + // Remove custom profile name if empty string is provided + return browser.storage.local.remove('customProfileName') + } + return browser.storage.local.set({ customProfileName }) +} + +export const getDisplayProfileName = async (): Promise => { + const customName = await getCustomProfileName() + if (customName) { + return customName + } + + // Fallback to a shortened version of the unique identifier + const profileId = await generateProfileIdentifier() + return `Profile-${profileId.slice(0, 8)}` +} diff --git a/vite.config.ts b/vite.config.ts index c7dde5b..4066140 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,6 +22,6 @@ export default defineConfig({ manifest: generateManifest, additionalInputs: ['src/consent/index.html', 'src/consent/main.ts'], browser: process.env.VITE_TARGET_BROWSER, - }), + }), ], })