Skip to content
Snippets Groups Projects
Verified Commit 5fe62195 authored by Pier Angelo Vendrame's avatar Pier Angelo Vendrame :jack_o_lantern:
Browse files

fixup! Bug 40597: Implement TorSettings module

Bug 41114: Refactor TorConnect.

Additional refactor to the AutoBootstrappingState: split the run in
more methods, removed some layers of try-catch.
parent 546449ce
No related branches found
No related tags found
1 merge request!938Bug 41114: Fix no-async-promise-executor on TorConnect and general refactor
......@@ -85,9 +85,13 @@ export class MoatRPC {
TorLauncherPrefs.bridgedb_reflector
);
const front = Services.prefs.getStringPref(TorLauncherPrefs.bridgedb_front);
const builder = new lazy.DomainFrontRequestBuilder();
await builder.init(reflector, front);
this.#requestBuilder = builder;
this.#requestBuilder = new lazy.DomainFrontRequestBuilder();
try {
await this.#requestBuilder.init(reflector, front);
} catch (e) {
this.#requestBuilder = null;
throw e;
}
}
async uninit() {
......
......
......@@ -160,6 +160,15 @@ class StateCallback {
await this.#promise;
lazy.logger.info(`${this.#state}'s run is done`);
} catch (err) {
if (this.transitioning) {
lazy.logger.error(
`A transition from ${
this.#state
} is already happening, silencing this exception.`,
err
);
return;
}
lazy.logger.error(
`${this.#state}'s run threw, transitioning to the Error state.`,
err
......@@ -393,6 +402,7 @@ class AutoBootstrappingState extends StateCallback {
#bootstrap = null;
#moat = null;
#settings;
#changedSettings = false;
#transitionPromise;
#transitionResolve;
......@@ -413,45 +423,96 @@ class AutoBootstrappingState extends StateCallback {
if (await this.#simulateCensorship(countryCode)) {
return;
}
await this.#initMoat();
if (this.transitioning) {
return;
}
await this.#fetchSettings(countryCode);
if (this.transitioning) {
return;
}
await this.#trySettings();
}
const throwError = (message, details) => {
let err = new Error(message);
err.details = details;
throw err;
};
/**
* Simulate a censorship event, if needed.
*
* @param {string} countryCode The country code passed to the state
* @returns {Promise<boolean>} true if we are simulating the censorship and
* the bootstrap should stop immediately, or false if the bootstrap should
* continue normally.
*/
async #simulateCensorship(countryCode) {
const censorshipLevel = Services.prefs.getIntPref(
TorConnectPrefs.censorship_level,
0
);
if (censorshipLevel <= 0) {
return false;
}
// Lookup user's potential censorship circumvention settings from Moat
// service.
try {
// Very severe censorship: always fail even after manually selecting
// location specific settings.
if (censorshipLevel === 3) {
await debugSleep(2500);
this.changeState(
TorConnectState.Error,
"Error: censorship simulation",
""
);
return true;
}
// Severe censorship: only fail after auto selecting, but succeed after
// manually selecting a country.
if (censorshipLevel === 2 && !countryCode) {
await debugSleep(2500);
this.changeState(
TorConnectState.Error,
"Error: Severe Censorship simulation",
""
);
return true;
}
return false;
}
/**
* Initialize the MoatRPC to communicate with the backend.
*/
async #initMoat() {
this.#moat = new lazy.MoatRPC();
// We need to wait Moat's initialization even when we are requested to
// transition to another state to be sure its uninit will have its
// intended effect. So, do not use Promise.race here.
// transition to another state to be sure its uninit will have its intended
// effect. So, do not use Promise.race here.
await this.#moat.init();
if (this.transitioning) {
return;
}
const settings = await Promise.race([
/**
* Lookup user's potential censorship circumvention settings from Moat
* service.
*/
async #fetchSettings(countryCode) {
// For now, throw any errors we receive from the backend, except when it was
// unable to detect user's country/region.
// If we use specialized error objects, we could pass the original errors to
// them.
const settings = await Promise.resolve([
this.#moat.circumvention_settings(
[...TorSettings.builtinBridgeTypes, "vanilla"],
countryCode
),
this.#transitionPromise,
]);
if (this.transitioning) {
return;
}
if (settings?.country) {
TorConnect._detectedLocation = settings.country;
}
if (settings?.settings && settings.settings.length) {
this.#settings = settings.settings;
} else {
try {
} else if (!this.transitioning) {
// Keep consistency with the other call.
this.#settings = await Promise.race([
this.#moat.circumvention_defaults([
...TorSettings.builtinBridgeTypes,
......@@ -459,39 +520,38 @@ class AutoBootstrappingState extends StateCallback {
]),
this.#transitionPromise,
]);
} catch (err) {
lazy.logger.error(
"We did not get localized settings, and default settings failed as well",
err
);
}
}
if (this.transitioning) {
return;
}
if (
(!this.#settings || this.#settings.length === 0) &&
!this.transitioning
) {
// Both localized and fallback have, we can just throw to transition to
// the error state (but only if we aren't already transitioning).
// TODO: Let the UI layer localize the strings.
if (!this.#settings || this.#settings.length === 0) {
// The fallback has failed as well, so throw the original error
if (!TorConnect._detectedLocation) {
// unable to determine country
throwError(
this.#throwError(
TorStrings.torConnect.autoBootstrappingFailed,
TorStrings.torConnect.cannotDetermineCountry
);
} else {
// no settings available for country
throwError(
this.#throwError(
TorStrings.torConnect.autoBootstrappingFailed,
TorStrings.torConnect.noSettingsForCountry
);
}
}
}
// apply each of our settings and try to bootstrap with each
try {
/**
* Try to apply the settings we fetched.
*/
async #trySettings() {
// Otherwise, apply each of our settings and try to bootstrap with each.
for (const [index, currentSetting] of this.#settings.entries()) {
// we want to break here so we can fall through and restore original settings
if (this.transitioning) {
break;
}
......@@ -502,28 +562,28 @@ class AutoBootstrappingState extends StateCallback {
}`
);
// Send the new settings directly to the provider. We will
// save them only if the bootstrap succeeds.
// FIXME: We should somehow signal TorSettings users that we
// have set custom settings, and they should not apply
// theirs until we are done with trying ours.
// Otherwise, the new settings provided by the user while we
// were bootstrapping could be the ones that cause the
// bootstrap to succeed, but we overwrite them (unless we
// backup the original settings, and then save our new
// settings only if they have not changed).
// Another idea (maybe easier to implement) is to disable
// the settings UI while *any* bootstrap is going on.
// Send the new settings directly to the provider. We will save them only
// if the bootstrap succeeds.
// FIXME: We should somehow signal TorSettings users that we have set
// custom settings, and they should not apply theirs until we are done
// with trying ours.
// Otherwise, the new settings provided by the user while we were
// bootstrapping could be the ones that cause the bootstrap to succeed,
// but we overwrite them (unless we backup the original settings, and then
// save our new settings only if they have not changed).
// Another idea (maybe easier to implement) is to disable the settings
// UI while *any* bootstrap is going on.
// This is also documented in tor-browser#41921.
const provider = await lazy.TorProviderBuilder.build();
// We need to merge with old settings, in case the user is
// using a proxy or is behind a firewall.
this.#changedSettings = true;
// We need to merge with old settings, in case the user is using a proxy
// or is behind a firewall.
await provider.writeSettings({
...TorSettings.getSettings(),
...currentSetting,
});
// build out our bootstrap request
// Build out our bootstrap request.
this.#bootstrap = new lazy.TorBootstrapRequest();
this.#bootstrap.onbootstrapstatus = (progress, status) => {
TorConnect._updateBootstrapStatus(progress, status);
......@@ -532,14 +592,14 @@ class AutoBootstrappingState extends StateCallback {
lazy.logger.error(`Auto-Bootstrap error => ${message}; ${details}`);
};
// begin bootstrap
// Begin the bootstrap.
if (
await Promise.race([
this.#bootstrap.bootstrap(),
this.#transitionPromise,
])
) {
// persist the current settings to preferences
// Persist the current settings to preferences.
TorSettings.setSettings(currentSetting);
TorSettings.saveToPrefs();
await TorSettings.applySettings();
......@@ -548,50 +608,20 @@ class AutoBootstrappingState extends StateCallback {
}
}
// Bootstrap failed for all potential settings, so restore the
// original settings the provider.
await this.#restoreOriginalSettings();
// Only explicitly change state here if something else has not
// transitioned us.
// Only explicitly change state here if something else has not transitioned
// us.
if (!this.transitioning) {
throwError(
this.#throwError(
TorStrings.torConnect.autoBootstrappingFailed,
TorStrings.torConnect.autoBootstrappingAllFailed
);
}
return;
} catch (err) {
await this.#restoreOriginalSettings();
// throw to outer catch to transition us.
throw err;
}
} catch (err) {
if (this.#moat?.inited && !TorConnect._countryCodes?.length) {
// lookup countries which have settings available
try {
TorConnect._countryCodes = await this.#moat.circumvention_countries();
} catch (e) {
lazy.logger.warn(
"Could not get the list of countried for which we have circumvention data",
e
);
}
}
if (!this.transitioning) {
this.changeState(TorConnectState.Error, err?.message, err?.details);
} else {
lazy.logger.error(
"Received AutoBootstrapping error after transitioning",
err
);
}
} finally {
// important to uninit MoatRPC object or else the pt process will live
// as long as the browser.
this.#moat?.uninit();
}
#throwError(message, details) {
let err = new Error(message);
err.details = details;
throw err;
}
transitionRequested() {
......@@ -603,14 +633,7 @@ class AutoBootstrappingState extends StateCallback {
this.#moat?.uninit();
this.#moat = null;
// If next state is configuring, it means the user canceled the bootstrap
if (nextState === TorConnectState.Configuring) {
await this.#bootstrap?.cancel();
await this.#restoreOriginalSettings();
}
}
async #restoreOriginalSettings() {
if (this.#changedSettings && nextState !== TorConnectState.Bootstrapped) {
try {
await TorSettings.applySettings();
} catch (e) {
......@@ -621,48 +644,10 @@ class AutoBootstrappingState extends StateCallback {
}
}
/**
* Simulate a censorship event, if needed.
*
* @param {string} countryCode The country code passed to the state
* @returns {Promise<boolean>} true if we are simulating the censorship and
* the bootstrap should stop immediately, or false if the bootstrap should
* continue normally.
*/
async #simulateCensorship(countryCode) {
const censorshipLevel = Services.prefs.getIntPref(
TorConnectPrefs.censorship_level,
0
);
if (censorshipLevel <= 0) {
return false;
}
// Very severe censorship: always fail even after manually selecting
// location specific settings.
if (censorshipLevel === 3) {
await debugSleep(2500);
this.changeState(
TorConnectState.Error,
"Error: censorship simulation",
""
);
return true;
}
// Severe censorship: only fail after auto selecting, but succeed after
// manually selecting a country.
if (censorshipLevel === 2 && !countryCode) {
await debugSleep(2500);
this.changeState(
TorConnectState.Error,
"Error: Severe Censorship simulation",
""
);
return true;
// If next state is configuring, it means the user canceled the bootstrap
if (nextState === TorConnectState.Configuring) {
await this.#bootstrap?.cancel();
}
return false;
}
}
......@@ -1225,8 +1210,8 @@ export const TorConnect = {
},
async getCountryCodes() {
// Difference with the getter: this is to be called by TorConnectParent, and downloads
// the country codes if they are not already in cache.
// Difference with the getter: this is to be called by TorConnectParent, and
// downloads the country codes if they are not already in cache.
if (this._countryCodes.length) {
return this._countryCodes;
}
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment