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
JavaScript
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};