• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / 8b39734c-31ce-4dbf-9b05-171797c18cae

02 Oct 2025 04:03PM UTC coverage: 88.815% (-0.05%) from 88.863%
8b39734c-31ce-4dbf-9b05-171797c18cae

Pull #5924

circleci

vpremamozilla
MPP-4348: propagate UTM params from landing page to SubPlat and Accounts URLs
Pull Request #5924: MPP-4348: propagate UTM params from landing page to SubPlat and Accounts URLs

2932 of 3953 branches covered (74.17%)

Branch coverage included in aggregate %.

13 of 21 new or added lines in 5 files covered. (61.9%)

4 existing lines in 3 files now uncovered.

18086 of 19712 relevant lines covered (91.75%)

11.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.0
/frontend/src/hooks/fxaFlowTracker.ts
1
import { useState } from "react";
3✔
2
import { IntersectionOptions, useInView } from "react-intersection-observer";
3✔
3
import { getRuntimeConfig } from "../config";
3✔
4
import { useRuntimeData } from "./api/runtimeData";
3✔
5
import { useGaEvent, EventArgs } from "./gaEvent";
3✔
6

7
export type FxaFlowTrackerArgs = Omit<
8
  EventArgs,
9
  "action" | "nonInteraction"
10
> & { entrypoint: string };
11

12
export type FlowData = {
13
  flowId: string;
14
  flowBeginTime: number;
15
};
16

17
/**
18
 * This is similar to {@see useGaViewPing}, but this also gives you a `flowId`
19
 * and `flowBeginTime` that you can send to FxA to track sign-in/-up flows.
20
 */
21
export function useFxaFlowTracker(
×
22
  args: FxaFlowTrackerArgs | null,
23
  options?: IntersectionOptions,
24
) {
25
  const runtimeData = useRuntimeData();
×
26
  const gaEvent = useGaEvent();
×
27
  const { ref } = useInView({
×
28
    threshold: 1,
29
    ...options,
30
    onChange: (inView, entry) => {
31
      if (args === null || !inView) {
×
32
        return;
×
33
      }
34
      gaEvent({
×
35
        ...args,
36
        action: "View",
37
        nonInteraction: true,
38
      });
39

40
      fetch(
×
41
        `${fxaOrigin}/metrics-flow?form_type=other&entrypoint=${encodeURIComponent(
42
          args.entrypoint,
43
        )}&utm_source=${encodeURIComponent(document.location.host)}`,
44
      )
45
        .then(async (response) => {
46
          if (!response.ok) {
×
47
            return;
×
48
          }
49
          const data = await response.json();
×
50
          if (
×
51
            typeof data.flowId !== "string" ||
×
52
            typeof data.flowBeginTime !== "number"
53
          ) {
54
            return;
×
55
          }
56
          setFlowData({
×
57
            flowBeginTime: data.flowBeginTime,
58
            flowId: data.flowId,
59
          });
60
        })
61
        .catch(() => {
62
          // Do nothing; if we can't contact the metrics endpoint,
63
          // we accept not being able to measure this.
64
        });
65

66
      if (typeof options?.onChange === "function") {
×
67
        options.onChange(inView, entry);
×
68
      }
69
    },
70
  });
71
  const [flowData, setFlowData] = useState<FlowData>();
×
72

73
  // If the API hasn't responded with the environment variables by the time this
74
  // hook fires, we assume we're dealing with the production instance of FxA:
75
  const fxaOrigin =
76
    runtimeData.data?.FXA_ORIGIN ?? "https://accounts.firefox.com";
×
77

78
  return { flowData: flowData, ref: ref };
×
79
}
80

81
export function getLoginUrl(entrypoint: string, flowData?: FlowData): string {
3✔
82
  const loginUrl = getRuntimeConfig().fxaLoginUrl;
3✔
83
  // document is undefined when prerendering the website,
84
  // so just use the production URL there:
85
  const urlObject = new URL(
3✔
86
    loginUrl,
87
    typeof document !== "undefined"
3!
88
      ? document.location.origin
89
      : "https://relay.firefox.com",
90
  );
91
  urlObject.searchParams.append("form_type", "button");
3✔
92
  urlObject.searchParams.append("entrypoint", entrypoint);
3✔
93
  if (flowData) {
3!
94
    urlObject.searchParams.append("flowId", flowData.flowId);
3✔
95
    urlObject.searchParams.append(
3✔
96
      "flowBeginTime",
97
      flowData.flowBeginTime.toString(),
98
    );
99
  }
100

101
  if (typeof window !== "undefined") {
3!
102
    const inbound = new URLSearchParams(window.location.search);
3✔
103
    [
3✔
104
      "utm_source",
105
      "utm_campaign",
106
      "utm_medium",
107
      "utm_content",
108
      "utm_term",
109
    ].forEach((k) => {
110
      const v = inbound.get(k);
15✔
111
      if (v && !urlObject.searchParams.has(k)) {
15!
NEW
112
        urlObject.searchParams.append(k, v);
×
113
      }
114
    });
115
  }
116

117
  const fullUrl = urlObject.href;
3✔
118
  // If the configured fxaLoginUrl was a relative URL,
119
  // the URL we return should be relative as well, rather than potentially
120
  // including the `https://relay.firefox.com` we set as the base URL so that
121
  // the `URL()` constructor could parse it:
122
  const newLoginUrl = fullUrl.substring(fullUrl.indexOf(loginUrl));
3✔
123
  return newLoginUrl;
3✔
124
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc