UNPKG

serverless-aws-lambda

Version:

AWS Application Load Balancer and API Gateway - Lambda dev tool for Serverless. Allows Express synthax in handlers. Supports packaging, local invoking and offline ALB, APG, S3, SNS, SQS, DynamoDB Stream server mocking.

39 lines (38 loc) 10.7 kB
import{randomUUID as S}from"crypto";import{randomUUID as E}from"crypto";var P=(t,e,s)=>{switch(t){case"=":return e==s;case">":return e>s;case"<":return e<s;case">=":return e>=s;case"<=":return e>=s;default:return!1}},T={exists:(t,e,s)=>{if(s===!0)return e in t;if(s===!1)return!(e in t);throw new Error("stream filter 'exists' value must be 'true' or 'false'")},prefix:(t,e,s)=>{if(typeof s!="string")throw new Error("SQS filter 'prefix' value must be typeof 'string'");let n=typeof t[e]=="string"?t[e]:void 0;return n?n.startsWith(s):!1},numeric:(t,e,s)=>{if(!Array.isArray(s)||![2,4].includes(s.length))throw new Error("SQS filter 'numeric' value must be an array with 2 or 4 items");if(!(e in t))return!1;let n=[],[r,o]=s;if(n.push(P(r,t[e],o)),s.length==4){let[,,i,l]=s;n.push(P(i,t[e],l))}return n.every(i=>i===!0)},"anything-but":(t,e,s)=>{if(!Array.isArray(s)||!s.every(r=>typeof r=="string"))throw new Error("SQS filter 'anything-but' value must be an array of string");let n=typeof t[e]=="string"?t[e]:void 0;return n?!s.includes(n):!1}},k=(t,e,s)=>{let n=typeof s;if(n=="string"||n===null)return t[e]==s;if(n=="object"&&!Array.isArray(s)){let r=[];for(let[o,i]of Object.entries(s))o in T&&r.push(T[o](t,e,i));return r.every(o=>o===!0)}return!1},I=(t,e)=>{let s=[];for(let[n,r]of Object.entries(t)){let o=[];Array.isArray(r)?o=r.map(i=>k(e,n,i)):e[n]&&(o=[I(r,e[n])]),s.push(o.some(i=>i===!0))}return s.every(n=>n===!0)};var m=class t extends Error{Code;SenderFault=!0;constructor({Code:e,Message:s,SenderFault:n}){super(s),this.Code=e,typeof n=="boolean"&&(this.SenderFault=n)}toXml(e){return t.genericErrorResponse({RequestId:e,Code:this.Code,Message:this.message,SenderFault:this.SenderFault})}static genericErrorResponse({RequestId:e,Code:s,Message:n,SenderFault:r}){return`<?xml version="1.0"?> <ErrorResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> <Error> <Type>${r?"Sender":"Server"}</Type> <Code>${s??"UnknownOperation"}</Code> <Message>${n}</Message> </Error> <RequestId>${e}</RequestId> </ErrorResponse>`}};var O=new Set(["Binary","Number","String"]),x=t=>{let e={},s={},n={},r=new Set([]);for(let o of t){let[i,l,...a]=o.split("=");if(i.startsWith("MessageAttributes")){let[u,c,d,g,p]=i.split(".");if(g=="Name")s[l]={Type:"",Value:""},n[d]=l;else if(g=="Value")if(p=="DataType")s[n[d]].Type=l;else{let f=n[d];if(r.has(f))throw new m({Code:"ParameterValueInvalid",Message:`Message attribute name '${f}' has multiple values.`});let M=[l,...a].join("="),b=s[f].Type;if(M){if(b=="Binary"&&p=="StringValue")throw new m({Code:"ParameterValueInvalid",Message:`The message attribute '${f}' with type 'Binary' must use field 'Binary'.`});if((b=="String"||b=="Number")&&p=="BinaryValue")throw new m({Code:"ParameterValueInvalid",Message:`The message attribute '${f}' with type '${b}' must use field 'String'.`})}s[f].Value=M,r.add(f)}}else e[i]=[l,...a].join("=")}return Object.keys(s).length&&(B(s),e.MessageAttributes=s),e},U=t=>{for(let e of t)if(t.filter(s=>s==e).length>1)throw new m({Code:"BatchEntryIdsNotDistinct",Message:"Two or more batch entries in the request have the same Id."})},B=t=>{for(let[e,s]of Object.entries(t)){let{Type:n,Value:r}=s;if(!O.has(n))throw new m({Code:"ParameterValueInvalid",Message:`The message attribute '${e}' has an invalid message attribute type, the set of supported type prefixes is Binary, Number, and String.`});if((n=="String"||n=="Number")&&!r)throw new m({Code:"ParameterValueInvalid",Message:`The message attribute '${e}' must contain non-empty message attribute value for message attribute type '${n}'.`});if(n=="Number"&&isNaN(r))throw new m({Code:"ParameterValueInvalid",Message:`Could not cast message attribute '${e}' value to number.`})}},N=t=>{let e={},s=[],n=E(),r=[],o=[],i=new Map,l=new Set([]);for(let a of t){let[u,c]=a.split("=");if(u.startsWith("PublishBatchRequestEntries")){let[,,d,g,p,f,M,b]=u.split("."),y=i.get(d);if(y){if(g=="Message"||g=="MessageStructure"||g=="Subject"||g=="Id")y.value[g]=c;else if(g=="MessageAttributes"){let h=y.attributes[f];if(h)if(b=="DataType")y.value.MessageAttributes[h].Type=c;else{let C=`${d}-${h}`;if(l.has(C))throw new m({Code:"ParameterValueInvalid",Message:`Message attribute name '${h}' has multiple values.`});let[,...V]=a.split("="),$=V.join("="),v=y.value.MessageAttributes[h].Type;if($){if(v=="Binary"&&b=="StringValue")throw new m({Code:"ParameterValueInvalid",Message:`The message attribute '${h}' with type 'Binary' must use field 'Binary'.`});if((v=="String"||v=="Number")&&b=="BinaryValue")throw new m({Code:"ParameterValueInvalid",Message:`The message attribute '${h}' with type '${v}' must use field 'String'.`})}y.value.MessageAttributes[h].Value=$,l.add(C)}else y.attributes[f]=c,y.value.MessageAttributes?y.value.MessageAttributes[c]={}:y.value.MessageAttributes={[c]:{}}}}else{let h={attributes:{},value:{}};g=="Message"||g=="MessageStructure"||g=="Subject"||g=="Id"?h.value[g]=c:g=="MessageAttributes"&&(h.attributes[f]=c,h.value.MessageAttributes={[c]:{}}),i.set(d,h)}}else e[u]=c}for(let a of i.values()){let u={Type:"Notification",MessageId:E(),TopicArn:e.TopicArn,Subject:a.value.Subject??null,Timestamp:new Date().toISOString(),SignatureVersion:"1",Signature:"fake",SigningCertUrl:"fake",UnsubscribeUrl:"fake",MessageStructure:a.value.MessageStructure};"Message"in a.value&&(u.Message=a.value.Message),a.value.MessageAttributes&&(u.MessageAttributes=a.value.MessageAttributes);let c={EventSource:"aws:sns",EventVersion:"1.0",EventSubscriptionArn:`${e.TopicArn}:${n}`,Sns:u};try{j(u),delete u.MessageStructure,r.push(c)}catch(d){d instanceof m&&o.push({Id:a.value.Id,Code:d.Code,SenderFault:d.SenderFault,Message:d.message})}finally{s.push(a.value.Id)}}return U(s),r.forEach(a=>{a.Sns.MessageAttributes&&B(a.Sns.MessageAttributes)}),{Records:r,Ids:s,Failed:o}},j=t=>{if(!("Message"in t))throw new m({Code:"ValidationError",Message:"1 validation error detected: Value null at 'message' failed to satisfy constraint: Member must not be null"});if(t.Message=="")throw new m({Code:"InvalidParameter",Message:"Invalid parameter: Empty message"});if(t.MessageStructure=="json"){try{t.Message=JSON.parse(t.Message)}catch{throw new m({Code:"InvalidParameter",Message:"Invalid parameter: Message Structure - Failed to parse JSON"})}let e=typeof t.Message.default;if(e=="number"||e=="boolean"||t.Message.default===null)t.Message=String(t.Message.default);else if(e=="string")t.Message=t.Message.default;else throw new m({Code:"InvalidParameter",Message:"Invalid parameter: Message Structure - No default entry in JSON message body"})}},A=(t,e)=>(j(t),{Records:[{EventSource:"aws:sns",EventVersion:"1.0",EventSubscriptionArn:t.TopicArn,Sns:{Type:"Notification",MessageId:e,TopicArn:t.TopicArn,Subject:t.Subject??null,Message:t.Message,Timestamp:new Date().toISOString(),SignatureVersion:"1",Signature:"fake",SigningCertUrl:"fake",UnsubscribeUrl:"fake",MessageAttributes:t.MessageAttributes}}]}),w=(t,e)=>{let s=t.TopicArn.split(":"),n=s[s.length-1],r=[];return e.forEach(o=>{let i=o.sns.find(l=>{if(!l||l.name!==n)return!1;if(!l.filter)return!0;let a={};if(l.filterScope=="MessageAttributes"){if(!t.MessageAttributes)return!1;for(let[u,c]of Object.entries(t.MessageAttributes))a[u]=c.Value}else if(l.filterScope=="MessageBody"){if(t.MessageStructure!="json"||!t.Message)return!1;try{a=JSON.parse(t.Message)}catch{}}return I(l.filter,a)});i&&r.push({handler:o,event:i})}),r},R=(t,e)=>`<PublishResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> <PublishResult> <MessageId>${t}</MessageId> </PublishResult> <ResponseMetadata> <RequestId>${e}</RequestId> </ResponseMetadata> </PublishResponse>`,F=(t,e,s)=>{let n="<Successful/>",r="<Failed/>";return e.length&&(n=`<Successful> ${e.map(i=>`<member> <MessageId>${i.MessageId}</MessageId> <Id>${i.Id}</Id> </member>`).join(` `)} </Successful>`),s.length&&(r=`<Failed> ${s.map(i=>`<member> <Code>${i.Code}</Code> <SenderFault>${i.SenderFault}</SenderFault> <Id>${i.Id}</Id> <Message>${i.Message}</Message> </member>`).join(` `)} </Failed>`),`<PublishBatchResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> <PublishBatchResult> ${r} ${n} </PublishBatchResult> <ResponseMetadata> <RequestId>${t}</RequestId> </ResponseMetadata> </PublishBatchResponse>`};var D=()=>({name:"sns-plugin",offline:{request:[{method:"POST",filter:"/@sns/parsed",callback:async function(t,e){let s=Buffer.alloc(0),n=S(),r=t.headers["amz-sdk-invocation-id"]??S();t.on("data",u=>{s+=u});let o=await new Promise(u=>{t.on("end",async()=>{u(JSON.parse(s.toString()))})}),i=w(o,this.lambdas),l=[];if(i.length){let u=A(o,n);i.forEach(c=>{l.find(d=>d.handler.name==c.handler.name)||l.push(c)});for(let{handler:c,event:d}of l){let g=`SNS: ${d.name}`;d.displayName&&(g+=` | ${d.displayName}`),console.log(`\x1B[35m${g}\x1B[0m`);try{await c.invoke(u,{kind:"sns",event:d})}catch(p){console.log(p)}}}e.statusCode=200,e.setHeader("Content-Type","text/xml");let a=R(n,r);e.end(a)}},{method:"POST",filter:"/@sns",callback:async function(t,e){let s=Buffer.alloc(0),n=S(),r=t.headers["amz-sdk-invocation-id"]??S(),o=a=>{e.statusCode=400,a instanceof m?e.end(a.toXml(r)):e.end(m.genericErrorResponse({RequestId:r,Message:a.toString?.()??"Unknown error"}))};e.setHeader("Content-Type","text/xml"),e.setHeader("x-amzn-requestid",S()),t.on("data",a=>{s+=a});let i=await new Promise(a=>{t.on("end",async()=>{a(decodeURIComponent(s.toString())?.split("&"))})}),l=i.find(a=>a.startsWith("Action="))?.split("=")[1];if(l)if(l=="Publish")try{let a=x(i),u=w(a,this.lambdas),c=[],d=A(a,n);if(u.length){u.forEach(p=>{c.find(f=>f.handler.name==p.handler.name)||c.push(p)});for(let{handler:p,event:f}of c){let M=`SNS: ${f.name}`;f.displayName&&(M+=` | ${f.displayName}`),console.log(`\x1B[35m${M}\x1B[0m`);try{await p.invoke(d,{kind:"sns",event:f})}catch(b){console.log(b)}}}e.statusCode=200;let g=R(n,r);e.end(g)}catch(a){o(a)}else if(l=="PublishBatch")try{let a=N(i),u=[],c=a.Failed,d=[];a.Records.forEach((p,f)=>{let M=a.Ids[f];u.push({Id:M,MessageId:p.Sns.MessageId});let b=w(p.Sns,this.lambdas);b.length&&b.forEach(y=>{d.find(h=>h.handler.name==y.handler.name)||d.push(y)})}),e.statusCode=200;let g=F(r,u,c);e.end(g);for(let{handler:p,event:f}of d)try{await p.invoke({Records:a.Records},{kind:"sns",event:f})}catch(M){console.log(M)}}catch(a){o(a)}else e.statusCode=400,e.end(m.genericErrorResponse({RequestId:r,Message:`Action "${l}" is currently not supported`}))}}]}}),Z=D;export{Z as default,D as snsPlugin};