exlo 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.mjs +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/defineConfig.d.ts +2 -0
- package/dist/defineConfig.mjs +1 -1
- package/dist/lib/runtime/rapidApi.d.ts +1 -1
- package/dist/lib/runtime/runners/custom/runner.d.ts +60 -0
- package/dist/lib/runtime/runners/llrt/runner.d.ts +12 -0
- package/dist/lib/server/daemon.d.ts +3 -1
- package/dist/main.d.ts +1 -1
- package/dist/plugins/sns/index.mjs +1 -1
- package/dist/plugins/sns/server.d.ts +4 -4
- package/dist/plugins/vitest/coverage.d.ts +1 -1
- package/dist/standalone.js +13 -13
- package/dist/standalone.mjs +13 -13
- package/dist/standalone_types.d.ts +11 -3
- package/package.json +2 -2
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFile as I}from"node:fs/promises";import p from"node:path";import{pathToFileURL as O}from"node:url";import{parseArgs as Y}from"node:util";var j="exlo",b="0.0.
|
|
2
|
+
import{readFile as I}from"node:fs/promises";import p from"node:path";import{pathToFileURL as O}from"node:url";import{parseArgs as Y}from"node:util";var j="exlo",b="0.0.25";var g=!1,R=e=>{g=e},A=()=>g,l=(e,t)=>{g&&console.log(`\x1B[${e}m${t}\x1B[0m`)},$=e=>console.log(`\x1B[31m${e}\x1B[0m`),C=e=>l("32",e),q=e=>l("33",e),L=e=>l("36",e),S=e=>l("90",e),F=e=>l("94",e),_=e=>l("95",e),D=e=>g?console.log(e):void 0,n={GREEN:C,YELLOW:q,CYAN:L,BR_BLUE:F,RED:$,GREY:S,PINK:_,setDebug:R,getDebug:A,info:D};var y={config:{type:"string",short:"c",default:"exlo.config.ts",description:"Path to exlo 'defineConfig' file."},debug:{type:"boolean",default:!1,description:"Enable debug mode. When enabled exlo will print usefull informations."},create:{type:"boolean",default:!1,description:"Create exlo 'defineConfig' file based on passed options."},runtime:{type:"string",short:"r",description:"Lambda default runtime (ex: nodejs22.x, python3.7, ruby2.7 etc.)."},timeout:{type:"string",short:"t",default:"3",description:"Lambda default timeout."},port:{type:"string",short:"p",default:"0",description:"Lambda server port."},definitions:{type:"string",short:"d",description:"Path to .json, .mjs, .cjs file with Lambda function definitions."},functions:{type:"string",short:"f",multiple:!0,description:"Glob pattern to automatically find and define Lambda handlers."},exclude:{type:"string",short:"X",multiple:!0,default:[".(test|spec)."],description:"RegExp string to exclude found enteries from --functions."},"handler-name":{type:"string",default:"handler",description:"Handler function name. To be used with --functions."},env:{type:"string",short:"e",multiple:!0,default:[],description:"Environment variables to be injected into Lambdas. All existing AWS_* are automatically injected.",example:"-e API_KEY=supersecret -e API_URL=https://website.com"},"optimize-build":{type:"boolean",default:!1,description:"externalize dependencies and other stuff"},"shim-require":{type:"boolean",default:!1,description:"shim 'require()', '__dirname' and '__filename' when bundeling Lambdas with ESM format"},help:{type:"boolean",short:"h",skipFullPrint:!0},version:{type:"boolean",short:"v",skipFullPrint:!0},"esbuild-external":{type:"string",multiple:!0,skip:!0},"esbuild-resolveExtensions":{type:"string",multiple:!0,skip:!0},"esbuild-mainFields":{type:"string",multiple:!0,skip:!0},"esbuild-conditions":{type:"string",multiple:!0,skip:!0},"esbuild-entryPoints":{type:"string",multiple:!0,skip:!0},"esbuild-inject":{type:"string",multiple:!0,skip:!0},"esbuild-nodePaths":{type:"string",multiple:!0,skip:!0}};function M(){n.setDebug(!0),n.GREY("Usage examples:"),console.log("exlo -c exlo.config.ts"),console.log(`exlo -p 3000 --debug --functions "src/lambdas/**/*.ts"
|
|
3
3
|
`),n.BR_BLUE("Options:");for(let[e,t]of Object.entries(y)){let i=e;if(t.skip)continue;if(t.short&&(i+=`, -${t.short}`),t.skipFullPrint){n.CYAN(` --${i}
|
|
4
4
|
`);continue}let s=` type: ${t.type}`;t.description&&(s+=`
|
|
5
5
|
description: ${t.description}`),"default"in t&&(s+=`
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import type { BuildOptions, BuildResult } from "esbuild";
|
|
3
|
+
import type { LlrtOptions } from "./lib/runtime/runners/llrt/runner";
|
|
3
4
|
import type { awslambda } from "./lib/runtime/runners/node/awslambda";
|
|
4
5
|
import type { HttpMethod } from "./lib/server/handlers";
|
|
5
6
|
export interface OfflineConfig {
|
|
@@ -23,6 +24,7 @@ export interface Config {
|
|
|
23
24
|
server?: OfflineConfig;
|
|
24
25
|
buildCallback?: (result: BuildResult, isRebuild: boolean) => Promise<void> | void;
|
|
25
26
|
onKill?: (() => Promise<void> | void)[];
|
|
27
|
+
llrt?: LlrtOptions;
|
|
26
28
|
}
|
|
27
29
|
export interface ServerConfig {
|
|
28
30
|
stage?: string;
|
package/dist/defineConfig.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { SQS } from "@aws-sdk/client-sqs";
|
|
|
6
6
|
import type { BuildResult, PluginBuild } from "esbuild";
|
|
7
7
|
import type { Config } from "./config";
|
|
8
8
|
import type { ILambdaMock } from "./lib/runtime/rapidApi";
|
|
9
|
+
import type { LlrtOptions } from "./lib/runtime/runners/llrt/runner";
|
|
9
10
|
import type { HttpMethod } from "./lib/server/handlers";
|
|
10
11
|
import type { log } from "./lib/utils/colorize";
|
|
11
12
|
import type { IAwsProvider, IFunctionUrlCors, ILambdaFunction } from "./standalone_types";
|
|
@@ -129,6 +130,7 @@ export interface Options {
|
|
|
129
130
|
[lambdaName: string]: ILambdaFunction;
|
|
130
131
|
};
|
|
131
132
|
defaults?: Partial<IAwsProviderDefaults>;
|
|
133
|
+
llrt?: LlrtOptions;
|
|
132
134
|
}
|
|
133
135
|
export type ConfigDefiner = (this: ClientConfigParams, internalResources: ClientConfigParams) => Promise<Omit<Config, "config" | "options">>;
|
|
134
136
|
export declare function defineConfig(options: Options): ConfigDefiner;
|
package/dist/defineConfig.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var h="0.0.
|
|
1
|
+
var h="0.0.25";function k(e){let c=new Set;e.plugins&&(e.plugins=e.plugins.filter((i,a)=>typeof i!="object"||!i||!("name"in i)?!1:((!i.name||!i.name.length||typeof i.name!="string")&&(i.name=`plugin-${a}`,console.warn(`No Plugin name provided at index ${a}`)),c.has(i.name)?i.name=i.name+a:c.add(i.name),!0)));async function f({stop:i,lambdas:a,region:p,esbuild:y,getServices:v,setService:d,addLambda:g,log:l,setEndpoint:b,getEndpoint:x,getAwsProviderDefaults:C,setAwsProviderDefaults:S}){let r={build:{esbuild:e?.build?.esbuild??{},shimRequire:e?.build?.shimRequire,optimizeBuild:e?.build?.optimizeBuild},server:{port:e.server?.port,setEndpointEnvVar:e.server?.setEndpointEnvVar},onKill:[],llrt:e.llrt};if(e.defaults&&typeof e.defaults=="object"&&!Array.isArray(e.defaults)&&S(e.defaults),e.functions&&typeof e.functions=="object"&&!Array.isArray(e.functions))for(let[m,s]of Object.entries(e.functions))g(m,s);e.services&&(e.services.sqs&&d("sqs",e.services.sqs),e.services.sns&&d("sns",e.services.sns),e.services.dynamodb&&d("dynamodb",e.services.dynamodb));let o={stop:i,lambdas:a,region:p,esbuild:y,options:e,config:r,getServices:v,setService:d,addLambda:g,log:l,setEndpoint:b,getEndpoint:x,getAwsProviderDefaults:C};if(e.plugins){r.server.onReady=async(s,n)=>{for(let t of e.plugins)if(t.server?.onReady)try{await t.server.onReady.call(o,s,n)}catch(u){l.RED(t.name),console.error(u)}},r.buildCallback=async(s,n)=>{for(let t of e.plugins)if(t.buildCallback)try{await t.buildCallback.call(o,s,n)}catch(u){l.RED(t.name),console.error(u),n||process.exit(1)}};let m=e.plugins.reduce((s,n)=>(n.server?.request?.length&&s.push(...n.server.request),s),[]);m?.length&&(r.server.request=m.map(s=>(s.callback=s.callback.bind(o),s)));for(let s of e.plugins)if(s.onKill&&r.onKill.push(s.onKill.bind(o)),s.onInit)try{await s.onInit.call(o)}catch(n){throw l.RED(s.name),n}}return r}return f.plugins=e.plugins??[],f}export{k as defineConfig,h as exloVersion};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
|
+
import type { FSWatcher } from "node:fs";
|
|
3
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
4
|
+
import type { Runner } from "../index";
|
|
5
|
+
export declare class CustomRunner implements Runner {
|
|
6
|
+
private cwd?;
|
|
7
|
+
private static collectBody;
|
|
8
|
+
invoke: Runner["invoke"];
|
|
9
|
+
mount: Runner["mount"];
|
|
10
|
+
unmount: Runner["unmount"];
|
|
11
|
+
name: string;
|
|
12
|
+
timeout: number;
|
|
13
|
+
memorySize: number;
|
|
14
|
+
environment: {
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
};
|
|
17
|
+
handlerName: string;
|
|
18
|
+
runtime: string;
|
|
19
|
+
bin?: string;
|
|
20
|
+
runner?: ChildProcess;
|
|
21
|
+
isMounted: boolean;
|
|
22
|
+
watchers: FSWatcher[];
|
|
23
|
+
filesTime: Map<string, number>;
|
|
24
|
+
private port?;
|
|
25
|
+
watcher?: FSWatcher;
|
|
26
|
+
emitRebuild: Function;
|
|
27
|
+
invocations: {
|
|
28
|
+
id: string;
|
|
29
|
+
event: any;
|
|
30
|
+
clientContext?: string;
|
|
31
|
+
inFlight: boolean;
|
|
32
|
+
resolve: (value: unknown) => void;
|
|
33
|
+
reject: (reason?: any) => void;
|
|
34
|
+
}[];
|
|
35
|
+
queue: ServerResponse<IncomingMessage>[];
|
|
36
|
+
getWaitingInvocation(): {
|
|
37
|
+
id: string;
|
|
38
|
+
event: any;
|
|
39
|
+
clientContext?: string;
|
|
40
|
+
inFlight: boolean;
|
|
41
|
+
resolve: (value: unknown) => void;
|
|
42
|
+
reject: (reason?: any) => void;
|
|
43
|
+
} | undefined;
|
|
44
|
+
private server;
|
|
45
|
+
createOwnedServer(): Promise<void>;
|
|
46
|
+
constructor({ name, timeout, memorySize, environment, handlerPath, handlerName, runtime, }: {
|
|
47
|
+
name: string;
|
|
48
|
+
handlerPath: string;
|
|
49
|
+
handlerName: string;
|
|
50
|
+
runtime: string;
|
|
51
|
+
timeout: number;
|
|
52
|
+
memorySize: number;
|
|
53
|
+
environment: {
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
};
|
|
56
|
+
}, emitRebuild: Function, cwd?: string | undefined);
|
|
57
|
+
load: () => Promise<undefined>;
|
|
58
|
+
onComplete: (awsRequestId: string) => void;
|
|
59
|
+
handleInvoke(): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CustomRunner } from "../custom/runner";
|
|
2
|
+
export interface LlrtOptions {
|
|
3
|
+
boostrapPath: string;
|
|
4
|
+
environment?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
type Csp = ConstructorParameters<typeof CustomRunner>;
|
|
7
|
+
export declare class LlrtRunner extends CustomRunner {
|
|
8
|
+
static llrtEnvs: Record<string, string>;
|
|
9
|
+
static llrtBoostrapPath: string;
|
|
10
|
+
constructor(lambda: Csp[0], emitRebuild: Csp[1]);
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Socket } from "node:net";
|
|
2
|
+
import type { IAwsProvider, IServiceProvider } from "../../standalone_types";
|
|
2
3
|
import { type ILambdaMock } from "../runtime/rapidApi";
|
|
4
|
+
import { type LlrtOptions } from "../runtime/runners/llrt/runner";
|
|
3
5
|
import { Handlers } from "./handlers";
|
|
4
|
-
import type { IAwsProvider, IServiceProvider } from "../../standalone_types";
|
|
5
6
|
interface IDaemonConfig {
|
|
6
7
|
debug: boolean;
|
|
7
8
|
}
|
|
@@ -10,6 +11,7 @@ export declare class Daemon extends Handlers {
|
|
|
10
11
|
runtimeConfig: any;
|
|
11
12
|
sco: Socket[];
|
|
12
13
|
aws: IAwsProvider;
|
|
14
|
+
llrt?: LlrtOptions;
|
|
13
15
|
customOfflineRequests: {
|
|
14
16
|
method?: string | string[];
|
|
15
17
|
filter: RegExp | string;
|
package/dist/main.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare class ExloApp extends Daemon {
|
|
|
53
53
|
getAwsProviderDefaults: () => {
|
|
54
54
|
accountId: string;
|
|
55
55
|
region: string;
|
|
56
|
-
runtime: `nodejs${number}${string}` | `python${number}${string}` | `ruby${number}${string}`;
|
|
56
|
+
runtime: "llrt" | "custom" | `nodejs${number}${string}` | `python${number}${string}` | `ruby${number}${string}`;
|
|
57
57
|
memorySize: number;
|
|
58
58
|
timeout: number;
|
|
59
59
|
environment: {
|
|
@@ -28,7 +28,7 @@ import{SNS as Te}from"@aws-sdk/client-sns";var a=class s extends Error{Code;Send
|
|
|
28
28
|
<RequestId>${s}</RequestId>
|
|
29
29
|
</ResponseMetadata>
|
|
30
30
|
</PublishBatchResponse>`};function I(s){if(typeof s!="string")return null;let e=s.split(":");if(e.length<6||e[0]!=="arn")return null;let[t,i,r,n,o,...l]=e;if(!["aws","aws-cn","aws-us-gov"].includes(i))return null;let p=l.join(":"),f,g;return p.indexOf("/")!==-1?[f,g]=p.split("/"):p.indexOf(":")!==-1?[f,g]=p.split(":"):g=p,{partition:i,service:r,region:n,accountId:o,resourceType:f,resourceId:g}}function x(s){return s?s.service=="sqs"&&!s.resourceType:!1}function z(s){return s?s.service=="lambda"&&s.resourceType=="function":!1}function $(s){if(typeof s?.SubscriptionArn!="string")throw new u("Invalid parameter: SubscriptionArn Reason: An ARN must be a string");let e=s.SubscriptionArn.split(":");if(e<6)throw new u(`Invalid parameter: SubscriptionArn Reason: An ARN must have at least 6 elements, not ${e}`);let t=I(s.SubscriptionArn);if(t?.service!="sns"||!t.resourceType||!t.resourceId)throw new u("Invalid parameter: SubscriptionId");let i=s.SubscriptionArn;return{topicArn:i.slice(0,i.indexOf(`:${t.resourceId}`)),topicName:t.resourceType,subscriptionId:t.resourceId,SubscriptionArn:i}}import{randomUUID as k}from"node:crypto";import{setTimeout as ye}from"node:timers/promises";function ne(s,e){if(typeof e!="number")return!1;for(let t=0;t<s.length;t+=2){let i=s[t],r=s[t+1];if(!re(e,i,r))return!1}return!0}function re(s,e,t){switch(e){case"=":return s===t;case">":return s>t;case">=":return s>=t;case"<":return s<t;case"<=":return s<=t;default:return!1}}function G(s){return s.split(".").map(Number).reduce((e,t)=>(e<<8)+t,0)>>>0}function oe(s,e){let[t,i]=s.split("/"),r=G(e),n=G(t),o=~(2**(32-Number(i))-1)>>>0;return(r&o)===(n&o)}function R(s,e){if(s&&typeof s=="object"&&!Array.isArray(s)){if(s["anything-but"])return Array.isArray(s["anything-but"])?s["anything-but"].indexOf(e)==-1:!R(s["anything-but"],e);if(s["equals-ignore-case"])return typeof e=="string"&&e.toLowerCase()==s["equals-ignore-case"].toLowerCase();if(s.cidr)return typeof e!="string"?!1:oe(s.cidr,e);if(s.prefix)return typeof e=="string"&&e.startsWith(s.prefix);if(s.suffix)return typeof e=="string"&&e.endsWith(s.suffix);if(s.exists!==void 0)return s.exists?e!==void 0:e===void 0;if(s.numeric)return ne(s.numeric,e);if(s.$or)return s.$or.some(t=>A(t,e))}if(Array.isArray(s))if(Array.isArray(e)){if(!e.length){let t=s[0];if(m(t)&&"exists"in t)return!!t.exists}return s.some(t=>e.some(i=>R(t,i)))}else return s.some(t=>R(t,e));return e===s}function A(s,e){let t=[];for(let[i,r]of Object.entries(s)){if(Array.isArray(r)){if(i=="$or"){if(!r.some(o=>A(o,e)))return!1;t.push(!0)}else{if(!R(r,e?.[i]))return!1;t.push(!0)}continue}t.push(A(r,e?.[i]))}return!t.some(i=>!i)}var d=class extends u{constructor(e){super(`Invalid parameter: FilterPolicy: ${e}`),this.name="SnsFilterPolicyError"}},J=["<",">","<=",">="],ae=["exists","numeric","anything-but","equals-ignore-case","prefix","suffix","cidr"],ce=/^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\/(?:3[0-2]|[12]?[0-9])$/;function ue(s){if(typeof s!="boolean")throw new d("exists match pattern must be either true or false")}function le(s){if(typeof s!="string")throw new d("equals-ignore-case match pattern must be a string")}function _(s){if(typeof s!="string")throw new d("prefix match pattern must be a string");if(!s)throw new d("Null prefix not allowed")}function pe(s){if(typeof s!="string")throw new d("suffix match pattern must be a string");if(!s)throw new d("Null suffix not allowed")}function de(s){if(typeof s=="boolean"||s===null)throw new d("Value of anything-but must be an array or single string/number value");if(m(s)){let e=Object.keys(s);if(!e.length)throw new d("Anything-But expression name not found");if(e.length>1)throw new d("Only one key allowed in match expression");let t=e[0];if(t!="prefix")throw new d(`Unsupported anything-but pattern: ${t}`);_(s.prefix)}if(Array.isArray(s)){let e;for(let t of s){let i=typeof t;if(i=="string"||i=="number"){if(e||(e=i),e!=i)throw new d("Inside anything but list, either all values are number or string, mixed type is not supported");continue}throw new d("Inside anything but list, start|null|boolean is not supported")}}}function fe(s){if(!Array.isArray(s))throw new d("Value of numeric must be an array");if(!s.length)throw new d("Invalid member in numeric match");let e=s[0];if(e!="=")throw new d(`Unrecognized numeric range operator: ${e}`);if(e!="="&&!J.includes(e))throw new d(`Unrecognized numeric range operator: ${e}`);if(typeof s[1]!="number")throw new d("Value of equals must be numeric");if(s.length>2){if(e=="=")throw new d("Too many elements in numeric expression");let i=s[2];if(e==i||!J.includes(i)||s.length==3)throw new d(`Bad numeric range operator: ${i}`);if(typeof s[4]!="number")throw new d(`Value of ${i} must be numeric`);if(s.length!=4)throw new d("Too many terms in numeric range expression")}}function ge(s){if(typeof s!="string")throw new d("cidr match pattern must be a string");if(!ce.test(s))throw s.includes("/")?new d("Malformed CIDR"):new d("Malformed CIDR, one '/' required")}function he(s,e){switch(s){case"exists":ue(e);break;case"numeric":fe(e);break;case"anything-but":de(e);break;case"equals-ignore-case":le(e);break;case"prefix":_(e);break;case"suffix":pe(e);break;case"cidr":ge(e);break;default:throw new d(`Unrecognized match type ${s}`)}}function me(s,e,t){let i=0;for(let r of s){if(typeof r=="string"||typeof r=="number"||typeof r=="boolean"||r===null){i++;continue}if(Array.isArray(r))throw new d("Match value must be String, number, true, false, or null");let n=Object.keys(r);if(!n.length)throw new d("Empty objects are not allowed");if(n.length>1)throw new d("Only one key allowed in match expression");let o=n[0];he(o,r[o]),i++}i&&(e.values=e.values*t*i)}function be(s,e,t){if(s.length<2)throw new d("There must have at least 2 Objects in $or relationship.");let i=e.isTopLevel,r=e.values,n=[];for(let l of s){if(!m(l))throw new d("Only JSON object is allowed in array of $or relationship.");let p=Object.keys(l);if(!p.length)throw new d("Empty objects are not allowed");let f=p.find(g=>ae.includes(g));if(f)throw new d(`${f} is Ruler reserved fieldName which cannot be used inside $or.`);e.isTopLevel=i,e.values=1,q(l,e,t),n.push(e.values)}let o=n.reduce((l,p)=>l+=p,0);e.values=r*o}function q(s,e,t){let i=e.isTopLevel,r=Object.keys(s);if(e.isTopLevel){for(let n of r)n!="$or"&&e.keys.add(n);e.isTopLevel=!1}else e.hasNestedField=!0;for(let[n,o]of Object.entries(s)){if(Array.isArray(o)){if(!o.length)throw new d("Empty arrays are not allowed");n=="$or"?(e.isTopLevel=i,be(o,e,t)):me(o,e,t);continue}if(m(o)){q(o,e,t+1);continue}throw new d(`"${n}" must be an object or an array`)}e.isTopLevel=!1}function B(s){if(Buffer.from(s,"utf-8").byteLength>262144)throw new d("Filter policy too big");let e;try{e=JSON.parse(s)}catch{throw new d("failed to parse JSON")}if(!m(e))throw new d("Filter is not an object");let t={keys:new Set,values:1,isTopLevel:!0,hasNestedField:!1};if(q(e,t,1),t.keys.size>5)throw new d("Filter policy can not have more than 5 keys");if(t.values>150)throw new d("Filter policy is too complex");return{pattern:e,counters:t}}function H(s){let e=B(s);if(e.counters.hasNestedField)throw new d("Filter policy scope MessageAttributes does not support nested filter policy");return e}var N=class{constructor(e,t,i,r,n){this.config=e;this.topicArn=t;this.getLambdas=r;this.getSqsClient=n;this.id=k(),this.arn=`${t}:${this.id}`,this.validateProtocol(),this.validateAttributes(),this.internalAttributes={ConfirmationWasAuthenticated:this.config.Protocol.startsWith("http")?"false":"true",PendingConfirmation:this.config.Protocol.startsWith("http")?"true":"false",Owner:i,SubscriptionPrincipal:`arn:aws:iam::${i}:user/exlo`}}id;arn;deleted=!1;filterPattern;fitlerScope="MessageAttributes";queueName="";lambdaName="";queueUrl;confirmationToken;internalAttributes;validateProtocol(){if(this.config.Protocol=="sqs"){let e=I(this.config.Endpoint);if(!x(e))throw new u("Invalid SQS ARN: Must be an arn string (example: 'arn:aws:sqs:eu-west-1:123456789012:MyQueue')");this.queueName=e.resourceId;return}if(this.config.Protocol=="lambda"){let e=I(this.config.Endpoint);if(!z(e))throw new u("Invalid parameter: Lambda endpoint ARN");this.lambdaName=e.resourceId;return}if(this.config.Protocol=="http"){if(!this.config.Endpoint.startsWith("http://"))throw new u("Invalid parameter: Endpoint must match the specified protocol");return}if(this.config.Protocol=="https"&&!this.config.Endpoint.startsWith("https://"))throw new u("Invalid parameter: Endpoint must match the specified protocol");console.warn("Only SQS, Lambda and HTTP/S Endpoints are currently supported.")}validateAttributes(){if(this.config.Attributes){if("RedrivePolicy"in this.config.Attributes)try{let e=JSON.parse(this.config.Attributes.RedrivePolicy);x(I(e.deadLetterTargetArn))}catch{}if("RawMessageDelivery"in this.config.Attributes){if(!["true","false"].includes(this.config.Attributes.RawMessageDelivery))throw new u(`Invalid parameter: Attributes Reason: RawMessageDelivery: Invalid value [${this.config.Attributes.RawMessageDelivery}]. Must be true or false.`);if(!["sqs","http","https"].includes(this.config.Protocol))throw new u(`Invalid parameter: Attributes Reason: Delivery protocol [${this.config.Protocol}] does not support raw message delivery.`)}if("FilterPolicyScope"in this.config.Attributes){if(!["MessageBody","MessageAttributes"].includes(this.config.Attributes.FilterPolicyScope))throw new u(`Invalid parameter: Attributes Reason: FilterPolicyScope: Invalid value [${this.config.Attributes.FilterPolicyScope}]. Please use either MessageBody or MessageAttributes`);this.fitlerScope=this.config.Attributes.FilterPolicyScope}if(this.config.Attributes.FilterPolicy)if(this.fitlerScope=="MessageAttributes"){let{pattern:e}=H(this.config.Attributes.FilterPolicy);this.filterPattern=e}else{let{pattern:e}=B(this.config.Attributes.FilterPolicy);this.filterPattern=e}}}async notify(e){let t=e.message;if(e.messageStructure=="json"){if(t=e.defaultMessage,this.config.Protocol=="sqs"){if(e.sqsMessage=="")return;e.sqsMessage&&(t=e.sqsMessage)}else if(this.config.Protocol=="http"){if(e.httpMessage=="")return;e.httpMessage&&(t=e.httpMessage)}else if(this.config.Protocol=="https"){if(e.httpsMessage=="")return;e.httpsMessage&&(t=e.httpsMessage)}else if(this.config.Protocol=="lambda"){if(e.lambdaMessage=="")return;e.lambdaMessage&&(t=e.lambdaMessage)}}if(!this.shouldNotify(e.messageAttributes,t))return;let i={};for(let[n,o]of Object.entries(e.messageAttributes))this.config.Protocol=="lambda"?o.Type.startsWith("String")||o.Type.startsWith("Number")?i[n]={Type:"String",Value:o.Value}:o.Type.startsWith("Binary")?i[n]={Type:"Binary",Value:o.Value}:i[n]={Type:o.Type,Value:o.Value}:i[n]={Type:o.Type,Value:o.Value};let r={Type:"Notification",MessageId:e.messageId,TopicArn:this.topicArn,Message:t,Timestamp:e.timestamp,UnsubscribeUrl:`${e.serverHost}/?Action=Unsubscribe&SubscriptionArn=${this.arn}`,MessageAttributes:i};this.config.Protocol=="lambda"?(r.SignatureVersion="1",r.Signature="fakeandinvalidsignature",r.SigningCertUrl=`${e.serverHost}/SimpleNotificationService-fakecert.pem`,e.subject?r.Subject=e.subject:r.Subject=null,await this.notifyLambda(r)):this.config.Protocol=="sqs"?(e.subject&&(r.Subject=e.subject),await this.notifySqs(r,e)):(this.config.Protocol=="http"||this.config.Protocol=="https")&&(e.subject&&(r.Subject=e.subject),await this.notifyHttp(r))}getAttributes(){return{...this.config.Attributes,...this.internalAttributes,Endpoint:this.config.Endpoint,Protocol:this.config.Protocol,SubscriptionArn:this.arn}}shouldNotify(e,t){if(this.internalAttributes.PendingConfirmation!="true"){if(!this.filterPattern)return!0;if(this.fitlerScope=="MessageAttributes"){let i={};for(let[r,n]of Object.entries(e)){if(n.Type.startsWith("Binary."))return!1;if(!n.Type.startsWith("Binary")){if(n.Type.startsWith("Number")){i[r]=Number(n.Value);continue}if(n.Type=="String.Array")try{i[r]=JSON.parse(n.Value);continue}catch{}n.Type.startsWith("String")&&(i[r]=n.Value)}}return A(this.filterPattern,i)}if(!t)return!1;try{let i=JSON.parse(t);return m(i)?A(this.filterPattern,i):!1}catch{return!1}}}async notifyLambda(e){let t=this.getLambdas().find(r=>r.name==this.lambdaName);if(!t)return;let i={Records:[{EventSource:"aws:sns",EventVersion:"1.0",EventSubscriptionArn:this.arn,Sns:e}]};await t.invoke(i)}async getQueueUrl(){if(!this.queueUrl){let e=this.getSqsClient(),{QueueUrl:t}=await e.getQueueUrl({QueueName:this.queueName});this.queueUrl=t}return this.queueUrl}async notifyHttp(e){let t={"content-type":"text/plain; charset=UTF-8","user-agent":"Amazon Simple Notification Service Agent","x-amz-sns-subscription-arn":this.arn,"x-amz-sns-message-type":"Notification","x-amz-sns-message-id":e.MessageId,"x-amz-sns-topic-arn":this.topicArn},i;if(this.config.Attributes?.RawMessageDelivery=="true")i=e.Message,t["x-amz-sns-rawdelivery"]="true";else{let r=Object.keys(e.MessageAttributes).length;i=JSON.stringify({...e,MessageAttributes:r?e.MessageAttributes:void 0})}try{await K(this.config.Endpoint,{method:"POST",body:i,headers:t},3)}catch{console.error(`Unable to publish SNS Message for Endpoint: ${this.config.Endpoint}`)}}async notifySqs(e,t){let i=this.getSqsClient(),r=await this.getQueueUrl(),n={QueueUrl:r,MessageAttributes:{}};if(this.config.Attributes?.RawMessageDelivery=="true"){n.MessageBody=e.Message;let o=0;for(let[l,p]of Object.entries(e.MessageAttributes))o++,n.MessageAttributes[l]={DataType:p.Type},p.Type.startsWith("Binary")?n.MessageAttributes[l].BinaryValue=Buffer.from(p.Value,"base64"):n.MessageAttributes[l].StringValue=p.Value;if(o>10)throw new a({Code:"ServiceError",Message:"Can not send more than 10 message attributes to SQS endpoint with RawMessageDelivery enabled"})}else n.MessageBody=JSON.stringify(e);t.messageGroupId&&(n.MessageGroupId=t.messageGroupId),r.endsWith(".fifo")&&(n.MessageDeduplicationId=t.messageDeduplicationId),await i.sendMessage(n)}async redrive(){}requestConfirmation(e){if(!["http","https"].includes(this.config.Protocol))return;let t=k(),i=Buffer.from(JSON.stringify({u:k(),a:this.arn,d:Date.now()})).toString("base64url"),r=JSON.stringify({Type:"SubscriptionConfirmation",MessageId:t,Token:i,TopicArn:this.topicArn,Message:`You have chosen to subscribe to the topic ${this.topicArn}.
|
|
31
|
-
To confirm the subscription, visit the SubscribeURL included in this message.`,SubscribeURL:`${e}/?Action=ConfirmSubscription&TopicArn=${this.topicArn}&Token=${i}`,Timestamp:new Date().toISOString(),SignatureVersion:"1",Signature:"fakeandinvalidsignature",SigningCertURL:`${e}/SimpleNotificationService-fakecert.pem`}),n=this.config,o=this.internalAttributes;this.confirmationToken=i,K(this.config.Endpoint,{method:"POST",body:r,headers:{"x-amz-sns-message-id":t,"x-amz-sns-topic-arn":this.topicArn,"user-agent":"Amazon Simple Notification Service Agent","x-amz-sns-message-type":"SubscriptionConfirmation"}}).then(()=>{o.ConfirmationWasAuthenticated="true"}).catch(()=>{console.error(`Unable to request SNS Confirmation for Endpoint: ${n.Endpoint}`)})}confirm(){this.internalAttributes.PendingConfirmation="false"}};async function K(s,e={},t=30,i=1e3){let r=0;for(;r<=t;)try{let n=await fetch(s,e);if(n.ok)return n;throw new Error(`Non-200 response: ${n.statusText}`)}catch(n){if(r<t)await ye(i),r++;else throw new Error(`Failed after ${t} attempts: ${String(n)}`)}throw new Error("Unknown error.")}import{randomUUID as Se}from"node:crypto";import{createHash as we}from"node:crypto";var Ie=s=>we("sha256").update(s).digest("hex"),V=class{policy={http:{defaultHealthyRetryPolicy:{minDelayTarget:20,maxDelayTarget:20,numRetries:3,numMaxDelayRetries:0,numNoDelayRetries:0,numMinDelayRetries:0,backoffFunction:"linear"},disableSubscriptionOverrides:!1,defaultRequestPolicy:{headerContentType:"text/plain; charset=UTF-8"}}};toString(){return JSON.stringify(this.policy)}},F=class{constructor(e,t){this.topicArn=e;this.accountId=t;this.policy={Version:"2008-10-17",Id:"__default_policy_ID",Statement:[{Sid:"__default_statement_ID",Effect:"Allow",Principal:{AWS:"*"},Action:["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish"],Resource:this.topicArn,Condition:{StringEquals:{"AWS:SourceOwner":this.accountId}}}]}}policy;toString(){return JSON.stringify(this.policy)}},P=class{constructor(e,t,i,r){this.config=e;this.serverHost=t;this.region=i;this.accountId=r;this.validateTopic(),this.setTopicDefaultAttributes(),this.TopicArn=`arn:aws:sns:${this.region}:${this.accountId}:${this.config.Name}`,this.internalAttributes={Policy:new F(this.TopicArn,r),EffectiveDeliveryPolicy:new V,Owner:this.accountId,TopicArn:this.TopicArn}}internalAttributes;TopicArn;subscribers=[];sequenceNumber=0;getAttributes(){let e=0,t=0,i=0;for(let n of this.subscribers){if(n.deleted){t++;continue}n.internalAttributes.PendingConfirmation=="true"?i++:e++}return{...this.config.Attributes,...this.internalAttributes,SubscriptionsConfirmed:e,SubscriptionsDeleted:t,SubscriptionsPending:i}}setTopicDefaultAttributes(){this.config.Attributes??={},this.config.Attributes.DisplayName||(this.config.Attributes.DisplayName=""),this.config.Attributes.TracingConfig||(this.config.Attributes.TracingConfig="PassThrough"),this.config.Attributes.SignatureVersion||(this.config.Attributes.SignatureVersion="1"),this.config.Attributes.FifoTopic?(this.config.Attributes.ContentBasedDeduplication==null&&(this.config.Attributes.ContentBasedDeduplication=!1),this.config.Attributes.FifoThroughputScope||(this.config.Attributes.FifoThroughputScope="Topic")):this.config.Attributes.FifoTopic=!1}validateTopic(){if(!this.isValidSnsTopicName(this.config.Name))throw new u("Invalid parameter: Topic Name");if(this.config.Name.endsWith(".fifo")&&!this.config.Attributes?.FifoTopic||this.config.Attributes?.FifoTopic&&!this.config.Name.endsWith(".fifo"))throw new u("Invalid parameter: Topic Name");if(this.config.Attributes?.TracingConfig!=null&&!["PassThrough","Active"].includes(this.config.Attributes?.TracingConfig))throw new u(`Attributes Reason: Invalid tracing config value: ${this.config.Attributes?.TracingConfig}`);this.config.Name.endsWith(".fifo")?this.validateFifoTopic():this.validateStandartTopic()}isValidSnsTopicName(e){return!(e.length<1||e.length>256||!/^[A-Za-z0-9_-]+(\.fifo)?$/.test(e)||e.endsWith(".fifo")&&e.slice(0,-5).length<1)}validateFifoTopic(){}validateStandartTopic(){if(this.config.Attributes){if("ContentBasedDeduplication"in this.config.Attributes)throw new u("Attributes Reason: Unknown attribute ContentBasedDeduplication");if("ArchivePolicy"in this.config.Attributes)throw new u("Attributes Reason: Unknown attribute ArchivePolicy");if("FifoThroughputScope"in this.config.Attributes)throw new u("Attributes Reason: Unknown attribute FifoThroughputScope")}}getSequenceNumber(){return this.sequenceNumber++,`1${`${this.sequenceNumber}000`.padStart(19,"0")}`}validateMsg(e){if(e.messageSize>262144)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: Message too long"});if(this.config.Name.endsWith(".fifo")){if(!e.messageGroupId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: The MessageGroupId parameter is required for FIFO topics"});if(!e.messageDeduplicationId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: The topic should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly"})}else{if(e.messageGroupId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: MessageGroupId Reason: The request includes MessageGroupId parameter that is not valid for this topic type"});if(e.messageDeduplicationId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: MessageDeduplicationId Reason: The request includes MessageDeduplicationId parameter that is not valid for this topic type"})}}createTopicMessage(e){let t=Se(),i={...e,messageId:t,timestamp:new Date().toISOString(),serverHost:this.serverHost};return this.config.Name.endsWith(".fifo")&&this.config.Attributes?.ContentBasedDeduplication&&(i.messageDeduplicationId||(i.messageDeduplicationId=Ie(i.message))),this.validateMsg(i),i}publish(e){let t=this.createTopicMessage(e),i=this.config.Name.endsWith(".fifo")?this.getSequenceNumber():void 0;for(let r of this.subscribers)r.deleted||r.notify(t).catch(n=>{console.error(n)});return{messageId:t.messageId,sequenceNumber:i}}publishBatchFifo(e){let t=[];for(let r of e.parsedMessages)t.push({id:r.Id,msg:this.createTopicMessage({...r.message,topicArn:e.topicArn,topicName:e.topicName})});let i=[];for(let r of t){i.push({Id:r.id,MessageId:r.msg.messageId,SequenceNumber:this.getSequenceNumber()});for(let n of this.subscribers)n.deleted||n.notify(r.msg).catch(o=>{console.error(o)})}return{successful:i,failed:[]}}publishBatch(e){if(e.parsedMessages.reduce((r,n)=>r+n.message.messageSize,0)>262144)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: Message too long"});if(this.config.Name.endsWith(".fifo"))return this.publishBatchFifo(e);let i={successful:[],failed:[]};for(let r of e.parsedMessages)try{let{messageId:n}=this.publish({...r.message,topicArn:e.topicArn,topicName:e.topicName});i.successful.push({Id:r.Id,MessageId:n})}catch(n){n instanceof a?i.failed.push({Id:r.Id,Code:n.Code,Message:n.message,SenderFault:n.SenderFault}):i.failed.push({Id:r.Id,Code:"UnexpectedError",Message:n?.toString?.()??"Unknown error",SenderFault:!1})}return i}subscribe(e){let t=e.config.Endpoint=="sqs"&&e.config.Endpoint.endsWith(".fifo");if(this.config.Name.endsWith(".fifo")){if(!t)throw new u("Only FIFO SQS is allowed to subscribe to FIFO Topic.")}else if(t)throw new u("FIFO SQS is not allowed to subscribe to Standart Topic.");this.subscribers.push(e)}};var E=class s{constructor(e,t,i,r,n){this.port=e;this.region=t;this.accountId=i;this.getLambdas=r;this.getSqsClient=n}server;topics=[];async start(){this.server=Ae((e,t)=>{this.requestHandler(e,t)}),await new Promise(e=>{this.server.listen(this.port,e)}),this.port=this.server.address().port,this.server.unref()}createTopic(e){let t=new P(e,`http://localhost:${this.port}`,this.region,this.accountId),i=this.topics.find(r=>r.config.Name==e.Name);if(!i)return this.topics.push(t),t;if(!ve(t.config,i.config))throw new u("Attributes Reason: Topic already exists with different attributes");return i}subscribe(e,t){let i=e.subscribers.find(n=>n.config.Endpoint==t.Endpoint&&!n.deleted);if(i)return i;let r=new N(t,e.TopicArn,this.accountId,this.getLambdas,this.getSqsClient);return e.subscribe(r),r}stop(){this.server?.close()}getTopicByName(e){let t=this.topics.find(i=>i.config.Name==e);if(!t)throw new a({Code:"NotFound",Message:"Topic does not exist",statusCode:404});return t}requestSubscriptionsConfirmation(){let e=`http://localhost:${this.port}`;for(let t of this.topics)for(let i of t.subscribers)i.requestConfirmation(e)}static async collectBody(e){let t=Buffer.alloc(0);return e.on("data",i=>{t=Buffer.concat([t,i])}),new Promise(i=>{e.on("end",async()=>{i(t?t.toString("utf-8"):void 0)})})}static async getRequestBody(e){let{method:t,url:i}=e,r;if(t=="GET")try{let n=new URL(i,"http://localhost:300");n.search&&(r=n.search.slice(1))}catch{}else t=="POST"&&(r=await s.collectBody(e));return r}async requestHandler(e,t){let i=C();t.setHeader("x-amzn-requestid",i),t.setHeader("Content-Type","application/xml");let r="";try{let n=await s.getRequestBody(e);if(n){let o=Me(n);switch(typeof o.Action=="string"&&(r=o.Action),r){case"Publish":return this.handlePublishRequest(o,t,i);case"PublishBatch":return this.handlePublishBatchRequest(o,t,i);case"CreateTopic":return this.handleCreateTopicRequest(o,t,i);case"GetTopicAttributes":return this.handleGetTopicAttributes(o,t,i);case"DeleteTopic":return this.handleDeleteTopic(o,t,i);case"ListTopics":return this.handleListTopics(o,t,i);case"Subscribe":return this.handleSubscribe(o,t,i);case"Unsubscribe":return this.handleUnsubscribe(o,t,i);case"ListSubscriptions":return this.handleListSubscriptions(o,t,i);case"ListSubscriptionsByTopic":return this.handleListSubscriptionsByTopic(o,t,i);case"GetSubscriptionAttributes":return this.handleGetSubscriptionAttributes(o,t,i);case"ConfirmSubscription":return this.handleConfirmSubscription(o,t,i);default:break}}}catch(n){n instanceof a?(t.statusCode=n.statusCode,t.end(n.toXml(i))):(t.statusCode=500,t.end(a.genericErrorResponse({Code:"InternalError",RequestId:i,Message:n?.toString?.()??"Unknown error"})));return}t.statusCode=500,t.end(new a({Code:"UnsupportedOperation",Message:`This Action ${r?`(${r})`:""} is currently not supported`}).toXml(i))}handlePublishRequest(e,t,i){let r=L(e),n=this.topics.find(f=>f.config.Name==r.topicName);if(!n)throw new a({Code:"NotFound",Message:"Topic does not exist",statusCode:404});let{messageId:o,sequenceNumber:l}=n.publish(r),p=W(o,i,l);t.statusCode=200,t.end(p)}handlePublishBatchRequest(e,t,i){let r=U(e),n=this.topics.find(p=>p.config.Name==r.topicName);if(!n)throw new a({Code:"NotFound",Message:"Topic does not exist",statusCode:404});let o=n.publishBatch(r),l=Q(i,o.successful,o.failed);t.statusCode=200,t.end(l)}handleCreateTopicRequest(e,t,i){let r=O(e),n=this.createTopic(r);t.statusCode=200,t.end(`<CreateTopicResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/">
|
|
31
|
+
To confirm the subscription, visit the SubscribeURL included in this message.`,SubscribeURL:`${e}/?Action=ConfirmSubscription&TopicArn=${this.topicArn}&Token=${i}`,Timestamp:new Date().toISOString(),SignatureVersion:"1",Signature:"fakeandinvalidsignature",SigningCertURL:`${e}/SimpleNotificationService-fakecert.pem`}),n=this.config,o=this.internalAttributes;this.confirmationToken=i,K(this.config.Endpoint,{method:"POST",body:r,headers:{"x-amz-sns-message-id":t,"x-amz-sns-topic-arn":this.topicArn,"user-agent":"Amazon Simple Notification Service Agent","x-amz-sns-message-type":"SubscriptionConfirmation"}}).then(()=>{o.ConfirmationWasAuthenticated="true"}).catch(()=>{console.error(`Unable to request SNS Confirmation for Endpoint: ${n.Endpoint}`)})}confirm(){this.internalAttributes.PendingConfirmation="false"}};async function K(s,e={},t=30,i=1e3){let r=0;for(;r<=t;)try{let n=await fetch(s,e);if(n.ok)return n;throw new Error(`Non-200 response: ${n.statusText}`)}catch(n){if(r<t)await ye(i),r++;else throw new Error(`Failed after ${t} attempts: ${String(n)}`)}throw new Error("Unknown error.")}import{randomUUID as Se}from"node:crypto";import{createHash as we}from"node:crypto";var Ie=s=>we("sha256").update(s).digest("hex"),V=class{policy={http:{defaultHealthyRetryPolicy:{minDelayTarget:20,maxDelayTarget:20,numRetries:3,numMaxDelayRetries:0,numNoDelayRetries:0,numMinDelayRetries:0,backoffFunction:"linear"},disableSubscriptionOverrides:!1,defaultRequestPolicy:{headerContentType:"text/plain; charset=UTF-8"}}};toString(){return JSON.stringify(this.policy)}},F=class{constructor(e,t){this.topicArn=e;this.accountId=t;this.policy={Version:"2008-10-17",Id:"__default_policy_ID",Statement:[{Sid:"__default_statement_ID",Effect:"Allow",Principal:{AWS:"*"},Action:["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish"],Resource:this.topicArn,Condition:{StringEquals:{"AWS:SourceOwner":this.accountId}}}]}}policy;toString(){return JSON.stringify(this.policy)}},P=class{constructor(e,t,i,r){this.config=e;this.serverHost=t;this.region=i;this.accountId=r;this.validateTopic(),this.setTopicDefaultAttributes(),this.TopicArn=`arn:aws:sns:${this.region}:${this.accountId}:${this.config.Name}`,this.internalAttributes={Policy:new F(this.TopicArn,r),EffectiveDeliveryPolicy:new V,Owner:this.accountId,TopicArn:this.TopicArn}}internalAttributes;TopicArn;subscribers=[];sequenceNumber=0;getAttributes(){let e=0,t=0,i=0;for(let n of this.subscribers){if(n.deleted){t++;continue}n.internalAttributes.PendingConfirmation=="true"?i++:e++}return{...this.config.Attributes,...this.internalAttributes,SubscriptionsConfirmed:e,SubscriptionsDeleted:t,SubscriptionsPending:i}}setTopicDefaultAttributes(){this.config.Attributes??={},this.config.Attributes.DisplayName||(this.config.Attributes.DisplayName=""),this.config.Attributes.TracingConfig||(this.config.Attributes.TracingConfig="PassThrough"),this.config.Attributes.SignatureVersion||(this.config.Attributes.SignatureVersion="1"),this.config.Attributes.FifoTopic?(this.config.Attributes.ContentBasedDeduplication==null&&(this.config.Attributes.ContentBasedDeduplication=!1),this.config.Attributes.FifoThroughputScope||(this.config.Attributes.FifoThroughputScope="Topic")):this.config.Attributes.FifoTopic=!1}validateTopic(){if(!this.isValidSnsTopicName(this.config.Name))throw new u("Invalid parameter: Topic Name");if(this.config.Name.endsWith(".fifo")&&!this.config.Attributes?.FifoTopic||this.config.Attributes?.FifoTopic&&!this.config.Name.endsWith(".fifo"))throw new u("Invalid parameter: Topic Name");if(this.config.Attributes?.TracingConfig!=null&&!["PassThrough","Active"].includes(this.config.Attributes?.TracingConfig))throw new u(`Attributes Reason: Invalid tracing config value: ${this.config.Attributes?.TracingConfig}`);this.config.Name.endsWith(".fifo")?this.validateFifoTopic():this.validateStandartTopic()}isValidSnsTopicName(e){return!(e.length<1||e.length>256||!/^[A-Za-z0-9_-]+(\.fifo)?$/.test(e)||e.endsWith(".fifo")&&e.slice(0,-5).length<1)}validateFifoTopic(){}validateStandartTopic(){if(this.config.Attributes){if("ContentBasedDeduplication"in this.config.Attributes)throw new u("Attributes Reason: Unknown attribute ContentBasedDeduplication");if("ArchivePolicy"in this.config.Attributes)throw new u("Attributes Reason: Unknown attribute ArchivePolicy");if("FifoThroughputScope"in this.config.Attributes)throw new u("Attributes Reason: Unknown attribute FifoThroughputScope")}}getSequenceNumber(){return this.sequenceNumber++,`1${`${this.sequenceNumber}000`.padStart(19,"0")}`}validateMsg(e){if(e.messageSize>262144)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: Message too long"});if(this.config.Name.endsWith(".fifo")){if(!e.messageGroupId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: The MessageGroupId parameter is required for FIFO topics"});if(!e.messageDeduplicationId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: The topic should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly"})}else{if(e.messageGroupId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: MessageGroupId Reason: The request includes MessageGroupId parameter that is not valid for this topic type"});if(e.messageDeduplicationId)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: MessageDeduplicationId Reason: The request includes MessageDeduplicationId parameter that is not valid for this topic type"})}}createTopicMessage(e){let t=Se(),i={...e,messageId:t,timestamp:new Date().toISOString(),serverHost:this.serverHost};return this.config.Name.endsWith(".fifo")&&this.config.Attributes?.ContentBasedDeduplication&&(i.messageDeduplicationId||(i.messageDeduplicationId=Ie(i.message))),this.validateMsg(i),i}publish(e){let t=this.createTopicMessage(e),i=this.config.Name.endsWith(".fifo")?this.getSequenceNumber():void 0;for(let r of this.subscribers)r.deleted||r.notify(t).catch(n=>{console.error(n)});return{messageId:t.messageId,sequenceNumber:i}}publishBatchFifo(e){let t=[];for(let r of e.parsedMessages)t.push({id:r.Id,msg:this.createTopicMessage({...r.message,topicArn:e.topicArn,topicName:e.topicName})});let i=[];for(let r of t){i.push({Id:r.id,MessageId:r.msg.messageId,SequenceNumber:this.getSequenceNumber()});for(let n of this.subscribers)n.deleted||n.notify(r.msg).catch(o=>{console.error(o)})}return{successful:i,failed:[]}}publishBatch(e){if(e.parsedMessages.reduce((r,n)=>r+n.message.messageSize,0)>262144)throw new a({Code:"InvalidParameter",Message:"Invalid parameter: Message too long"});if(this.config.Name.endsWith(".fifo"))return this.publishBatchFifo(e);let i={successful:[],failed:[]};for(let r of e.parsedMessages)try{let{messageId:n}=this.publish({...r.message,topicArn:e.topicArn,topicName:e.topicName});i.successful.push({Id:r.Id,MessageId:n})}catch(n){n instanceof a?i.failed.push({Id:r.Id,Code:n.Code,Message:n.message,SenderFault:n.SenderFault}):i.failed.push({Id:r.Id,Code:"UnexpectedError",Message:n?.toString?.()??"Unknown error",SenderFault:!1})}return i}subscribe(e){let t=e.config.Endpoint=="sqs"&&e.config.Endpoint.endsWith(".fifo");if(this.config.Name.endsWith(".fifo")){if(!t)throw new u("Only FIFO SQS is allowed to subscribe to FIFO Topic.")}else if(t)throw new u("FIFO SQS is not allowed to subscribe to Standart Topic.");this.subscribers.push(e)}};var E=class s{constructor(e,t,i,r,n){this.port=e;this.region=t;this.accountId=i;this.getLambdas=r;this.getSqsClient=n}server;topics=[];async start(){this.server=Ae((e,t)=>{this.requestHandler(e,t)}),await new Promise(e=>{this.server.listen(this.port,e)}),this.port=this.server.address().port,this.server.unref()}stop(){this.server?.close()}static async collectBody(e){let t=Buffer.alloc(0);return e.on("data",i=>{t=Buffer.concat([t,i])}),new Promise(i=>{e.on("end",async()=>{i(t?t.toString("utf-8"):void 0)})})}static async getRequestBody(e){let{method:t,url:i}=e,r;if(t=="GET")try{let n=new URL(i,"http://localhost:300");n.search&&(r=n.search.slice(1))}catch{}else t=="POST"&&(r=await s.collectBody(e));return r}async requestHandler(e,t){let i=C();t.setHeader("x-amzn-requestid",i),t.setHeader("Content-Type","application/xml");let r="";try{let n=await s.getRequestBody(e);if(n){let o=Me(n);switch(typeof o.Action=="string"&&(r=o.Action),r){case"Publish":return this.handlePublishRequest(o,t,i);case"PublishBatch":return this.handlePublishBatchRequest(o,t,i);case"CreateTopic":return this.handleCreateTopicRequest(o,t,i);case"GetTopicAttributes":return this.handleGetTopicAttributes(o,t,i);case"DeleteTopic":return this.handleDeleteTopic(o,t,i);case"ListTopics":return this.handleListTopics(o,t,i);case"Subscribe":return this.handleSubscribe(o,t,i);case"Unsubscribe":return this.handleUnsubscribe(o,t,i);case"ListSubscriptions":return this.handleListSubscriptions(o,t,i);case"ListSubscriptionsByTopic":return this.handleListSubscriptionsByTopic(o,t,i);case"GetSubscriptionAttributes":return this.handleGetSubscriptionAttributes(o,t,i);case"ConfirmSubscription":return this.handleConfirmSubscription(o,t,i);default:break}}}catch(n){n instanceof a?(t.statusCode=n.statusCode,t.end(n.toXml(i))):(t.statusCode=500,t.end(a.genericErrorResponse({Code:"InternalError",RequestId:i,Message:n?.toString?.()??"Unknown error"})));return}t.statusCode=500,t.end(new a({Code:"UnsupportedOperation",Message:`This Action ${r?`(${r})`:""} is currently not supported`}).toXml(i))}createTopic(e){let t=new P(e,`http://localhost:${this.port}`,this.region,this.accountId),i=this.topics.find(r=>r.config.Name==e.Name);if(!i)return this.topics.push(t),t;if(!ve(t.config,i.config))throw new u("Attributes Reason: Topic already exists with different attributes");return i}subscribe(e,t){let i=e.subscribers.find(n=>n.config.Endpoint==t.Endpoint&&!n.deleted);if(i)return i;let r=new N(t,e.TopicArn,this.accountId,this.getLambdas,this.getSqsClient);return e.subscribe(r),r}getTopicByName(e){let t=this.topics.find(i=>i.config.Name==e);if(!t)throw new a({Code:"NotFound",Message:"Topic does not exist",statusCode:404});return t}requestSubscriptionsConfirmation(){let e=`http://localhost:${this.port}`;for(let t of this.topics)for(let i of t.subscribers)i.requestConfirmation(e)}handlePublishRequest(e,t,i){let r=L(e),n=this.topics.find(f=>f.config.Name==r.topicName);if(!n)throw new a({Code:"NotFound",Message:"Topic does not exist",statusCode:404});let{messageId:o,sequenceNumber:l}=n.publish(r),p=W(o,i,l);t.statusCode=200,t.end(p)}handlePublishBatchRequest(e,t,i){let r=U(e),n=this.topics.find(p=>p.config.Name==r.topicName);if(!n)throw new a({Code:"NotFound",Message:"Topic does not exist",statusCode:404});let o=n.publishBatch(r),l=Q(i,o.successful,o.failed);t.statusCode=200,t.end(l)}handleCreateTopicRequest(e,t,i){let r=O(e),n=this.createTopic(r);t.statusCode=200,t.end(`<CreateTopicResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/">
|
|
32
32
|
<CreateTopicResult>
|
|
33
33
|
<TopicArn>arn:aws:sns:${this.region}:${this.accountId}:${n.config.Name}</TopicArn>
|
|
34
34
|
</CreateTopicResult>
|
|
@@ -13,14 +13,14 @@ export declare class SnsServer {
|
|
|
13
13
|
private topics;
|
|
14
14
|
constructor(port: number, region: string, accountId: string, getLambdas: () => ILambda[], getSqsClient: () => SQS);
|
|
15
15
|
start(): Promise<void>;
|
|
16
|
-
createTopic(config: ISnsTopic): SnsTopic;
|
|
17
|
-
subscribe(topic: SnsTopic, config: ISnsTopicSubscriber): SnsSubscriber;
|
|
18
16
|
stop(): void;
|
|
19
|
-
getTopicByName(topicName: string): SnsTopic;
|
|
20
|
-
requestSubscriptionsConfirmation(): void;
|
|
21
17
|
private static collectBody;
|
|
22
18
|
private static getRequestBody;
|
|
23
19
|
private requestHandler;
|
|
20
|
+
createTopic(config: ISnsTopic): SnsTopic;
|
|
21
|
+
subscribe(topic: SnsTopic, config: ISnsTopicSubscriber): SnsSubscriber;
|
|
22
|
+
getTopicByName(topicName: string): SnsTopic;
|
|
23
|
+
requestSubscriptionsConfirmation(): void;
|
|
24
24
|
private handlePublishRequest;
|
|
25
25
|
private handlePublishBatchRequest;
|
|
26
26
|
private handleCreateTopicRequest;
|
|
@@ -3,4 +3,4 @@ import type { Vitest } from "vitest/node";
|
|
|
3
3
|
export declare function initMonocart(): Promise<void>;
|
|
4
4
|
export declare function generateLambdaCoverage(vitest: Vitest, coverageMap: any, rawCoverageDir: string): Promise<void>;
|
|
5
5
|
export declare function enhanceCoverageProvider(vitest: Vitest, coverageMaps: any[]): Promise<void>;
|
|
6
|
-
export declare function getBaseCoverageProvider(): Promise<BaseCoverageProvider
|
|
6
|
+
export declare function getBaseCoverageProvider(): Promise<BaseCoverageProvider>;
|