Skip to content

Commit

Permalink
support voice prompt translation in 4 languages
Browse files Browse the repository at this point in the history
  • Loading branch information
f-w committed Nov 26, 2018
1 parent 8ec3e65 commit 60d72c5
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 15 deletions.
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,37 @@ The tests cover the following cases:


###### API Specification:
Request Type | API Endpoint | Parameters | Returns | Purpose
------------ | ------------- | ------------- | ------------- | -------------
HTTP GET | / or /status | | OK | Returns "OK" if the service is running
HTTP POST | /captcha | request body: { nonce: string } | { "nonce": string, "captcha": string, validation": JSON}| Retrieve a captcha to be displayed to a user
HTTP POST | /captcha/audio | request body: { validation: JSON } | { "audio": dataUri}| Retrieve the audio for a given captcha validation object, returns MP3 in DataUri format
HTTP POST | /verify/captcha | request body: { nonce: string, answer: string, validation: JSON } | { valid: true/false, jwt: JWT } | Compare the answer to the encryptedAnswer, return a signed JWT if successful
HTTP POST | /verify/jwt | request body: { nonce: string, token: JWT } | { valid: true/false } | Validate a signed JWT by resource server

* GET / or /status
* returns: "OK" if the service is running
* POST /captcha
* input: request body: { nonce: \<string\> }
* returns: {nonce: \<string\>, captcha: \<string\>, validation: \<JSON\>}

Retrieve a captcha to be displayed to a user
* POST /captcha/audio
* input: request body: { validation: \<JSON\> [, translation: \<string\>\|true] }
* returns: {audio: \<dataUri\>}

Retrieve the audio for a given captcha validation object, returns MP3 in DataUri format. The audio prompt is spoken in English unless *transaltion* is specified. *transaltion* can be either a string of one of following supported language acronyms

* *en* (English)
* *fr* (French)
* *pa* (Punjabi)
* *zh* (Mandarin Chinese)

or the boolean value of *true*. If *transaltion* is set to *true*, then the first language set in the http *Accept-Language* request header matching the above list is chosen. If there is no match, then fall backs to English.

* POST /verify/captcha
* input: request body: { nonce: \<string\>, answer: \<string\>, validation: \<JSON\> }
* returns: { valid: true\|false, jwt: \<JWT\> }

Compare the answer to the encryptedAnswer, return a signed JWT if the answer if valid
* POST /verify/jwt
* input: request body: { nonce: \<string\>, token: \<JWT\> }
* returns: { valid: true\|false }

Validate a signed JWT by resource server

#### API Demo
You can try it out the API for yourself at our demo environment by following the above API specs:
Expand Down
3 changes: 3 additions & 0 deletions app.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
},
{
"path": "../text2wav.node.js"
},
{
"path": "../MyGovBC-CAPTCHA-Widget"
}
],
"settings": {}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mygovbc-captcha-service",
"version": "7.1.0",
"version": "7.2.0",
"description": "A reusable, secure and reliable CAPTCHA microservice for service providers to use within online digital services.",
"main": "lib/index.js",
"scripts": {
Expand All @@ -24,7 +24,7 @@
"os": "^0.1.1",
"streamifier": "^0.1.1",
"svg-captcha": "^1.3.1",
"text2wav": "^0.0.7",
"text2wav": "^0.0.10",
"ts-node": "^7.0.1",
"typescript": "^3.1.3",
"wav": "^1.0.1",
Expand Down
34 changes: 28 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,17 @@ app.post('/verify/captcha', async function (req: Request, res: Response) {
////////////////////////////////////////////////////////
interface GetAudioRequestBody {
validation: object,
translation?: string | boolean,
}

var getAudio = async function (body: GetAudioRequestBody) {
const voicePromptLanguageMap: { [index: string]: string } = {
en: 'Please type in following letters or numbers', // english
fr: 'Veuillez saisir les lettres ou les chiffres suivants', // french
pa: 'ਕਿਰਪਾ ਕਰਕੇ ਹੇਠ ਲਿਖੇ ਅੱਖਰ ਜਾਂ ਨੰਬਰ ਟਾਈਪ ਕਰੋ', // punjabi
zh: '请输入以下英文字母或数字', // mandarin chinese
}

var getAudio = async function (body: GetAudioRequestBody, req?: Request) {
winston.debug(`getting audio for`, body)
try {
// Ensure audio is enabled.
Expand All @@ -362,11 +370,25 @@ var getAudio = async function (body: GetAudioRequestBody) {

// Insert leading text and commas to slow down reader
var captchaCharArray = decryptedBody.answer.toString().split("")
var spokenCatpcha = "Please type in following letters or numbers: "
let language = 'en'
if (body.translation) {
if (typeof body.translation == 'string') {
if (voicePromptLanguageMap.hasOwnProperty(<string>body.translation)) {
language = <string>body.translation
}
}
else if (body.translation === true && req && req.headers['accept-language']) {
let lang = (<string>req.headers['accept-language']).split(',').map(e => e.split(';')[0].split('-')[0]).find(e => voicePromptLanguageMap.hasOwnProperty(e))
if (lang) {
language = lang
}
}
}
var spokenCatpcha = voicePromptLanguageMap[language] + ": "
for (var i = 0; i < captchaCharArray.length; i++) {
spokenCatpcha += captchaCharArray[i] + ", "
}
let audioDataUri = await getMp3DataUriFromText(spokenCatpcha)
let audioDataUri = await getMp3DataUriFromText(spokenCatpcha, language)
// Now pass back the full payload ,
return {
audio: audioDataUri
Expand All @@ -380,7 +402,7 @@ var getAudio = async function (body: GetAudioRequestBody) {
}

app.post('/captcha/audio', async function (req: Request, res: Response) {
let ret = await getAudio(req.body)
let ret = await getAudio(req.body, req)
return res.send(ret)
})

Expand Down Expand Up @@ -440,7 +462,7 @@ app.post('/verify/jwt', async function (req: Request, res: Response) {
* Audio routines
*/
////////////////////////////////////////////////////////
function getMp3DataUriFromText(text: string) {
function getMp3DataUriFromText(text: string, language: string = 'en') {
winston.debug("Starting audio generation...")
return new Promise(async function (resolve) {

Expand Down Expand Up @@ -480,7 +502,7 @@ function getMp3DataUriFromText(text: string) {

// Generate audio, Base64 encoded WAV in DataUri format including mime type header
winston.debug("Generate speach as WAV in ArrayBuffer")
var audioArrayBuffer = await text2wav(text)
let audioArrayBuffer = await text2wav(text, { voice: language })

// convert to buffer
winston.debug("Convert arraybuffer to buffer")
Expand Down

0 comments on commit 60d72c5

Please sign in to comment.