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

mozilla / fx-private-relay / d4d9f278-d845-4992-8c81-4f3757c427a1

08 Sep 2025 02:07PM UTC coverage: 86.303% (-1.8%) from 88.121%
d4d9f278-d845-4992-8c81-4f3757c427a1

Pull #5842

circleci

joeherm
fix(deploy): Update CircleCI to use common Dockerfile for building frontend
Pull Request #5842: fix(deploy): Unify Dockerfiles

2744 of 3951 branches covered (69.45%)

Branch coverage included in aggregate %.

17910 of 19981 relevant lines covered (89.64%)

9.96 hits per line

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

11.76
/frontend/src/hooks/api/profile.ts
1
import useSWR, { SWRConfig, SWRResponse } from "swr";
17✔
2
import { DateString } from "../../functions/parseDate";
3
import { apiFetch, authenticatedFetch, FetchError } from "./api";
17✔
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
  has_megabundle: boolean;
12
  subdomain: string | null;
13
  onboarding_state: number;
14
  onboarding_free_state: number;
15
  forwarded_first_reply: boolean;
16
  avatar: string;
17
  date_subscribed: null | DateString;
18
  remove_level_one_email_trackers: boolean;
19
  next_email_try: DateString;
20
  bounce_status: [false, ""] | [true, "soft"] | [true, "hard"];
21
  api_token: string;
22
  emails_blocked: number;
23
  emails_forwarded: number;
24
  emails_replied: number;
25
  level_one_trackers_blocked: number;
26
  store_phone_log: boolean;
27
  metrics_enabled: boolean;
28
};
29

30
export type ProfilesData = [ProfileData];
31

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

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

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

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

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

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

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