Skip to content

Commit

Permalink
feat: new error flow management
Browse files Browse the repository at this point in the history
Errors are not silent anymore! As long as the dependencies
throw errors, the manager will catch and handle them properly.
  • Loading branch information
HunteRoi committed Nov 16, 2022
1 parent 379f810 commit 4896e32
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 80 deletions.
38 changes: 10 additions & 28 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ const sgMail = new SendGridService({

const manager = new VerificationManager(client, db, sgMail,
{
errorMessage: (user, error) => `Could not send the code to ${user.data.to} because of the following error: ${error.message}!`,
pendingMessage: (user) =>
`The verification code has just been sent to ${user.data}.`,
`The verification code has just been sent to ${user.data.to}.`,
alreadyPendingMessage: (user) =>
`You already have a verification code pending! It was sent to ${user.data}.`,
`You already have a verification code pending! It was sent to ${user.data.to}.`,
alreadyActiveMessage: (user) =>
`An account is already verified with this data!`,
validCodeMessage: (user, validCode) =>
Expand Down Expand Up @@ -94,32 +95,13 @@ client.on('interactionCreate', async (interaction) => {
}
});

manager.on(VerificationManagerEvents.codeCreate, (code) => {
console.log('A code has just been created!', code);
});
manager.on(
VerificationManagerEvents.codeVerify,
(user, userid, code, isVerified) =>
console.log(
`The user ${user?.username} (${userid}) has ${
isVerified ? 'successfully verified' : 'unsuccessfully tried to verify'
} the code ${code}.`
)
);
manager.on(VerificationManagerEvents.userCreate, (user) =>
console.log(`The user ${user?.username} has just been created!`)
);
manager.on(VerificationManagerEvents.userAwait, (user) =>
console.log(`The user ${user?.username} is waiting to verify their code!`)
);
manager.on(VerificationManagerEvents.userActive, (user) =>
console.log(`The user ${user?.username} is already active!`)
);
manager.on(VerificationManagerEvents.senderCall, () =>
console.log('Sender API called!')
);
manager.on(VerificationManagerEvents.storingSystemCall, () =>
console.log('Storing system called!')
manager.on(VerificationManagerEvents.codeCreate, (code) => console.log('A code has just been created!', code));
manager.on(VerificationManagerEvents.codeVerify, (user, userid, code, isVerified) =>
console.log(`The user ${user?.username} (${userid}) has ${isVerified ? 'successfully verified' : 'unsuccessfully tried to verify'} the code ${code}.`)
);
manager.on(VerificationManagerEvents.userCreate, (user) => console.log(`The user ${user?.username} has just been created!`));
manager.on(VerificationManagerEvents.userAwait, (user) => console.log(`The user ${user?.username} is waiting to verify their code!`));
manager.on(VerificationManagerEvents.userActive, (user) => console.log(`The user ${user?.username} is already active!`));
manager.on(VerificationManagerEvents.error, (user, error) => console.error('An error occured', user, error));

client.login('TOKEN');
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hunteroi/discord-verification",
"version": "1.1.1",
"version": "1.2.0",
"description": "A framework to integrate a verification system with your Discord guild built with DiscordJS",
"main": "lib/index.js",
"scripts": {
Expand Down
107 changes: 68 additions & 39 deletions src/VerificationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import { VerificationManagerEvents } from './VerificationManagerEvents';
* @template TUser
*/
export class VerificationManager<TUser extends IUser> extends EventEmitter {
private readonly options: VerificationOptions<TUser>;
private readonly storingSystem: IStoringSystem<TUser>;
private readonly senderAPI: ISenderAPI;
private readonly codeGenerator: CodeGeneratorService;
readonly #options: VerificationOptions<TUser>;
readonly #storingSystem: IStoringSystem<TUser>;
readonly #senderAPI: ISenderAPI;
readonly #codeGenerator: CodeGeneratorService;

/**
* The Discord client instance.
Expand All @@ -43,6 +43,7 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
* length: 6
* },
* maxNbCodeCalledBeforeResend: 3,
* errorMessage: (user: TUser, error: unknown) => `An error occured when trying to send something to ${user?.username}!`
* pendingMessage: (user: TUser) => `The verification code has just been sent to ${user.data}.`,
* alreadyPendingMessage: (user: TUser) => `You already have a verification code pending! It was sent to ${user.data}.`,
* alreadyActiveMessage: (user: TUser) => `You are already verified with the email ${user.data}!`,
Expand All @@ -60,6 +61,8 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
length: 6,
},
maxNbCodeCalledBeforeResend: 3,
errorMessage: (user: TUser, error: unknown) =>
`An error occured when trying to send something to ${user?.username}!`,
pendingMessage: (user: TUser) =>
`The verification code has just been sent to ${user.data}.`,
alreadyPendingMessage: (user: TUser) =>
Expand All @@ -77,18 +80,18 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
if (!client) throw 'Client is required';
if (!storingSystem) throw 'StoringSystem is required';
if (!senderAPI) throw 'SenderAPI is required';
this.validateOptions(options);
this.#validateOptions(options);

this.client = client;
this.options = options;
this.storingSystem = storingSystem;
this.senderAPI = senderAPI;
this.codeGenerator = new CodeGeneratorService(
this.options.codeGenerationOptions.charactersWhitelist
this.#options = options;
this.#storingSystem = storingSystem;
this.#senderAPI = senderAPI;
this.#codeGenerator = new CodeGeneratorService(
this.#options.codeGenerationOptions.charactersWhitelist
);
}

private validateOptions(options: VerificationOptions<TUser>) {
#validateOptions(options: VerificationOptions<TUser>) {
if (!options) throw "'options' is required";

if (!options.codeGenerationOptions || !options.codeGenerationOptions.length) {
Expand All @@ -97,6 +100,9 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
if (!options.maxNbCodeCalledBeforeResend) {
options.maxNbCodeCalledBeforeResend = 3;
}
if (!options.errorMessage) {
options.errorMessage = (user: TUser, error: unknown) => `An error occured when trying to send something to ${user?.username}!`;
}
if (!options.pendingMessage) {
options.pendingMessage = (user: TUser) => `The verification code has just been sent to ${user.data}.`;
}
Expand All @@ -123,13 +129,13 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
* @return {Promise<string>} the result of the operation as a string, based on the result of the call to one of the manager's options' methods.
* @memberof VerificationManager
*/
async sendCode(userid: Snowflake, data: any): Promise<string> {
let user: TUser = (await this.storingSystem.read(userid)) ?? (await this.storingSystem.readBy((user: TUser) => user.data === data));
public async sendCode(userid: Snowflake, data: any): Promise<string> {
let user: TUser = (await this.#storingSystem.read(userid)) ?? (await this.#storingSystem.readBy((user: TUser) => user.data === data));

if (!user) {
const discordUser = await this.client.users.fetch(userid);
const code = this.codeGenerator.generateCode(
this.options.codeGenerationOptions.length
const code = this.#codeGenerator.generateCode(
this.#options.codeGenerationOptions.length
);
this.emit(VerificationManagerEvents.codeCreate, code);

Expand All @@ -143,34 +149,47 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
nbVerifyCalled: 0,
} as TUser;

await this.storingSystem.write(user);
this.emit(VerificationManagerEvents.userCreate, user);
try {
await this.#senderAPI.send({
...data,
name: user.username,
code
});

await this.senderAPI.send({
...data,
name: user.username,
code
});
await this.#storingSystem.write(user);
this.emit(VerificationManagerEvents.userCreate, user);

this.emit(VerificationManagerEvents.userAwait, user);
return this.#options.pendingMessage(user);
}
catch (error: unknown) {
this.emit(VerificationManagerEvents.error, user, error);
return this.#options.errorMessage(user, error);
}

this.emit(VerificationManagerEvents.userAwait, user);
return this.options.pendingMessage(user);
} else if (user && user.userid === userid && user.status === UserStatus.pending) {
user.nbCodeCalled++;
if (user.nbCodeCalled % this.options.maxNbCodeCalledBeforeResend === 0) {
await this.senderAPI.send({
...user.data,
name: user.username,
code: user.code,
});
}
await this.#storingSystem.write(user);

await this.storingSystem.write(user);
if (user.nbCodeCalled % this.#options.maxNbCodeCalledBeforeResend === 0) {
try {
await this.#senderAPI.send({
...user.data,
name: user.username,
code: user.code,
});
}
catch (error: unknown) {
this.emit(VerificationManagerEvents.error, user, error);
return this.#options.errorMessage(user, error);
}
}

this.emit(VerificationManagerEvents.userAwait, user);
return this.options.alreadyPendingMessage(user);
return this.#options.alreadyPendingMessage(user);
} else {
this.emit(VerificationManagerEvents.userActive, user);
return this.options.alreadyActiveMessage(user);
return this.#options.alreadyActiveMessage(user);
}
}

Expand All @@ -183,10 +202,10 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
* @return {Promise<string>} the result of the operation as a string, based on the result of the call to one of the manager's options' methods.
* @memberof VerificationManager
*/
async verifyCode(userid: string, code: string): Promise<string> {
public async verifyCode(userid: string, code: string): Promise<string> {
let isVerified = false;

let user: TUser = await this.storingSystem.read(userid);
let user: TUser = await this.#storingSystem.read(userid);

if (user) {
user.nbVerifyCalled++;
Expand All @@ -199,7 +218,7 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
user.code = null;
}

await this.storingSystem.write(user);
await this.#storingSystem.write(user);
}

this.emit(
Expand All @@ -211,8 +230,8 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
);

return isVerified
? this.options.validCodeMessage(user, code)
: this.options.invalidCodeMessage(user, code);
? this.#options.validCodeMessage(user, code)
: this.#options.invalidCodeMessage(user, code);
}
}

Expand Down Expand Up @@ -265,3 +284,13 @@ export class VerificationManager<TUser extends IUser> extends EventEmitter {
* @example
* manager.on(VerificationManagerEvents.userActive, (user) => {});
*/

/**
* Emitted when an error occurs.
*
* @event VerificationManager#error
* @param {TUser} user
* @param {unknown} error
* @example
* manager.on(VerificationManagerEvents.error, (user, error) => {});
*/
4 changes: 3 additions & 1 deletion src/VerificationManagerEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ export enum VerificationManagerEvents {

userCreate = 'userCreate',
userAwait = 'userAwait',
userActive = 'userActive'
userActive = 'userActive',

error = "error"
}
1 change: 1 addition & 0 deletions src/services/SendGridService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class SendGridService implements ISenderAPI {
await this.#mailService.send(message, false);
} catch (error) {
console.error(error);
throw error; // forces the VerificationManager flow to stop and return an error message.
}
}
}
3 changes: 2 additions & 1 deletion src/types/SenderAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export interface ISenderAPI {
/**
* Sends data to a user via a specific flow.
*
* @param {SenderAPIData} data
* @memberof ISenderAPI
*/
send: (data: SenderAPIData) => Promise<void>;
send(data: SenderAPIData): Promise<void>;
}
42 changes: 32 additions & 10 deletions src/types/VerificationOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,37 +42,59 @@ export interface VerificationOptions<TUser extends IUser> {
maxNbCodeCalledBeforeResend: number;

/**
* The message sent for the first time when the user's code has just been sent.
* Formats the message sent when calling the communication service throws an error.
*
* @param {TUser} user
* @param {unknown} error
* @return {string} the message
* @memberof VerificationOptions
*/
pendingMessage: (user: TUser) => string;
errorMessage(user: TUser, error: unknown): string;

/**
* The message sent once the user tries to generate a code when it's already sent to them.
* Formats the message sent for the first time when the user's code has just been sent.
*
* @param {TUser} user
* @return {string} the message
* @memberof VerificationOptions
*/
alreadyPendingMessage: (user: TUser) => string;
pendingMessage(user: TUser): string;

/**
* The message sent once the user tries to generate a code when they are already verified.
* Formats the message sent once the user tries to generate a code when it's already sent to them.
*
* @param {TUser} user
* @return {string} the message
* @memberof VerificationOptions
*/
alreadyActiveMessage: (user: TUser) => string;
alreadyPendingMessage(user: TUser): string;

/**
* The message sent once the code is checked and valid.
* Formats the message sent once the user tries to generate a code when they are already verified.
*
* @param {TUser} user
* @return {string} the message
* @memberof VerificationOptions
*/
validCodeMessage: (user: TUser, validCode: string) => string;
alreadyActiveMessage(user: TUser): string;

/**
* The message sent once the code is checked and invalid.
* Formats the message sent once the code is checked and valid.
*
* @param {TUser} user
* @param {string} validCode
* @return {string} the message
* @memberof VerificationOptions
*/
invalidCodeMessage: (user: TUser, invalidCode: string) => string;
validCodeMessage(user: TUser, validCode: string): string;

/**
* Formats the message sent once the code is checked and invalid.
*
* @param {TUser} user
* @param {string} invalidCode
* @return {string} the message
* @memberof VerificationOptions
*/
invalidCodeMessage(user: TUser, invalidCode: string): string;
}

0 comments on commit 4896e32

Please sign in to comment.