Skip to content

Commit

Permalink
fix(form): input navigation bug
Browse files Browse the repository at this point in the history
  • Loading branch information
ephrimlawrence committed May 2, 2024
1 parent 1e33bc1 commit b10ffdc
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 165 deletions.
2 changes: 1 addition & 1 deletion src/cli/simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class Simulator {
mode: resp[1] as any,
msisdn: resp[2],
sessionid: resp[3],
userdata: resp[4].replace('^', '\n'),
userdata: resp[4].replace(/\^/g, '\n'),
username: resp[5],
trafficid: resp[6],
other: resp[7],
Expand Down
16 changes: 8 additions & 8 deletions src/core/form_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,14 @@ export class FormMenuHandler {
}

private async resolveNextInput() {
if (
this.#currentInput?.next_input != null &&
this.#currentInput?.next_menu != null
) {
throw new Error(
`Input #${this.#currentInput} has both next_input and next_menu defined. Please define only one`,
);
}
// if (
// this.#currentInput?.next_input != null &&
// this.#currentInput?.next_menu != null
// ) {
// throw new Error(
// `Input #${this.#currentInput} has both next_input and next_menu defined. Please define only one`,
// );
// }

// No next input & next menu, terminate the session
if (
Expand Down
2 changes: 1 addition & 1 deletion src/gateways/wigal.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class WigalGateway extends Gateway {
private async wigalResponse(): Promise<string> {
const data = (await this.state)!;
return `${this.request.query?.network}|${data?.mode}|${data?.msisdn}|${data?.sessionId
}|${this.response.data?.replace('\n', '^') ?? ''}|${this.request.query?.username}|${this.request.query?.trafficid
}|${this.response.data?.replace(/\n/g, '^') ?? ''}|${this.request.query?.username}|${this.request.query?.trafficid
}|${data?.menu?.nextMenu || ""}`;
}
}
199 changes: 102 additions & 97 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
BaseMenu,
DynamicMenu,
Menu,
MenuAction,
ValidationResponse,
BaseMenu,
DynamicMenu,
Menu,
MenuAction,
ValidationResponse,
} from "@src/menus";
import { State } from "@src/models";
import { FormInput } from "@src/types";
Expand All @@ -12,103 +12,108 @@ import { SupportedGateway } from "./constants";
import { menuType } from "./menu.helper";

export async function validateInput(opts: {
state: State;
menu?: Menu;
formInput?: FormInput;
request: Request;
response: Response;
state: State;
menu?: Menu;
formInput?: FormInput;
request: Request;
response: Response;
}): Promise<{ error: string | undefined; valid: boolean }> {
const { state, menu, formInput: input, request, response } = opts;

if (menu == null && input == null) {
throw new Error("Either menu or input must be defined");
}

let resp: { error: string | undefined; valid: boolean } = {
valid: true,
error: undefined,
},
status: ValidationResponse = true;

if (menu != null) {
if (menuType(menu) == "class") {
status = await (menu as unknown as BaseMenu).validate(state?.userData);
} else {
status = await (menu as DynamicMenu).validateInput(request, response);
}
}

if (input != null) {
if (input.validate == null) {
status = true;
} else if (typeof input.validate == "function") {
status = await input.validate(request, response);
} else {
try {
status = (input.validate as RegExp).test(state?.userData);
} catch (error) {}
}
}

if (typeof status == "string") {
resp = { valid: false, error: status };
} else if (typeof status == "boolean" && status == false) {
resp = { valid: false, error: undefined };
}

return resp;
const { state, menu, formInput: input, request, response } = opts;

if (menu == null && input == null) {
throw new Error("Either menu or input must be defined");
}

let resp: { error: string | undefined; valid: boolean } = {
valid: true,
error: undefined,
},
status: ValidationResponse = true;

if (menu != null) {
if (menuType(menu) == "class") {
status = await (menu as unknown as BaseMenu).validate(state?.userData);
} else {
status = await (menu as DynamicMenu).validateInput(request, response);
}
}

if (input != null) {
if (input.validate == null) {
status = true;
} else if (typeof input.validate == "function") {
status = await input.validate(request, response);
} else {
try {
status = (input.validate as RegExp).test(state?.userData);
} catch (error) { }
}
}

if (typeof status == "string") {
resp = { valid: false, error: status };
} else if (typeof status == "boolean" && status == false) {
resp = { valid: false, error: undefined };
}

return resp;
}

export async function buildUserResponse(opts: {
menu: Menu | undefined;
state: State;
errorMessage: string | undefined;
request: Request;
response: Response;
actions?: MenuAction[];
menu: Menu | undefined;
state: State;
errorMessage: string | undefined;
request: Request;
response: Response;
actions?: MenuAction[];
}) {
const { menu, state, errorMessage, response, request } = opts;

if (errorMessage != null) {
return errorMessage;
}

// No message to display, end session
if (menu == null && state.isEnd) {
return "";
}

// TODO: build paginated response

let message = "";
if (menuType(menu!) == "class") {
message = await (menu as unknown as BaseMenu).message();
} else {
message = await (menu as DynamicMenu).getMessage(request, response);
}

// Add actions to the message
let actions: MenuAction[] | undefined = opts.actions;

if (actions == null) {
if (menuType(menu!) == "class") {
actions = (await (menu as unknown as BaseMenu).actions()) || [];
} else {
actions = await (menu as DynamicMenu).getActions();
}
}

for await (const action of actions) {
if (action.display == null) continue;

if (typeof action.display == "function") {
message += "\n" + (await action.display(this.request, this.response));
} else {
message += "\n" + action.display || "";
}
}

return message;
const { menu, state, errorMessage, response, request } = opts;

if (errorMessage != null) {
return errorMessage;
}

// No message to display, end session
if (menu == null && state.isEnd) {
return "";
}

// TODO: build paginated response

let message: string | undefined = undefined;
if (menuType(menu!) == "class") {
message = await (menu as unknown as BaseMenu).message();
} else {
message = await (menu as DynamicMenu).getMessage(request, response);
}

// If message is null, set it to empty string
if (message == null) {
message = "";
}

// Add actions to the message
let actions: MenuAction[] | undefined = opts.actions;

if (actions == null) {
if (menuType(menu!) == "class") {
actions = (await (menu as unknown as BaseMenu).actions()) || [];
} else {
actions = await (menu as DynamicMenu).getActions();
}
}

for await (const action of actions) {
if (action.display == null) continue;

if (typeof action.display == "function") {
message += "\n" + (await action.display(this.request, this.response));
} else {
message += "\n" + action.display || "";
}
}

return message;
}

export { SupportedGateway };
Expand Down
116 changes: 58 additions & 58 deletions src/menus/base.menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,75 +4,75 @@ import { MenuAction } from "./action.menu";
import { Request, Response } from "@src/types/request";

export abstract class BaseMenu {
constructor(
protected readonly request: Request,
protected readonly response: Response,
) {}
constructor(
protected readonly request: Request,
protected readonly response: Response,
) { }

async validate(_data?: string): Promise<ValidationResponse> {
return true;
}
async validate(_data?: string): Promise<ValidationResponse> {
return true;
}

paginate(): Promise<boolean> | boolean {
return false;
}
paginate(): Promise<boolean> | boolean {
return false;
}

abstract message(): Promise<string> | string;
abstract message(): Promise<string> | string | undefined;

abstract nextMenu(): Promise<string | undefined> | string | undefined;
abstract nextMenu(): Promise<string | undefined> | string | undefined;

/**
* Terminate the current session
*
*/
end(): Promise<boolean> | boolean {
return false;
}
/**
* Terminate the current session
*
*/
end(): Promise<boolean> | boolean {
return false;
}

get sessionId(): string {
// FIXME: this is not reliable, add to request object
return this.request.query?.sessionid!;
}
get sessionId(): string {
// FIXME: this is not reliable, add to request object
return this.request.query?.sessionid!;
}

isStart(): Promise<boolean> | boolean {
return false;
}
isStart(): Promise<boolean> | boolean {
return false;
}

get session(): Session {
return {
get: async <T>(key: string, defaultValue?: any) => {
return await Config.getInstance().session?.get<T>(
this.sessionId!,
key,
defaultValue,
);
},
getAll: <T>() => {
return Config.getInstance().session?.getAll<T>(this.sessionId!);
},
set: (key: string, val: any) =>
Config.getInstance().session?.set(this.sessionId!, key, val),
};
}
get session(): Session {
return {
get: async <T>(key: string, defaultValue?: any) => {
return await Config.getInstance().session?.get<T>(
this.sessionId!,
key,
defaultValue,
);
},
getAll: <T>() => {
return Config.getInstance().session?.getAll<T>(this.sessionId!);
},
set: (key: string, val: any) =>
Config.getInstance().session?.set(this.sessionId!, key, val),
};
}

/**
* Returns the current msisdn/phone number of the session.
*/
get msisdn(): string {
return this.request.state.msisdn;
}
/**
* Returns the current msisdn/phone number of the session.
*/
get msisdn(): string {
return this.request.state.msisdn;
}

async back(): Promise<string | undefined> {
return undefined;
}
async back(): Promise<string | undefined> {
return undefined;
}

abstract actions(): Promise<MenuAction[]> | MenuAction[];
abstract actions(): Promise<MenuAction[]> | MenuAction[];

async inputs(): Promise<FormInput[]> {
return [];
}
async inputs(): Promise<FormInput[]> {
return [];
}

isForm(): boolean {
return false;
}
isForm(): boolean {
return false;
}
}

0 comments on commit b10ffdc

Please sign in to comment.