xref: /expo/packages/@expo/cli/src/api/user/user.ts (revision d566f199)
1import { promises as fs } from 'fs';
2import gql from 'graphql-tag';
3
4import * as Log from '../../log';
5import * as Analytics from '../../utils/analytics/rudderstackClient';
6import { getDevelopmentCodeSigningDirectory } from '../../utils/codesigning';
7import { graphqlClient } from '../graphql/client';
8import { CurrentUserQuery } from '../graphql/generated';
9import { UserQuery } from '../graphql/queries/UserQuery';
10import { fetchAsync } from '../rest/client';
11import UserSettings from './UserSettings';
12
13export type Actor = NonNullable<CurrentUserQuery['meActor']>;
14
15let currentUser: Actor | undefined;
16
17export const ANONYMOUS_USERNAME = 'anonymous';
18
19/**
20 * Resolve the name of the actor, either normal user or robot user.
21 * This should be used whenever the "current user" needs to be displayed.
22 * The display name CANNOT be used as project owner.
23 */
24export function getActorDisplayName(user?: Actor): string {
25  switch (user?.__typename) {
26    case 'User':
27      return user.username;
28    case 'Robot':
29      return user.firstName ? `${user.firstName} (robot)` : 'robot';
30    default:
31      return ANONYMOUS_USERNAME;
32  }
33}
34
35export async function getUserAsync(): Promise<Actor | undefined> {
36  if (!currentUser && (UserSettings.getAccessToken() || UserSettings.getSession()?.sessionSecret)) {
37    const user = await UserQuery.currentUserAsync();
38    currentUser = user ?? undefined;
39    if (user) {
40      await Analytics.setUserDataAsync(user.id, {
41        username: getActorDisplayName(user),
42        user_id: user.id,
43        user_type: user.__typename,
44      });
45    }
46  }
47  return currentUser;
48}
49
50export async function loginAsync(json: {
51  username: string;
52  password: string;
53  otp?: string;
54}): Promise<void> {
55  const res = await fetchAsync('auth/loginAsync', {
56    method: 'POST',
57    body: JSON.stringify(json),
58  });
59  const {
60    data: { sessionSecret },
61  } = await res.json();
62  const result = await graphqlClient
63    .query(
64      gql`
65        query UserQuery {
66          viewer {
67            id
68            username
69          }
70        }
71      `,
72      {},
73      {
74        fetchOptions: {
75          headers: {
76            'expo-session': sessionSecret,
77          },
78        },
79        additionalTypenames: [] /* UserQuery has immutable fields */,
80      }
81    )
82    .toPromise();
83  const {
84    data: { viewer },
85  } = result;
86  await UserSettings.setSessionAsync({
87    sessionSecret,
88    userId: viewer.id,
89    username: viewer.username,
90    currentConnection: 'Username-Password-Authentication',
91  });
92}
93
94export async function logoutAsync(): Promise<void> {
95  currentUser = undefined;
96  await Promise.all([
97    fs.rm(getDevelopmentCodeSigningDirectory(), { recursive: true, force: true }),
98    UserSettings.setSessionAsync(undefined),
99  ]);
100  Log.log('Logged out');
101}
102