ananse
Version:
Ananse is a lightweight NodeJs framework with batteries included for building efficient, scalable and maintainable USSD applications.
1 lines • 9.63 kB
Source Map (JSON)
{"version":3,"sources":["../../src/cli/simulator.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { SupportedGateway } from \"@src/helpers/constants\";\nimport { randomUUID } from \"crypto\";\nimport * as readline from \"readline\";\n\nconst rl = readline.createInterface({\n\tinput: process.stdin,\n\toutput: process.stdout,\n});\n\nconst CACHE: Record<\n\tSupportedGateway,\n\t{ sessionId?: string; operator?: string }\n> = { wigal: {}, emergent_technology: {} };\n\nclass Simulator {\n\targs: {\n\t\tphone?: string;\n\t\turl?: string;\n\t\tprovider?: SupportedGateway;\n\t\tdebug?: boolean | string;\n\t} = {};\n\n\t// TODO: Implement emergent ussd\n\tget provider() {\n\t\treturn this.args.provider;\n\t}\n\n\tget baseUrl(): string {\n\t\treturn `${this.args.url || \"http://localhost:3000\"}`;\n\t}\n\n\t// get generateRequestUrl(): string {\n\t// if (this.provider == \"wigal\") {\n\t// return `${this.baseUrl}?network=wigal_tigo_gh&sessionid=12345&mode=start&msisdn=${this.args.phone}&userdata&username=stevkky&trafficid=adc62161-05b2-4af5-98b1-a66c67f85c9d&other=first_menu`;\n\t// }\n\n\t// throw new Error(`${this.provider} is not implemented`);\n\t// }\n\n\tasync init() {\n\t\tthis.parseArguments();\n\t}\n\n\tprivate parseArguments() {\n\t\tthis.args[\"phone\"] = process.argv.slice(2)[0];\n\t\tthis.args[\"provider\"] = process.argv.slice(2)[1] as SupportedGateway;\n\t\tthis.args[\"url\"] = process.argv.slice(2)[2];\n\n\t\tif (this.args.phone == null || this.args.phone?.trim() == \"\") {\n\t\t\tconsole.log(\"Please provide a phone number!\");\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!/[0-9]{10,}/.test(this.args.phone)) {\n\t\t\tconsole.log(\"Invalid phone number!\");\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\tprocess.argv.slice(2).forEach((arg) => {\n\t\t\tconst [key, value] = arg.split(\"=\");\n\n\t\t\t// @ts-ignore\n\t\t\tthis.args[key.replace(/(-)+/, \"\")] = value;\n\t\t});\n\t}\n\n\tasync start(url?: string, requestBody?: any) {\n\t\ttry {\n\t\t\tif (this.provider == SupportedGateway.wigal) {\n\t\t\t\tconst resp = await fetch(url || (this.reply() as string));\n\t\t\t\tconst data = await resp.text();\n\n\t\t\t\tlet wigal = this.parseResponse(data);\n\n\t\t\t\tconsole.log(\"\");\n\t\t\t\tconsole.log(this.displayText(wigal.userdata));\n\t\t\t\tconsole.log(\"\");\n\n\t\t\t\tif (wigal.isEndSession) process.exit(0);\n\n\t\t\t\trl.question(\"Response: \", async (input) => {\n\t\t\t\t\treturn await this.start(this.reply(wigal, input) as string);\n\t\t\t\t});\n\t\t\t} else if (this.provider == SupportedGateway.emergent_technology) {\n\t\t\t\tconst { url, body } = this.reply(requestBody) as {\n\t\t\t\t\turl: string;\n\t\t\t\t\tbody: any;\n\t\t\t\t};\n\n\t\t\t\tconst resp = await fetch(url, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t});\n\n\t\t\t\tconst json: { Message: string; Type: \"Release\" | \"Response\" } =\n\t\t\t\t\tawait resp.json();\n\n\t\t\t\tconsole.log(\"\");\n\t\t\t\tconsole.log(this.displayText(json.Message));\n\t\t\t\tconsole.log(\"\");\n\n\t\t\t\tif (json.Type == \"Release\") process.exit(0);\n\n\t\t\t\trl.question(\"Response: \", async (input) => {\n\t\t\t\t\tconst { url, body } = this.reply(json, input) as {\n\t\t\t\t\t\turl: string;\n\t\t\t\t\t\tbody: any;\n\t\t\t\t\t};\n\n\t\t\t\t\treturn await this.start(url, body);\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.log(\"Simulator error: \", e);\n\t\t\tthis.log(e);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\treply(data?: any, input?: string): string | { url: string; body: any } {\n\t\tif (this.provider === SupportedGateway.wigal) {\n\t\t\t// If '#' in input, encode i\n\t\t\tinput = input?.replace(/#/g, \"%23\");\n\n\t\t\t// Wigal reply\n\t\t\tdata ??= {};\n\t\t\tdata.userdata = input != null ? input : data.userdata;\n\n\t\t\tconst url = `${this.baseUrl}?network=${\n\t\t\t\tdata.network || \"wigal_mtn_gh\"\n\t\t\t}&sessionid=${data.sessionid || randomUUID()}&mode=${\n\t\t\t\tdata.mode || \"start\"\n\t\t\t}&msisdn=${data.msisdn || this.args.phone}&userdata=${input}&username=${\n\t\t\t\tdata.username || \"test_user\"\n\t\t\t}&trafficid=${randomUUID()}&other=${data.other || \"\"}`;\n\n\t\t\tthis.log(url);\n\t\t\treturn url;\n\t\t}\n\n\t\tif (this.provider == SupportedGateway.emergent_technology) {\n\t\t\t// Emergent Technology\n\t\t\tdata ??= {\n\t\t\t\tMobile: this.args.phone,\n\t\t\t\tMessage: \"*714#\",\n\t\t\t};\n\t\t\tdata.Message = input != null ? input : data.Message;\n\t\t\t// SessionId is not included in the response, so we read from cache first\n\t\t\tdata.SessionId = CACHE[this.provider].sessionId || randomUUID();\n\t\t\tdata.Type ??= \"Initiation\";\n\t\t\tdata.Mobile = this.args.phone;\n\t\t\tdata.Operator = \"Vodafone\";\n\t\t\tdata.ServiceCode = \"714\";\n\n\t\t\t// TODO: Auto detect operator/network from phone number\n\n\t\t\tCACHE[this.provider].sessionId = data.SessionId;\n\n\t\t\treturn { url: this.baseUrl, body: data };\n\t\t}\n\n\t\tthrow new Error(`Reply is not implemented for ${this.provider}`);\n\t}\n\n\tparseResponse(data: string) {\n\t\tthis.log(data);\n\n\t\tif (this.provider == \"wigal\") {\n\t\t\tlet resp = data.split(\"|\");\n\t\t\treturn {\n\t\t\t\tnetwork: resp[0],\n\t\t\t\tmode: resp[1] as any,\n\t\t\t\tmsisdn: resp[2],\n\t\t\t\tsessionid: resp[3],\n\t\t\t\tuserdata: resp[4].replace(/\\^/g, \"\\n\"),\n\t\t\t\tusername: resp[5],\n\t\t\t\ttrafficid: resp[6],\n\t\t\t\tother: resp[7],\n\t\t\t\tisEndSession: resp[1] == \"end\",\n\t\t\t};\n\t\t}\n\t\tthrow new Error(`Response parsing is not implemented for ${this.provider}`);\n\t}\n\n\tdisplayText(text: string | undefined) {\n\t\ttext ??= \"Unable to parse text from response\";\n\n\t\treturn text?.replace(/\\^/g, \"\\n\");\n\t}\n\n\tlog(data: any) {\n\t\t// this.args.debug = true;\n\t\tif (this.args.debug === true || this.args.debug === \"true\") {\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(data);\n\t\t\tconsole.log(\"\");\n\t\t}\n\t}\n}\n\nconst simulator = new Simulator();\nsimulator.init().then(() => simulator.start());\n"],"mappings":"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","names":["url","body"]}