Skip to content

Commit 2e4ab31

Browse files
committed
Use UITour in SwitchingDevicesWizardManager to get FxA signed-in status (#1276)
For versions of Firefox >= 114, we can use UITour.observe to notice for changes to the FxA sign-in state. For versions older than that, we'll use simple polling at a 500ms interval. This patch also sets up the initial set of steps for the step-indicator in the <form-wizard>.
1 parent 5bfd41a commit 2e4ab31

4 files changed

Lines changed: 384 additions & 21 deletions

File tree

kitsune/sumo/static/sumo/js/device-migration-wizard.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import UITour from "./libs/uitour";
21
import trackEvent from "sumo/js/analytics";
32
import SwitchingDevicesWizardManager from "sumo/js/switching-devices-wizard-manager";
43
import "sumo/js/form-wizard";

kitsune/sumo/static/sumo/js/switching-devices-wizard-manager.js

Lines changed: 144 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import BrowserDetect from "./browserdetect";
2+
import UITour from "./libs/uitour";
23

34
/**
45
* This class is responsible for managing the state for a wizard that
@@ -11,6 +12,11 @@ import BrowserDetect from "./browserdetect";
1112
export default class SwitchingDevicesWizardManager {
1213
#formWizard = null;
1314

15+
// For versions of Firefox < 114, we use this value as a polling interval
16+
// to check for FxA sign-in state changes.
17+
#pollIntervalMs = 500;
18+
#pollIntervalID = null;
19+
1420
// Some of these #state properties use snake_case since there are some
1521
// properties that are going to be sent as queryParameters, and this
1622
// lets us not worry about translating them from camelCase to snake_case.
@@ -70,6 +76,8 @@ export default class SwitchingDevicesWizardManager {
7076
steps = Object.freeze([
7177
{
7278
name: "sign-into-fxa",
79+
status: "active",
80+
label: gettext("Create an account"),
7381
exitConditionsMet(state) {
7482
return state.fxaSignedIn;
7583
},
@@ -92,6 +100,8 @@ export default class SwitchingDevicesWizardManager {
92100
},
93101
{
94102
name: "configure-sync",
103+
status: "unavailable",
104+
label: gettext("Sync your data"),
95105
exitConditionsMet(state) {
96106
return state.syncEnabled && state.confirmedSyncChoices;
97107
},
@@ -103,6 +113,8 @@ export default class SwitchingDevicesWizardManager {
103113
},
104114
{
105115
name: "setup-new-device",
116+
status: "unavailable",
117+
label: gettext("Set up your new device"),
106118
exitConditionsMet(state) {
107119
return false;
108120
},
@@ -123,8 +135,10 @@ export default class SwitchingDevicesWizardManager {
123135
* An optional fake object to pass to the BrowserDetect library to simulate
124136
* what is returned from a WebChannel on a Firefox Desktop browser for
125137
* a request for troubleshooting data.
138+
* @param {number} [pollIntervalMs=undefined]
139+
* The interval for polling for FxA state changes while under test.
126140
*/
127-
constructor(formWizard, fakeUA, fakeTroubleshooting) {
141+
constructor(formWizard, fakeUA, fakeTroubleshooting, pollIntervalMs) {
128142
this.#formWizard = formWizard;
129143

130144
if (!this.#formWizard.hasAttribute("fxa-root")) {
@@ -134,6 +148,8 @@ export default class SwitchingDevicesWizardManager {
134148
);
135149
}
136150

151+
this.#setupFormWizardStepIndicator();
152+
137153
this.#state.fxaRoot = this.#formWizard.getAttribute("fxa-root");
138154

139155
// If the page was loaded with any UTM or entrypoint parameters, we'll want
@@ -155,7 +171,7 @@ export default class SwitchingDevicesWizardManager {
155171
}
156172
}
157173

158-
this.#init(fakeUA, fakeTroubleshooting);
174+
this.#init(fakeUA, fakeTroubleshooting, pollIntervalMs);
159175
}
160176

161177
/**
@@ -170,20 +186,44 @@ export default class SwitchingDevicesWizardManager {
170186
* An optional fake object to pass to the BrowserDetect library to simulate
171187
* what is returned from a WebChannel on a Firefox Desktop browser for
172188
* a request for troubleshooting data.
189+
* @param {number} [pollIntervalMs=undefined]
190+
* The interval for polling for FxA state changes while under test.
173191
*/
174-
async #init(fakeUA, fakeTroubleshooting) {
192+
async #init(fakeUA, fakeTroubleshooting, pollIntervalMs = this.#pollIntervalMs) {
175193
try {
176194
let detect = new BrowserDetect(fakeUA, null, fakeTroubleshooting);
177195
let browser = await detect.getBrowser();
178196
let platform = await detect.getOS();
179197
if (browser.mozilla && !platform.mobile) {
198+
await new Promise(resolve => {
199+
UITour.ping(resolve);
200+
});
201+
202+
await this.#updateFxAState();
203+
204+
if (browser.version.major >= 114) {
205+
// Firefox 114+ allows us to get notified when the FxA sign-in state
206+
// changes through UITour.
207+
await new Promise(resolve => {
208+
UITour.observe((name, params) => {
209+
this.#onUITourNotification(name, params);
210+
}, resolve);
211+
});
212+
} else {
213+
// For older Firefox versions, we'll do polling.
214+
this.#pollIntervalID = window.setInterval(() => {
215+
this.#updateFxAState();
216+
}, pollIntervalMs);
217+
}
218+
180219
// We need to get some query parameters from the FxA server before we
181220
// show the user any kind of form to create or sign-in to an account.
182221
// See https://mozilla.github.io/ecosystem-platform/relying-parties/reference/metrics-for-relying-parties#relying-party-hosted-email-form.
183222
await this.#requestMetricsParams();
184223
return;
185224
}
186225
} catch (e) {
226+
console.error(e);
187227
// Intentional fall-through - we want to do this if any part of the
188228
// UA computation didn't meet our criteria OR failed.
189229
}
@@ -198,6 +238,15 @@ export default class SwitchingDevicesWizardManager {
198238
);
199239
}
200240

