@accounts/graphql-client
Version:
GraphQL client transport for accounts
282 lines (250 loc) • 7.4 kB
text/typescript
import { type AccountsClient, type TransportInterface } from '@accounts/client';
import {
type CreateUser,
type ImpersonationResult,
type LoginResult,
type User,
type CreateUserResult,
} from '@accounts/types';
import { print, type DocumentNode } from 'graphql/language';
import { type TypedDocumentNode } from '@graphql-typed-document-node/core';
import {
CreateUserDocument,
AddEmailDocument,
AuthenticateWithServiceDocument,
GetTwoFactorSecretDocument,
ChangePasswordDocument,
LogoutDocument,
VerifyEmailDocument,
SendResetPasswordEmailDocument,
SendVerificationEmailDocument,
ResetPasswordDocument,
TwoFactorSetDocument,
TwoFactorUnsetDocument,
RefreshTokensDocument,
GetUserDocument,
ImpersonateDocument,
AuthenticateDocument,
RequestMagicLinkEmailDocument,
} from './graphql-operations';
import { GraphQLErrorList } from './GraphQLErrorList';
import { replaceUserFieldsFragment } from './utils/replace-user-fragment';
export interface AuthenticateParams {
[key: string]: string | object;
}
export interface OptionsType {
graphQLClient: any;
userFieldsFragment?: DocumentNode;
}
export default class GraphQLClient implements TransportInterface {
public client!: AccountsClient;
private options: OptionsType;
constructor(options: OptionsType) {
this.options = options;
}
/**
* Create a user with basic user info
*
* @param {CreateUser} user user object
* @returns {Promise<CreateUserResult>} contains user's ID and LoginResult object if autologin is enabled
* @memberof GraphQLClient
*/
public async createUser(user: CreateUser): Promise<CreateUserResult> {
return this.mutate(
this.options.userFieldsFragment
? replaceUserFieldsFragment(CreateUserDocument, this.options.userFieldsFragment)
: CreateUserDocument,
'createUser',
{ user }
);
}
/**
* @inheritDoc
*/
public async authenticateWithService(
service: string,
authenticateParams: { [key: string]: string | object }
): Promise<boolean> {
return this.mutate(AuthenticateWithServiceDocument, 'verifyAuthentication', {
serviceName: service,
params: authenticateParams,
});
}
/**
* @inheritDoc
*/
public async loginWithService(
service: string,
authenticateParams: AuthenticateParams
): Promise<LoginResult> {
return this.mutate(
this.options.userFieldsFragment
? replaceUserFieldsFragment(AuthenticateDocument, this.options.userFieldsFragment)
: AuthenticateDocument,
'authenticate',
{
serviceName: service,
params: authenticateParams,
}
);
}
/**
* @inheritDoc
*/
public async getUser(): Promise<User> {
return this.query(
this.options.userFieldsFragment
? replaceUserFieldsFragment(GetUserDocument, this.options.userFieldsFragment)
: GetUserDocument,
'getUser'
);
}
/**
* @inheritDoc
*/
public async logout(): Promise<void> {
return this.mutate(LogoutDocument, 'logout');
}
/**
* @inheritDoc
*/
public async refreshTokens(accessToken: string, refreshToken: string): Promise<LoginResult> {
return this.mutate(RefreshTokensDocument, 'refreshTokens', { accessToken, refreshToken });
}
/**
* @inheritDoc
*/
public async verifyEmail(token: string): Promise<void> {
return this.mutate(VerifyEmailDocument, 'verifyEmail', { token });
}
/**
* @inheritDoc
*/
public async sendResetPasswordEmail(email: string): Promise<void> {
return this.mutate(SendResetPasswordEmailDocument, 'sendResetPasswordEmail', { email });
}
/**
* @inheritDoc
*/
public async sendVerificationEmail(email: string): Promise<void> {
return this.mutate(SendVerificationEmailDocument, 'sendVerificationEmail', { email });
}
/**
* @inheritDoc
*/
public async resetPassword(token: string, newPassword: string): Promise<LoginResult | null> {
return this.mutate(ResetPasswordDocument, 'resetPassword', { token, newPassword });
}
/**
* @inheritDoc
*/
public async addEmail(newEmail: string): Promise<void> {
return this.mutate(AddEmailDocument, 'addEmail', { newEmail });
}
/**
* @inheritDoc
*/
public async changePassword(oldPassword: string, newPassword: string): Promise<void> {
return this.mutate(ChangePasswordDocument, 'changePassword', { oldPassword, newPassword });
}
/**
* @inheritDoc
*/
public async getTwoFactorSecret(): Promise<any> {
return this.query(GetTwoFactorSecretDocument, 'twoFactorSecret', {});
}
/**
* @inheritDoc
*/
public async twoFactorSet(secret: any, code: string): Promise<void> {
return this.mutate(TwoFactorSetDocument, 'twoFactorSet', { secret, code });
}
/**
* @inheritDoc
*/
public async twoFactorUnset(code: string): Promise<void> {
return this.mutate(TwoFactorUnsetDocument, 'twoFactorUnset', { code });
}
/**
* @inheritDoc
*/
public async impersonate(
token: string,
impersonated: {
username?: string;
userId?: string;
email?: string;
}
): Promise<ImpersonationResult> {
return this.mutate(
this.options.userFieldsFragment
? replaceUserFieldsFragment(ImpersonateDocument, this.options.userFieldsFragment)
: ImpersonateDocument,
'impersonate',
{
accessToken: token,
impersonated: {
userId: impersonated.userId,
username: impersonated.username,
email: impersonated.email,
},
}
);
}
private async mutate<TData = any, TVariables = Record<string, any>>(
mutation: TypedDocumentNode<TData, TVariables>,
resultField: any,
variables?: TVariables
): Promise<any> {
// If we are executing a refresh token mutation do not call refresh session again
// otherwise it will end up in an infinite loop
const tokens =
(mutation as any) === RefreshTokensDocument
? await this.client.getTokens()
: await this.client.refreshSession();
const headers: { Authorization?: string } = {};
if (tokens) {
headers.Authorization = `Bearer ${tokens.accessToken}`;
}
const { data, errors } = await this.options.graphQLClient.mutate({
mutation,
variables,
context: {
headers,
},
});
if (errors) {
throw new GraphQLErrorList(errors, `in mutation: \r\n ${print(mutation)}`);
}
return data[resultField];
}
private async query<TData = any, TVariables = Record<string, any>>(
query: TypedDocumentNode<TData, TVariables>,
resultField: any,
variables?: TVariables
): Promise<any> {
const tokens = await this.client.refreshSession();
const headers: { Authorization?: string } = {};
if (tokens) {
headers.Authorization = `Bearer ${tokens.accessToken}`;
}
const { data, errors } = await this.options.graphQLClient.query({
query,
variables,
fetchPolicy: 'network-only',
context: {
headers,
},
});
if (errors) {
throw new GraphQLErrorList(errors, `in query: \r\n ${print(query)}`);
}
return data[resultField];
}
/**
* @inheritDoc
*/
public async requestMagicLinkEmail(email: string): Promise<void> {
return this.mutate(RequestMagicLinkEmailDocument, 'requestMagicLinkEmail', { email });
}
}