Skip to content

Commit

Permalink
Update the uninstall URL each time the ATB setting is updated (#2156)
Browse files Browse the repository at this point in the history
When a user uninstalls the extension, an survey is displayed which
aims to find out what the user's reason was. Some parameters are
included with that request (e.g. the browser name and version) to
help us diagnose issues. The ATB setting is included as well, so it's
important that we update the uninstall URL each time the ATB setting
is changed. So far, changes to the ATB setting were only reflected
after the next time the updateUninstallURL event fired, which led to a
delay of up to 10 minutes.
  • Loading branch information
kzar committed Aug 1, 2023
1 parent c9a132a commit 4fcbb1b
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 16 deletions.
23 changes: 16 additions & 7 deletions shared/js/background/atb.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const ATB = (() => {
if (!atbSetting) {
atbSetting = ATB_ERROR_COHORT
settings.updateSetting('atb', ATB_ERROR_COHORT)
ATB.setOrUpdateATBdnrRule(ATB_ERROR_COHORT)
errorParam = '&e=1'
}

Expand All @@ -63,10 +62,8 @@ const ATB = (() => {

if (res.data.updateVersion) {
settings.updateSetting('atb', res.data.updateVersion)
ATB.setOrUpdateATBdnrRule(res.data.updateVersion)
} else if (atbSetting === ATB_ERROR_COHORT) {
settings.updateSetting('atb', res.data.version)
ATB.setOrUpdateATBdnrRule(res.data.version)
}
})
},
Expand Down Expand Up @@ -105,7 +102,6 @@ const ATB = (() => {
const url = ddgAtbURL + randomValue + '&browser=' + parseUserAgentString().browser
return load.JSONfromExternalFile(url).then((res) => {
settings.updateSetting('atb', res.data.version)
ATB.setOrUpdateATBdnrRule(res.data.version)
}, () => {
console.log('couldn\'t reach atb.js for initial server call, trying again')
numTries += 1
Expand Down Expand Up @@ -189,7 +185,6 @@ const ATB = (() => {

if (atb) {
settings.updateSetting('atb', atb)
ATB.setOrUpdateATBdnrRule(atb)
}

ATB.finalizeATB(params)
Expand Down Expand Up @@ -280,9 +275,23 @@ const ATB = (() => {
}
})()

settings.ready().then(async () => {
settings.ready().then(() => {
const updateUninstallURL =
async () => { browserWrapper.setUninstallURL(await ATB.getSurveyURL()) }

// set initial uninstall url
browserWrapper.setUninstallURL(await ATB.getSurveyURL())
updateUninstallURL()

// Ensure the uninstall URL and the ATB declarativeNetRequest rules are also
// kept up to date as the ATB values are updated.
settings.onSettingUpdate.addEventListener(
'atb', event => {
const atb = event instanceof CustomEvent ? event.detail : undefined
updateUninstallURL()
ATB.setOrUpdateATBdnrRule(atb)
}
)
settings.onSettingUpdate.addEventListener('set_atb', updateUninstallURL)
})

export default ATB
4 changes: 0 additions & 4 deletions shared/js/background/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,6 @@ browserWrapper.createAlarm('updateHTTPSLists', {
browserWrapper.createAlarm('updateLists', {
periodInMinutes: tdsStorage.updatePeriodInMinutes
})
// update uninstall URL every 10 minutes
browserWrapper.createAlarm('updateUninstallURL', { periodInMinutes: 10 })
// remove expired HTTPS service entries
browserWrapper.createAlarm('clearExpiredHTTPSServiceCache', { periodInMinutes: 60 })
// Rotate the user agent spoofed
Expand All @@ -538,8 +536,6 @@ browser.alarms.onAlarm.addListener(async alarmEvent => {
} catch (e) {
console.log(e)
}
} else if (alarmEvent.name === 'updateUninstallURL') {
browser.runtime.setUninstallURL(await ATB.getSurveyURL())
} else if (alarmEvent.name === 'updateLists') {
await settings.ready()

Expand Down
14 changes: 11 additions & 3 deletions shared/js/background/settings.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const defaultSettings = require('../../data/defaultSettings')
const browserWrapper = require('./wrapper')

const onSettingUpdate = new EventTarget()

// @ts-expect-error Workaround for the tests in unit-tests/node/reference-tests.
if (typeof CustomEvent === 'undefined') globalThis.CustomEvent = Event

/**
* Settings whose defaults can by managed by the system administrator
*/
Expand Down Expand Up @@ -97,7 +102,7 @@ function buildSettingsFromDefaults () {
}

function syncSettingTolocalStorage () {
browserWrapper.syncToStorage({ settings })
return browserWrapper.syncToStorage({ settings })
}

function getSetting (name) {
Expand All @@ -123,7 +128,9 @@ function updateSetting (name, value) {
}

settings[name] = value
syncSettingTolocalStorage()
syncSettingTolocalStorage().then(() => {
onSettingUpdate.dispatchEvent(new CustomEvent(name, { detail: value }))
})
}

function removeSetting (name) {
Expand All @@ -148,5 +155,6 @@ module.exports = {
updateSetting,
removeSetting,
logSettings,
ready
ready,
onSettingUpdate
}
1 change: 1 addition & 0 deletions shared/js/background/storage/tds.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class TDSStorage {

this.isInstalling = false

// TODO: Use an EventTarget instead.
this._onUpdatedListeners = new Map()

this._onReadyResolvers = new Map()
Expand Down
2 changes: 1 addition & 1 deletion shared/js/background/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function getManifestVersion () {
}

export function syncToStorage (data) {
browser.storage.local.set(data)
return browser.storage.local.set(data)
}

// @ts-ignore
Expand Down
60 changes: 60 additions & 0 deletions unit-test/background/atb.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,63 @@ describe('complex install workflow cases', () => {
})
})
})

describe('atb.getUninstallURL()', () => {
it('should update the uninstall URL correctly as the ATB values are updated', async () => {
settingHelper.stub({ atb: null })

let uninstallUrl = null
const setUninstallURLSpy = spyOn(browser.runtime, 'setUninstallURL').and.callFake(url => {
uninstallUrl = url
})

const checkUninstallUrl = (expectedAtb, expectedSetAtb) => {
const { origin, pathname, searchParams } = new URL(uninstallUrl)

expect(origin).toEqual('https://duckduckgo.com')
expect(pathname).toEqual('/atb.js')
expect(searchParams.get('uninstall')).toEqual('1')
expect(searchParams.get('action')).toEqual('survey')
expect(searchParams.get('atb')).toEqual(expectedAtb)
expect(searchParams.get('set_atb')).toEqual(expectedSetAtb)
expect(searchParams.get('browser')).toEqual('Chrome')
expect(searchParams.get('bv')).toEqual('108')
expect(searchParams.get('v')).toEqual('1234.56')
}

// Initial state.
expect(settings.getSetting('atb')).toEqual(null)
expect(setUninstallURLSpy).not.toHaveBeenCalled()
expect(uninstallUrl).toEqual(null)

// Before the atb is set.
uninstallUrl = await atb.getSurveyURL()
checkUninstallUrl(null, null)

// After the atb is set.
stubLoadJSON({ returnedAtb: 'v119-8' })
await atb.setInitialVersions()
await new Promise(resolve => setTimeout(resolve, 0))
expect(settings.getSetting('atb')).toEqual('v119-8')
expect(setUninstallURLSpy).toHaveBeenCalledTimes(1)
checkUninstallUrl('v119-8', null)

// After set_atb is set.
await atb.updateSetAtb()
await new Promise(resolve => setTimeout(resolve, 0))
expect(setUninstallURLSpy).toHaveBeenCalledTimes(2)
checkUninstallUrl('v119-8', 'v119-8')

// After atb is updated.
settings.updateSetting('atb', 'v119-8a')
await new Promise(resolve => setTimeout(resolve, 0))
expect(setUninstallURLSpy).toHaveBeenCalledTimes(3)
checkUninstallUrl('v119-8a', 'v119-8')

// After set_atb is updated.
settings.updateSetting('set_atb', 'v119-8b')
await new Promise(resolve => setTimeout(resolve, 0))
expect(setUninstallURLSpy).toHaveBeenCalledTimes(4)
checkUninstallUrl('v119-8a', 'v119-8b')
})
})
7 changes: 6 additions & 1 deletion unit-test/helpers/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ const stub = (startingVals) => {
.and.callFake(key => settingObj[key])

const update = spyOn(origSettings, 'updateSetting')
.and.callFake((key, val) => { settingObj[key] = val })
.and.callFake((key, val) => {
settingObj[key] = val
origSettings.onSettingUpdate.dispatchEvent(
new CustomEvent(key, { detail: val })
)
})

const remove = spyOn(origSettings, 'removeSetting')
.and.callFake(key => { delete settingObj[key] })
Expand Down

0 comments on commit 4fcbb1b

Please sign in to comment.