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,
- }),
+ }),
],
})