UNPKG

twilly

Version:

Node.js Twilio API abstraction layer for Express applications

2 lines 18.7 kB
module.exports=function(e){var t={};function n(o){if(t[o])return t[o].exports;var s=t[o]={i:o,l:!1,exports:{}};return e[o].call(s.exports,s,s.exports,n),s.l=!0,s.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)n.d(o,s,function(t){return e[t]}.bind(null,s));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=5)}([function(e,t){e.exports=require("express")},function(e,t){e.exports=require("crypto")},function(e,t){e.exports=require("twilio/lib/twiml/MessagingResponse")},function(e,t){e.exports=require("twilio")},function(e,t){e.exports=require("cookie-parser")},function(e,t,n){"use strict";n.r(t);var o=n(0),s=n(1);const i=Symbol("name"),r=Symbol("actions"),a=Symbol("actionNames"),c=Symbol("selectActionResolver"),l=Symbol("selectName"),u=Symbol("setName");class d{constructor(){this[a]=new Set,this[r]=[]}static validString(e){return"string"==typeof e&&Boolean(e.length)}static validFlowAction(e){return Boolean(e)&&e.hasOwnProperty("name")&&e.hasOwnProperty("resolve")}get length(){return this[r].length}get name(){return this[i]}addAction(e,t){if(!d.validString(e))throw new TypeError("Flow addAction expects a non-empty string as the first argument");if(this[a].has(e))throw new TypeError(`Every Flow's action names must be unique. Unexpected duplicate name: ${e}`);if("function"!=typeof t)throw new TypeError("Flow addAction expects a function as the second argument");return this[a].add(e),this[r].push({name:e,resolve:t}),this}addActions(...e){if(0===(e=e.reduce((e,t)=>(Array.isArray(t)?e=[...e,...t]:e.push(t),e),[])).length)throw new TypeError("Flow addActions must add at least one action to the flow");return e.map(e=>{if(!e||!e.hasOwnProperty("name")||"function"==typeof e||Array.isArray(e)||!d.validString(e.name))throw new TypeError("Flow addActions expects an array of objects with a name property set to a non-empty string");if(!e.hasOwnProperty("resolve")||"function"!=typeof e.resolve)throw new TypeError("Flow addActions expects an array of objects with a resolve property set to a function");this.addAction(e.name,e.resolve)}),this}[c](e){const t=this[r][e];return d.validFlowAction(t)?t.resolve:null}[l](e){const t=this[r][e];return d.validFlowAction(t)?t.name:null}[u](e){if("string"!=typeof e||!e.length)throw new TypeError("Flow constructor expects a non-empty string as the first argument");this[i]=e}}const h=Symbol("getContext"),y=Symbol("name"),m=Symbol("getContext"),f=Symbol("messageSid"),w=Symbol("setMessageSid"),p=Symbol("setMessageSids"),g=Symbol("setName");class b{addTypeToContext(e){const t=Object.assign({createdAt:new Date,type:this.constructor.name,actionName:this[y]},e);return this[f]&&(t.messageSid=this[f]),t}get name(){return this[y]}get sid(){return this[f]}[m](){return this.addTypeToContext(this[h]())}[w](e){this[f]=e}[p](e){this[f]=e}[g](e){this[y]=e}}const S=Symbol("body");class x extends b{constructor(e){if("string"!=typeof e||!e.length)throw new TypeError("Reply constructor expects a non-empty string as the first argument");super(),this[S]=e,this[h]=()=>({body:e})}get body(){return this[S]}}function v(e,t){const n=Object(s.createHmac)("sha256",e);return n.update(t),n.digest("hex")}function A(){return Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15)}class E extends b{constructor(e){super(),this.messageBody=e,this[h]=this.getExitContext.bind(this)}getExitContext(){return{messageBody:this.messageBody}}}const F=Symbol("to"),C=Symbol("body");class T extends b{constructor(e,t){if(!("string"==typeof e&&e.length||Array.isArray(e)&&e.every(e=>"string"==typeof e&&Boolean(e.length))))throw new TypeError("Message constructor expects a non-empty string or an array of non-empty strings as the first argument");if("string"!=typeof t||!t.length)throw new TypeError("Message constructor expects a non-empty string as the second argument");super(),this[F]=Array.isArray(e)?e:[e],this[C]=t,this[h]=()=>({to:this.to,body:t})}get to(){return this[F]}get body(){return this[C]}getContext(){return{body:this[C],to:this.to}}}var k=function(e,t,n,o){return new(n||(n=Promise))(function(s,i){function r(e){try{c(o.next(e))}catch(e){i(e)}}function a(e){try{c(o.throw(e))}catch(e){i(e)}}function c(e){e.done?s(e.value):new n(function(t){t(e.value)}).then(r,a)}c((o=o.apply(e,t||[])).next())})};const O=10,R=Symbol("mutlipleChoice"),M=Symbol("text"),j={[R]:"multipleChoice",[M]:"text"},I={choices:[],continueOnFailure:!1,failedToAnswerResponse:"Sorry, I do not understand your answer.",invalidAnswerResponse:"Hmm. I didn't quite understand your answer. Please try again.",maxRetries:1,type:M,validateAnswer:()=>!0},N=Symbol("answer"),q=Symbol("answerValidator"),K=Symbol("body"),B=Symbol("choices"),P=Symbol("failedToAnswerResponse"),Q=Symbol("invalidAnswerResponse"),$=Symbol("isAnswered"),_=Symbol("isFailed"),U=Symbol("maxRetries"),D=Symbol("shouldSendInvalidResponse"),H=Symbol("type"),V=Symbol("evaluate"),z=Symbol("setIsAnswered"),G=Symbol("setIsFailed"),J=Symbol("shouldContinueOnFailure"),L=Symbol("setShouldSendInvalidRes");class W extends b{constructor(e,{choices:t=I.choices,continueOnFailure:n=I.continueOnFailure,failedToAnswerResponse:o=I.failedToAnswerResponse,invalidAnswerResponse:s=I.invalidAnswerResponse,maxRetries:i=I.maxRetries,type:r=I.type,validateAnswer:a=I.validateAnswer}=I){if(!W.validString(e))throw new TypeError("The first argument of the Question constructor must be a non-empty string");if(r===M&&t!==I.choices)throw new TypeError("A text Question cannot have a 'choices' option");if(r===R&&(!t||!Array.isArray(t)||t.length<2||t.some(e=>"function"!=typeof e)))throw new TypeError("Multiple choice Questions must include a 'choices' option, an array of at least 2 functions of a string which return a boolen");if(r===R&&a!==I.validateAnswer)throw new TypeError("Multiple choice Questions cannot have a validateAnswer option");if(!W.validString(o))throw new TypeError("Question failedToAnswerResponse option must be a non-empty string");if(!W.validString(s))throw new TypeError("Question invalidAnswerResponse option must be a non-empty string");if(isNaN(i)||(e=>e(i)<0||e(i)>O)(Math.round))throw new TypeError(`Question maxRetries option must be a number from 0 to ${O}.`);if(r!==M&&r!==R&&(r=M),"function"!=typeof a)throw new TypeError("Question validateAnswer option must be a function");super(),this[N]=null,this[q]=a,this[K]=e,this[B]=t,this[P]=o,this[Q]=s,this[$]=!1,this[U]=Number(i),this[_]=!1,this[J]=Boolean(n),this[D]=!1,this[H]=r,this[h]=this.getQuestionContext.bind(this)}static validString(e){return"string"==typeof e&&Boolean(e.length)}getQuestionContext(){return{answer:this.answer,body:this.body,questionType:j[this.type],wasAnswered:this.isAnswered,wasFailed:this.isFailed}}handleInvalidAnswer(e){e.question.attempts.length>=this.maxRetries?this[_]=!0:this[D]=!0}setAnswer(e){this[$]=!0,this[N]=e}get answer(){return this[N]}get body(){return this[K]}get choices(){return this[B]}get failedAnswerResponse(){return this[P]}get invalidAnswerResponse(){return this[Q]}get isAnswered(){return this[$]}get isComplete(){return this.isAnswered||this.isFailed}get isFailed(){return this[_]}get maxRetries(){return this[U]}get type(){return this[H]}get shouldSendInvalidRes(){return this[D]}get validateAnswer(){return this[q]}[V](e,t){return k(this,void 0,void 0,function*(){if(t.question.isAnswering)if(this.type===W.Types.Text&&(yield this.validateAnswer(e.body.Body)))this.setAnswer(e.body.Body);else{if(this.type===W.Types.MultipleChoice){const t=yield Promise.all(this.choices.map(t=>t(e.body.Body))),n=t.map((e,t)=>t).filter(e=>t[e]);if(1===n.length)return void this.setAnswer(n[0])}this.handleInvalidAnswer(t)}})}[z](){this[$]=!0}[G](){this[_]=!0}[L](){this[D]=!0}}W.Types={MultipleChoice:R,Text:M};const X=Symbol("flowName");class Y extends b{constructor(e){if("string"!=typeof e||!e.length)throw new TypeError("Trigger constructor expects a non-empty string as its constructor argument");super(),this[X]=e,this[h]=()=>({triggerFlowName:e})}get flowName(){return this[X]}}const Z=Symbol("validateInput");class ee{constructor(e){this.schema=e,ee[Z](e)}static[Z](e){if("object"!=typeof e||null===e||Array.isArray(e))throw new TypeError("The argument of the flow schema constructor must be an object");Object.keys(e).map(t=>{const n=e[t];if(!(n instanceof d||n instanceof ee))throw new TypeError("Each key of a FlowSchema must be a Flow or another FlowSchema")})}}const te="__ROOT__";var ne=function(e,t,n,o){return new(n||(n=Promise))(function(s,i){function r(e){try{c(o.next(e))}catch(e){i(e)}}function a(e){try{c(o.throw(e))}catch(e){i(e)}}function c(e){e.done?s(e.value):new n(function(t){t(e.value)}).then(r,a)}c((o=o.apply(e,t||[])).next())})};function oe(...e){return(t=le(function({body:e=we.body,cookieKey:t=we.cookieKey,from:n=we.from}=we){return{cookies:{[t]:{}},body:{Body:e,From:n}}}()))=>(function(...e){return e.reduce((e,t)=>(...n)=>t(e(...n)))})(...e)(t)}const se=/(\b)(exit)(\b)/i,ie=e=>se.test(e),re={onInteractionEnd:()=>null,testForExit:ie};class ae{constructor(e,t,{onInteractionEnd:n=re.onInteractionEnd,testForExit:o=re.testForExit}=re){if(!(e instanceof d))throw new TypeError("root parameter must be an instance of Flow");if(0===e.length)throw new TypeError("All Flows must perform at least one action. Check the root Flow");if(t&&!(t instanceof ee))throw new TypeError("schema parameter must be an instance of FlowSchema");if("function"!=typeof o)throw new TypeError("testForExit parameter must be a function");if(this.root=e,t&&(this.schema=function e(t,n,o=new Map,s=new Set,i=""){s.has(t)||(t[u](te),o.set(te,t),s.add(t));const r=Object.keys(n.schema);if(0===r.length)throw new TypeError("All FlowSchemas must contain at least one Flow object");return r.reduce((o,r)=>{const a=n.schema[r],c=i?`${i}.${r}`:r;if(a instanceof ee)return s.has(a)?o:(s.add(a),e(t,a,o,s,c));if(s.has(a))throw new TypeError(`All Flows must be unique. Unexpected duplicate name: ${a.name}`);if(s.add(a),a[u](c),o.has(a.name))return o;if(0===a.length)throw new TypeError(`All Flows must perform at least one action. Check flow: ${a.name}`);return o.set(a.name,a),o},o)}(e,t),1===this.schema.size))throw new TypeError("If you provide the schema parameter, it must include a flow distinct from the root Flow");this.onInteractionEnd=n,this.testForExit=o}getCurrentFlow(e){if(!e.flow||e.flow===this.root.name)return this.root;if(!this.schema||!this.schema.has(e.flow))throw new TypeError(`Received invalid flow name in SMS cookie: ${e.flow}`);return this.schema.get(e.flow)}resolveActionFromState(e,t,n){return ne(this,void 0,void 0,function*(){if(t.isComplete)return null;if(this.testForExit&&(yield this.testForExit(e.body.Body)))return new E(e.body.Body);const o=Number(t.flowKey),s=this.getCurrentFlow(t),i=s[c](o);if(!i)return null;const r=yield i(function e(t){const n={};return Object.keys(t).map(o=>{const s=t[o];null===s||s instanceof Date||"object"!=typeof s?n[o]=s:n[o]=e(s)}),n}(t.flowContext),n);return r instanceof W&&(yield r[V](e,t)),r instanceof b?(r[g](s[l](o)),r):null})}resolveNextStateFromAction(e,t,n){const o=this.getCurrentFlow(t);if(!(n instanceof b))return ce(t);if(n instanceof E)return oe(e=>me(e,o,n),ce)(t);switch(n.constructor){case W:return(()=>{const s=n;return t.question.isAnswering&&(t=function(e,t){return Object.assign({},e,{question:Object.assign({},e.question,{attempts:[...e.question.attempts,t]})})}(t,e.body.Body)),s.isAnswered?oe(e=>me(e,o,n),e=>ue(e,o))(t):s.isFailed?s[J]?oe(e=>me(e,o,n),e=>ue(e,o))(t):oe(e=>me(e,o,n),ce)(t):t.question.isAnswering?me(t,o,n):oe(de,e=>me(e,o,n))(t)})();case Y:return(()=>{const e=n;if(o===this.root&&!this.schema)throw new Error("Cannot use Trigger action without a defined Flow schema");if(e.flowName!==this.root.name&&!this.schema.has(e.flowName))throw new Error("Trigger constructors expect a name of an existing Flow");return oe(e=>me(e,o,n),t=>(function(e,t){return Object.assign({},e,{flow:t.flowName,flowKey:0,flowContext:{}})})(t,e))(t)})();default:return oe(e=>me(e,o,n),e=>ue(e,o))(t)}}}function ce(e){return Object.assign({},e,{isComplete:!0})}function le(e){return{createdAt:new Date,flow:null,flowContext:{},flowKey:0,from:e.body.From,interactionComplete:!1,interactionContext:[],interactionId:A(),isComplete:!1,question:{attempts:[],isAnswering:!1}}}function ue(e,t){const n=Object.assign({},e);return n.flowKey=Number(e.flowKey)+1,n.flowKey===t.length?ce(e):n}function de(e){return Object.assign({},e,{question:{attempts:[],isAnswering:!0}})}function he(e,t){return[...(e.flowContext[t.name]||{}).messageSid||[],...t.sid||[]]}function ye(e,t,n){return n instanceof W?Object.assign({},n[m](),{messageSid:he(e,n)}):n[m]()}function me(e,t,n){if(!e)return null;if(!t[a].has(n.name))throw new Error(`Flow ${t.name} does not have an action named ${n.name}`);return e.flow||(e.flow=t.name),Object.assign({},e,{flowContext:Object.assign({},e.flowContext,{[n.name]:ye(e,0,n)}),interactionContext:[...e.interactionContext,Object.assign({},ye(e,0,n),{flowName:t.name})]})}const fe={"Content-Type":"text/xml"},we={body:A(),cookieKey:A(),from:A()};const pe=n(2),ge=200,be='<?xml version="1.0" encoding="UTF-8"?><Response />',Se=Symbol("xml");class xe{constructor(e){this.res=e,this[Se]=be}get xml(){return this[Se]}setMessage(e){const t=new pe;return t.message(e),this[Se]=t.toString(),this}send(e=ge){this.res.writeHead(e,fe),this.res.end(this.xml)}}var ve=function(e,t,n,o){return new(n||(n=Promise))(function(s,i){function r(e){try{c(o.next(e))}catch(e){i(e)}}function a(e){try{c(o.throw(e))}catch(e){i(e)}}function c(e){e.done?s(e.value):new n(function(t){t(e.value)}).then(r,a)}c((o=o.apply(e,t||[])).next())})};const Ae=n(3);class Ee{static isValidString(e){return"string"==typeof e&&Boolean(e.length)}static typeCheckArguments(e){Object.keys(e).map(t=>{if(!Ee.isValidString(e[t]))throw new TypeError(`${t} twilly option must be a non-empty string`)})}constructor({accountSid:e,authToken:t,cookieKey:n,messagingServiceSid:o,sendOnExit:s}){Ee.typeCheckArguments({accountSid:e,authToken:t,cookieKey:n,messagingServiceSid:o,sendOnExit:s}),this.cookieKey=n,this.messagingServiceSid=o,this.sendOnExit=s,this.twilio=Ae(e,t)}sendSmsMessage(e,t){return ve(this,void 0,void 0,function*(){if(Array.isArray(e)){return(yield Promise.all(e.map(e=>this.twilio.messages.create({body:t,messagingServiceSid:this.messagingServiceSid,to:e})))).map(e=>e.sid)}const{sid:n}=yield this.twilio.messages.create({to:e,body:t,messagingServiceSid:this.messagingServiceSid});return n})}clearSmsCookie(e){e.clearCookie(this.cookieKey)}getSmsCookieFromRequest(e){return e.cookies[this.cookieKey]||le(e)}handleAction(e,t){return ve(this,void 0,void 0,function*(){let n,o;switch(t.constructor){case E:return n=yield this.sendSmsMessage(e.body.From,this.sendOnExit),void t[w](n);case T:return o=yield this.sendSmsMessage(t.to,t.body),void t[p](o);case W:return void(yield(()=>ve(this,void 0,void 0,function*(){const n=t;if(o=[],!n.isAnswered){if(n.isFailed)return o.push(yield this.sendSmsMessage(e.body.From,n.failedAnswerResponse)),void t[p](o);n.shouldSendInvalidRes&&o.push(yield this.sendSmsMessage(e.body.From,n.invalidAnswerResponse)),o.push(yield this.sendSmsMessage(e.body.From,n.body)),t[p](o)}}))());case x:return n=yield this.sendSmsMessage(e.body.From,t.body),void t[w](n);default:return}})}sendEmptyResponse(e){return new xe(e).send()}sendMessageNotification(e){return ve(this,void 0,void 0,function*(){yield this.sendSmsMessage(e.to,e.body)})}sendSmsResponse(e,t){return new xe(e).setMessage(t).send()}setSmsCookie(e,t){e.cookie(this.cookieKey,t)}}n.d(t,"twilly",function(){return Re}),n.d(t,"Flow",function(){return d}),n.d(t,"FlowSchema",function(){return ee}),n.d(t,"Message",function(){return T}),n.d(t,"Question",function(){return W}),n.d(t,"Reply",function(){return x}),n.d(t,"Trigger",function(){return Y});var Fe=function(e,t,n,o){return new(n||(n=Promise))(function(s,i){function r(e){try{c(o.next(e))}catch(e){i(e)}}function a(e){try{c(o.throw(e))}catch(e){i(e)}}function c(e){e.done?s(e.value):new n(function(t){t(e.value)}).then(r,a)}c((o=o.apply(e,t||[])).next())})};const Ce=n(4),Te=1e3,ke=/^\/?$/i;const Oe={cookieKey:null,cookieSecret:null,getUserContext:()=>null,onCatchError:()=>null,onInteractionEnd:null,onMessage:null,schema:null,sendOnExit:"Goodbye.",testForExit:ie};function Re({accountSid:e,authToken:t,messagingServiceSid:n,root:s,schema:i=Oe.schema,cookieKey:r=Oe.cookieKey,cookieSecret:a=Oe.cookieSecret,getUserContext:c=Oe.getUserContext,onCatchError:l=Oe.onCatchError,onInteractionEnd:u=Oe.onInteractionEnd,onMessage:d=Oe.onMessage,sendOnExit:h=Oe.sendOnExit,testForExit:y=Oe.testForExit}){r||(r=v(e,e).slice(0,16)),a||(a=v(e,t));const m=new ae(s,i,{onInteractionEnd:u,testForExit:y}),f=new Ee({accountSid:e,authToken:t,cookieKey:r,messagingServiceSid:n,sendOnExit:h}),w=Object(o.Router)();return w.use(Ce(a)),w.post(ke,function(e,t,n,o,s,i,r){return Fe(this,void 0,void 0,function*(){let a=null,c=null;try{a=s.getSmsCookieFromRequest(i),c=yield e(i.body.From);let l=yield o.resolveActionFromState(i,a,c);if(n)try{const e=yield n(a.interactionContext,c,i.body.Body);e instanceof T&&(yield s.sendMessageNotification(e))}catch(e){t(a.interactionContext,c,e)}for(;null!==l&&(yield s.handleAction(i,l),yield new Promise(e=>setTimeout(e,Te)),!((a=yield o.resolveNextStateFromAction(i,a,l)).isComplete||l instanceof W&&!l.isComplete))&&null!==(l=yield o.resolveActionFromState(i,a,c)););if(a.isComplete){if(null!==o.onInteractionEnd)try{const e=yield o.onInteractionEnd(a.interactionContext,c);e instanceof T&&(yield s.sendMessageNotification(e))}catch(e){t(a.interactionContext,c,e)}s.clearSmsCookie(r)}else s.setSmsCookie(r,a);s.sendEmptyResponse(r)}catch(e){const n=yield t(a.interactionContext,c,e);try{n instanceof x&&(yield s.handleAction(i,n),a=me(a,o.getCurrentFlow(a),n)),null!==o.onInteractionEnd&&(yield o.onInteractionEnd(a.interactionContext,c))}catch(e){yield t(a.interactionContext,c,e)}s.clearSmsCookie(r),s.sendEmptyResponse(r)}})}.bind(null,c,l,d,m,f)),w}}]); //# sourceMappingURL=index.js.map