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

mozilla / fx-private-relay / 74d01625-9661-4180-904e-0bd85eb6c11f

29 May 2025 12:03PM CUT coverage: 85.583% (+0.2%) from 85.353%
74d01625-9661-4180-904e-0bd85eb6c11f

push

circleci

web-flow
Merge pull request #5573 from mozilla/MPP-4155-new-megabundle-banner

MPP-4155 New Megabundle Banner Landing Page

2490 of 3629 branches covered (68.61%)

Branch coverage included in aggregate %.

60 of 62 new or added lines in 8 files covered. (96.77%)

19 existing lines in 6 files now uncovered.

17598 of 19843 relevant lines covered (88.69%)

9.97 hits per line

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

12.9
/frontend/src/hooks/api/profile.ts
1
import useSWR, { SWRConfig, SWRResponse } from "swr";
15✔
2
import { DateString } from "../../functions/parseDate";
3
import { apiFetch, authenticatedFetch, FetchError } from "./api";
15✔
4

5
export type ProfileData = {
6
  id: number;
7
  server_storage: boolean;
8
  has_premium: boolean;
9
  has_phone: boolean;
10
  has_vpn: boolean;
11
  subdomain: string | null;
12
  onboarding_state: number;
13
  onboarding_free_state: number;
14
  forwarded_first_reply: boolean;
15
  avatar: string;
16
  date_subscribed: null | DateString;
17
  remove_level_one_email_trackers: boolean;
18
  next_email_try: DateString;
19
  bounce_status: [false, ""] | [true, "soft"] | [true, "hard"];
20
  api_token: string;
21
  emails_blocked: number;
22
  emails_forwarded: number;
23
  emails_replied: number;
24
  level_one_trackers_blocked: number;
25
  store_phone_log: boolean;
26
  metrics_enabled: boolean;
27
};
28

29
export type ProfilesData = [ProfileData];
30

31
export type ProfileUpdateFn = (
32
  id: ProfileData["id"],
33
  data: Omit<Partial<ProfileData>, "subdomain">,
34
) => Promise<Response>;
35
export type SetSubdomainFn = (
36
  subdomain: Exclude<ProfileData["subdomain"], null>,
37
) => Promise<Response>;
38

39
/**
40
 * Fetch the user's profile data from our API using [SWR](https://swr.vercel.app).
41
 */
42
export function useProfiles(): SWRResponse<ProfilesData, unknown> & {
13✔
43
  update: ProfileUpdateFn;
44
  setSubdomain: SetSubdomainFn;
45
} {
UNCOV
46
  const profiles = useSWR("/profiles/", profileFetcher, {
×
47
    revalidateOnFocus: false,
48
    onErrorRetry: (
49
      error: unknown | FetchError,
50
      key: string,
51
      // SWR's type definitions do not expose the type required here, at the
52
      // time of writing, and they're not reconcilable with anything other than
53
      // `any`. Since we're just passing on the value unmodified anyway, this
54
      // should not be a problem:
55
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
      config: any,
57
      revalidate: Parameters<typeof SWRConfig.defaultValue.onErrorRetry>[3],
58
      revalidateOpts: Parameters<typeof SWRConfig.defaultValue.onErrorRetry>[4],
59
    ) => {
UNCOV
60
      if (error instanceof FetchError && error.response.status === 401) {
×
61
        // When the user is not logged in, this API returns a 401.
62
        // If so, do not retry.
63
        return;
×
64
      }
UNCOV
65
      SWRConfig.defaultValue.onErrorRetry(
×
66
        error,
67
        key,
68
        config,
69
        revalidate,
70
        revalidateOpts,
71
      );
72
    },
73
  }) as SWRResponse<ProfilesData, FetchError>;
74

75
  /**
76
   * Update a user's profile. Note that setting a subdomain currently requires
77
   * the use of `setSubdomain`, because that calls a special API endpoint that
78
   * will also hash the subdomain to prevent duplicate subdomains.
79
   */
UNCOV
80
  const update: ProfileUpdateFn = async (id, data) => {
×
81
    const response = await apiFetch(`/profiles/${id}/`, {
×
82
      method: "PATCH",
83
      body: JSON.stringify(data),
84
    });
85
    profiles.mutate();
×
86
    return response;
×
87
  };
UNCOV
88
  const setSubdomain: SetSubdomainFn = async (subdomain) => {
×
89
    const response = await authenticatedFetch("/accounts/profile/subdomain", {
×
90
      method: "POST",
91
      body: new URLSearchParams({ subdomain: subdomain }).toString(),
92
      headers: {
93
        "Content-Type": "application/x-www-form-urlencoded",
94
      },
95
    });
96
    profiles.mutate();
×
97
    return response;
×
98
  };
99

UNCOV
100
  return {
×
101
    ...profiles,
102
    update: update,
103
    setSubdomain: setSubdomain,
104
  };
105
}
106

107
/**
108
 * Instead of using the `fetcher` from `api.ts`, this fetcher is specific to the profiles API.
109
 * The reason that it's needed is that we have to tell the back-end to re-fetch data from
110
 * Mozilla Accounts if the user was sent back here after trying to subscribe to Premium.
111
 */
112
const profileFetcher = async (
15✔
113
  url: string,
114
  requestInit: RequestInit,
115
): Promise<ProfilesData> => {
116
  const isToldByFxaToRefresh =
UNCOV
117
    document.location.search.indexOf("fxa_refresh=1") !== -1;
×
118

UNCOV
119
  if (isToldByFxaToRefresh) {
×
120
    const refreshResponse = await authenticatedFetch(
×
121
      "/accounts/profile/refresh",
122
    );
123
    await refreshResponse.json();
×
124
  }
125

UNCOV
126
  const response = await apiFetch(url, requestInit);
×
127
  if (!response.ok) {
×
128
    throw new FetchError(response);
×
129
  }
130
  const data: ProfilesData = await response.json();
×
131
  return data;
×
132
};
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