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