mirror of
https://github.com/jambonz/jambonz-node.git
synced 2025-12-19 05:17:49 +00:00
initial checkin
This commit is contained in:
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
test/*
|
||||
126
.eslintrc.json
Normal file
126
.eslintrc.json
Normal file
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": false,
|
||||
"modules": false
|
||||
},
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"plugins": ["promise"],
|
||||
"rules": {
|
||||
"promise/always-return": "error",
|
||||
"promise/no-return-wrap": "error",
|
||||
"promise/param-names": "error",
|
||||
"promise/catch-or-return": "error",
|
||||
"promise/no-native": "off",
|
||||
"promise/no-nesting": "warn",
|
||||
"promise/no-promise-in-callback": "warn",
|
||||
"promise/no-callback-in-promise": "warn",
|
||||
"promise/no-return-in-finally": "warn",
|
||||
|
||||
// Possible Errors
|
||||
// http://eslint.org/docs/rules/#possible-errors
|
||||
"comma-dangle": [2, "only-multiline"],
|
||||
"no-control-regex": 2,
|
||||
"no-debugger": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-boolean-cast" : 2,
|
||||
"no-extra-parens": [2, "functions"],
|
||||
"no-extra-semi": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-proto": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unreachable": 2,
|
||||
"use-isnan": 2,
|
||||
"valid-typeof": 2,
|
||||
|
||||
// Best Practices
|
||||
// http://eslint.org/docs/rules/#best-practices
|
||||
"no-fallthrough": 2,
|
||||
"no-octal": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-self-assign": 2,
|
||||
"no-unused-labels": 2,
|
||||
|
||||
// Strict Mode
|
||||
// http://eslint.org/docs/rules/#strict-mode
|
||||
"strict": [2, "never"],
|
||||
|
||||
// Variables
|
||||
// http://eslint.org/docs/rules/#variables
|
||||
"no-delete-var": 2,
|
||||
"no-undef": 2,
|
||||
"no-unused-vars": [2, {"args": "none"}],
|
||||
|
||||
// Node.js and CommonJS
|
||||
// http://eslint.org/docs/rules/#nodejs-and-commonjs
|
||||
"no-mixed-requires": 2,
|
||||
"no-new-require": 2,
|
||||
"no-path-concat": 2,
|
||||
"no-restricted-modules": [2, "sys", "_linklist"],
|
||||
|
||||
// Stylistic Issues
|
||||
// http://eslint.org/docs/rules/#stylistic-issues
|
||||
"comma-spacing": 2,
|
||||
"eol-last": 2,
|
||||
"indent": [2, 2, {"SwitchCase": 1}],
|
||||
"keyword-spacing": 2,
|
||||
"max-len": [2, 120, 2],
|
||||
"new-parens": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multiple-empty-lines": [2, {"max": 2}],
|
||||
"no-trailing-spaces": [2, {"skipBlankLines": false }],
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"semi": 2,
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, "never"],
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": 2,
|
||||
|
||||
// ECMAScript 6
|
||||
// http://eslint.org/docs/rules/#ecmascript-6
|
||||
"arrow-parens": [2, "always"],
|
||||
"arrow-spacing": [2, {"before": true, "after": true}],
|
||||
"constructor-super": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-confusing-arrow": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-new-symbol": 2,
|
||||
"no-this-before-super": 2,
|
||||
"prefer-const": 2
|
||||
},
|
||||
"globals": {
|
||||
"DTRACE_HTTP_CLIENT_REQUEST" : false,
|
||||
"LTTNG_HTTP_CLIENT_REQUEST" : false,
|
||||
"COUNTER_HTTP_CLIENT_REQUEST" : false,
|
||||
"DTRACE_HTTP_CLIENT_RESPONSE" : false,
|
||||
"LTTNG_HTTP_CLIENT_RESPONSE" : false,
|
||||
"COUNTER_HTTP_CLIENT_RESPONSE" : false,
|
||||
"DTRACE_HTTP_SERVER_REQUEST" : false,
|
||||
"LTTNG_HTTP_SERVER_REQUEST" : false,
|
||||
"COUNTER_HTTP_SERVER_REQUEST" : false,
|
||||
"DTRACE_HTTP_SERVER_RESPONSE" : false,
|
||||
"LTTNG_HTTP_SERVER_RESPONSE" : false,
|
||||
"COUNTER_HTTP_SERVER_RESPONSE" : false,
|
||||
"DTRACE_NET_STREAM_END" : false,
|
||||
"LTTNG_NET_STREAM_END" : false,
|
||||
"COUNTER_NET_SERVER_CONNECTION_CLOSE" : false,
|
||||
"DTRACE_NET_SERVER_CONNECTION" : false,
|
||||
"LTTNG_NET_SERVER_CONNECTION" : false,
|
||||
"COUNTER_NET_SERVER_CONNECTION" : false
|
||||
}
|
||||
}
|
||||
16
.github/workflows/build.yml
vendored
Normal file
16
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- run: npm ci
|
||||
- run: npm run jslint
|
||||
- run: npm test
|
||||
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# github pages site
|
||||
_site
|
||||
|
||||
#transient test cases
|
||||
examples/nosave.*.js
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
.nyc_output/
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
.DS_Store
|
||||
|
||||
examples/*
|
||||
10
lib/index.js
Normal file
10
lib/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const Jambonz = require('./rest/jambonz');
|
||||
|
||||
const initializer = (accountSid, apiKey, opts) => {
|
||||
return new Jambonz(accountSid, apiKey, opts);
|
||||
};
|
||||
|
||||
initializer.Jambonz = Jambonz;
|
||||
initializer.WebhookResponse = require('./jambonz/webhook-response');
|
||||
|
||||
module.exports = initializer;
|
||||
387
lib/jambonz/specs.json
Normal file
387
lib/jambonz/specs.json
Normal file
@@ -0,0 +1,387 @@
|
||||
{
|
||||
"sip_decline": {
|
||||
"properties": {
|
||||
"status": "number",
|
||||
"reason": "string",
|
||||
"headers": "object"
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"dequeue": {
|
||||
"properties": {
|
||||
"name": "string",
|
||||
"actionHook": "object|string",
|
||||
"timeout": "number",
|
||||
"beep": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"enqueue": {
|
||||
"properties": {
|
||||
"name": "string",
|
||||
"actionHook": "object|string",
|
||||
"waitHook": "object|string",
|
||||
"_": "object"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"leave": {
|
||||
"properties": {
|
||||
|
||||
}
|
||||
},
|
||||
"hangup": {
|
||||
"properties": {
|
||||
"headers": "object"
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"play": {
|
||||
"properties": {
|
||||
"url": "string",
|
||||
"loop": "number",
|
||||
"earlyMedia": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"say": {
|
||||
"properties": {
|
||||
"text": "string|array",
|
||||
"loop": "number",
|
||||
"synthesizer": "#synthesizer",
|
||||
"earlyMedia": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"gather": {
|
||||
"properties": {
|
||||
"actionHook": "object|string",
|
||||
"finishOnKey": "string",
|
||||
"input": "array",
|
||||
"numDigits": "number",
|
||||
"partialResultHook": "object|string",
|
||||
"speechTimeout": "number",
|
||||
"timeout": "number",
|
||||
"recognizer": "#recognizer",
|
||||
"play": "#play",
|
||||
"say": "#say"
|
||||
},
|
||||
"required": [
|
||||
"actionHook"
|
||||
]
|
||||
},
|
||||
"conference": {
|
||||
"properties": {
|
||||
"name": "string",
|
||||
"beep": "boolean",
|
||||
"startConferenceOnEnter": "boolean",
|
||||
"endConferenceOnExit": "boolean",
|
||||
"maxParticipants": "number",
|
||||
"actionHook": "object|string",
|
||||
"waitHook": "object|string",
|
||||
"statusEvents": "array",
|
||||
"statusHook": "object|string",
|
||||
"enterHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"dial": {
|
||||
"properties": {
|
||||
"actionHook": "object|string",
|
||||
"answerOnBridge": "boolean",
|
||||
"callerId": "string",
|
||||
"confirmHook": "object|string",
|
||||
"dialMusic": "string",
|
||||
"dtmfCapture": "object",
|
||||
"dtmfHook": "object|string",
|
||||
"headers": "object",
|
||||
"listen": "#listen",
|
||||
"target": ["#target"],
|
||||
"timeLimit": "number",
|
||||
"timeout": "number",
|
||||
"proxy": "string",
|
||||
"transcribe": "#transcribe"
|
||||
},
|
||||
"required": [
|
||||
"target"
|
||||
]
|
||||
},
|
||||
"dialogflow": {
|
||||
"properties": {
|
||||
"credentials": "object|string",
|
||||
"project": "string",
|
||||
"environment": "string",
|
||||
"lang": "string",
|
||||
"actionHook": "object|string",
|
||||
"eventHook": "object|string",
|
||||
"events": "[string]",
|
||||
"welcomeEvent": "string",
|
||||
"welcomeEventParams": "object",
|
||||
"noInputTimeout": "number",
|
||||
"noInputEvent": "string",
|
||||
"passDtmfAsTextInput": "boolean",
|
||||
"thinkingMusic": "string",
|
||||
"tts": "#synthesizer",
|
||||
"bargein": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"project",
|
||||
"credentials",
|
||||
"lang"
|
||||
]
|
||||
},
|
||||
"dtmf": {
|
||||
"properties": {
|
||||
"dtmf": "string",
|
||||
"duration": "number"
|
||||
},
|
||||
"required": [
|
||||
"dtmf"
|
||||
]
|
||||
},
|
||||
"lex": {
|
||||
"properties": {
|
||||
"botId": "string",
|
||||
"botAlias": "string",
|
||||
"credentials": "object",
|
||||
"region": "string",
|
||||
"locale": "string",
|
||||
"intent": "#lexIntent",
|
||||
"welcomeMessage": "string",
|
||||
"metadata": "object",
|
||||
"bargein": "boolean",
|
||||
"passDtmf": "boolean",
|
||||
"actionHook": "object|string",
|
||||
"eventHook": "object|string",
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"enum": ["lex", "tts"]
|
||||
},
|
||||
"noInputTimeout": "number",
|
||||
"tts": "#synthesizer"
|
||||
},
|
||||
"required": [
|
||||
"botId",
|
||||
"botAlias",
|
||||
"region",
|
||||
"prompt"
|
||||
]
|
||||
},
|
||||
"listen": {
|
||||
"properties": {
|
||||
"actionHook": "object|string",
|
||||
"auth": "#auth",
|
||||
"finishOnKey": "string",
|
||||
"maxLength": "number",
|
||||
"metadata": "object",
|
||||
"mixType": {
|
||||
"type": "string",
|
||||
"enum": ["mono", "stereo", "mixed"]
|
||||
},
|
||||
"passDtmf": "boolean",
|
||||
"playBeep": "boolean",
|
||||
"sampleRate": "number",
|
||||
"timeout": "number",
|
||||
"transcribe": "#transcribe",
|
||||
"url": "string",
|
||||
"wsAuth": "#auth",
|
||||
"earlyMedia": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
"properties": {
|
||||
"provider": "string",
|
||||
"to": "string",
|
||||
"from": "string",
|
||||
"text": "string",
|
||||
"media": "string|array",
|
||||
"actionHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"to",
|
||||
"from"
|
||||
]
|
||||
},
|
||||
"pause": {
|
||||
"properties": {
|
||||
"length": "number"
|
||||
},
|
||||
"required": [
|
||||
"length"
|
||||
]
|
||||
},
|
||||
"redirect": {
|
||||
"properties": {
|
||||
"actionHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"actionHook"
|
||||
]
|
||||
},
|
||||
"rest_dial": {
|
||||
"properties": {
|
||||
"account_sid": "string",
|
||||
"application_sid": "string",
|
||||
"call_hook": "object|string",
|
||||
"call_status_hook": "object|string",
|
||||
"from": "string",
|
||||
"speech_synthesis_vendor": "string",
|
||||
"speech_synthesis_voice": "string",
|
||||
"speech_synthesis_language": "string",
|
||||
"speech_recognizer_vendor": "string",
|
||||
"speech_recognizer_language": "string",
|
||||
"tag": "object",
|
||||
"to": "#target",
|
||||
"headers": "object",
|
||||
"timeout": "number"
|
||||
},
|
||||
"required": [
|
||||
"call_hook",
|
||||
"from",
|
||||
"to"
|
||||
]
|
||||
},
|
||||
"tag": {
|
||||
"properties": {
|
||||
"data": "object"
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"transcribe": {
|
||||
"properties": {
|
||||
"transcriptionHook": "string",
|
||||
"recognizer": "#recognizer",
|
||||
"earlyMedia": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"transcriptionHook",
|
||||
"recognizer"
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["phone", "sip", "user", "teams"]
|
||||
},
|
||||
"confirmHook": "object|string",
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST"]
|
||||
},
|
||||
"name": "string",
|
||||
"number": "string",
|
||||
"sipUri": "string",
|
||||
"auth": "#auth",
|
||||
"vmail": "boolean",
|
||||
"tenant": "string"
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"auth": {
|
||||
"properties": {
|
||||
"username": "string",
|
||||
"password": "string"
|
||||
},
|
||||
"required": [
|
||||
"username",
|
||||
"password"
|
||||
]
|
||||
},
|
||||
"synthesizer": {
|
||||
"properties": {
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"enum": ["google", "aws", "polly", "default"]
|
||||
},
|
||||
"language": "string",
|
||||
"voice": "string",
|
||||
"gender": {
|
||||
"type": "string",
|
||||
"enum": ["MALE", "FEMALE", "NEUTRAL"]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"vendor"
|
||||
]
|
||||
},
|
||||
"recognizer": {
|
||||
"properties": {
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"enum": ["google", "aws", "default"]
|
||||
},
|
||||
"language": "string",
|
||||
"hints": "array",
|
||||
"altLanguages": "array",
|
||||
"profanityFilter": "boolean",
|
||||
"interim": "boolean",
|
||||
"singleUtterance": "boolean",
|
||||
"dualChannel": "boolean",
|
||||
"separateRecognitionPerChannel": "boolean",
|
||||
"punctuation": "boolean",
|
||||
"enhancedModel": "boolean",
|
||||
"words": "boolean",
|
||||
"diarization": "boolean",
|
||||
"diarizationMinSpeakers": "number",
|
||||
"diarizationMaxSpeakers": "number",
|
||||
"interactionType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unspecified",
|
||||
"discussion",
|
||||
"presentation",
|
||||
"phone_call",
|
||||
"voicemail",
|
||||
"voice_search",
|
||||
"voice_command",
|
||||
"dictation"
|
||||
]
|
||||
},
|
||||
"naicsCode": "number",
|
||||
"identifyChannels": "boolean",
|
||||
"vocabularyName": "string",
|
||||
"vocabularyFilterName": "string",
|
||||
"filterMethod": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"remove",
|
||||
"mask",
|
||||
"tag"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"vendor"
|
||||
]
|
||||
},
|
||||
"lexIntent": {
|
||||
"properties": {
|
||||
"name": "string",
|
||||
"slots": "object"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
}
|
||||
76
lib/jambonz/utils.js
Normal file
76
lib/jambonz/utils.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const debug = require('debug')('jambonz:jambonz-node');
|
||||
const assert = require('assert');
|
||||
const specs = new Map(Object.entries(require('./specs.json')));
|
||||
|
||||
/**
|
||||
* copied from jambonz-feature-server/lib/tasks.js
|
||||
*/
|
||||
function validate(name, data) {
|
||||
debug(`validating ${name} with data ${JSON.stringify(data)}`);
|
||||
// validate the instruction is supported
|
||||
if (!specs.has(name)) throw new Error(`invalid instruction: ${name}`);
|
||||
|
||||
// check type of each element and make sure required elements are present
|
||||
const specData = specs.get(name);
|
||||
let required = specData.required || [];
|
||||
for (const dKey in data) {
|
||||
if (dKey in specData.properties) {
|
||||
const dVal = data[dKey];
|
||||
const dSpec = specData.properties[dKey];
|
||||
debug(`Task:validate validating property ${dKey} with value ${JSON.stringify(dVal)}`);
|
||||
|
||||
if (typeof dSpec === 'string' && dSpec === 'array') {
|
||||
if (!Array.isArray(dVal)) throw new Error(`${name}: property ${dKey} is not an array`);
|
||||
}
|
||||
else if (typeof dSpec === 'string' && dSpec.includes('|')) {
|
||||
const types = dSpec.split('|').map((t) => t.trim());
|
||||
if (!types.includes(typeof dVal) && !(types.includes('array') && Array.isArray(dVal))) {
|
||||
throw new Error(`${name}: property ${dKey} has invalid data type, must be one of ${types}`);
|
||||
}
|
||||
}
|
||||
else if (typeof dSpec === 'string' && ['number', 'string', 'object', 'boolean'].includes(dSpec)) {
|
||||
// simple types
|
||||
if (typeof dVal !== specData.properties[dKey]) {
|
||||
throw new Error(`${name}: property ${dKey} has invalid data type`);
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(dSpec) && dSpec[0].startsWith('#')) {
|
||||
const name = dSpec[0].slice(1);
|
||||
for (const item of dVal) {
|
||||
validate(name, item);
|
||||
}
|
||||
}
|
||||
else if (typeof dSpec === 'object') {
|
||||
// complex types
|
||||
const type = dSpec.type;
|
||||
assert.ok(['number', 'string', 'object', 'boolean'].includes(type),
|
||||
`invalid or missing type in spec ${JSON.stringify(dSpec)}`);
|
||||
if (type === 'string' && dSpec.enum) {
|
||||
assert.ok(Array.isArray(dSpec.enum), `enum must be an array ${JSON.stringify(dSpec.enum)}`);
|
||||
if (!dSpec.enum.includes(dVal)) throw new Error(`invalid value ${dVal} must be one of ${dSpec.enum}`);
|
||||
}
|
||||
}
|
||||
else if (typeof dSpec === 'string' && dSpec.startsWith('#')) {
|
||||
// reference to another datatype (i.e. nested type)
|
||||
const name = dSpec.slice(1);
|
||||
//const obj = {};
|
||||
//obj[name] = dVal;
|
||||
validate(name, dVal);
|
||||
}
|
||||
else {
|
||||
assert.ok(0, `invalid spec ${JSON.stringify(dSpec)}`);
|
||||
}
|
||||
required = required.filter((item) => item !== dKey);
|
||||
}
|
||||
else throw new Error(`${name}: unknown property ${dKey}`);
|
||||
}
|
||||
if (required.length > 0) throw new Error(`${name}: missing value for ${required}`);
|
||||
}
|
||||
/**
|
||||
* end of copy
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
validate,
|
||||
specs
|
||||
};
|
||||
33
lib/jambonz/webhook-response.js
Normal file
33
lib/jambonz/webhook-response.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const {validate, specs} = require('./utils');
|
||||
|
||||
class WebhookResponse {
|
||||
constructor() {
|
||||
|
||||
this.payload = [];
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.payload.length;
|
||||
}
|
||||
|
||||
set length(len) {
|
||||
this.payload.length = len;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
addVerb(verb, payload) {
|
||||
validate(verb, payload);
|
||||
this.payload.push({verb, ...payload});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [verb] of specs) {
|
||||
WebhookResponse.prototype[verb] = function(payload) {
|
||||
return WebhookResponse.prototype.addVerb.call(this, verb, payload);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = WebhookResponse;
|
||||
14
lib/rest/jambonz.js
Normal file
14
lib/rest/jambonz.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const assert = require('assert');
|
||||
class Jambonz {
|
||||
constructor(accountSid, apiKey, opts) {
|
||||
assert.ok(typeof accountSid === 'string', 'accountSid required');
|
||||
assert.ok(typeof apiKey === 'string', 'apiKey required');
|
||||
opts = opts || {};
|
||||
|
||||
this.endpoint = opts.endpoint;
|
||||
|
||||
// TODO: test credentials, throw exception on failure
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Jambonz;
|
||||
2342
package-lock.json
generated
Normal file
2342
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@jambonz/node-client",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"test": "NODE_ENV=test node test/ | ./node_modules/.bin/tap-spec",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
"author": "Dave Horton",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:jambonz/jambonz-node.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bent": "^7.3.12",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"nyc": "^15.1.0",
|
||||
"pino": "^6.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"blue-tape": "^1.0.0",
|
||||
"clear-module": "^4.1.1",
|
||||
"tap-spec": "^5.0.0"
|
||||
}
|
||||
}
|
||||
16
test/data/bad/bad-enum.json
Normal file
16
test/data/bad/bad-enum.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"dial": {
|
||||
"actionHook": "http://example.com",
|
||||
"callerId": "+1312888899",
|
||||
"target": [
|
||||
{
|
||||
"type": "foo",
|
||||
"number": "sip:1617333456@sip.trunk1.com",
|
||||
"auth": {
|
||||
"user": "foo",
|
||||
"password": "bar"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
4
test/data/bad/bad-payload.json
Normal file
4
test/data/bad/bad-payload.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"key1": "value",
|
||||
"key2": "value"
|
||||
}
|
||||
1
test/data/bad/bad-payload2.json
Normal file
1
test/data/bad/bad-payload2.json
Normal file
@@ -0,0 +1 @@
|
||||
[1, 2]
|
||||
5
test/data/bad/invalid-type.json
Normal file
5
test/data/bad/invalid-type.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"sip:decline": {
|
||||
"status": "hello"
|
||||
}
|
||||
}
|
||||
15
test/data/bad/missing-required-property.json
Normal file
15
test/data/bad/missing-required-property.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"dial": {
|
||||
"actionHook": "http://example.com",
|
||||
"callerId": "+1312888899",
|
||||
"target": [
|
||||
{
|
||||
"type": "sip",
|
||||
"number": "sip:1617333456@sip.trunk1.com",
|
||||
"auth": {
|
||||
"password": "bar"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
3
test/data/bad/unknown-instruction.json
Normal file
3
test/data/bad/unknown-instruction.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
6
test/data/bad/unknown-property.json
Normal file
6
test/data/bad/unknown-property.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sip:decline": {
|
||||
"status": 480,
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
31
test/data/good/alternate-syntax.json
Normal file
31
test/data/good/alternate-syntax.json
Normal file
@@ -0,0 +1,31 @@
|
||||
[
|
||||
{
|
||||
"verb": "gather",
|
||||
"actionHook": "https://00dd977a.ngrok.io/gather",
|
||||
"input": ["speech"],
|
||||
"timeout": 12,
|
||||
"recognizer": {
|
||||
"vendor": "google",
|
||||
"language": "en-US",
|
||||
"hints": ["sales", "support", "engineering", "human resources", "HR", "operator", "agent"]
|
||||
},
|
||||
"say": {
|
||||
"text": "Please say the name of the department that you would like to speak with. To speak to an operator, just say operator.",
|
||||
"synthesizer": {
|
||||
"vendor": "google",
|
||||
"language": "en-US"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"verb": "say",
|
||||
"text": "I'm sorry, I did not hear a response. Goodbye.",
|
||||
"synthesizer": {
|
||||
"vendor": "google",
|
||||
"language": "en-US"
|
||||
}
|
||||
},
|
||||
{
|
||||
"verb": "hangup"
|
||||
}
|
||||
]
|
||||
21
test/data/good/dial-listen.json
Normal file
21
test/data/good/dial-listen.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"dial": {
|
||||
"actionHook": "http://example.com",
|
||||
"callerId": "+1312888899",
|
||||
"target": [
|
||||
{
|
||||
"type": "phone",
|
||||
"number": "+15083084809"
|
||||
}
|
||||
],
|
||||
"listen": {
|
||||
"url": "wss://myrecorder.example.com:4433",
|
||||
"mixType" : "stereo",
|
||||
"sampleRate": 8000,
|
||||
"passDtmf": true,
|
||||
"metadata": {
|
||||
"clientId": "12udih"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
test/data/good/dial-phone.json
Normal file
12
test/data/good/dial-phone.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"dial": {
|
||||
"actionHook": "http://example.com",
|
||||
"callerId": "+1312888899",
|
||||
"target": [
|
||||
{
|
||||
"type": "phone",
|
||||
"number": "+15083084809"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
16
test/data/good/dial-sip.json
Normal file
16
test/data/good/dial-sip.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"dial": {
|
||||
"actionHook": "http://example.com",
|
||||
"callerId": "+1312888899",
|
||||
"target": [
|
||||
{
|
||||
"type": "sip",
|
||||
"number": "sip:1617333456@sip.trunk1.com",
|
||||
"auth": {
|
||||
"username": "foo",
|
||||
"password": "bar"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
21
test/data/good/dial-transcribe.json
Normal file
21
test/data/good/dial-transcribe.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"dial": {
|
||||
"actionHook": "http://example.com",
|
||||
"callerId": "+1312888899",
|
||||
"target": [
|
||||
{
|
||||
"type": "phone",
|
||||
"number": "+15083084809"
|
||||
}
|
||||
],
|
||||
"transcribe": {
|
||||
"transcriptionHook": "/transcribe",
|
||||
"recognizer": {
|
||||
"vendor": "google",
|
||||
"language" : "en-US",
|
||||
"dualChannel" : true,
|
||||
"interim": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
test/data/good/dial-user.json
Normal file
12
test/data/good/dial-user.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"dial": {
|
||||
"actionHook": "http://example.com",
|
||||
"callerId": "+1312888899",
|
||||
"target": [
|
||||
{
|
||||
"type": "user",
|
||||
"name": "spike@sip.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
5
test/data/good/pause.json
Normal file
5
test/data/good/pause.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"pause": {
|
||||
"length": 3
|
||||
}
|
||||
}
|
||||
9
test/data/good/say-text-array.json
Normal file
9
test/data/good/say-text-array.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"say": {
|
||||
"text": ["hi there", "John"],
|
||||
"synthesizer": {
|
||||
"vendor": "google",
|
||||
"language": "en-US"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
test/data/good/say.json
Normal file
9
test/data/good/say.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"say": {
|
||||
"text": "hi there",
|
||||
"synthesizer": {
|
||||
"vendor": "google",
|
||||
"language": "en-US"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
test/data/good/sip-decline.json
Normal file
9
test/data/good/sip-decline.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"sip:decline": {
|
||||
"status": 480,
|
||||
"reason": "Gone Fishin",
|
||||
"headers": {
|
||||
"Retry-After": 300
|
||||
}
|
||||
}
|
||||
}
|
||||
54
test/index.js
Normal file
54
test/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const test = require('blue-tape');
|
||||
const assert = require('assert');
|
||||
|
||||
const fetchData = (json) => {
|
||||
const keys = Object.keys(json);
|
||||
return json[keys[0]];
|
||||
}
|
||||
|
||||
test('unit tests', (t) => {
|
||||
const WebhookResponse = require('..').WebhookResponse;
|
||||
|
||||
let app = new WebhookResponse();
|
||||
app.sip_decline(fetchData(require('./data/good/sip-decline')));
|
||||
t.pass('sip_decline: passes');
|
||||
app.say(fetchData(require('./data/good/say')));
|
||||
t.pass('say: passes');
|
||||
app.say(fetchData(require('./data/good/say-text-array')));
|
||||
t.pass('say: passes with array of text');
|
||||
app.pause(fetchData(require('./data/good/pause')));
|
||||
t.pass('pause: passes with array of text');
|
||||
app.dial(fetchData(require('./data/good/dial-sip')));
|
||||
t.pass('dial: passes with target sip');
|
||||
app.dial(fetchData(require('./data/good/dial-phone')));
|
||||
t.pass('dial: passes with target phone');
|
||||
app.dial(fetchData(require('./data/good/dial-user')));
|
||||
t.pass('dial: passes with target user');
|
||||
app.dial(fetchData(require('./data/good/dial-listen')));
|
||||
t.pass('dial: passes with embedded listen');
|
||||
|
||||
|
||||
//let payload = app.toJSON();
|
||||
//console.log(payload);
|
||||
//let task = makeTask(logger, require('./data/good/sip-decline'));
|
||||
//t.ok(task.name === 'sip:decline', 'parsed sip:decline');
|
||||
|
||||
//t.throws(errInvalidInstruction, /malformed jambonz application payload/, 'throws error for invalid instruction');
|
||||
//t.throws(errUnknownProperty, /unknown property/, 'throws error for invalid instruction');
|
||||
//t.throws(errMissingProperty, /missing value/, 'throws error for missing required property');
|
||||
//t.throws(errInvalidType, /invalid data type/, 'throws error for invalid data type');
|
||||
//t.throws(errBadEnum, /must be one of/, 'throws error for invalid enum');
|
||||
//t.throws(errBadPayload, /malformed jambonz application payload/, 'throws error for invalid payload with multiple keys');
|
||||
//t.throws(errBadPayload2, /malformed jambonz application payload/, 'throws error for invalid payload that is not an object');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
const errInvalidInstruction = () => makeTask(logger, require('./data/bad/unknown-instruction'));
|
||||
const errUnknownProperty = () => makeTask(logger, require('./data/bad/unknown-property'));
|
||||
const errMissingProperty = () => makeTask(logger, require('./data/bad/missing-required-property'));
|
||||
const errInvalidType = () => makeTask(logger, require('./data/bad/invalid-type'));
|
||||
const errBadEnum = () => makeTask(logger, require('./data/bad/bad-enum'));
|
||||
const errBadPayload = () => makeTask(logger, require('./data/bad/bad-payload'));
|
||||
const errBadPayload2 = () => makeTask(logger, require('./data/bad/bad-payload2'));
|
||||
Reference in New Issue
Block a user