241+
/**
242+
* A testing-only function to do any cleanup once an instance of
243+
* SwitchingDevicesWizardManager is being thrown away.
244+
*/
245+
destroy() {
246+
window.clearInterval(this.#pollIntervalID);
247+
this.#pollIntervalID = null;
248+
}
249+
201250
/**
202251
* Returns a fresh copy of the internal state object. This is mainly
203252
* for testing.
@@ -218,8 +267,16 @@ export default class SwitchingDevicesWizardManager {
218267
* mechanism as when using `Object.assign`.
219268
*/
220269
#updateState(stateDiff) {
221-
this.#state = Object.assign(this.#state, stateDiff);
222-
this.#recomputeCurrentStep();
270+
let oldState = this.state;
271+
let newState = Object.assign(this.#state, stateDiff);
272+
this.#state = newState;
273+
274+
for (let prop in oldState) {
275+
if (oldState[prop] !== newState[prop]) {
276+
this.#recomputeCurrentStep();
277+
break;
278+
}
279+
}
223280
}
224281

225282
/**
@@ -238,6 +295,70 @@ export default class SwitchingDevicesWizardManager {
238295
}
239296
}
240297

298+
/**
299+
* Sets up the step indicator on the #formWizard based on the
300+
* default state of this.steps. This should be called either
301+
* during initialization or if the whole wizard is being reset
302+
* back to the starting state.
303+
*/
304+
#setupFormWizardStepIndicator() {
305+
let fwSteps = this.steps.map(({ name, status, label }) => {
306+
return { name, status, label };
307+
});
308+
this.#formWizard.steps = fwSteps;
309+
}
310+
311+
/**
312+
* Handler for UITour notifications. Specifically, this monitors
313+
* for changes to the FxA sign-in state.
314+
*
315+
* @param {string} name
316+
* The name of the UITour notification.
317+
* @param {object} params
318+
* Extra parameters that UITour sends with each notification.
319+
* @return {Promise<undefined>}
320+
* Resolves after recomputing the FxA sign-in state.
321+
*/
322+
async #onUITourNotification(name, params) {
323+
if (name == "FxA:SignedInStateChange") {
324+
await this.#updateFxAState();
325+
}
326+
}
327+
328+
/**
329+
* Uses UITour to query for the current FxA sign-in state. A user
330+
* is considered signed in if their FxA account is both setup and
331+
* the account state is "ok" (as in, they have verified their email
332+
* address).
333+
*
334+
* Once the sign-in state is determined from UITour, the internal
335+
* state of the SwichingDevicesWizardManager is updated to reflect
336+
* it.
337+
*
338+
* In the event that the state is transitioning from "signed in" to
339+
* "signed out", the #formWizard is reset back to its starting point.
340+
*/
341+
async #updateFxAState() {
342+
let fxaConfig = await new Promise(resolve => {
343+
UITour.getConfiguration("fxa", resolve);
344+
});
345+
346+
if (fxaConfig.setup && fxaConfig.accountStateOK) {
347+
this.#updateState({
348+
fxaSignedIn: true,
349+
syncEnabled: !!fxaConfig.browserServices?.sync?.setup,
350+
});
351+
} else if (this.#state.fxaSignedIn) {
352+
// If we've gone from being signed in to signed out, we need to
353+
// reset our state.
354+
this.#setupFormWizardStepIndicator();
355+
this.#updateState({
356+
fxaSignedIn: false,
357+
syncEnabled: false,
358+
});
359+
}
360+
}
361+
241362
/**
242363
* Makes contact with the FxA endpoint pointed at by state.fxaRoot/metrics-flow to
243364
* get the flow_id and flow_begin_time parameters. Once those parameters are
@@ -258,18 +379,26 @@ export default class SwitchingDevicesWizardManager {
258379
params.set(paramName, this.#state[paramName]);
259380
}
260381
}
261-
let response = await window.fetch(
262-
`${this.#state.fxaRoot}/metrics-flow?${params}`
263-
);
382+
try {
383+
let response = await window.fetch(
384+
`${this.#state.fxaRoot}/metrics-flow?${params}`
385+
);
264386

265-
if (response.status == 200) {
266-
let { flowId, flowBeginTime } = await response.json();
387+
if (response.status == 200) {
388+
let { flowId, flowBeginTime } = await response.json();
267389

268-
let stateDiff = {
269-
flow_id: flowId,
270-
flow_begin_time: flowBeginTime,
271-
};
272-
this.#updateState(stateDiff);
390+
let stateDiff = {
391+
flow_id: flowId,
392+
flow_begin_time: flowBeginTime,
393+
};
394+
this.#updateState(stateDiff);
395+
}
396+
} catch (e) {
397+
// Log but intentionally ignore this case. If getting the metrics flow
398+
// parameters somehow failed (say, for example, a problem on the FxA
399+
// handler for metrics-flow), this shouldn't prevent the user from
400+
// completing their task.
401+
console.error(e);
273402
}
274403
}
275404
}

0 commit comments

Comments
 (0)