... |
... |
@@ -117,52 +117,6 @@ XPCOMUtils.defineLazyGetter( |
117
|
117
|
└───────────────────────┘
|
118
|
118
|
*/
|
119
|
119
|
|
120
|
|
-/* Maps allowed state transitions
|
121
|
|
- TorConnectStateTransitions[state] maps to an array of allowed states to transition to
|
122
|
|
- This is just an encoding of the above transition diagram that we verify at runtime
|
123
|
|
-*/
|
124
|
|
-const TorConnectStateTransitions = Object.freeze(
|
125
|
|
- new Map([
|
126
|
|
- [
|
127
|
|
- TorConnectState.Initial,
|
128
|
|
- [
|
129
|
|
- TorConnectState.Disabled,
|
130
|
|
- TorConnectState.Bootstrapping,
|
131
|
|
- TorConnectState.Configuring,
|
132
|
|
- TorConnectState.Error,
|
133
|
|
- ],
|
134
|
|
- ],
|
135
|
|
- [
|
136
|
|
- TorConnectState.Configuring,
|
137
|
|
- [
|
138
|
|
- TorConnectState.AutoBootstrapping,
|
139
|
|
- TorConnectState.Bootstrapping,
|
140
|
|
- TorConnectState.Error,
|
141
|
|
- ],
|
142
|
|
- ],
|
143
|
|
- [
|
144
|
|
- TorConnectState.AutoBootstrapping,
|
145
|
|
- [
|
146
|
|
- TorConnectState.Configuring,
|
147
|
|
- TorConnectState.Bootstrapped,
|
148
|
|
- TorConnectState.Error,
|
149
|
|
- ],
|
150
|
|
- ],
|
151
|
|
- [
|
152
|
|
- TorConnectState.Bootstrapping,
|
153
|
|
- [
|
154
|
|
- TorConnectState.Configuring,
|
155
|
|
- TorConnectState.Bootstrapped,
|
156
|
|
- TorConnectState.Error,
|
157
|
|
- ],
|
158
|
|
- ],
|
159
|
|
- [TorConnectState.Error, [TorConnectState.Configuring]],
|
160
|
|
- [TorConnectState.Bootstrapped, [TorConnectState.Configuring]],
|
161
|
|
- // terminal states
|
162
|
|
- [TorConnectState.Disabled, []],
|
163
|
|
- ])
|
164
|
|
-);
|
165
|
|
-
|
166
|
120
|
/* Topics Notified by the TorConnect module */
|
167
|
121
|
export const TorConnectTopics = Object.freeze({
|
168
|
122
|
StateChange: "torconnect:state-change",
|
... |
... |
@@ -171,81 +125,612 @@ export const TorConnectTopics = Object.freeze({ |
171
|
125
|
BootstrapError: "torconnect:bootstrap-error",
|
172
|
126
|
});
|
173
|
127
|
|
174
|
|
-// The StateCallback is a wrapper around an async function which executes during
|
175
|
|
-// the lifetime of a TorConnect State. A system is also provided to allow this
|
176
|
|
-// ongoing function to early-out via a per StateCallback on_transition callback
|
177
|
|
-// which may be called externally when we need to early-out and move on to another
|
178
|
|
-// state (for example, from Bootstrapping to Configuring in the event the user
|
179
|
|
-// cancels a bootstrap attempt)
|
|
128
|
+// The StateCallback is the base class to implement the various states.
|
|
129
|
+// All states should extend it and implement a `run` function, which can
|
|
130
|
+// optionally be async, and define an array of valid transitions.
|
|
131
|
+// The parent class will handle everything else, including the transition to
|
|
132
|
+// other states when the run function is complete etc...
|
|
133
|
+// A system is also provided to allow this function to early-out. The runner
|
|
134
|
+// should check the transitioning getter when appropriate and return.
|
|
135
|
+// In addition to that, a state can implement a transitionRequested callback,
|
|
136
|
+// which can be used in conjunction with a mechanism like Promise.race.
|
|
137
|
+// This allows to handle, for example, users' requests to cancel a bootstrap
|
|
138
|
+// attempt.
|
|
139
|
+// A state can optionally define a cleanup function, that will be run in all
|
|
140
|
+// cases before transitioning to the next state.
|
180
|
141
|
class StateCallback {
|
181
|
|
- constructor(state, callback) {
|
182
|
|
- this._state = state;
|
183
|
|
- this._callback = callback;
|
184
|
|
- this._init();
|
185
|
|
- }
|
|
142
|
+ #state;
|
|
143
|
+ #promise;
|
|
144
|
+ #transitioning = false;
|
186
|
145
|
|
187
|
|
- _init() {
|
188
|
|
- // this context object is bound to the callback each time transition is
|
189
|
|
- // attempted via begin()
|
190
|
|
- this._context = {
|
191
|
|
- // This callback may be overwritten in the _callback for each state
|
192
|
|
- // States may have various pieces of work which need to occur
|
193
|
|
- // before they can be exited (eg resource cleanup)
|
194
|
|
- // See the _stateCallbacks map for examples
|
195
|
|
- on_transition: nextState => {},
|
196
|
|
-
|
197
|
|
- // flag used to determine if a StateCallback should early-out
|
198
|
|
- // its work
|
199
|
|
- _transitioning: false,
|
200
|
|
-
|
201
|
|
- // may be called within the StateCallback to determine if exit is possible
|
202
|
|
- get transitioning() {
|
203
|
|
- return this._transitioning;
|
204
|
|
- },
|
205
|
|
- };
|
|
146
|
+ constructor(stateName) {
|
|
147
|
+ this.#state = stateName;
|
206
|
148
|
}
|
207
|
149
|
|
208
|
150
|
async begin(...args) {
|
209
|
|
- lazy.logger.trace(`Entering ${this._state} state`);
|
210
|
|
- this._init();
|
|
151
|
+ lazy.logger.trace(`Entering ${this.#state} state`);
|
|
152
|
+ // Make sure we always have an actual promise.
|
211
|
153
|
try {
|
212
|
|
- // this Promise will block until this StateCallback has completed its work
|
213
|
|
- await Promise.resolve(this._callback.call(this._context, ...args));
|
214
|
|
- lazy.logger.info(`Exited ${this._state} state`);
|
215
|
|
-
|
216
|
|
- // handled state transition
|
217
|
|
- Services.obs.notifyObservers(
|
218
|
|
- { state: this._nextState },
|
219
|
|
- TorConnectTopics.StateChange
|
220
|
|
- );
|
221
|
|
- TorConnect._callback(this._nextState).begin(...this._nextStateArgs);
|
222
|
|
- } catch (obj) {
|
223
|
|
- TorConnect._changeState(
|
224
|
|
- TorConnectState.Error,
|
225
|
|
- obj?.message,
|
226
|
|
- obj?.details
|
|
154
|
+ this.#promise = Promise.resolve(this.run(...args));
|
|
155
|
+ } catch (err) {
|
|
156
|
+ this.#promise = Promise.reject(err);
|
|
157
|
+ }
|
|
158
|
+ try {
|
|
159
|
+ // If the callback throws, transition to error as soon as possible.
|
|
160
|
+ await this.#promise;
|
|
161
|
+ lazy.logger.info(`${this.#state}'s run is done`);
|
|
162
|
+ } catch (err) {
|
|
163
|
+ if (this.transitioning) {
|
|
164
|
+ lazy.logger.error(
|
|
165
|
+ `A transition from ${
|
|
166
|
+ this.#state
|
|
167
|
+ } is already happening, silencing this exception.`,
|
|
168
|
+ err
|
|
169
|
+ );
|
|
170
|
+ return;
|
|
171
|
+ }
|
|
172
|
+ lazy.logger.error(
|
|
173
|
+ `${this.#state}'s run threw, transitioning to the Error state.`,
|
|
174
|
+ err
|
227
|
175
|
);
|
|
176
|
+ this.changeState(TorConnectState.Error, err?.message, err?.details);
|
|
177
|
+ }
|
|
178
|
+ }
|
|
179
|
+
|
|
180
|
+ async end(nextState) {
|
|
181
|
+ lazy.logger.trace(
|
|
182
|
+ `Ending state ${this.#state} (to transition to ${nextState})`
|
|
183
|
+ );
|
|
184
|
+
|
|
185
|
+ if (this.#transitioning) {
|
|
186
|
+ // Should we check turn this into an error?
|
|
187
|
+ // It will make dealing with the error state harder.
|
|
188
|
+ lazy.logger.warn("this.#transitioning is already true.");
|
|
189
|
+ }
|
|
190
|
+
|
|
191
|
+ // Signal we should bail out ASAP.
|
|
192
|
+ this.#transitioning = true;
|
|
193
|
+ if (this.transitionRequested) {
|
|
194
|
+ this.transitionRequested();
|
228
|
195
|
}
|
|
196
|
+
|
|
197
|
+ lazy.logger.debug(
|
|
198
|
+ `Waiting for the ${
|
|
199
|
+ this.#state
|
|
200
|
+ }'s callback to return before the transition.`
|
|
201
|
+ );
|
|
202
|
+ try {
|
|
203
|
+ await this.#promise;
|
|
204
|
+ } finally {
|
|
205
|
+ lazy.logger.debug(`Calling ${this.#state}'s cleanup, if implemented.`);
|
|
206
|
+ if (this.cleanup) {
|
|
207
|
+ try {
|
|
208
|
+ await this.cleanup(nextState);
|
|
209
|
+ lazy.logger.debug(`${this.#state}'s cleanup function done.`);
|
|
210
|
+ } catch (e) {
|
|
211
|
+ lazy.logger.warn(`${this.#state}'s cleanup function threw.`, e);
|
|
212
|
+ }
|
|
213
|
+ }
|
|
214
|
+ }
|
|
215
|
+ }
|
|
216
|
+
|
|
217
|
+ changeState(stateName, ...args) {
|
|
218
|
+ TorConnect._changeState(stateName, ...args);
|
229
|
219
|
}
|
230
|
220
|
|
231
|
|
- transition(nextState, ...args) {
|
232
|
|
- this._nextState = nextState;
|
233
|
|
- this._nextStateArgs = [...args];
|
|
221
|
+ get transitioning() {
|
|
222
|
+ return this.#transitioning;
|
|
223
|
+ }
|
234
|
224
|
|
235
|
|
- // calls the on_transition callback to resolve any async work or do per-state cleanup
|
236
|
|
- // this call to on_transition should resolve the async work currentlying going on in this.begin()
|
237
|
|
- this._context.on_transition(nextState);
|
238
|
|
- this._context._transitioning = true;
|
|
225
|
+ get state() {
|
|
226
|
+ return this.#state;
|
239
|
227
|
}
|
240
|
228
|
}
|
241
|
229
|
|
242
|
230
|
// async method to sleep for a given amount of time
|
243
|
|
-const debug_sleep = async ms => {
|
|
231
|
+const debugSleep = async ms => {
|
244
|
232
|
return new Promise((resolve, reject) => {
|
245
|
233
|
setTimeout(resolve, ms);
|
246
|
234
|
});
|
247
|
235
|
};
|
248
|
236
|
|
|
237
|
+class InitialState extends StateCallback {
|
|
238
|
+ allowedTransitions = Object.freeze([
|
|
239
|
+ TorConnectState.Disabled,
|
|
240
|
+ TorConnectState.Bootstrapping,
|
|
241
|
+ TorConnectState.Configuring,
|
|
242
|
+ TorConnectState.Error,
|
|
243
|
+ ]);
|
|
244
|
+
|
|
245
|
+ constructor() {
|
|
246
|
+ super(TorConnectState.Initial);
|
|
247
|
+ }
|
|
248
|
+
|
|
249
|
+ run() {
|
|
250
|
+ // TODO: Block this transition until we successfully build a TorProvider.
|
|
251
|
+ }
|
|
252
|
+}
|
|
253
|
+
|
|
254
|
+class ConfiguringState extends StateCallback {
|
|
255
|
+ allowedTransitions = Object.freeze([
|
|
256
|
+ TorConnectState.AutoBootstrapping,
|
|
257
|
+ TorConnectState.Bootstrapping,
|
|
258
|
+ TorConnectState.Error,
|
|
259
|
+ ]);
|
|
260
|
+
|
|
261
|
+ constructor() {
|
|
262
|
+ super(TorConnectState.Configuring);
|
|
263
|
+ }
|
|
264
|
+
|
|
265
|
+ run() {
|
|
266
|
+ // The configuring state does not do anything.
|
|
267
|
+ }
|
|
268
|
+}
|
|
269
|
+
|
|
270
|
+class BootstrappingState extends StateCallback {
|
|
271
|
+ #bootstrap = null;
|
|
272
|
+ #bootstrapError = "";
|
|
273
|
+ #bootstrapErrorDetails = "";
|
|
274
|
+ #internetTest = null;
|
|
275
|
+ #cancelled = false;
|
|
276
|
+
|
|
277
|
+ allowedTransitions = Object.freeze([
|
|
278
|
+ TorConnectState.Configuring,
|
|
279
|
+ TorConnectState.Bootstrapped,
|
|
280
|
+ TorConnectState.Error,
|
|
281
|
+ ]);
|
|
282
|
+
|
|
283
|
+ constructor() {
|
|
284
|
+ super(TorConnectState.Bootstrapping);
|
|
285
|
+ }
|
|
286
|
+
|
|
287
|
+ async run() {
|
|
288
|
+ if (await this.#simulateCensorship()) {
|
|
289
|
+ return;
|
|
290
|
+ }
|
|
291
|
+
|
|
292
|
+ this.#bootstrap = new lazy.TorBootstrapRequest();
|
|
293
|
+ this.#bootstrap.onbootstrapstatus = (progress, status) => {
|
|
294
|
+ TorConnect._updateBootstrapStatus(progress, status);
|
|
295
|
+ };
|
|
296
|
+ this.#bootstrap.onbootstrapcomplete = () => {
|
|
297
|
+ this.#internetTest.cancel();
|
|
298
|
+ this.changeState(TorConnectState.Bootstrapped);
|
|
299
|
+ };
|
|
300
|
+ this.#bootstrap.onbootstraperror = (message, details) => {
|
|
301
|
+ if (this.#cancelled) {
|
|
302
|
+ // We ignore this error since it occurred after cancelling (by the
|
|
303
|
+ // user). We assume the error is just a side effect of the cancelling.
|
|
304
|
+ // E.g. If the cancelling is triggered late in the process, we get
|
|
305
|
+ // "Building circuits: Establishing a Tor circuit failed".
|
|
306
|
+ // TODO: Maybe move this logic deeper in the process to know when to
|
|
307
|
+ // filter out such errors triggered by cancelling.
|
|
308
|
+ lazy.logger.warn(`Post-cancel error => ${message}; ${details}`);
|
|
309
|
+ return;
|
|
310
|
+ }
|
|
311
|
+ // We have to wait for the Internet test to finish before sending the
|
|
312
|
+ // bootstrap error
|
|
313
|
+ this.#bootstrapError = message;
|
|
314
|
+ this.#bootstrapErrorDetails = details;
|
|
315
|
+ this.#maybeTransitionToError();
|
|
316
|
+ };
|
|
317
|
+
|
|
318
|
+ this.#internetTest = new InternetTest();
|
|
319
|
+ this.#internetTest.onResult = status => {
|
|
320
|
+ TorConnect._internetStatus = status;
|
|
321
|
+ this.#maybeTransitionToError();
|
|
322
|
+ };
|
|
323
|
+ this.#internetTest.onError = () => {
|
|
324
|
+ this.#maybeTransitionToError();
|
|
325
|
+ };
|
|
326
|
+
|
|
327
|
+ this.#bootstrap.bootstrap();
|
|
328
|
+ }
|
|
329
|
+
|
|
330
|
+ async cleanup(nextState) {
|
|
331
|
+ if (nextState === TorConnectState.Configuring) {
|
|
332
|
+ // stop bootstrap process if user cancelled
|
|
333
|
+ this.#cancelled = true;
|
|
334
|
+ this.#internetTest?.cancel();
|
|
335
|
+ await this.#bootstrap?.cancel();
|
|
336
|
+ }
|
|
337
|
+ }
|
|
338
|
+
|
|
339
|
+ #maybeTransitionToError() {
|
|
340
|
+ if (
|
|
341
|
+ this.#internetTest.status === InternetStatus.Unknown &&
|
|
342
|
+ this.#internetTest.error === null &&
|
|
343
|
+ this.#internetTest.enabled
|
|
344
|
+ ) {
|
|
345
|
+ // We have been called by a failed bootstrap, but the internet test has
|
|
346
|
+ // not run yet - force it to run immediately!
|
|
347
|
+ this.#internetTest.test();
|
|
348
|
+ // Return from this call, because the Internet test's callback will call
|
|
349
|
+ // us again.
|
|
350
|
+ return;
|
|
351
|
+ }
|
|
352
|
+ // Do not transition to the offline error until we are sure that also the
|
|
353
|
+ // bootstrap failed, in case Moat is down but the bootstrap can proceed
|
|
354
|
+ // anyway.
|
|
355
|
+ if (this.#bootstrapError === "") {
|
|
356
|
+ return;
|
|
357
|
+ }
|
|
358
|
+ if (this.#internetTest.status === InternetStatus.Offline) {
|
|
359
|
+ this.changeState(
|
|
360
|
+ TorConnectState.Error,
|
|
361
|
+ TorStrings.torConnect.offline,
|
|
362
|
+ ""
|
|
363
|
+ );
|
|
364
|
+ } else {
|
|
365
|
+ // Give priority to the bootstrap error, in case the Internet test fails
|
|
366
|
+ TorConnect._hasBootstrapEverFailed = true;
|
|
367
|
+ this.changeState(
|
|
368
|
+ TorConnectState.Error,
|
|
369
|
+ this.#bootstrapError,
|
|
370
|
+ this.#bootstrapErrorDetails
|
|
371
|
+ );
|
|
372
|
+ }
|
|
373
|
+ }
|
|
374
|
+
|
|
375
|
+ async #simulateCensorship() {
|
|
376
|
+ // debug hook to simulate censorship preventing bootstrapping
|
|
377
|
+ const censorshipLevel = Services.prefs.getIntPref(
|
|
378
|
+ TorConnectPrefs.censorship_level,
|
|
379
|
+ 0
|
|
380
|
+ );
|
|
381
|
+ if (censorshipLevel <= 0) {
|
|
382
|
+ return false;
|
|
383
|
+ }
|
|
384
|
+
|
|
385
|
+ await debugSleep(1500);
|
|
386
|
+ if (this.transitioning) {
|
|
387
|
+ // Already left this state.
|
|
388
|
+ return true;
|
|
389
|
+ }
|
|
390
|
+ TorConnect._hasBootstrapEverFailed = true;
|
|
391
|
+ if (censorshipLevel === 2) {
|
|
392
|
+ const codes = Object.keys(TorConnect._countryNames);
|
|
393
|
+ TorConnect._detectedLocation =
|
|
394
|
+ codes[Math.floor(Math.random() * codes.length)];
|
|
395
|
+ }
|
|
396
|
+ this.changeState(
|
|
397
|
+ TorConnectState.Error,
|
|
398
|
+ "Bootstrap failed (for debugging purposes)",
|
|
399
|
+ "Error: Censorship simulation"
|
|
400
|
+ );
|
|
401
|
+ return true;
|
|
402
|
+ }
|
|
403
|
+}
|
|
404
|
+
|
|
405
|
+class AutoBootstrappingState extends StateCallback {
|
|
406
|
+ #moat;
|
|
407
|
+ #settings;
|
|
408
|
+ #changedSettings = false;
|
|
409
|
+ #transitionPromise;
|
|
410
|
+ #transitionResolve;
|
|
411
|
+
|
|
412
|
+ allowedTransitions = Object.freeze([
|
|
413
|
+ TorConnectState.Configuring,
|
|
414
|
+ TorConnectState.Bootstrapped,
|
|
415
|
+ TorConnectState.Error,
|
|
416
|
+ ]);
|
|
417
|
+
|
|
418
|
+ constructor() {
|
|
419
|
+ super(TorConnectState.AutoBootstrapping);
|
|
420
|
+ this.#transitionPromise = new Promise(resolve => {
|
|
421
|
+ this.#transitionResolve = resolve;
|
|
422
|
+ });
|
|
423
|
+ }
|
|
424
|
+
|
|
425
|
+ async run(countryCode) {
|
|
426
|
+ if (await this.#simulateCensorship(countryCode)) {
|
|
427
|
+ return;
|
|
428
|
+ }
|
|
429
|
+ await this.#initMoat();
|
|
430
|
+ if (this.transitioning) {
|
|
431
|
+ return;
|
|
432
|
+ }
|
|
433
|
+ await this.#fetchSettings(countryCode);
|
|
434
|
+ if (this.transitioning) {
|
|
435
|
+ return;
|
|
436
|
+ }
|
|
437
|
+ await this.#trySettings();
|
|
438
|
+ }
|
|
439
|
+
|
|
440
|
+ /**
|
|
441
|
+ * Simulate a censorship event, if needed.
|
|
442
|
+ *
|
|
443
|
+ * @param {string} countryCode The country code passed to the state
|
|
444
|
+ * @returns {Promise<boolean>} true if we are simulating the censorship and
|
|
445
|
+ * the bootstrap should stop immediately, or false if the bootstrap should
|
|
446
|
+ * continue normally.
|
|
447
|
+ */
|
|
448
|
+ async #simulateCensorship(countryCode) {
|
|
449
|
+ const censorshipLevel = Services.prefs.getIntPref(
|
|
450
|
+ TorConnectPrefs.censorship_level,
|
|
451
|
+ 0
|
|
452
|
+ );
|
|
453
|
+ if (censorshipLevel <= 0) {
|
|
454
|
+ return false;
|
|
455
|
+ }
|
|
456
|
+
|
|
457
|
+ // Very severe censorship: always fail even after manually selecting
|
|
458
|
+ // location specific settings.
|
|
459
|
+ if (censorshipLevel === 3) {
|
|
460
|
+ await debugSleep(2500);
|
|
461
|
+ if (!this.transitioning) {
|
|
462
|
+ this.changeState(
|
|
463
|
+ TorConnectState.Error,
|
|
464
|
+ "Error: censorship simulation",
|
|
465
|
+ ""
|
|
466
|
+ );
|
|
467
|
+ }
|
|
468
|
+ return true;
|
|
469
|
+ }
|
|
470
|
+
|
|
471
|
+ // Severe censorship: only fail after auto selecting, but succeed after
|
|
472
|
+ // manually selecting a country.
|
|
473
|
+ if (censorshipLevel === 2 && !countryCode) {
|
|
474
|
+ await debugSleep(2500);
|
|
475
|
+ if (!this.transitioning) {
|
|
476
|
+ this.changeState(
|
|
477
|
+ TorConnectState.Error,
|
|
478
|
+ "Error: Severe Censorship simulation",
|
|
479
|
+ ""
|
|
480
|
+ );
|
|
481
|
+ }
|
|
482
|
+ return true;
|
|
483
|
+ }
|
|
484
|
+
|
|
485
|
+ return false;
|
|
486
|
+ }
|
|
487
|
+
|
|
488
|
+ /**
|
|
489
|
+ * Initialize the MoatRPC to communicate with the backend.
|
|
490
|
+ */
|
|
491
|
+ async #initMoat() {
|
|
492
|
+ this.#moat = new lazy.MoatRPC();
|
|
493
|
+ // We need to wait Moat's initialization even when we are requested to
|
|
494
|
+ // transition to another state to be sure its uninit will have its intended
|
|
495
|
+ // effect. So, do not use Promise.race here.
|
|
496
|
+ await this.#moat.init();
|
|
497
|
+ }
|
|
498
|
+
|
|
499
|
+ /**
|
|
500
|
+ * Lookup user's potential censorship circumvention settings from Moat
|
|
501
|
+ * service.
|
|
502
|
+ */
|
|
503
|
+ async #fetchSettings(countryCode) {
|
|
504
|
+ // For now, throw any errors we receive from the backend, except when it was
|
|
505
|
+ // unable to detect user's country/region.
|
|
506
|
+ // If we use specialized error objects, we could pass the original errors to
|
|
507
|
+ // them.
|
|
508
|
+ const maybeSettings = await Promise.race([
|
|
509
|
+ this.#moat.circumvention_settings(
|
|
510
|
+ [...TorSettings.builtinBridgeTypes, "vanilla"],
|
|
511
|
+ countryCode
|
|
512
|
+ ),
|
|
513
|
+ // This might set maybeSettings to undefined.
|
|
514
|
+ this.#transitionPromise,
|
|
515
|
+ ]);
|
|
516
|
+ if (maybeSettings?.country) {
|
|
517
|
+ TorConnect._detectedLocation = maybeSettings.country;
|
|
518
|
+ }
|
|
519
|
+
|
|
520
|
+ if (maybeSettings?.settings && maybeSettings.settings.length) {
|
|
521
|
+ this.#settings = maybeSettings.settings;
|
|
522
|
+ } else if (!this.transitioning) {
|
|
523
|
+ // Keep consistency with the other call.
|
|
524
|
+ this.#settings = await Promise.race([
|
|
525
|
+ this.#moat.circumvention_defaults([
|
|
526
|
+ ...TorSettings.builtinBridgeTypes,
|
|
527
|
+ "vanilla",
|
|
528
|
+ ]),
|
|
529
|
+ // This might set this.#settings to undefined.
|
|
530
|
+ this.#transitionPromise,
|
|
531
|
+ ]);
|
|
532
|
+ }
|
|
533
|
+
|
|
534
|
+ if (!this.#settings?.length && !this.transitioning) {
|
|
535
|
+ // Both localized and fallback have, we can just throw to transition to
|
|
536
|
+ // the error state (but only if we aren't already transitioning).
|
|
537
|
+ // TODO: Let the UI layer localize the strings.
|
|
538
|
+
|
|
539
|
+ if (!TorConnect._detectedLocation) {
|
|
540
|
+ // unable to determine country
|
|
541
|
+ this.#throwError(
|
|
542
|
+ TorStrings.torConnect.autoBootstrappingFailed,
|
|
543
|
+ TorStrings.torConnect.cannotDetermineCountry
|
|
544
|
+ );
|
|
545
|
+ } else {
|
|
546
|
+ // no settings available for country
|
|
547
|
+ this.#throwError(
|
|
548
|
+ TorStrings.torConnect.autoBootstrappingFailed,
|
|
549
|
+ TorStrings.torConnect.noSettingsForCountry
|
|
550
|
+ );
|
|
551
|
+ }
|
|
552
|
+ }
|
|
553
|
+ }
|
|
554
|
+
|
|
555
|
+ /**
|
|
556
|
+ * Try to apply the settings we fetched.
|
|
557
|
+ */
|
|
558
|
+ async #trySettings() {
|
|
559
|
+ // Otherwise, apply each of our settings and try to bootstrap with each.
|
|
560
|
+ for (const [index, currentSetting] of this.#settings.entries()) {
|
|
561
|
+ if (this.transitioning) {
|
|
562
|
+ break;
|
|
563
|
+ }
|
|
564
|
+
|
|
565
|
+ lazy.logger.info(
|
|
566
|
+ `Attempting Bootstrap with configuration ${index + 1}/${
|
|
567
|
+ this.#settings.length
|
|
568
|
+ }`
|
|
569
|
+ );
|
|
570
|
+
|
|
571
|
+ // Send the new settings directly to the provider. We will save them only
|
|
572
|
+ // if the bootstrap succeeds.
|
|
573
|
+ // FIXME: We should somehow signal TorSettings users that we have set
|
|
574
|
+ // custom settings, and they should not apply theirs until we are done
|
|
575
|
+ // with trying ours.
|
|
576
|
+ // Otherwise, the new settings provided by the user while we were
|
|
577
|
+ // bootstrapping could be the ones that cause the bootstrap to succeed,
|
|
578
|
+ // but we overwrite them (unless we backup the original settings, and then
|
|
579
|
+ // save our new settings only if they have not changed).
|
|
580
|
+ // Another idea (maybe easier to implement) is to disable the settings
|
|
581
|
+ // UI while *any* bootstrap is going on.
|
|
582
|
+ // This is also documented in tor-browser#41921.
|
|
583
|
+ const provider = await lazy.TorProviderBuilder.build();
|
|
584
|
+ this.#changedSettings = true;
|
|
585
|
+ // We need to merge with old settings, in case the user is using a proxy
|
|
586
|
+ // or is behind a firewall.
|
|
587
|
+ await provider.writeSettings({
|
|
588
|
+ ...TorSettings.getSettings(),
|
|
589
|
+ ...currentSetting,
|
|
590
|
+ });
|
|
591
|
+
|
|
592
|
+ // Build out our bootstrap request.
|
|
593
|
+ const bootstrap = new lazy.TorBootstrapRequest();
|
|
594
|
+ bootstrap.onbootstrapstatus = (progress, status) => {
|
|
595
|
+ TorConnect._updateBootstrapStatus(progress, status);
|
|
596
|
+ };
|
|
597
|
+ bootstrap.onbootstraperror = (message, details) => {
|
|
598
|
+ lazy.logger.error(`Auto-Bootstrap error => ${message}; ${details}`);
|
|
599
|
+ };
|
|
600
|
+
|
|
601
|
+ // Begin the bootstrap.
|
|
602
|
+ const success = await Promise.race([
|
|
603
|
+ bootstrap.bootstrap(),
|
|
604
|
+ this.#transitionPromise,
|
|
605
|
+ ]);
|
|
606
|
+ // Either the bootstrap request has finished, or a transition (caused by
|
|
607
|
+ // an error or by user's cancelation) started.
|
|
608
|
+ // However, we cannot be already transitioning in case of success, so if
|
|
609
|
+ // we are we should cancel the current bootstrap.
|
|
610
|
+ // With the current TorProvider, this will set DisableNetwork=1 again,
|
|
611
|
+ // which is what the user wanted if they canceled.
|
|
612
|
+ if (this.transitioning) {
|
|
613
|
+ if (success) {
|
|
614
|
+ lazy.logger.warn(
|
|
615
|
+ "We were already transitioning after a success, we were not expecting this."
|
|
616
|
+ );
|
|
617
|
+ }
|
|
618
|
+ bootstrap.cancel();
|
|
619
|
+ return;
|
|
620
|
+ }
|
|
621
|
+ if (success) {
|
|
622
|
+ // Persist the current settings to preferences.
|
|
623
|
+ TorSettings.setSettings(currentSetting);
|
|
624
|
+ TorSettings.saveToPrefs();
|
|
625
|
+ // Do not await `applySettings`. Otherwise this opens up a window of
|
|
626
|
+ // time where the user can still "Cancel" the bootstrap.
|
|
627
|
+ // We are calling `applySettings` just to be on the safe side, but the
|
|
628
|
+ // settings we are passing now should be exactly the same we already
|
|
629
|
+ // passed earlier.
|
|
630
|
+ TorSettings.applySettings().catch(e =>
|
|
631
|
+ lazy.logger.error("TorSettings.applySettings threw unexpectedly.", e)
|
|
632
|
+ );
|
|
633
|
+ this.changeState(TorConnectState.Bootstrapped);
|
|
634
|
+ return;
|
|
635
|
+ }
|
|
636
|
+ }
|
|
637
|
+
|
|
638
|
+ // Only explicitly change state here if something else has not transitioned
|
|
639
|
+ // us.
|
|
640
|
+ if (!this.transitioning) {
|
|
641
|
+ this.#throwError(
|
|
642
|
+ TorStrings.torConnect.autoBootstrappingFailed,
|
|
643
|
+ TorStrings.torConnect.autoBootstrappingAllFailed
|
|
644
|
+ );
|
|
645
|
+ }
|
|
646
|
+ }
|
|
647
|
+
|
|
648
|
+ #throwError(message, details) {
|
|
649
|
+ let err = new Error(message);
|
|
650
|
+ err.details = details;
|
|
651
|
+ throw err;
|
|
652
|
+ }
|
|
653
|
+
|
|
654
|
+ transitionRequested() {
|
|
655
|
+ this.#transitionResolve();
|
|
656
|
+ }
|
|
657
|
+
|
|
658
|
+ async cleanup(nextState) {
|
|
659
|
+ // No need to await.
|
|
660
|
+ this.#moat?.uninit();
|
|
661
|
+ this.#moat = null;
|
|
662
|
+
|
|
663
|
+ if (this.#changedSettings && nextState !== TorConnectState.Bootstrapped) {
|
|
664
|
+ try {
|
|
665
|
+ await TorSettings.applySettings();
|
|
666
|
+ } catch (e) {
|
|
667
|
+ // We cannot do much if the original settings were bad or
|
|
668
|
+ // if the connection closed, so just report it in the
|
|
669
|
+ // console.
|
|
670
|
+ lazy.logger.warn("Failed to restore original settings.", e);
|
|
671
|
+ }
|
|
672
|
+ }
|
|
673
|
+ }
|
|
674
|
+}
|
|
675
|
+
|
|
676
|
+class BootstrappedState extends StateCallback {
|
|
677
|
+ // We may need to leave the bootstrapped state if the tor daemon
|
|
678
|
+ // exits (if it is restarted, we will have to bootstrap again).
|
|
679
|
+ allowedTransitions = Object.freeze([TorConnectState.Configuring]);
|
|
680
|
+
|
|
681
|
+ constructor() {
|
|
682
|
+ super(TorConnectState.Bootstrapped);
|
|
683
|
+ }
|
|
684
|
+
|
|
685
|
+ run() {
|
|
686
|
+ // Notify observers of bootstrap completion.
|
|
687
|
+ Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);
|
|
688
|
+ }
|
|
689
|
+}
|
|
690
|
+
|
|
691
|
+class ErrorState extends StateCallback {
|
|
692
|
+ allowedTransitions = Object.freeze([TorConnectState.Configuring]);
|
|
693
|
+
|
|
694
|
+ static #hasEverHappened = false;
|
|
695
|
+
|
|
696
|
+ constructor() {
|
|
697
|
+ super(TorConnectState.Error);
|
|
698
|
+ ErrorState.#hasEverHappened = true;
|
|
699
|
+ }
|
|
700
|
+
|
|
701
|
+ run(errorMessage, errorDetails) {
|
|
702
|
+ TorConnect._errorMessage = errorMessage;
|
|
703
|
+ TorConnect._errorDetails = errorDetails;
|
|
704
|
+ lazy.logger.error(
|
|
705
|
+ `Entering error state (${errorMessage}, ${errorDetails})`
|
|
706
|
+ );
|
|
707
|
+
|
|
708
|
+ Services.obs.notifyObservers(
|
|
709
|
+ { message: errorMessage, details: errorDetails },
|
|
710
|
+ TorConnectTopics.BootstrapError
|
|
711
|
+ );
|
|
712
|
+
|
|
713
|
+ this.changeState(TorConnectState.Configuring);
|
|
714
|
+ }
|
|
715
|
+
|
|
716
|
+ static get hasEverHappened() {
|
|
717
|
+ return ErrorState.#hasEverHappened;
|
|
718
|
+ }
|
|
719
|
+}
|
|
720
|
+
|
|
721
|
+class DisabledState extends StateCallback {
|
|
722
|
+ // Trap state: no way to leave the Disabled state.
|
|
723
|
+ allowedTransitions = Object.freeze([]);
|
|
724
|
+
|
|
725
|
+ constructor() {
|
|
726
|
+ super(TorConnectState.DisabledState);
|
|
727
|
+ }
|
|
728
|
+
|
|
729
|
+ async run() {
|
|
730
|
+ lazy.logger.debug("Entered the disabled state.");
|
|
731
|
+ }
|
|
732
|
+}
|
|
733
|
+
|
249
|
734
|
export const InternetStatus = Object.freeze({
|
250
|
735
|
Unknown: -1,
|
251
|
736
|
Offline: 0,
|
... |
... |
@@ -253,1005 +738,581 @@ export const InternetStatus = Object.freeze({ |
253
|
738
|
});
|
254
|
739
|
|
255
|
740
|
class InternetTest {
|
|
741
|
+ #enabled;
|
|
742
|
+ #status = InternetStatus.Unknown;
|
|
743
|
+ #error = null;
|
|
744
|
+ #pending = false;
|
|
745
|
+ #canceled = false;
|
|
746
|
+ #timeout = 0;
|
|
747
|
+
|
256
|
748
|
constructor() {
|
257
|
|
- this._enabled = Services.prefs.getBoolPref(
|
|
749
|
+ this.#enabled = Services.prefs.getBoolPref(
|
258
|
750
|
TorConnectPrefs.allow_internet_test,
|
259
|
751
|
true
|
260
|
752
|
);
|
261
|
|
-
|
262
|
|
- this._status = InternetStatus.Unknown;
|
263
|
|
- this._error = null;
|
264
|
|
- this._pending = false;
|
265
|
|
- if (this._enabled) {
|
266
|
|
- this._timeout = setTimeout(() => {
|
267
|
|
- this._timeout = null;
|
|
753
|
+ if (this.#enabled) {
|
|
754
|
+ this.#timeout = setTimeout(() => {
|
|
755
|
+ this.#timeout = 0;
|
268
|
756
|
this.test();
|
269
|
|
- }, this.timeoutRand());
|
|
757
|
+ }, this.#timeoutRand());
|
270
|
758
|
}
|
271
|
|
- this.onResult = (online, date) => {};
|
|
759
|
+ this.onResult = online => {};
|
272
|
760
|
this.onError = err => {};
|
273
|
761
|
}
|
274
|
762
|
|
275
|
|
- test() {
|
276
|
|
- if (this._pending || !this._enabled) {
|
|
763
|
+ /**
|
|
764
|
+ * Perform the internet test.
|
|
765
|
+ *
|
|
766
|
+ * While this is an async method, the callers are not expected to await it,
|
|
767
|
+ * as we are also using callbacks.
|
|
768
|
+ */
|
|
769
|
+ async test() {
|
|
770
|
+ if (this.#pending || !this.#enabled) {
|
277
|
771
|
return;
|
278
|
772
|
}
|
279
|
773
|
this.cancel();
|
280
|
|
- this._pending = true;
|
|
774
|
+ this.#pending = true;
|
|
775
|
+ this.#canceled = false;
|
281
|
776
|
|
282
|
777
|
lazy.logger.info("Starting the Internet test");
|
283
|
|
- this._testAsync()
|
284
|
|
- .then(status => {
|
285
|
|
- this._pending = false;
|
286
|
|
- this._status = status.successful
|
287
|
|
- ? InternetStatus.Online
|
288
|
|
- : InternetStatus.Offline;
|
289
|
|
- lazy.logger.info(`Performed Internet test, outcome ${this._status}`);
|
290
|
|
- this.onResult(this.status, status.date);
|
291
|
|
- })
|
292
|
|
- .catch(error => {
|
293
|
|
- this._error = error;
|
294
|
|
- this._pending = false;
|
295
|
|
- this.onError(error);
|
296
|
|
- });
|
297
|
|
- }
|
298
|
|
-
|
299
|
|
- cancel() {
|
300
|
|
- if (this._timeout !== null) {
|
301
|
|
- clearTimeout(this._timeout);
|
302
|
|
- this._timeout = null;
|
303
|
|
- }
|
304
|
|
- }
|
305
|
|
-
|
306
|
|
- async _testAsync() {
|
307
|
|
- // Callbacks for the Internet test are desirable, because we will be
|
308
|
|
- // waiting both for the bootstrap, and for the Internet test.
|
309
|
|
- // However, managing Moat with async/await is much easier as it avoids a
|
310
|
|
- // callback hell, and it makes extra explicit that we are uniniting it.
|
311
|
778
|
const mrpc = new lazy.MoatRPC();
|
312
|
|
- let status = null;
|
313
|
|
- let error = null;
|
314
|
779
|
try {
|
315
|
780
|
await mrpc.init();
|
316
|
|
- status = await mrpc.testInternetConnection();
|
|
781
|
+ const status = await mrpc.testInternetConnection();
|
|
782
|
+ this.#status = status.successful
|
|
783
|
+ ? InternetStatus.Online
|
|
784
|
+ : InternetStatus.Offline;
|
|
785
|
+ // TODO: We could consume the date we got from the HTTP request to detect
|
|
786
|
+ // big clock skews that might prevent a successfull bootstrap.
|
|
787
|
+ lazy.logger.info(`Performed Internet test, outcome ${this.#status}`);
|
317
|
788
|
} catch (err) {
|
318
|
789
|
lazy.logger.error("Error while checking the Internet connection", err);
|
319
|
|
- error = err;
|
|
790
|
+ this.#error = err;
|
|
791
|
+ this.#pending = false;
|
320
|
792
|
} finally {
|
321
|
793
|
mrpc.uninit();
|
322
|
794
|
}
|
323
|
|
- if (error !== null) {
|
324
|
|
- throw error;
|
|
795
|
+
|
|
796
|
+ if (this.#canceled) {
|
|
797
|
+ return;
|
|
798
|
+ }
|
|
799
|
+ if (this.#error) {
|
|
800
|
+ this.onError(this.#error);
|
|
801
|
+ } else {
|
|
802
|
+ this.onResult(this.#status);
|
|
803
|
+ }
|
|
804
|
+ }
|
|
805
|
+
|
|
806
|
+ cancel() {
|
|
807
|
+ this.#canceled = true;
|
|
808
|
+ if (this.#timeout) {
|
|
809
|
+ clearTimeout(this.#timeout);
|
|
810
|
+ this.#timeout = 0;
|
325
|
811
|
}
|
326
|
|
- return status;
|
327
|
812
|
}
|
328
|
813
|
|
329
|
814
|
get status() {
|
330
|
|
- return this._status;
|
|
815
|
+ return this.#status;
|
331
|
816
|
}
|
332
|
817
|
|
333
|
818
|
get error() {
|
334
|
|
- return this._error;
|
|
819
|
+ return this.#error;
|
335
|
820
|
}
|
336
|
821
|
|
337
|
822
|
get enabled() {
|
338
|
|
- return this._enabled;
|
|
823
|
+ return this.#enabled;
|
339
|
824
|
}
|
340
|
825
|
|
341
|
|
- // We randomize the Internet test timeout to make fingerprinting it harder, at least a little bit...
|
342
|
|
- timeoutRand() {
|
|
826
|
+ // We randomize the Internet test timeout to make fingerprinting it harder, at
|
|
827
|
+ // least a little bit...
|
|
828
|
+ #timeoutRand() {
|
343
|
829
|
const offset = 30000;
|
344
|
830
|
const randRange = 5000;
|
345
|
831
|
return offset + randRange * (Math.random() * 2 - 1);
|
346
|
832
|
}
|
347
|
833
|
}
|
348
|
834
|
|
349
|
|
-export const TorConnect = (() => {
|
350
|
|
- let retval = {
|
351
|
|
- _state: TorConnectState.Initial,
|
352
|
|
- _bootstrapProgress: 0,
|
353
|
|
- _bootstrapStatus: null,
|
354
|
|
- _internetStatus: InternetStatus.Unknown,
|
355
|
|
- // list of country codes Moat has settings for
|
356
|
|
- _countryCodes: [],
|
357
|
|
- _countryNames: Object.freeze(
|
358
|
|
- (() => {
|
359
|
|
- const codes = Services.intl.getAvailableLocaleDisplayNames("region");
|
360
|
|
- const names = Services.intl.getRegionDisplayNames(undefined, codes);
|
361
|
|
- let codesNames = {};
|
362
|
|
- for (let i = 0; i < codes.length; i++) {
|
363
|
|
- codesNames[codes[i]] = names[i];
|
364
|
|
- }
|
365
|
|
- return codesNames;
|
366
|
|
- })()
|
367
|
|
- ),
|
368
|
|
- _detectedLocation: "",
|
369
|
|
- _errorMessage: null,
|
370
|
|
- _errorDetails: null,
|
371
|
|
- _logHasWarningOrError: false,
|
372
|
|
- _hasEverFailed: false,
|
373
|
|
- _hasBootstrapEverFailed: false,
|
374
|
|
- _transitionPromise: null,
|
375
|
|
-
|
376
|
|
- // This is used as a helper to make the state of about:torconnect persistent
|
377
|
|
- // during a session, but TorConnect does not use this data at all.
|
378
|
|
- _uiState: {},
|
379
|
|
-
|
380
|
|
- /* These functions represent ongoing work associated with one of our states
|
381
|
|
- Some of these functions are mostly empty, apart from defining an
|
382
|
|
- on_transition function used to resolve their Promise */
|
383
|
|
- _stateCallbacks: Object.freeze(
|
384
|
|
- new Map([
|
385
|
|
- /* Initial is never transitioned to */
|
386
|
|
- [
|
387
|
|
- TorConnectState.Initial,
|
388
|
|
- new StateCallback(TorConnectState.Initial, async function () {
|
389
|
|
- // The initial state doesn't actually do anything, so here is a skeleton for other
|
390
|
|
- // states which do perform work
|
391
|
|
- await new Promise(async (resolve, reject) => {
|
392
|
|
- // This function is provided to signal to the callback that it is complete.
|
393
|
|
- // It is called as a result of _changeState and at the very least must
|
394
|
|
- // resolve the root Promise object within the StateCallback function
|
395
|
|
- // The on_transition callback may also perform necessary cleanup work
|
396
|
|
- this.on_transition = nextState => {
|
397
|
|
- resolve();
|
398
|
|
- };
|
399
|
|
-
|
400
|
|
- try {
|
401
|
|
- // each state may have a sequence of async work to do
|
402
|
|
- let asyncWork = async () => {};
|
403
|
|
- await asyncWork();
|
404
|
|
-
|
405
|
|
- // after each block we may check for an opportunity to early-out
|
406
|
|
- if (this.transitioning) {
|
407
|
|
- return;
|
408
|
|
- }
|
409
|
|
-
|
410
|
|
- // repeat the above pattern as necessary
|
411
|
|
- } catch (err) {
|
412
|
|
- // any thrown exceptions here will trigger a transition to the Error state
|
413
|
|
- TorConnect._changeState(
|
414
|
|
- TorConnectState.Error,
|
415
|
|
- err?.message,
|
416
|
|
- err?.details
|
417
|
|
- );
|
418
|
|
- }
|
419
|
|
- });
|
420
|
|
- }),
|
421
|
|
- ],
|
422
|
|
- /* Configuring */
|
423
|
|
- [
|
424
|
|
- TorConnectState.Configuring,
|
425
|
|
- new StateCallback(TorConnectState.Configuring, async function () {
|
426
|
|
- await new Promise(async (resolve, reject) => {
|
427
|
|
- this.on_transition = nextState => {
|
428
|
|
- resolve();
|
429
|
|
- };
|
430
|
|
- });
|
431
|
|
- }),
|
432
|
|
- ],
|
433
|
|
- /* Bootstrapping */
|
434
|
|
- [
|
435
|
|
- TorConnectState.Bootstrapping,
|
436
|
|
- new StateCallback(TorConnectState.Bootstrapping, async function () {
|
437
|
|
- // wait until bootstrap completes or we get an error
|
438
|
|
- await new Promise(async (resolve, reject) => {
|
439
|
|
- // debug hook to simulate censorship preventing bootstrapping
|
440
|
|
- if (
|
441
|
|
- Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) >
|
442
|
|
- 0
|
443
|
|
- ) {
|
444
|
|
- this.on_transition = nextState => {
|
445
|
|
- resolve();
|
446
|
|
- };
|
447
|
|
- await debug_sleep(1500);
|
448
|
|
- TorConnect._hasBootstrapEverFailed = true;
|
449
|
|
- if (
|
450
|
|
- Services.prefs.getIntPref(
|
451
|
|
- TorConnectPrefs.censorship_level,
|
452
|
|
- 0
|
453
|
|
- ) === 2
|
454
|
|
- ) {
|
455
|
|
- const codes = Object.keys(TorConnect._countryNames);
|
456
|
|
- TorConnect._detectedLocation =
|
457
|
|
- codes[Math.floor(Math.random() * codes.length)];
|
458
|
|
- }
|
459
|
|
- TorConnect._changeState(
|
460
|
|
- TorConnectState.Error,
|
461
|
|
- "Bootstrap failed (for debugging purposes)",
|
462
|
|
- "Error: Censorship simulation",
|
463
|
|
- true
|
464
|
|
- );
|
465
|
|
- return;
|
466
|
|
- }
|
467
|
|
-
|
468
|
|
- const tbr = new lazy.TorBootstrapRequest();
|
469
|
|
- const internetTest = new InternetTest();
|
470
|
|
- let cancelled = false;
|
471
|
|
-
|
472
|
|
- let bootstrapError = "";
|
473
|
|
- let bootstrapErrorDetails = "";
|
474
|
|
- const maybeTransitionToError = () => {
|
475
|
|
- if (
|
476
|
|
- internetTest.status === InternetStatus.Unknown &&
|
477
|
|
- internetTest.error === null &&
|
478
|
|
- internetTest.enabled
|
479
|
|
- ) {
|
480
|
|
- // We have been called by a failed bootstrap, but the internet test has not run yet - force
|
481
|
|
- // it to run immediately!
|
482
|
|
- internetTest.test();
|
483
|
|
- // Return from this call, because the Internet test's callback will call us again
|
484
|
|
- return;
|
485
|
|
- }
|
486
|
|
- // Do not transition to the offline error until we are sure that also the bootstrap failed, in
|
487
|
|
- // case Moat is down but the bootstrap can proceed anyway.
|
488
|
|
- if (bootstrapError === "") {
|
489
|
|
- return;
|
490
|
|
- }
|
491
|
|
- if (internetTest.status === InternetStatus.Offline) {
|
492
|
|
- TorConnect._changeState(
|
493
|
|
- TorConnectState.Error,
|
494
|
|
- TorStrings.torConnect.offline,
|
495
|
|
- "",
|
496
|
|
- true
|
497
|
|
- );
|
498
|
|
- } else {
|
499
|
|
- // Give priority to the bootstrap error, in case the Internet test fails
|
500
|
|
- TorConnect._hasBootstrapEverFailed = true;
|
501
|
|
- TorConnect._changeState(
|
502
|
|
- TorConnectState.Error,
|
503
|
|
- bootstrapError,
|
504
|
|
- bootstrapErrorDetails,
|
505
|
|
- true
|
506
|
|
- );
|
507
|
|
- }
|
508
|
|
- };
|
509
|
|
-
|
510
|
|
- this.on_transition = async nextState => {
|
511
|
|
- if (nextState === TorConnectState.Configuring) {
|
512
|
|
- // stop bootstrap process if user cancelled
|
513
|
|
- cancelled = true;
|
514
|
|
- internetTest.cancel();
|
515
|
|
- await tbr.cancel();
|
516
|
|
- }
|
517
|
|
- resolve();
|
518
|
|
- };
|
519
|
|
-
|
520
|
|
- tbr.onbootstrapstatus = (progress, status) => {
|
521
|
|
- TorConnect._updateBootstrapStatus(progress, status);
|
522
|
|
- };
|
523
|
|
- tbr.onbootstrapcomplete = () => {
|
524
|
|
- internetTest.cancel();
|
525
|
|
- TorConnect._changeState(TorConnectState.Bootstrapped);
|
526
|
|
- };
|
527
|
|
- tbr.onbootstraperror = (message, details) => {
|
528
|
|
- if (cancelled) {
|
529
|
|
- // We ignore this error since it occurred after cancelling (by
|
530
|
|
- // the user). We assume the error is just a side effect of the
|
531
|
|
- // cancelling.
|
532
|
|
- // E.g. If the cancelling is triggered late in the process, we
|
533
|
|
- // get "Building circuits: Establishing a Tor circuit failed".
|
534
|
|
- // TODO: Maybe move this logic deeper in the process to know
|
535
|
|
- // when to filter out such errors triggered by cancelling.
|
536
|
|
- lazy.logger.warn(
|
537
|
|
- `Post-cancel error => ${message}; ${details}`
|
538
|
|
- );
|
539
|
|
- return;
|
540
|
|
- }
|
541
|
|
- // We have to wait for the Internet test to finish before sending the bootstrap error
|
542
|
|
- bootstrapError = message;
|
543
|
|
- bootstrapErrorDetails = details;
|
544
|
|
- maybeTransitionToError();
|
545
|
|
- };
|
546
|
|
-
|
547
|
|
- internetTest.onResult = (status, date) => {
|
548
|
|
- // TODO: Use the date to save the clock skew?
|
549
|
|
- TorConnect._internetStatus = status;
|
550
|
|
- maybeTransitionToError();
|
551
|
|
- };
|
552
|
|
- internetTest.onError = () => {
|
553
|
|
- maybeTransitionToError();
|
554
|
|
- };
|
555
|
|
-
|
556
|
|
- tbr.bootstrap();
|
557
|
|
- });
|
558
|
|
- }),
|
559
|
|
- ],
|
560
|
|
- /* AutoBootstrapping */
|
561
|
|
- [
|
562
|
|
- TorConnectState.AutoBootstrapping,
|
563
|
|
- new StateCallback(TorConnectState.AutoBootstrapping, async function (
|
564
|
|
- countryCode
|
565
|
|
- ) {
|
566
|
|
- await new Promise(async (resolve, reject) => {
|
567
|
|
- this.on_transition = nextState => {
|
568
|
|
- resolve();
|
569
|
|
- };
|
570
|
|
-
|
571
|
|
- // debug hook to simulate censorship preventing bootstrapping
|
572
|
|
- {
|
573
|
|
- const censorshipLevel = Services.prefs.getIntPref(
|
574
|
|
- TorConnectPrefs.censorship_level,
|
575
|
|
- 0
|
576
|
|
- );
|
577
|
|
- if (censorshipLevel > 1) {
|
578
|
|
- this.on_transition = nextState => {
|
579
|
|
- resolve();
|
580
|
|
- };
|
581
|
|
- // always fail even after manually selecting location specific settings
|
582
|
|
- if (censorshipLevel == 3) {
|
583
|
|
- await debug_sleep(2500);
|
584
|
|
- TorConnect._changeState(
|
585
|
|
- TorConnectState.Error,
|
586
|
|
- "Error: censorship simulation",
|
587
|
|
- "",
|
588
|
|
- true
|
589
|
|
- );
|
590
|
|
- return;
|
591
|
|
- // only fail after auto selecting, manually selecting succeeds
|
592
|
|
- } else if (censorshipLevel == 2 && !countryCode) {
|
593
|
|
- await debug_sleep(2500);
|
594
|
|
- TorConnect._changeState(
|
595
|
|
- TorConnectState.Error,
|
596
|
|
- "Error: Severe Censorship simulation",
|
597
|
|
- "",
|
598
|
|
- true
|
599
|
|
- );
|
600
|
|
- return;
|
601
|
|
- }
|
602
|
|
- }
|
603
|
|
- }
|
604
|
|
-
|
605
|
|
- const throw_error = (message, details) => {
|
606
|
|
- let err = new Error(message);
|
607
|
|
- err.details = details;
|
608
|
|
- throw err;
|
609
|
|
- };
|
610
|
|
-
|
611
|
|
- // lookup user's potential censorship circumvention settings from Moat service
|
612
|
|
- try {
|
613
|
|
- this.mrpc = new lazy.MoatRPC();
|
614
|
|
- await this.mrpc.init();
|
615
|
|
-
|
616
|
|
- if (this.transitioning) {
|
617
|
|
- return;
|
618
|
|
- }
|
619
|
|
-
|
620
|
|
- const settings = await this.mrpc.circumvention_settings(
|
621
|
|
- [...TorSettings.builtinBridgeTypes, "vanilla"],
|
622
|
|
- countryCode
|
623
|
|
- );
|
624
|
|
-
|
625
|
|
- if (this.transitioning) {
|
626
|
|
- return;
|
627
|
|
- }
|
628
|
|
-
|
629
|
|
- if (settings?.country) {
|
630
|
|
- TorConnect._detectedLocation = settings.country;
|
631
|
|
- }
|
632
|
|
- if (settings?.settings && settings.settings.length) {
|
633
|
|
- this.settings = settings.settings;
|
634
|
|
- } else {
|
635
|
|
- try {
|
636
|
|
- this.settings = await this.mrpc.circumvention_defaults([
|
637
|
|
- ...TorSettings.builtinBridgeTypes,
|
638
|
|
- "vanilla",
|
639
|
|
- ]);
|
640
|
|
- } catch (err) {
|
641
|
|
- lazy.logger.error(
|
642
|
|
- "We did not get localized settings, and default settings failed as well",
|
643
|
|
- err
|
644
|
|
- );
|
645
|
|
- }
|
646
|
|
- }
|
647
|
|
- if (this.settings === null || this.settings.length === 0) {
|
648
|
|
- // The fallback has failed as well, so throw the original error
|
649
|
|
- if (!TorConnect._detectedLocation) {
|
650
|
|
- // unable to determine country
|
651
|
|
- throw_error(
|
652
|
|
- TorStrings.torConnect.autoBootstrappingFailed,
|
653
|
|
- TorStrings.torConnect.cannotDetermineCountry
|
654
|
|
- );
|
655
|
|
- } else {
|
656
|
|
- // no settings available for country
|
657
|
|
- throw_error(
|
658
|
|
- TorStrings.torConnect.autoBootstrappingFailed,
|
659
|
|
- TorStrings.torConnect.noSettingsForCountry
|
660
|
|
- );
|
661
|
|
- }
|
662
|
|
- }
|
663
|
|
-
|
664
|
|
- const restoreOriginalSettings = async () => {
|
665
|
|
- try {
|
666
|
|
- await TorSettings.applySettings();
|
667
|
|
- } catch (e) {
|
668
|
|
- // We cannot do much if the original settings were bad or
|
669
|
|
- // if the connection closed, so just report it in the
|
670
|
|
- // console.
|
671
|
|
- lazy.logger.warn("Failed to restore original settings.", e);
|
672
|
|
- }
|
673
|
|
- };
|
674
|
|
-
|
675
|
|
- // apply each of our settings and try to bootstrap with each
|
676
|
|
- try {
|
677
|
|
- for (const [
|
678
|
|
- index,
|
679
|
|
- currentSetting,
|
680
|
|
- ] of this.settings.entries()) {
|
681
|
|
- // we want to break here so we can fall through and restore original settings
|
682
|
|
- if (this.transitioning) {
|
683
|
|
- break;
|
684
|
|
- }
|
685
|
|
-
|
686
|
|
- lazy.logger.info(
|
687
|
|
- `Attempting Bootstrap with configuration ${index + 1}/${
|
688
|
|
- this.settings.length
|
689
|
|
- }`
|
690
|
|
- );
|
691
|
|
-
|
692
|
|
- // Send the new settings directly to the provider. We will
|
693
|
|
- // save them only if the bootstrap succeeds.
|
694
|
|
- // FIXME: We should somehow signal TorSettings users that we
|
695
|
|
- // have set custom settings, and they should not apply
|
696
|
|
- // theirs until we are done with trying ours.
|
697
|
|
- // Otherwise, the new settings provided by the user while we
|
698
|
|
- // were bootstrapping could be the ones that cause the
|
699
|
|
- // bootstrap to succeed, but we overwrite them (unless we
|
700
|
|
- // backup the original settings, and then save our new
|
701
|
|
- // settings only if they have not changed).
|
702
|
|
- // Another idea (maybe easier to implement) is to disable
|
703
|
|
- // the settings UI while *any* bootstrap is going on.
|
704
|
|
- // This is also documented in tor-browser#41921.
|
705
|
|
- const provider = await lazy.TorProviderBuilder.build();
|
706
|
|
- // We need to merge with old settings, in case the user is
|
707
|
|
- // using a proxy or is behind a firewall.
|
708
|
|
- await provider.writeSettings({
|
709
|
|
- ...TorSettings.getSettings(),
|
710
|
|
- ...currentSetting,
|
711
|
|
- });
|
712
|
|
-
|
713
|
|
- // build out our bootstrap request
|
714
|
|
- const tbr = new lazy.TorBootstrapRequest();
|
715
|
|
- tbr.onbootstrapstatus = (progress, status) => {
|
716
|
|
- TorConnect._updateBootstrapStatus(progress, status);
|
717
|
|
- };
|
718
|
|
- tbr.onbootstraperror = (message, details) => {
|
719
|
|
- lazy.logger.error(
|
720
|
|
- `Auto-Bootstrap error => ${message}; ${details}`
|
721
|
|
- );
|
722
|
|
- };
|
723
|
|
-
|
724
|
|
- // update transition callback for user cancel
|
725
|
|
- this.on_transition = async nextState => {
|
726
|
|
- if (nextState === TorConnectState.Configuring) {
|
727
|
|
- await tbr.cancel();
|
728
|
|
- await restoreOriginalSettings();
|
729
|
|
- }
|
730
|
|
- resolve();
|
731
|
|
- };
|
732
|
|
-
|
733
|
|
- // begin bootstrap
|
734
|
|
- if (await tbr.bootstrap()) {
|
735
|
|
- // persist the current settings to preferences
|
736
|
|
- TorSettings.setSettings(currentSetting);
|
737
|
|
- TorSettings.saveToPrefs();
|
738
|
|
- await TorSettings.applySettings();
|
739
|
|
- TorConnect._changeState(TorConnectState.Bootstrapped);
|
740
|
|
- return;
|
741
|
|
- }
|
742
|
|
- }
|
743
|
|
-
|
744
|
|
- // Bootstrap failed for all potential settings, so restore the
|
745
|
|
- // original settings the provider.
|
746
|
|
- await restoreOriginalSettings();
|
747
|
|
-
|
748
|
|
- // Only explicitly change state here if something else has not
|
749
|
|
- // transitioned us.
|
750
|
|
- if (!this.transitioning) {
|
751
|
|
- throw_error(
|
752
|
|
- TorStrings.torConnect.autoBootstrappingFailed,
|
753
|
|
- TorStrings.torConnect.autoBootstrappingAllFailed
|
754
|
|
- );
|
755
|
|
- }
|
756
|
|
- return;
|
757
|
|
- } catch (err) {
|
758
|
|
- await restoreOriginalSettings();
|
759
|
|
- // throw to outer catch to transition us.
|
760
|
|
- throw err;
|
761
|
|
- }
|
762
|
|
- } catch (err) {
|
763
|
|
- if (this.mrpc?.inited) {
|
764
|
|
- // lookup countries which have settings available
|
765
|
|
- TorConnect._countryCodes =
|
766
|
|
- await this.mrpc.circumvention_countries();
|
767
|
|
- }
|
768
|
|
- if (!this.transitioning) {
|
769
|
|
- TorConnect._changeState(
|
770
|
|
- TorConnectState.Error,
|
771
|
|
- err?.message,
|
772
|
|
- err?.details,
|
773
|
|
- true
|
774
|
|
- );
|
775
|
|
- } else {
|
776
|
|
- lazy.logger.error(
|
777
|
|
- "Received AutoBootstrapping error after transitioning",
|
778
|
|
- err
|
779
|
|
- );
|
780
|
|
- }
|
781
|
|
- } finally {
|
782
|
|
- // important to uninit MoatRPC object or else the pt process will live as long as tor-browser
|
783
|
|
- this.mrpc?.uninit();
|
784
|
|
- }
|
785
|
|
- });
|
786
|
|
- }),
|
787
|
|
- ],
|
788
|
|
- /* Bootstrapped */
|
789
|
|
- [
|
790
|
|
- TorConnectState.Bootstrapped,
|
791
|
|
- new StateCallback(TorConnectState.Bootstrapped, async function () {
|
792
|
|
- await new Promise((resolve, reject) => {
|
793
|
|
- // We may need to leave the bootstrapped state if the tor daemon
|
794
|
|
- // exits (if it is restarted, we will have to bootstrap again).
|
795
|
|
- this.on_transition = nextState => {
|
796
|
|
- resolve();
|
797
|
|
- };
|
798
|
|
- // notify observers of bootstrap completion
|
799
|
|
- Services.obs.notifyObservers(
|
800
|
|
- null,
|
801
|
|
- TorConnectTopics.BootstrapComplete
|
802
|
|
- );
|
803
|
|
- });
|
804
|
|
- }),
|
805
|
|
- ],
|
806
|
|
- /* Error */
|
807
|
|
- [
|
808
|
|
- TorConnectState.Error,
|
809
|
|
- new StateCallback(TorConnectState.Error, async function (
|
810
|
|
- errorMessage,
|
811
|
|
- errorDetails,
|
812
|
|
- bootstrappingFailure
|
813
|
|
- ) {
|
814
|
|
- await new Promise((resolve, reject) => {
|
815
|
|
- this.on_transition = async nextState => {
|
816
|
|
- resolve();
|
817
|
|
- };
|
818
|
|
-
|
819
|
|
- TorConnect._errorMessage = errorMessage;
|
820
|
|
- TorConnect._errorDetails = errorDetails;
|
821
|
|
- lazy.logger.error(
|
822
|
|
- `Entering error state (${errorMessage}, ${errorDetails})`
|
823
|
|
- );
|
824
|
|
-
|
825
|
|
- Services.obs.notifyObservers(
|
826
|
|
- { message: errorMessage, details: errorDetails },
|
827
|
|
- TorConnectTopics.BootstrapError
|
828
|
|
- );
|
829
|
|
-
|
830
|
|
- TorConnect._changeState(TorConnectState.Configuring);
|
831
|
|
- });
|
832
|
|
- }),
|
833
|
|
- ],
|
834
|
|
- /* Disabled */
|
835
|
|
- [
|
836
|
|
- TorConnectState.Disabled,
|
837
|
|
- new StateCallback(TorConnectState.Disabled, async function () {
|
838
|
|
- await new Promise((resolve, reject) => {
|
839
|
|
- // no-op, on_transition not defined because no way to leave Disabled state
|
840
|
|
- });
|
841
|
|
- }),
|
842
|
|
- ],
|
843
|
|
- ])
|
844
|
|
- ),
|
845
|
|
-
|
846
|
|
- _callback(state) {
|
847
|
|
- return this._stateCallbacks.get(state);
|
848
|
|
- },
|
849
|
|
-
|
850
|
|
- _changeState(newState, ...args) {
|
851
|
|
- if (newState === TorConnectState.Error) {
|
852
|
|
- this._hasEverFailed = true;
|
|
835
|
+export const TorConnect = {
|
|
836
|
+ _stateHandler: new InitialState(),
|
|
837
|
+ _bootstrapProgress: 0,
|
|
838
|
+ _bootstrapStatus: null,
|
|
839
|
+ _internetStatus: InternetStatus.Unknown,
|
|
840
|
+ // list of country codes Moat has settings for
|
|
841
|
+ _countryCodes: [],
|
|
842
|
+ _countryNames: Object.freeze(
|
|
843
|
+ (() => {
|
|
844
|
+ const codes = Services.intl.getAvailableLocaleDisplayNames("region");
|
|
845
|
+ const names = Services.intl.getRegionDisplayNames(undefined, codes);
|
|
846
|
+ let codesNames = {};
|
|
847
|
+ for (let i = 0; i < codes.length; i++) {
|
|
848
|
+ codesNames[codes[i]] = names[i];
|
853
|
849
|
}
|
854
|
|
- const prevState = this._state;
|
|
850
|
+ return codesNames;
|
|
851
|
+ })()
|
|
852
|
+ ),
|
|
853
|
+ _detectedLocation: "",
|
|
854
|
+ _errorMessage: null,
|
|
855
|
+ _errorDetails: null,
|
|
856
|
+ _logHasWarningOrError: false,
|
|
857
|
+ _hasBootstrapEverFailed: false,
|
|
858
|
+ _transitionPromise: null,
|
|
859
|
+
|
|
860
|
+ // This is used as a helper to make the state of about:torconnect persistent
|
|
861
|
+ // during a session, but TorConnect does not use this data at all.
|
|
862
|
+ _uiState: {},
|
|
863
|
+
|
|
864
|
+ _stateCallbacks: Object.freeze(
|
|
865
|
+ new Map([
|
|
866
|
+ // Initial is never transitioned to
|
|
867
|
+ [TorConnectState.Initial, InitialState],
|
|
868
|
+ [TorConnectState.Configuring, ConfiguringState],
|
|
869
|
+ [TorConnectState.Bootstrapping, BootstrappingState],
|
|
870
|
+ [TorConnectState.AutoBootstrapping, AutoBootstrappingState],
|
|
871
|
+ [TorConnectState.Bootstrapped, BootstrappedState],
|
|
872
|
+ [TorConnectState.Error, ErrorState],
|
|
873
|
+ [TorConnectState.Disabled, DisabledState],
|
|
874
|
+ ])
|
|
875
|
+ ),
|
|
876
|
+
|
|
877
|
+ _makeState(state) {
|
|
878
|
+ const klass = this._stateCallbacks.get(state);
|
|
879
|
+ if (!klass) {
|
|
880
|
+ throw new Error(`${state} is not a valid state.`);
|
|
881
|
+ }
|
|
882
|
+ return new klass();
|
|
883
|
+ },
|
|
884
|
+
|
|
885
|
+ async _changeState(newState, ...args) {
|
|
886
|
+ if (this._stateHandler.transitioning) {
|
|
887
|
+ // Avoid an exception to prevent it to be propagated to the original
|
|
888
|
+ // begin call.
|
|
889
|
+ lazy.logger.warn("Already transitioning");
|
|
890
|
+ return;
|
|
891
|
+ }
|
|
892
|
+ const prevState = this._stateHandler;
|
|
893
|
+
|
|
894
|
+ // ensure this is a valid state transition
|
|
895
|
+ if (!prevState.allowedTransitions.includes(newState)) {
|
|
896
|
+ throw Error(
|
|
897
|
+ `TorConnect: Attempted invalid state transition from ${prevState.state} to ${newState}`
|
|
898
|
+ );
|
|
899
|
+ }
|
855
|
900
|
|
856
|
|
- // ensure this is a valid state transition
|
857
|
|
- if (!TorConnectStateTransitions.get(prevState)?.includes(newState)) {
|
858
|
|
- throw Error(
|
859
|
|
- `TorConnect: Attempted invalid state transition from ${prevState} to ${newState}`
|
|
901
|
+ lazy.logger.trace(
|
|
902
|
+ `Try transitioning from ${prevState.state} to ${newState}`,
|
|
903
|
+ args
|
|
904
|
+ );
|
|
905
|
+ try {
|
|
906
|
+ await prevState.end(newState);
|
|
907
|
+ } catch (e) {
|
|
908
|
+ // We take for granted that the begin of this state will call us again,
|
|
909
|
+ // to request the transition to the error state.
|
|
910
|
+ if (newState !== TorConnectState.Error) {
|
|
911
|
+ lazy.logger.debug(
|
|
912
|
+ `Refusing the transition from ${prevState.state} to ${newState} because the previous state threw.`
|
860
|
913
|
);
|
|
914
|
+ return;
|
861
|
915
|
}
|
|
916
|
+ }
|
862
|
917
|
|
863
|
|
- lazy.logger.trace(`Try transitioning from ${prevState} to ${newState}`);
|
864
|
|
-
|
865
|
|
- // set our new state first so that state transitions can themselves trigger
|
866
|
|
- // a state transition
|
867
|
|
- this._state = newState;
|
868
|
|
-
|
869
|
|
- // call our state function and forward any args
|
870
|
|
- this._callback(prevState).transition(newState, ...args);
|
871
|
|
- },
|
|
918
|
+ // Set our new state first so that state transitions can themselves
|
|
919
|
+ // trigger a state transition.
|
|
920
|
+ this._stateHandler = this._makeState(newState);
|
|
921
|
+ Services.obs.notifyObservers(
|
|
922
|
+ { state: newState },
|
|
923
|
+ TorConnectTopics.StateChange
|
|
924
|
+ );
|
|
925
|
+ this._stateHandler.begin(...args);
|
|
926
|
+ },
|
872
|
927
|
|
873
|
|
- _updateBootstrapStatus(progress, status) {
|
874
|
|
- this._bootstrapProgress = progress;
|
875
|
|
- this._bootstrapStatus = status;
|
|
928
|
+ _updateBootstrapStatus(progress, status) {
|
|
929
|
+ this._bootstrapProgress = progress;
|
|
930
|
+ this._bootstrapStatus = status;
|
876
|
931
|
|
877
|
|
- lazy.logger.info(
|
878
|
|
- `Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`
|
879
|
|
- );
|
880
|
|
- Services.obs.notifyObservers(
|
881
|
|
- {
|
882
|
|
- progress: TorConnect._bootstrapProgress,
|
883
|
|
- status: TorConnect._bootstrapStatus,
|
884
|
|
- hasWarnings: TorConnect._logHasWarningOrError,
|
885
|
|
- },
|
886
|
|
- TorConnectTopics.BootstrapProgress
|
887
|
|
- );
|
888
|
|
- },
|
|
932
|
+ lazy.logger.info(
|
|
933
|
+ `Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`
|
|
934
|
+ );
|
|
935
|
+ Services.obs.notifyObservers(
|
|
936
|
+ {
|
|
937
|
+ progress: TorConnect._bootstrapProgress,
|
|
938
|
+ status: TorConnect._bootstrapStatus,
|
|
939
|
+ hasWarnings: TorConnect._logHasWarningOrError,
|
|
940
|
+ },
|
|
941
|
+ TorConnectTopics.BootstrapProgress
|
|
942
|
+ );
|
|
943
|
+ },
|
|
944
|
+
|
|
945
|
+ // init should be called by TorStartupService
|
|
946
|
+ init() {
|
|
947
|
+ lazy.logger.debug("TorConnect.init()");
|
|
948
|
+ this._stateHandler.begin();
|
|
949
|
+
|
|
950
|
+ if (!this.enabled) {
|
|
951
|
+ // Disabled
|
|
952
|
+ this._changeState(TorConnectState.Disabled);
|
|
953
|
+ } else {
|
|
954
|
+ let observeTopic = addTopic => {
|
|
955
|
+ Services.obs.addObserver(this, addTopic);
|
|
956
|
+ lazy.logger.debug(`Observing topic '${addTopic}'`);
|
|
957
|
+ };
|
|
958
|
+
|
|
959
|
+ // Wait for TorSettings, as we will need it.
|
|
960
|
+ // We will wait for a TorProvider only after TorSettings is ready,
|
|
961
|
+ // because the TorProviderBuilder initialization might not have finished
|
|
962
|
+ // at this point, and TorSettings initialization is a prerequisite for
|
|
963
|
+ // having a provider.
|
|
964
|
+ // So, we prefer initializing TorConnect as soon as possible, so that
|
|
965
|
+ // the UI will be able to detect it is in the Initializing state and act
|
|
966
|
+ // consequently.
|
|
967
|
+ TorSettings.initializedPromise.then(() => this._settingsInitialized());
|
|
968
|
+
|
|
969
|
+ // register the Tor topics we always care about
|
|
970
|
+ observeTopic(TorTopics.ProcessExited);
|
|
971
|
+ observeTopic(TorTopics.LogHasWarnOrErr);
|
|
972
|
+ }
|
|
973
|
+ },
|
889
|
974
|
|
890
|
|
- // init should be called by TorStartupService
|
891
|
|
- init() {
|
892
|
|
- lazy.logger.debug("TorConnect.init()");
|
893
|
|
- this._callback(TorConnectState.Initial).begin();
|
|
975
|
+ async observe(subject, topic, data) {
|
|
976
|
+ lazy.logger.debug(`Observed ${topic}`);
|
894
|
977
|
|
895
|
|
- if (!this.enabled) {
|
896
|
|
- // Disabled
|
897
|
|
- this._changeState(TorConnectState.Disabled);
|
898
|
|
- } else {
|
899
|
|
- let observeTopic = addTopic => {
|
900
|
|
- Services.obs.addObserver(this, addTopic);
|
901
|
|
- lazy.logger.debug(`Observing topic '${addTopic}'`);
|
902
|
|
- };
|
903
|
|
-
|
904
|
|
- // Wait for TorSettings, as we will need it.
|
905
|
|
- // We will wait for a TorProvider only after TorSettings is ready,
|
906
|
|
- // because the TorProviderBuilder initialization might not have finished
|
907
|
|
- // at this point, and TorSettings initialization is a prerequisite for
|
908
|
|
- // having a provider.
|
909
|
|
- // So, we prefer initializing TorConnect as soon as possible, so that
|
910
|
|
- // the UI will be able to detect it is in the Initializing state and act
|
911
|
|
- // consequently.
|
912
|
|
- TorSettings.initializedPromise.then(() => this._settingsInitialized());
|
913
|
|
-
|
914
|
|
- // register the Tor topics we always care about
|
915
|
|
- observeTopic(TorTopics.ProcessExited);
|
916
|
|
- observeTopic(TorTopics.LogHasWarnOrErr);
|
|
978
|
+ switch (topic) {
|
|
979
|
+ case TorTopics.LogHasWarnOrErr: {
|
|
980
|
+ this._logHasWarningOrError = true;
|
|
981
|
+ break;
|
917
|
982
|
}
|
918
|
|
- },
|
919
|
|
-
|
920
|
|
- async observe(subject, topic, data) {
|
921
|
|
- lazy.logger.debug(`Observed ${topic}`);
|
922
|
|
-
|
923
|
|
- switch (topic) {
|
924
|
|
- case TorTopics.LogHasWarnOrErr: {
|
925
|
|
- this._logHasWarningOrError = true;
|
926
|
|
- break;
|
|
983
|
+ case TorTopics.ProcessExited: {
|
|
984
|
+ // Treat a failure as a possibly broken configuration.
|
|
985
|
+ // So, prevent quickstart at the next start.
|
|
986
|
+ Services.prefs.setBoolPref(TorLauncherPrefs.prompt_at_startup, true);
|
|
987
|
+ switch (this.state) {
|
|
988
|
+ case TorConnectState.Bootstrapping:
|
|
989
|
+ case TorConnectState.AutoBootstrapping:
|
|
990
|
+ case TorConnectState.Bootstrapped:
|
|
991
|
+ // If we are in the bootstrap or auto bootstrap, we could go
|
|
992
|
+ // through the error phase (and eventually we might do it, if some
|
|
993
|
+ // transition calls fail). However, this would start the
|
|
994
|
+ // connection assist, so we go directly to configuring.
|
|
995
|
+ // FIXME: Find a better way to handle this.
|
|
996
|
+ this._changeState(TorConnectState.Configuring);
|
|
997
|
+ break;
|
|
998
|
+ // Other states naturally resolve in configuration.
|
927
|
999
|
}
|
928
|
|
- case TorTopics.ProcessExited: {
|
929
|
|
- // Treat a failure as a possibly broken configuration.
|
930
|
|
- // So, prevent quickstart at the next start.
|
931
|
|
- Services.prefs.setBoolPref(TorLauncherPrefs.prompt_at_startup, true);
|
932
|
|
- switch (this._state) {
|
933
|
|
- case TorConnectState.Bootstrapping:
|
934
|
|
- case TorConnectState.AutoBootstrapping:
|
935
|
|
- case TorConnectState.Bootstrapped:
|
936
|
|
- // If we are in the bootstrap or auto bootstrap, we could go
|
937
|
|
- // through the error phase (and eventually we might do it, if some
|
938
|
|
- // transition calls fail). However, this would start the
|
939
|
|
- // connection assist, so we go directly to configuring.
|
940
|
|
- // FIXME: Find a better way to handle this.
|
941
|
|
- this._changeState(TorConnectState.Configuring);
|
942
|
|
- break;
|
943
|
|
- // Other states naturally resolve in configuration.
|
944
|
|
- }
|
945
|
|
- break;
|
946
|
|
- }
|
947
|
|
- default:
|
948
|
|
- // ignore
|
949
|
|
- break;
|
950
|
|
- }
|
951
|
|
- },
|
952
|
|
-
|
953
|
|
- async _settingsInitialized() {
|
954
|
|
- // TODO: Handle failures here, instead of the prompt to restart the
|
955
|
|
- // daemon when it exits (tor-browser#21053, tor-browser#41921).
|
956
|
|
- await lazy.TorProviderBuilder.build();
|
957
|
|
-
|
958
|
|
- // tor-browser#41907: This is only a workaround to avoid users being
|
959
|
|
- // bounced back to the initial panel without any explanation.
|
960
|
|
- // Longer term we should disable the clickable elements, or find a UX
|
961
|
|
- // to prevent this from happening (e.g., allow buttons to be clicked,
|
962
|
|
- // but show an intermediate starting state, or a message that tor is
|
963
|
|
- // starting while the butons are disabled, etc...).
|
964
|
|
- // See also tor-browser#41921.
|
965
|
|
- if (this.state !== TorConnectState.Initial) {
|
966
|
|
- lazy.logger.warn(
|
967
|
|
- "The TorProvider was built after the state had already changed."
|
968
|
|
- );
|
969
|
|
- return;
|
970
|
|
- }
|
971
|
|
- lazy.logger.debug("The TorProvider is ready, changing state.");
|
972
|
|
- if (this.shouldQuickStart) {
|
973
|
|
- // Quickstart
|
974
|
|
- this._changeState(TorConnectState.Bootstrapping);
|
975
|
|
- } else {
|
976
|
|
- // Configuring
|
977
|
|
- this._changeState(TorConnectState.Configuring);
|
|
1000
|
+ break;
|
978
|
1001
|
}
|
979
|
|
- },
|
980
|
|
-
|
981
|
|
- /*
|
982
|
|
- Various getters
|
983
|
|
- */
|
984
|
|
-
|
985
|
|
- /**
|
986
|
|
- * Whether TorConnect is enabled.
|
987
|
|
- *
|
988
|
|
- * @type {boolean}
|
989
|
|
- */
|
990
|
|
- get enabled() {
|
991
|
|
- // FIXME: This is called before the TorProvider is ready.
|
992
|
|
- // As a matter of fact, at the moment it is equivalent to the following
|
993
|
|
- // line, but this might become a problem in the future.
|
994
|
|
- return TorLauncherUtil.shouldStartAndOwnTor;
|
995
|
|
- },
|
996
|
|
-
|
997
|
|
- get shouldShowTorConnect() {
|
998
|
|
- // TorBrowser must control the daemon
|
999
|
|
- return (
|
1000
|
|
- this.enabled &&
|
1001
|
|
- // if we have succesfully bootstraped, then no need to show TorConnect
|
1002
|
|
- this.state !== TorConnectState.Bootstrapped
|
1003
|
|
- );
|
1004
|
|
- },
|
1005
|
|
-
|
1006
|
|
- /**
|
1007
|
|
- * Whether bootstrapping can currently begin.
|
1008
|
|
- *
|
1009
|
|
- * The value may change with TorConnectTopics.StateChanged.
|
1010
|
|
- *
|
1011
|
|
- * @param {boolean}
|
1012
|
|
- */
|
1013
|
|
- get canBeginBootstrap() {
|
1014
|
|
- return TorConnectStateTransitions.get(this.state).includes(
|
1015
|
|
- TorConnectState.Bootstrapping
|
1016
|
|
- );
|
1017
|
|
- },
|
1018
|
|
-
|
1019
|
|
- /**
|
1020
|
|
- * Whether auto-bootstrapping can currently begin.
|
1021
|
|
- *
|
1022
|
|
- * The value may change with TorConnectTopics.StateChanged.
|
1023
|
|
- *
|
1024
|
|
- * @param {boolean}
|
1025
|
|
- */
|
1026
|
|
- get canBeginAutoBootstrap() {
|
1027
|
|
- return TorConnectStateTransitions.get(this.state).includes(
|
1028
|
|
- TorConnectState.AutoBootstrapping
|
1029
|
|
- );
|
1030
|
|
- },
|
1031
|
|
-
|
1032
|
|
- get shouldQuickStart() {
|
1033
|
|
- // quickstart must be enabled
|
1034
|
|
- return (
|
1035
|
|
- TorSettings.quickstart.enabled &&
|
1036
|
|
- // and the previous bootstrap attempt must have succeeded
|
1037
|
|
- !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true)
|
|
1002
|
+ default:
|
|
1003
|
+ // ignore
|
|
1004
|
+ break;
|
|
1005
|
+ }
|
|
1006
|
+ },
|
|
1007
|
+
|
|
1008
|
+ async _settingsInitialized() {
|
|
1009
|
+ // TODO: Handle failures here, instead of the prompt to restart the
|
|
1010
|
+ // daemon when it exits (tor-browser#21053, tor-browser#41921).
|
|
1011
|
+ await lazy.TorProviderBuilder.build();
|
|
1012
|
+
|
|
1013
|
+ // tor-browser#41907: This is only a workaround to avoid users being
|
|
1014
|
+ // bounced back to the initial panel without any explanation.
|
|
1015
|
+ // Longer term we should disable the clickable elements, or find a UX
|
|
1016
|
+ // to prevent this from happening (e.g., allow buttons to be clicked,
|
|
1017
|
+ // but show an intermediate starting state, or a message that tor is
|
|
1018
|
+ // starting while the butons are disabled, etc...).
|
|
1019
|
+ // Notice that currently the initial state does not do anything.
|
|
1020
|
+ // Instead of just waiting, we could move this code in its callback.
|
|
1021
|
+ // See also tor-browser#41921.
|
|
1022
|
+ if (this.state !== TorConnectState.Initial) {
|
|
1023
|
+ lazy.logger.warn(
|
|
1024
|
+ "The TorProvider was built after the state had already changed."
|
1038
|
1025
|
);
|
1039
|
|
- },
|
1040
|
|
-
|
1041
|
|
- get state() {
|
1042
|
|
- return this._state;
|
1043
|
|
- },
|
1044
|
|
-
|
1045
|
|
- get bootstrapProgress() {
|
1046
|
|
- return this._bootstrapProgress;
|
1047
|
|
- },
|
|
1026
|
+ return;
|
|
1027
|
+ }
|
|
1028
|
+ lazy.logger.debug("The TorProvider is ready, changing state.");
|
|
1029
|
+ if (this.shouldQuickStart) {
|
|
1030
|
+ // Quickstart
|
|
1031
|
+ this._changeState(TorConnectState.Bootstrapping);
|
|
1032
|
+ } else {
|
|
1033
|
+ // Configuring
|
|
1034
|
+ this._changeState(TorConnectState.Configuring);
|
|
1035
|
+ }
|
|
1036
|
+ },
|
1048
|
1037
|
|
1049
|
|
- get bootstrapStatus() {
|
1050
|
|
- return this._bootstrapStatus;
|
1051
|
|
- },
|
|
1038
|
+ /*
|
|
1039
|
+ Various getters
|
|
1040
|
+ */
|
1052
|
1041
|
|
1053
|
|
- get internetStatus() {
|
1054
|
|
- return this._internetStatus;
|
1055
|
|
- },
|
|
1042
|
+ /**
|
|
1043
|
+ * Whether TorConnect is enabled.
|
|
1044
|
+ *
|
|
1045
|
+ * @type {boolean}
|
|
1046
|
+ */
|
|
1047
|
+ get enabled() {
|
|
1048
|
+ // FIXME: This is called before the TorProvider is ready.
|
|
1049
|
+ // As a matter of fact, at the moment it is equivalent to the following
|
|
1050
|
+ // line, but this might become a problem in the future.
|
|
1051
|
+ return TorLauncherUtil.shouldStartAndOwnTor;
|
|
1052
|
+ },
|
|
1053
|
+
|
|
1054
|
+ get shouldShowTorConnect() {
|
|
1055
|
+ // TorBrowser must control the daemon
|
|
1056
|
+ return (
|
|
1057
|
+ this.enabled &&
|
|
1058
|
+ // if we have succesfully bootstraped, then no need to show TorConnect
|
|
1059
|
+ this.state !== TorConnectState.Bootstrapped
|
|
1060
|
+ );
|
|
1061
|
+ },
|
|
1062
|
+
|
|
1063
|
+ /**
|
|
1064
|
+ * Whether bootstrapping can currently begin.
|
|
1065
|
+ *
|
|
1066
|
+ * The value may change with TorConnectTopics.StateChanged.
|
|
1067
|
+ *
|
|
1068
|
+ * @param {boolean}
|
|
1069
|
+ */
|
|
1070
|
+ get canBeginBootstrap() {
|
|
1071
|
+ return this._stateHandler.allowedTransitions.includes(
|
|
1072
|
+ TorConnectState.Bootstrapping
|
|
1073
|
+ );
|
|
1074
|
+ },
|
|
1075
|
+
|
|
1076
|
+ /**
|
|
1077
|
+ * Whether auto-bootstrapping can currently begin.
|
|
1078
|
+ *
|
|
1079
|
+ * The value may change with TorConnectTopics.StateChanged.
|
|
1080
|
+ *
|
|
1081
|
+ * @param {boolean}
|
|
1082
|
+ */
|
|
1083
|
+ get canBeginAutoBootstrap() {
|
|
1084
|
+ return this._stateHandler.allowedTransitions.includes(
|
|
1085
|
+ TorConnectState.AutoBootstrapping
|
|
1086
|
+ );
|
|
1087
|
+ },
|
|
1088
|
+
|
|
1089
|
+ get shouldQuickStart() {
|
|
1090
|
+ // quickstart must be enabled
|
|
1091
|
+ return (
|
|
1092
|
+ TorSettings.quickstart.enabled &&
|
|
1093
|
+ // and the previous bootstrap attempt must have succeeded
|
|
1094
|
+ !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true)
|
|
1095
|
+ );
|
|
1096
|
+ },
|
|
1097
|
+
|
|
1098
|
+ get state() {
|
|
1099
|
+ return this._stateHandler.state;
|
|
1100
|
+ },
|
|
1101
|
+
|
|
1102
|
+ get bootstrapProgress() {
|
|
1103
|
+ return this._bootstrapProgress;
|
|
1104
|
+ },
|
|
1105
|
+
|
|
1106
|
+ get bootstrapStatus() {
|
|
1107
|
+ return this._bootstrapStatus;
|
|
1108
|
+ },
|
|
1109
|
+
|
|
1110
|
+ get internetStatus() {
|
|
1111
|
+ return this._internetStatus;
|
|
1112
|
+ },
|
|
1113
|
+
|
|
1114
|
+ get countryCodes() {
|
|
1115
|
+ return this._countryCodes;
|
|
1116
|
+ },
|
|
1117
|
+
|
|
1118
|
+ get countryNames() {
|
|
1119
|
+ return this._countryNames;
|
|
1120
|
+ },
|
|
1121
|
+
|
|
1122
|
+ get detectedLocation() {
|
|
1123
|
+ return this._detectedLocation;
|
|
1124
|
+ },
|
|
1125
|
+
|
|
1126
|
+ get errorMessage() {
|
|
1127
|
+ return this._errorMessage;
|
|
1128
|
+ },
|
|
1129
|
+
|
|
1130
|
+ get errorDetails() {
|
|
1131
|
+ return this._errorDetails;
|
|
1132
|
+ },
|
|
1133
|
+
|
|
1134
|
+ get logHasWarningOrError() {
|
|
1135
|
+ return this._logHasWarningOrError;
|
|
1136
|
+ },
|
|
1137
|
+
|
|
1138
|
+ /**
|
|
1139
|
+ * Whether we have ever entered the Error state.
|
|
1140
|
+ *
|
|
1141
|
+ * @type {boolean}
|
|
1142
|
+ */
|
|
1143
|
+ get hasEverFailed() {
|
|
1144
|
+ return ErrorState.hasEverHappened;
|
|
1145
|
+ },
|
|
1146
|
+
|
|
1147
|
+ /**
|
|
1148
|
+ * Whether the Bootstrapping process has ever failed, not including when it
|
|
1149
|
+ * failed due to not being connected to the internet.
|
|
1150
|
+ *
|
|
1151
|
+ * This does not include a failure in AutoBootstrapping.
|
|
1152
|
+ *
|
|
1153
|
+ * @type {boolean}
|
|
1154
|
+ */
|
|
1155
|
+ get potentiallyBlocked() {
|
|
1156
|
+ return this._hasBootstrapEverFailed;
|
|
1157
|
+ },
|
|
1158
|
+
|
|
1159
|
+ get uiState() {
|
|
1160
|
+ return this._uiState;
|
|
1161
|
+ },
|
|
1162
|
+ set uiState(newState) {
|
|
1163
|
+ this._uiState = newState;
|
|
1164
|
+ },
|
|
1165
|
+
|
|
1166
|
+ /*
|
|
1167
|
+ These functions allow external consumers to tell TorConnect to transition states
|
|
1168
|
+ */
|
|
1169
|
+
|
|
1170
|
+ beginBootstrap() {
|
|
1171
|
+ lazy.logger.debug("TorConnect.beginBootstrap()");
|
|
1172
|
+ this._changeState(TorConnectState.Bootstrapping);
|
|
1173
|
+ },
|
|
1174
|
+
|
|
1175
|
+ cancelBootstrap() {
|
|
1176
|
+ lazy.logger.debug("TorConnect.cancelBootstrap()");
|
|
1177
|
+ if (
|
|
1178
|
+ this.state !== TorConnectState.AutoBootstrapping &&
|
|
1179
|
+ this.state !== TorConnectState.Bootstrapping
|
|
1180
|
+ ) {
|
|
1181
|
+ lazy.logger.warn(
|
|
1182
|
+ `Cannot cancel bootstrapping in the ${this.state} state`
|
|
1183
|
+ );
|
|
1184
|
+ return;
|
|
1185
|
+ }
|
|
1186
|
+ this._changeState(TorConnectState.Configuring);
|
|
1187
|
+ },
|
|
1188
|
+
|
|
1189
|
+ beginAutoBootstrap(countryCode) {
|
|
1190
|
+ lazy.logger.debug("TorConnect.beginAutoBootstrap()");
|
|
1191
|
+ this._changeState(TorConnectState.AutoBootstrapping, countryCode);
|
|
1192
|
+ },
|
|
1193
|
+
|
|
1194
|
+ /*
|
|
1195
|
+ Further external commands and helper methods
|
|
1196
|
+ */
|
|
1197
|
+ openTorPreferences() {
|
|
1198
|
+ if (TorLauncherUtil.isAndroid) {
|
|
1199
|
+ lazy.EventDispatcher.instance.sendRequest({
|
|
1200
|
+ type: "GeckoView:Tor:OpenSettings",
|
|
1201
|
+ });
|
|
1202
|
+ return;
|
|
1203
|
+ }
|
|
1204
|
+ const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
1205
|
+ win.switchToTabHavingURI("about:preferences#connection", true);
|
|
1206
|
+ },
|
|
1207
|
+
|
|
1208
|
+ /**
|
|
1209
|
+ * Open the "about:torconnect" tab.
|
|
1210
|
+ *
|
|
1211
|
+ * Bootstrapping or AutoBootstrapping can also be automatically triggered at
|
|
1212
|
+ * the same time, if the current state allows for it.
|
|
1213
|
+ *
|
|
1214
|
+ * Bootstrapping will not be triggered if the connection is
|
|
1215
|
+ * potentially blocked.
|
|
1216
|
+ *
|
|
1217
|
+ * @param {object} [options] - extra options.
|
|
1218
|
+ * @property {boolean} [options.beginBootstrap=false] - Whether to try and
|
|
1219
|
+ * begin Bootstrapping.
|
|
1220
|
+ * @property {string} [options.beginAutoBootstrap] - The location to use to
|
|
1221
|
+ * begin AutoBootstrapping, if possible.
|
|
1222
|
+ */
|
|
1223
|
+ openTorConnect(options) {
|
|
1224
|
+ const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
1225
|
+ win.switchToTabHavingURI("about:torconnect", true, {
|
|
1226
|
+ ignoreQueryString: true,
|
|
1227
|
+ });
|
|
1228
|
+ if (
|
|
1229
|
+ options?.beginBootstrap &&
|
|
1230
|
+ this.canBeginBootstrap &&
|
|
1231
|
+ !this.potentiallyBlocked
|
|
1232
|
+ ) {
|
|
1233
|
+ this.beginBootstrap();
|
|
1234
|
+ }
|
|
1235
|
+ // options.beginAutoBootstrap can be an empty string.
|
|
1236
|
+ if (
|
|
1237
|
+ options?.beginAutoBootstrap !== undefined &&
|
|
1238
|
+ this.canBeginAutoBootstrap
|
|
1239
|
+ ) {
|
|
1240
|
+ this.beginAutoBootstrap(options.beginAutoBootstrap);
|
|
1241
|
+ }
|
|
1242
|
+ },
|
1056
|
1243
|
|
1057
|
|
- get countryCodes() {
|
1058
|
|
- return this._countryCodes;
|
1059
|
|
- },
|
1060
|
|
-
|
1061
|
|
- get countryNames() {
|
1062
|
|
- return this._countryNames;
|
1063
|
|
- },
|
1064
|
|
-
|
1065
|
|
- get detectedLocation() {
|
1066
|
|
- return this._detectedLocation;
|
1067
|
|
- },
|
1068
|
|
-
|
1069
|
|
- get errorMessage() {
|
1070
|
|
- return this._errorMessage;
|
1071
|
|
- },
|
1072
|
|
-
|
1073
|
|
- get errorDetails() {
|
1074
|
|
- return this._errorDetails;
|
1075
|
|
- },
|
1076
|
|
-
|
1077
|
|
- get logHasWarningOrError() {
|
1078
|
|
- return this._logHasWarningOrError;
|
1079
|
|
- },
|
1080
|
|
-
|
1081
|
|
- /**
|
1082
|
|
- * Whether we have ever entered the Error state.
|
1083
|
|
- *
|
1084
|
|
- * @type {boolean}
|
1085
|
|
- */
|
1086
|
|
- get hasEverFailed() {
|
1087
|
|
- return this._hasEverFailed;
|
1088
|
|
- },
|
1089
|
|
-
|
1090
|
|
- /**
|
1091
|
|
- * Whether the Bootstrapping process has ever failed, not including when it
|
1092
|
|
- * failed due to not being connected to the internet.
|
1093
|
|
- *
|
1094
|
|
- * This does not include a failure in AutoBootstrapping.
|
1095
|
|
- *
|
1096
|
|
- * @type {boolean}
|
1097
|
|
- */
|
1098
|
|
- get potentiallyBlocked() {
|
1099
|
|
- return this._hasBootstrapEverFailed;
|
1100
|
|
- },
|
1101
|
|
-
|
1102
|
|
- get uiState() {
|
1103
|
|
- return this._uiState;
|
1104
|
|
- },
|
1105
|
|
- set uiState(newState) {
|
1106
|
|
- this._uiState = newState;
|
1107
|
|
- },
|
1108
|
|
-
|
1109
|
|
- /*
|
1110
|
|
- These functions allow external consumers to tell TorConnect to transition states
|
1111
|
|
- */
|
1112
|
|
-
|
1113
|
|
- beginBootstrap() {
|
1114
|
|
- lazy.logger.debug("TorConnect.beginBootstrap()");
|
1115
|
|
- this._changeState(TorConnectState.Bootstrapping);
|
1116
|
|
- },
|
|
1244
|
+ viewTorLogs() {
|
|
1245
|
+ const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
1246
|
+ win.switchToTabHavingURI("about:preferences#connection-viewlogs", true);
|
|
1247
|
+ },
|
1117
|
1248
|
|
1118
|
|
- cancelBootstrap() {
|
1119
|
|
- lazy.logger.debug("TorConnect.cancelBootstrap()");
|
1120
|
|
- this._changeState(TorConnectState.Configuring);
|
1121
|
|
- },
|
1122
|
|
-
|
1123
|
|
- beginAutoBootstrap(countryCode) {
|
1124
|
|
- lazy.logger.debug("TorConnect.beginAutoBootstrap()");
|
1125
|
|
- this._changeState(TorConnectState.AutoBootstrapping, countryCode);
|
1126
|
|
- },
|
1127
|
|
-
|
1128
|
|
- /*
|
1129
|
|
- Further external commands and helper methods
|
1130
|
|
- */
|
1131
|
|
- openTorPreferences() {
|
1132
|
|
- if (TorLauncherUtil.isAndroid) {
|
1133
|
|
- lazy.EventDispatcher.instance.sendRequest({
|
1134
|
|
- type: "GeckoView:Tor:OpenSettings",
|
1135
|
|
- });
|
1136
|
|
- return;
|
1137
|
|
- }
|
1138
|
|
- const win = lazy.BrowserWindowTracker.getTopWindow();
|
1139
|
|
- win.switchToTabHavingURI("about:preferences#connection", true);
|
1140
|
|
- },
|
1141
|
|
-
|
1142
|
|
- /**
|
1143
|
|
- * Open the "about:torconnect" tab.
|
1144
|
|
- *
|
1145
|
|
- * Bootstrapping or AutoBootstrapping can also be automatically triggered at
|
1146
|
|
- * the same time, if the current state allows for it.
|
1147
|
|
- *
|
1148
|
|
- * Bootstrapping will not be triggered if the connection is
|
1149
|
|
- * potentially blocked.
|
1150
|
|
- *
|
1151
|
|
- * @param {object} [options] - extra options.
|
1152
|
|
- * @property {boolean} [options.beginBootstrap=false] - Whether to try and
|
1153
|
|
- * begin Bootstrapping.
|
1154
|
|
- * @property {string} [options.beginAutoBootstrap] - The location to use to
|
1155
|
|
- * begin AutoBootstrapping, if possible.
|
1156
|
|
- */
|
1157
|
|
- openTorConnect(options) {
|
1158
|
|
- const win = lazy.BrowserWindowTracker.getTopWindow();
|
1159
|
|
- win.switchToTabHavingURI("about:torconnect", true, {
|
1160
|
|
- ignoreQueryString: true,
|
1161
|
|
- });
|
1162
|
|
- if (
|
1163
|
|
- options?.beginBootstrap &&
|
1164
|
|
- this.canBeginBootstrap &&
|
1165
|
|
- !this.potentiallyBlocked
|
1166
|
|
- ) {
|
1167
|
|
- this.beginBootstrap();
|
1168
|
|
- }
|
1169
|
|
- // options.beginAutoBootstrap can be an empty string.
|
1170
|
|
- if (
|
1171
|
|
- options?.beginAutoBootstrap !== undefined &&
|
1172
|
|
- this.canBeginAutoBootstrap
|
1173
|
|
- ) {
|
1174
|
|
- this.beginAutoBootstrap(options.beginAutoBootstrap);
|
1175
|
|
- }
|
1176
|
|
- },
|
1177
|
|
-
|
1178
|
|
- viewTorLogs() {
|
1179
|
|
- const win = lazy.BrowserWindowTracker.getTopWindow();
|
1180
|
|
- win.switchToTabHavingURI("about:preferences#connection-viewlogs", true);
|
1181
|
|
- },
|
1182
|
|
-
|
1183
|
|
- async getCountryCodes() {
|
1184
|
|
- // Difference with the getter: this is to be called by TorConnectParent, and downloads
|
1185
|
|
- // the country codes if they are not already in cache.
|
1186
|
|
- if (this._countryCodes.length) {
|
1187
|
|
- return this._countryCodes;
|
1188
|
|
- }
|
1189
|
|
- const mrpc = new lazy.MoatRPC();
|
1190
|
|
- try {
|
1191
|
|
- await mrpc.init();
|
1192
|
|
- this._countryCodes = await mrpc.circumvention_countries();
|
1193
|
|
- } catch (err) {
|
1194
|
|
- lazy.logger.error(
|
1195
|
|
- "An error occurred while fetching country codes",
|
1196
|
|
- err
|
1197
|
|
- );
|
1198
|
|
- } finally {
|
1199
|
|
- mrpc.uninit();
|
1200
|
|
- }
|
|
1249
|
+ async getCountryCodes() {
|
|
1250
|
+ // Difference with the getter: this is to be called by TorConnectParent, and
|
|
1251
|
+ // downloads the country codes if they are not already in cache.
|
|
1252
|
+ if (this._countryCodes.length) {
|
1201
|
1253
|
return this._countryCodes;
|
1202
|
|
- },
|
1203
|
|
-
|
1204
|
|
- getRedirectURL(url) {
|
1205
|
|
- return `about:torconnect?redirect=${encodeURIComponent(url)}`;
|
1206
|
|
- },
|
1207
|
|
-
|
1208
|
|
- /**
|
1209
|
|
- * Convert the given object into a list of valid URIs.
|
1210
|
|
- *
|
1211
|
|
- * The object is either from the user's homepage preference (which may
|
1212
|
|
- * contain multiple domains separated by "|") or uris passed to the browser
|
1213
|
|
- * via command-line.
|
1214
|
|
- *
|
1215
|
|
- * @param {string|string[]} uriVariant - The string to extract uris from.
|
1216
|
|
- *
|
1217
|
|
- * @return {string[]} - The array of uris found.
|
1218
|
|
- */
|
1219
|
|
- fixupURIs(uriVariant) {
|
1220
|
|
- let uriArray;
|
1221
|
|
- if (typeof uriVariant === "string") {
|
1222
|
|
- uriArray = uriVariant.split("|");
|
1223
|
|
- } else if (
|
1224
|
|
- Array.isArray(uriVariant) &&
|
1225
|
|
- uriVariant.every(entry => typeof entry === "string")
|
1226
|
|
- ) {
|
1227
|
|
- uriArray = uriVariant;
|
1228
|
|
- } else {
|
1229
|
|
- // about:tor as safe fallback
|
1230
|
|
- lazy.logger.error(
|
1231
|
|
- `Received unknown variant '${JSON.stringify(uriVariant)}'`
|
1232
|
|
- );
|
1233
|
|
- uriArray = ["about:tor"];
|
1234
|
|
- }
|
1235
|
|
-
|
1236
|
|
- // Attempt to convert user-supplied string to a uri, fallback to
|
1237
|
|
- // about:tor if cannot convert to valid uri object
|
1238
|
|
- return uriArray.map(
|
1239
|
|
- uriString =>
|
1240
|
|
- Services.uriFixup.getFixupURIInfo(
|
1241
|
|
- uriString,
|
1242
|
|
- Ci.nsIURIFixup.FIXUP_FLAG_NONE
|
1243
|
|
- ).preferredURI?.spec ?? "about:tor"
|
|
1254
|
+ }
|
|
1255
|
+ const mrpc = new lazy.MoatRPC();
|
|
1256
|
+ try {
|
|
1257
|
+ await mrpc.init();
|
|
1258
|
+ this._countryCodes = await mrpc.circumvention_countries();
|
|
1259
|
+ } catch (err) {
|
|
1260
|
+ lazy.logger.error("An error occurred while fetching country codes", err);
|
|
1261
|
+ } finally {
|
|
1262
|
+ mrpc.uninit();
|
|
1263
|
+ }
|
|
1264
|
+ return this._countryCodes;
|
|
1265
|
+ },
|
|
1266
|
+
|
|
1267
|
+ getRedirectURL(url) {
|
|
1268
|
+ return `about:torconnect?redirect=${encodeURIComponent(url)}`;
|
|
1269
|
+ },
|
|
1270
|
+
|
|
1271
|
+ /**
|
|
1272
|
+ * Convert the given object into a list of valid URIs.
|
|
1273
|
+ *
|
|
1274
|
+ * The object is either from the user's homepage preference (which may
|
|
1275
|
+ * contain multiple domains separated by "|") or uris passed to the browser
|
|
1276
|
+ * via command-line.
|
|
1277
|
+ *
|
|
1278
|
+ * @param {string|string[]} uriVariant - The string to extract uris from.
|
|
1279
|
+ *
|
|
1280
|
+ * @return {string[]} - The array of uris found.
|
|
1281
|
+ */
|
|
1282
|
+ fixupURIs(uriVariant) {
|
|
1283
|
+ let uriArray;
|
|
1284
|
+ if (typeof uriVariant === "string") {
|
|
1285
|
+ uriArray = uriVariant.split("|");
|
|
1286
|
+ } else if (
|
|
1287
|
+ Array.isArray(uriVariant) &&
|
|
1288
|
+ uriVariant.every(entry => typeof entry === "string")
|
|
1289
|
+ ) {
|
|
1290
|
+ uriArray = uriVariant;
|
|
1291
|
+ } else {
|
|
1292
|
+ // about:tor as safe fallback
|
|
1293
|
+ lazy.logger.error(
|
|
1294
|
+ `Received unknown variant '${JSON.stringify(uriVariant)}'`
|
1244
|
1295
|
);
|
1245
|
|
- },
|
1246
|
|
-
|
1247
|
|
- // called from browser.js on browser startup, passed in either the user's homepage(s)
|
1248
|
|
- // or uris passed via command-line; we want to replace them with about:torconnect uris
|
1249
|
|
- // which redirect after bootstrapping
|
1250
|
|
- getURIsToLoad(uriVariant) {
|
1251
|
|
- const uris = this.fixupURIs(uriVariant);
|
1252
|
|
- lazy.logger.debug(`Will load after bootstrap => [${uris.join(", ")}]`);
|
1253
|
|
- return uris.map(uri => this.getRedirectURL(uri));
|
1254
|
|
- },
|
1255
|
|
- };
|
1256
|
|
- return retval;
|
1257
|
|
-})(); /* TorConnect */ |
|
1296
|
+ uriArray = ["about:tor"];
|
|
1297
|
+ }
|
|
1298
|
+
|
|
1299
|
+ // Attempt to convert user-supplied string to a uri, fallback to
|
|
1300
|
+ // about:tor if cannot convert to valid uri object
|
|
1301
|
+ return uriArray.map(
|
|
1302
|
+ uriString =>
|
|
1303
|
+ Services.uriFixup.getFixupURIInfo(
|
|
1304
|
+ uriString,
|
|
1305
|
+ Ci.nsIURIFixup.FIXUP_FLAG_NONE
|
|
1306
|
+ ).preferredURI?.spec ?? "about:tor"
|
|
1307
|
+ );
|
|
1308
|
+ },
|
|
1309
|
+
|
|
1310
|
+ // called from browser.js on browser startup, passed in either the user's homepage(s)
|
|
1311
|
+ // or uris passed via command-line; we want to replace them with about:torconnect uris
|
|
1312
|
+ // which redirect after bootstrapping
|
|
1313
|
+ getURIsToLoad(uriVariant) {
|
|
1314
|
+ const uris = this.fixupURIs(uriVariant);
|
|
1315
|
+ lazy.logger.debug(`Will load after bootstrap => [${uris.join(", ")}]`);
|
|
1316
|
+ return uris.map(uri => this.getRedirectURL(uri));
|
|
1317
|
+ },
|
|
1318
|
+}; |