Skip to content

Commit e7df5cc

Browse files
BelKedcweiske
andauthored
feat: add automatic & manual hostname configuration (#158)
* Add generic settings button in popup .. and remove the browser edit button * Automatically detect and use hostname .. so that the bucket has the right host name. Resolves: #132 * Add manual hostname configuration * Display the settings below each other * Display hostname in the popup status * Handle undefined hostname * Lint * Enhance hostname detection with detailed logging and error handling --------- Co-authored-by: Christian Weiske <[email protected]>
1 parent 7fa2f8d commit e7df5cc

File tree

8 files changed

+160
-43
lines changed

8 files changed

+160
-43
lines changed

src/background/client.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@ import config from '../config'
33
import { AWClient, IEvent } from 'aw-client'
44
import retry from 'p-retry'
55
import { emitNotification, getBrowser, logHttpError } from './helpers'
6-
import { getSyncStatus, setSyncStatus } from '../storage'
6+
import { getHostname, getSyncStatus, setSyncStatus } from '../storage'
77

88
export const getClient = () =>
99
new AWClient('aw-client-web', { testing: config.isDevelopment })
1010

1111
// TODO: We might want to get the hostname somehow, maybe like this:
1212
// https://stackoverflow.com/questions/28223087/how-can-i-allow-firefox-or-chrome-to-read-a-pcs-hostname-or-other-assignable
13-
export function ensureBucket(client: AWClient, bucketId: string) {
13+
export function ensureBucket(
14+
client: AWClient,
15+
bucketId: string,
16+
hostname: string,
17+
) {
1418
return retry(
1519
() =>
1620
client
17-
.ensureBucket(bucketId, 'web.tab.current', 'unknown')
21+
.ensureBucket(bucketId, 'web.tab.current', hostname)
1822
.catch((err) => {
1923
console.error('Failed to create bucket, retrying...')
2024
logHttpError(err)
@@ -24,13 +28,41 @@ export function ensureBucket(client: AWClient, bucketId: string) {
2428
)
2529
}
2630

31+
export async function detectHostname(client: AWClient) {
32+
console.debug('Attempting to detect hostname from server...')
33+
return retry(
34+
() => {
35+
console.debug('Making request to server for hostname...')
36+
return client.getInfo()
37+
},
38+
{
39+
retries: 3,
40+
onFailedAttempt: (error) => {
41+
console.warn(
42+
`Failed to detect hostname (attempt ${error.attemptNumber}/${error.retriesLeft + error.attemptNumber}):`,
43+
error.message,
44+
)
45+
},
46+
},
47+
)
48+
.then((info) => {
49+
console.info('Successfully detected hostname:', info.hostname)
50+
return info.hostname
51+
})
52+
.catch((err) => {
53+
console.error('All attempts to detect hostname failed:', err)
54+
return undefined
55+
})
56+
}
57+
2758
export async function sendHeartbeat(
2859
client: AWClient,
2960
bucketId: string,
3061
timestamp: Date,
3162
data: IEvent['data'],
3263
pulsetime: number,
3364
) {
65+
const hostname = (await getHostname()) ?? 'unknown'
3466
const syncStatus = await getSyncStatus()
3567
return retry(
3668
() =>
@@ -41,7 +73,8 @@ export async function sendHeartbeat(
4173
}),
4274
{
4375
retries: 3,
44-
onFailedAttempt: () => ensureBucket(client, bucketId).then(() => {}),
76+
onFailedAttempt: () =>
77+
ensureBucket(client, bucketId, hostname).then(() => {}),
4578
},
4679
)
4780
.then(() => {
@@ -67,5 +100,10 @@ export async function sendHeartbeat(
67100

68101
export const getBucketId = async (): Promise<string> => {
69102
const browser = await getBrowser()
70-
return `aw-watcher-web-${browser}`
103+
const hostname = await getHostname()
104+
if (hostname !== undefined) {
105+
return `aw-watcher-web-${browser}_${hostname}`
106+
} else {
107+
return `aw-watcher-web-${browser}`
108+
}
71109
}

src/background/main.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import {
55
sendInitialHeartbeat,
66
tabActivatedListener,
77
} from './heartbeat'
8-
import { getClient } from './client'
8+
import { getClient, detectHostname } from './client'
99
import {
1010
getConsentStatus,
11+
getHostname,
1112
setBaseUrl,
1213
setConsentStatus,
1314
setEnabled,
15+
setHostname,
1416
waitForEnabled,
1517
} from '../storage'
1618

@@ -22,6 +24,22 @@ async function getIsConsentRequired() {
2224
.catch(() => true)
2325
}
2426

27+
async function autodetectHostname() {
28+
const hostname = await getHostname()
29+
if (hostname === undefined) {
30+
const detectedHostname = await detectHostname(client)
31+
if (detectedHostname !== undefined) {
32+
setHostname(detectedHostname)
33+
}
34+
}
35+
}
36+
37+
/** Init */
38+
console.info('Starting...')
39+
40+
console.debug('Creating client')
41+
const client = getClient()
42+
2543
browser.runtime.onInstalled.addListener(async () => {
2644
const { consent } = await getConsentStatus()
2745
const isConsentRequired = await getIsConsentRequired()
@@ -38,13 +56,9 @@ browser.runtime.onInstalled.addListener(async () => {
3856
url: browser.runtime.getURL('src/consent/index.html'),
3957
})
4058
}
41-
})
42-
43-
/** Init */
44-
console.info('Starting...')
4559

46-
console.debug('Creating client')
47-
const client = getClient()
60+
await autodetectHostname()
61+
})
4862

4963
console.debug('Creating alarms and tab listeners')
5064
browser.alarms.create(config.heartbeat.alarmName, {

src/popup/index.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
<b>Visit forum</b>
2929
</span>
3030
</a>
31+
<a id="settings-btn" href="#" title="Settings">
32+
<span class="button">
33+
<b></b>
34+
</span>
35+
</a>
3136
</div>
3237

3338
<table>
@@ -63,7 +68,15 @@
6368
<code id="status-browser">
6469
<!-- Filled by JS -->
6570
</code>
66-
<button id="edit-btn"></button>
71+
</td>
72+
</tr>
73+
74+
<tr>
75+
<th align="right">Hostname:</th>
76+
<td>
77+
<code id="status-hostname">
78+
<!-- Filled by JS -->
79+
</code>
6780
</td>
6881
</tr>
6982
</table>

src/popup/main.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
watchSyncDate,
1010
watchSyncSuccess,
1111
getBrowserName,
12+
getHostname,
1213
} from '../storage'
1314

1415
function setConnected(connected: boolean | undefined) {
@@ -33,6 +34,7 @@ async function renderStatus() {
3334
const syncStatus = await getSyncStatus()
3435
const consentStatus = await getConsentStatus()
3536
const browserName = await getBrowserName()
37+
const hostname = await getHostname()
3638

3739
// Enabled checkbox
3840
const enabledCheckbox = document.getElementById('status-enabled-checkbox')
@@ -75,10 +77,17 @@ async function renderStatus() {
7577
throw Error('Web UI link is not an anchor')
7678
webuiLink.href = baseUrl ?? '#'
7779

80+
// Browser name
7881
const browserNameElement = document.getElementById('status-browser')
7982
if (!(browserNameElement instanceof HTMLElement))
8083
throw Error('Browser name element is not defined')
8184
browserNameElement.innerText = browserName
85+
86+
// Hostname
87+
const hostnameElement = document.getElementById('status-hostname')
88+
if (!(hostnameElement instanceof HTMLElement))
89+
throw Error('Hostname element is not defined')
90+
hostnameElement.innerText = hostname
8291
}
8392

8493
function domListeners() {
@@ -98,10 +107,10 @@ function domListeners() {
98107
})
99108
})
100109

101-
const browserButton = document.getElementById('edit-btn')
102-
if (!(browserButton instanceof HTMLButtonElement))
103-
throw Error('Edit button is not a button')
104-
browserButton.addEventListener('click', () => {
110+
const settingsButton = document.getElementById('settings-btn')
111+
if (!(settingsButton instanceof HTMLAnchorElement))
112+
throw Error('Settings button is not a link')
113+
settingsButton.addEventListener('click', () => {
105114
browser.runtime.openOptionsPage()
106115
})
107116
}

src/settings/index.html

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,51 @@
88

99
<body>
1010
<form>
11-
<label for="browser">
12-
<strong>Browser:</strong>
13-
</label>
11+
<div>
12+
<label for="browser">
13+
<strong>Browser:</strong>
14+
</label>
1415

15-
<select id="browser" name="browser">
16-
<option value="chrome">Chrome</option>
17-
<option value="firefox">Firefox</option>
18-
<option value="opera">Opera</option>
19-
<option value="brave">Brave</option>
20-
<option value="edge">Edge</option>
21-
<option value="arc">Arc</option>
22-
<option value="vivaldi">Vivaldi</option>
23-
<option value="orion">Orion</option>
24-
<option value="yandex">Yandex</option>
25-
<option value="zen">Zen</option>
26-
<option value="other">Other...</option>
27-
</select>
16+
<select id="browser" name="browser">
17+
<option value="chrome">Chrome</option>
18+
<option value="firefox">Firefox</option>
19+
<option value="opera">Opera</option>
20+
<option value="brave">Brave</option>
21+
<option value="edge">Edge</option>
22+
<option value="arc">Arc</option>
23+
<option value="vivaldi">Vivaldi</option>
24+
<option value="orion">Orion</option>
25+
<option value="yandex">Yandex</option>
26+
<option value="zen">Zen</option>
27+
<option value="other">Other...</option>
28+
</select>
2829

29-
<input
30-
type="text"
31-
id="customBrowser"
32-
name="customBrowser"
33-
pattern="[a-z]*"
34-
placeholder="Custom browser name"
35-
/>
30+
<input
31+
type="text"
32+
id="customBrowser"
33+
name="customBrowser"
34+
pattern="[a-z]*"
35+
placeholder="Custom browser name"
36+
/>
37+
</div>
3638

37-
<button type="submit" class="accept">Save</button>
39+
<div>
40+
<label for="hostname">
41+
<strong>Hostname:</strong>
42+
</label>
43+
44+
<input
45+
type="text"
46+
id="hostname"
47+
name="hostname"
48+
pattern="[a-zA-Z0-9_.\-]*"
49+
required
50+
/>
51+
</div>
52+
53+
<div>
54+
<button type="submit">Save</button>
55+
</div>
3856
</form>
3957

4058
<script type="module" src="./main.ts"></script>

src/settings/main.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import browser from 'webextension-polyfill'
2-
import { getBrowserName, setBrowserName } from '../storage'
2+
import {
3+
getBrowserName,
4+
setBrowserName,
5+
getHostname,
6+
setHostname,
7+
} from '../storage'
38

49
interface HTMLElementEvent<T extends HTMLElement> extends Event {
510
target: T
@@ -22,6 +27,9 @@ async function saveOptions(e: SubmitEvent): Promise<void> {
2227
selectedBrowser = customBrowserInput.value.toLowerCase()
2328
}
2429

30+
const hostnameInput = document.querySelector<HTMLInputElement>('#hostname')
31+
let hostname = hostnameInput.value
32+
2533
const form = e.target as HTMLFormElement
2634
const button = form.querySelector<HTMLButtonElement>('button')
2735
if (!button) return
@@ -31,6 +39,7 @@ async function saveOptions(e: SubmitEvent): Promise<void> {
3139

3240
try {
3341
await setBrowserName(selectedBrowser)
42+
await setHostname(hostname)
3443
await reloadExtension()
3544
button.textContent = 'Save'
3645
button.classList.add('accept')
@@ -74,6 +83,12 @@ async function restoreOptions(): Promise<void> {
7483
customInput.style.display = 'none'
7584
customInput.required = false
7685
}
86+
87+
const hostname = await getHostname()
88+
const hostnameInput = document.querySelector<HTMLInputElement>('#hostname')
89+
if (hostname !== undefined) {
90+
hostnameInput.value = hostname
91+
}
7792
} catch (error) {
7893
console.error('Failed to restore options:', error)
7994
}

src/settings/style.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ body {
22
font-family: 'Segoe UI', 'Lucida Grande', Tahoma, sans-serif;
33
}
44

5-
form {
5+
form div {
66
display: flex;
77
gap: 10px;
88
align-items: center;
99
margin: 0.5em;
1010
}
1111

12+
input:invalid {
13+
border-color: red;
14+
}
15+
1216
#customBrowser {
1317
display: none;
1418
}

src/storage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,9 @@ export const getBrowserName = (): Promise<BrowserName | undefined> =>
9090
browser.storage.local.get('browserName').then((_) => _.browserName)
9191
export const setBrowserName = (browserName: BrowserName) =>
9292
browser.storage.local.set({ browserName })
93+
94+
type Hostname = string
95+
export const getHostname = (): Promise<Hostname | undefined> =>
96+
browser.storage.local.get('hostname').then((_) => _.hostname)
97+
export const setHostname = (hostname: Hostname) =>
98+
browser.storage.local.set({ hostname })

0 commit comments

Comments
 (0)