lambder 1.0.29 → 1.0.36
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/Lambder.d.ts +8 -4
- package/dist/Lambder.js +10 -2
- package/dist/Resolver.d.ts +15 -60
- package/dist/Resolver.js +21 -142
- package/dist/ResolverLight.d.ts +54 -0
- package/dist/ResolverLight.js +137 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
- package/src/Lambder.ts +24 -12
- package/src/Resolver.ts +41 -203
- package/src/ResolverLight.ts +224 -0
- package/src/index.ts +2 -0
package/dist/Lambder.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
2
|
-
import Resolver
|
|
2
|
+
import Resolver from "./Resolver.js";
|
|
3
|
+
import ResolverLight, { ResolverResponse } from "./ResolverLight.js";
|
|
3
4
|
type DDBSession = {
|
|
4
5
|
userId: string;
|
|
5
6
|
userIdHash: string;
|
|
@@ -35,6 +36,9 @@ type HookBeforeRenderFunction = (renderContext: RenderContext, resolver: Resolve
|
|
|
35
36
|
type HookAfterRenderFunction = (renderContext: RenderContext, resolver: Resolver, renderResult: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
|
|
36
37
|
type HookFallbackFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse | Promise<ResolverResponse>;
|
|
37
38
|
type HookCompletedFunction = (renderResult: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
|
|
39
|
+
type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext | null, resolver: ResolverLight) => ResolverResponse | Promise<ResolverResponse>;
|
|
40
|
+
type RouteFallbackHandlerFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse;
|
|
41
|
+
type ApiFallbackHandlerFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse;
|
|
38
42
|
export declare const createContext: (event: APIGatewayProxyEvent, lambdaContext: Context) => RenderContext;
|
|
39
43
|
export default class Lambder {
|
|
40
44
|
private apiPath;
|
|
@@ -52,9 +56,9 @@ export default class Lambder {
|
|
|
52
56
|
apiVersion?: string;
|
|
53
57
|
isCorsEnabled?: boolean;
|
|
54
58
|
});
|
|
55
|
-
setRouteFallbackHandler(routeFallbackHandler:
|
|
56
|
-
setApiFallbackHandler(apiFallbackHandler:
|
|
57
|
-
setGlobalErrorHandler(globalErrorHandler:
|
|
59
|
+
setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction): void;
|
|
60
|
+
setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction): void;
|
|
61
|
+
setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): void;
|
|
58
62
|
private validateSession;
|
|
59
63
|
private handleNoMatchedAction;
|
|
60
64
|
addModule(moduleFn: Function): Promise<void>;
|
package/dist/Lambder.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import querystring from "querystring";
|
|
2
2
|
import cookieParser from "cookie";
|
|
3
3
|
import Resolver from "./Resolver.js";
|
|
4
|
+
import ResolverLight from "./ResolverLight.js";
|
|
4
5
|
export const createContext = (event, lambdaContext) => {
|
|
5
6
|
const host = event.headers.Host || event.headers.host || "";
|
|
6
7
|
const url = event.path;
|
|
@@ -147,14 +148,21 @@ export default class Lambder {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
async render(event, lambdaContext) {
|
|
151
|
+
let eventRenderContext = null;
|
|
152
|
+
const resolverLight = new ResolverLight({
|
|
153
|
+
isCorsEnabled: this.isCorsEnabled,
|
|
154
|
+
publicPath: this.publicPath,
|
|
155
|
+
apiVersion: this.apiVersion,
|
|
156
|
+
});
|
|
150
157
|
try {
|
|
151
158
|
let renderContext = createContext(event, lambdaContext);
|
|
159
|
+
eventRenderContext = renderContext;
|
|
152
160
|
return await new Promise(async (resolve, reject) => {
|
|
153
161
|
const resolver = new Resolver({
|
|
154
162
|
isCorsEnabled: this.isCorsEnabled,
|
|
155
163
|
publicPath: this.publicPath,
|
|
156
164
|
apiVersion: this.apiVersion,
|
|
157
|
-
resolve, reject
|
|
165
|
+
resolve, reject
|
|
158
166
|
});
|
|
159
167
|
if (renderContext.method === "OPTIONS")
|
|
160
168
|
return resolver.cors();
|
|
@@ -187,7 +195,7 @@ export default class Lambder {
|
|
|
187
195
|
catch (err) {
|
|
188
196
|
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
189
197
|
if (this.globalErrorHandler) {
|
|
190
|
-
return this.globalErrorHandler(wrappedError);
|
|
198
|
+
return this.globalErrorHandler(wrappedError, eventRenderContext, resolverLight);
|
|
191
199
|
}
|
|
192
200
|
return { statusCode: 500, body: "Internal Server Error.", };
|
|
193
201
|
}
|
package/dist/Resolver.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
statusCode: number;
|
|
3
|
-
multiValueHeaders?: {
|
|
4
|
-
[key: string]: string[];
|
|
5
|
-
};
|
|
6
|
-
body: string | null;
|
|
7
|
-
isBase64Encoded?: boolean;
|
|
8
|
-
[key: string]: any;
|
|
9
|
-
};
|
|
1
|
+
import ResolverLight, { ResolverResponse } from "./ResolverLight";
|
|
10
2
|
interface IResolverMethods {
|
|
11
3
|
raw(param: {
|
|
12
4
|
statusCode: number;
|
|
@@ -15,29 +7,29 @@ interface IResolverMethods {
|
|
|
15
7
|
multiValueHeaders: {
|
|
16
8
|
[key: string]: string[];
|
|
17
9
|
};
|
|
18
|
-
}):
|
|
10
|
+
}): ResolverResponse;
|
|
19
11
|
json(data: {
|
|
20
12
|
[key: string]: any;
|
|
21
13
|
}, headers?: {
|
|
22
14
|
[key: string]: string | string[];
|
|
23
|
-
}):
|
|
24
|
-
xml(data: string):
|
|
15
|
+
}): ResolverResponse;
|
|
16
|
+
xml(data: string): ResolverResponse;
|
|
25
17
|
html(data: string, headers?: {
|
|
26
18
|
[key: string]: string | string[];
|
|
27
|
-
}):
|
|
19
|
+
}): ResolverResponse;
|
|
28
20
|
status301(url: string, headers?: {
|
|
29
21
|
[key: string]: string | string[];
|
|
30
|
-
}):
|
|
22
|
+
}): ResolverResponse;
|
|
31
23
|
status404(data: string, headers?: {
|
|
32
24
|
[key: string]: string | string[];
|
|
33
|
-
}):
|
|
34
|
-
cors():
|
|
25
|
+
}): ResolverResponse;
|
|
26
|
+
cors(): ResolverResponse;
|
|
35
27
|
fileBase64(fileBase64: string, mimeType: string, headers?: {
|
|
36
28
|
[key: string]: string | string[];
|
|
37
|
-
}):
|
|
29
|
+
}): ResolverResponse;
|
|
38
30
|
file(filePath: string, headers?: {
|
|
39
31
|
[key: string]: string | string[];
|
|
40
|
-
}):
|
|
32
|
+
}): ResolverResponse;
|
|
41
33
|
api({ payload, versionExpired, sessionExpired, notAuthorized, message, errorMessage, ...other }: {
|
|
42
34
|
payload?: any;
|
|
43
35
|
versionExpired?: boolean;
|
|
@@ -47,14 +39,11 @@ interface IResolverMethods {
|
|
|
47
39
|
errorMessage?: any;
|
|
48
40
|
}, headers?: {
|
|
49
41
|
[key: string]: string | string[];
|
|
50
|
-
}):
|
|
42
|
+
}): ResolverResponse;
|
|
51
43
|
}
|
|
52
|
-
export default class Resolver {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
private apiVersion;
|
|
56
|
-
resolve: Function;
|
|
57
|
-
reject: Function;
|
|
44
|
+
export default class Resolver extends ResolverLight {
|
|
45
|
+
resolve: (renderResult: ResolverResponse) => void;
|
|
46
|
+
reject: (err: Error) => void;
|
|
58
47
|
die: IResolverMethods;
|
|
59
48
|
constructor({ isCorsEnabled, publicPath, apiVersion, resolve, reject }: {
|
|
60
49
|
isCorsEnabled: boolean;
|
|
@@ -63,40 +52,6 @@ export default class Resolver {
|
|
|
63
52
|
resolve: (renderResult: ResolverResponse) => void;
|
|
64
53
|
reject: (err: Error) => void;
|
|
65
54
|
});
|
|
66
|
-
private
|
|
67
|
-
private checkFileExist;
|
|
68
|
-
raw(param: ResolverResponse): ResolverResponse;
|
|
69
|
-
json(data: {
|
|
70
|
-
[key: string]: any;
|
|
71
|
-
}, headers?: {
|
|
72
|
-
[key: string]: string | string[];
|
|
73
|
-
}): ResolverResponse;
|
|
74
|
-
xml(data: string): ResolverResponse;
|
|
75
|
-
html(data: string, headers?: {
|
|
76
|
-
[key: string]: string | string[];
|
|
77
|
-
}): ResolverResponse;
|
|
78
|
-
status301(url: string, headers?: {
|
|
79
|
-
[key: string]: string | string[];
|
|
80
|
-
}): ResolverResponse;
|
|
81
|
-
status404(data: string, headers?: {
|
|
82
|
-
[key: string]: string | string[];
|
|
83
|
-
}): ResolverResponse;
|
|
84
|
-
cors(): ResolverResponse;
|
|
85
|
-
fileBase64(fileBase64: string, mimeType: string, headers?: {
|
|
86
|
-
[key: string]: string | string[];
|
|
87
|
-
}): ResolverResponse;
|
|
88
|
-
file(filePath: string, headers?: {
|
|
89
|
-
[key: string]: string | string[];
|
|
90
|
-
}, fallbackFilePath?: string): ResolverResponse;
|
|
91
|
-
api({ payload, versionExpired, sessionExpired, notAuthorized, message, errorMessage, ...other }: {
|
|
92
|
-
payload?: any;
|
|
93
|
-
versionExpired?: boolean;
|
|
94
|
-
sessionExpired?: boolean;
|
|
95
|
-
notAuthorized?: boolean;
|
|
96
|
-
message?: any;
|
|
97
|
-
errorMessage?: any;
|
|
98
|
-
}, headers?: {
|
|
99
|
-
[key: string]: string | string[];
|
|
100
|
-
}): ResolverResponse;
|
|
55
|
+
private autoResolve;
|
|
101
56
|
}
|
|
102
57
|
export {};
|
package/dist/Resolver.js
CHANGED
|
@@ -1,153 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import mimeTypeResolver from "mime-types";
|
|
4
|
-
const convertToMultiHeader = (headers) => Object.fromEntries(Object.entries(headers || {}).map(([k, v]) => [k, Array.isArray(v) ? v : [v]]));
|
|
5
|
-
const CORS_HEADERS = convertToMultiHeader({
|
|
6
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7
|
-
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
|
|
8
|
-
"Access-Control-Allow-Origin": "*",
|
|
9
|
-
"Access-Control-Allow-Methods": "OPTIONS,POST",
|
|
10
|
-
});
|
|
11
|
-
export default class Resolver {
|
|
12
|
-
isCorsEnabled;
|
|
13
|
-
publicPath;
|
|
14
|
-
apiVersion;
|
|
1
|
+
import ResolverLight from "./ResolverLight";
|
|
2
|
+
export default class Resolver extends ResolverLight {
|
|
15
3
|
resolve;
|
|
16
4
|
reject;
|
|
17
5
|
die;
|
|
18
6
|
constructor({ isCorsEnabled, publicPath, apiVersion, resolve, reject }) {
|
|
19
|
-
|
|
20
|
-
this.publicPath = publicPath;
|
|
21
|
-
this.apiVersion = apiVersion ?? null;
|
|
7
|
+
super({ isCorsEnabled, publicPath, apiVersion });
|
|
22
8
|
this.resolve = resolve;
|
|
23
9
|
this.reject = reject;
|
|
10
|
+
// Bind ResolverLight methods to `this` for `die` and automatically resolve
|
|
24
11
|
this.die = {
|
|
25
|
-
raw: this.raw,
|
|
26
|
-
json: this.json,
|
|
27
|
-
xml: this.xml,
|
|
28
|
-
html: this.html,
|
|
29
|
-
status301: this.status301,
|
|
30
|
-
status404: this.status404,
|
|
31
|
-
cors: this.cors,
|
|
32
|
-
fileBase64: this.fileBase64,
|
|
33
|
-
file: this.file,
|
|
34
|
-
api: this.api,
|
|
12
|
+
raw: this.autoResolve(this.raw),
|
|
13
|
+
json: this.autoResolve(this.json),
|
|
14
|
+
xml: this.autoResolve(this.xml),
|
|
15
|
+
html: this.autoResolve(this.html),
|
|
16
|
+
status301: this.autoResolve(this.status301),
|
|
17
|
+
status404: this.autoResolve(this.status404),
|
|
18
|
+
cors: this.autoResolve(this.cors),
|
|
19
|
+
fileBase64: this.autoResolve(this.fileBase64),
|
|
20
|
+
file: this.autoResolve(this.file),
|
|
21
|
+
api: this.autoResolve(this.api),
|
|
35
22
|
};
|
|
36
23
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return "forbiddenpath";
|
|
44
|
-
}
|
|
45
|
-
return fs.readFileSync(absolutePath);
|
|
46
|
-
}
|
|
47
|
-
;
|
|
48
|
-
checkFileExist(filePath) {
|
|
49
|
-
const publicPath = path.resolve(this.publicPath);
|
|
50
|
-
const absolutePath = path.join(this.publicPath, filePath);
|
|
51
|
-
console.log("checkFileExist", { filePath, publicPath, absolutePath });
|
|
52
|
-
if (!absolutePath.includes(publicPath)) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
|
|
56
|
-
}
|
|
57
|
-
;
|
|
58
|
-
raw(param) {
|
|
59
|
-
return param;
|
|
60
|
-
}
|
|
61
|
-
;
|
|
62
|
-
json(data, headers) {
|
|
63
|
-
return this.raw({
|
|
64
|
-
statusCode: 200,
|
|
65
|
-
multiValueHeaders: {
|
|
66
|
-
"Content-Type": ["application/json; charset=utf-8"],
|
|
67
|
-
...(this.isCorsEnabled ? CORS_HEADERS : {}),
|
|
68
|
-
...convertToMultiHeader(headers)
|
|
69
|
-
},
|
|
70
|
-
body: JSON.stringify(data),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
xml(data) {
|
|
74
|
-
return this.raw({
|
|
75
|
-
statusCode: 200,
|
|
76
|
-
isBase64Encoded: true,
|
|
77
|
-
multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"] },
|
|
78
|
-
body: Buffer.from(data).toString("base64"),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
;
|
|
82
|
-
html(data, headers) {
|
|
83
|
-
return this.raw({
|
|
84
|
-
statusCode: 200,
|
|
85
|
-
isBase64Encoded: true,
|
|
86
|
-
multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
|
|
87
|
-
body: Buffer.from(data).toString("base64"),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
;
|
|
91
|
-
status301(url, headers) {
|
|
92
|
-
return this.raw({
|
|
93
|
-
statusCode: 301,
|
|
94
|
-
multiValueHeaders: { "Location": [url], ...convertToMultiHeader(headers) },
|
|
95
|
-
body: null
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
;
|
|
99
|
-
status404(data, headers) {
|
|
100
|
-
return this.raw({
|
|
101
|
-
statusCode: 404,
|
|
102
|
-
isBase64Encoded: true,
|
|
103
|
-
multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
|
|
104
|
-
body: Buffer.from(data).toString("base64"),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
;
|
|
108
|
-
cors() {
|
|
109
|
-
return this.raw({
|
|
110
|
-
statusCode: 200,
|
|
111
|
-
multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS : {},
|
|
112
|
-
body: JSON.stringify(""),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
;
|
|
116
|
-
fileBase64(fileBase64, mimeType, headers) {
|
|
117
|
-
return this.raw({
|
|
118
|
-
statusCode: 200,
|
|
119
|
-
isBase64Encoded: true,
|
|
120
|
-
multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
|
|
121
|
-
body: fileBase64,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
;
|
|
125
|
-
file(filePath, headers, fallbackFilePath) {
|
|
126
|
-
if (!this.checkFileExist(filePath)) {
|
|
127
|
-
if (fallbackFilePath && this.checkFileExist(fallbackFilePath)) {
|
|
128
|
-
return this.file(fallbackFilePath, headers);
|
|
129
|
-
}
|
|
130
|
-
return this.json({ error: "File not found: " + filePath });
|
|
131
|
-
}
|
|
132
|
-
const mimeType = mimeTypeResolver.lookup(filePath);
|
|
133
|
-
const body = this.readFileSync(filePath);
|
|
134
|
-
const bodyBase64 = Buffer.from(body).toString("base64");
|
|
135
|
-
console.log("bodyBase64.length", bodyBase64.length);
|
|
136
|
-
return this.fileBase64(bodyBase64, mimeType || "", headers);
|
|
137
|
-
}
|
|
138
|
-
;
|
|
139
|
-
api({ payload, versionExpired, sessionExpired, notAuthorized, message = null, errorMessage = null, ...other }, headers) {
|
|
140
|
-
if (Object.keys(other).length)
|
|
141
|
-
console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
|
|
142
|
-
return this.json({
|
|
143
|
-
apiVersion: this.apiVersion,
|
|
144
|
-
payload,
|
|
145
|
-
...(versionExpired ? { versionExpired } : {}),
|
|
146
|
-
...(sessionExpired ? { sessionExpired } : {}),
|
|
147
|
-
...(notAuthorized ? { notAuthorized } : {}),
|
|
148
|
-
...(message ? { message } : {}),
|
|
149
|
-
...(errorMessage ? { errorMessage } : {}),
|
|
150
|
-
}, headers);
|
|
24
|
+
autoResolve(method) {
|
|
25
|
+
return (...args) => {
|
|
26
|
+
const result = method.apply(this, args);
|
|
27
|
+
this.resolve(result);
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
151
30
|
}
|
|
152
|
-
;
|
|
153
31
|
}
|
|
32
|
+
;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type ResolverResponse = {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
multiValueHeaders?: {
|
|
4
|
+
[key: string]: string[];
|
|
5
|
+
};
|
|
6
|
+
body: string | null;
|
|
7
|
+
isBase64Encoded?: boolean;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
export default class ResolverLight {
|
|
11
|
+
private isCorsEnabled;
|
|
12
|
+
private publicPath;
|
|
13
|
+
private apiVersion;
|
|
14
|
+
constructor({ isCorsEnabled, publicPath, apiVersion }: {
|
|
15
|
+
isCorsEnabled: boolean;
|
|
16
|
+
publicPath: string;
|
|
17
|
+
apiVersion?: string | null;
|
|
18
|
+
});
|
|
19
|
+
private readFileSync;
|
|
20
|
+
private checkFileExist;
|
|
21
|
+
raw(param: ResolverResponse): ResolverResponse;
|
|
22
|
+
json(data: {
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}, headers?: {
|
|
25
|
+
[key: string]: string | string[];
|
|
26
|
+
}): ResolverResponse;
|
|
27
|
+
xml(data: string): ResolverResponse;
|
|
28
|
+
html(data: string, headers?: {
|
|
29
|
+
[key: string]: string | string[];
|
|
30
|
+
}): ResolverResponse;
|
|
31
|
+
status301(url: string, headers?: {
|
|
32
|
+
[key: string]: string | string[];
|
|
33
|
+
}): ResolverResponse;
|
|
34
|
+
status404(data: string, headers?: {
|
|
35
|
+
[key: string]: string | string[];
|
|
36
|
+
}): ResolverResponse;
|
|
37
|
+
cors(): ResolverResponse;
|
|
38
|
+
fileBase64(fileBase64: string, mimeType: string, headers?: {
|
|
39
|
+
[key: string]: string | string[];
|
|
40
|
+
}): ResolverResponse;
|
|
41
|
+
file(filePath: string, headers?: {
|
|
42
|
+
[key: string]: string | string[];
|
|
43
|
+
}, fallbackFilePath?: string): ResolverResponse;
|
|
44
|
+
api({ payload, versionExpired, sessionExpired, notAuthorized, message, errorMessage, ...other }: {
|
|
45
|
+
payload?: any;
|
|
46
|
+
versionExpired?: boolean;
|
|
47
|
+
sessionExpired?: boolean;
|
|
48
|
+
notAuthorized?: boolean;
|
|
49
|
+
message?: any;
|
|
50
|
+
errorMessage?: any;
|
|
51
|
+
}, headers?: {
|
|
52
|
+
[key: string]: string | string[];
|
|
53
|
+
}): ResolverResponse;
|
|
54
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import mimeTypeResolver from "mime-types";
|
|
4
|
+
const convertToMultiHeader = (headers) => Object.fromEntries(Object.entries(headers || {}).map(([k, v]) => [k, Array.isArray(v) ? v : [v]]));
|
|
5
|
+
const CORS_HEADERS = convertToMultiHeader({
|
|
6
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7
|
+
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
|
|
8
|
+
"Access-Control-Allow-Origin": "*",
|
|
9
|
+
"Access-Control-Allow-Methods": "OPTIONS,POST",
|
|
10
|
+
});
|
|
11
|
+
export default class ResolverLight {
|
|
12
|
+
isCorsEnabled;
|
|
13
|
+
publicPath;
|
|
14
|
+
apiVersion;
|
|
15
|
+
constructor({ isCorsEnabled, publicPath, apiVersion }) {
|
|
16
|
+
this.isCorsEnabled = isCorsEnabled;
|
|
17
|
+
this.publicPath = publicPath;
|
|
18
|
+
this.apiVersion = apiVersion ?? null;
|
|
19
|
+
}
|
|
20
|
+
;
|
|
21
|
+
readFileSync(filePath) {
|
|
22
|
+
const publicPath = path.resolve(this.publicPath);
|
|
23
|
+
const absolutePath = path.join(publicPath, filePath);
|
|
24
|
+
console.log("readFileSync", { filePath, publicPath, absolutePath });
|
|
25
|
+
if (!absolutePath.includes(publicPath)) {
|
|
26
|
+
return "forbiddenpath";
|
|
27
|
+
}
|
|
28
|
+
return fs.readFileSync(absolutePath);
|
|
29
|
+
}
|
|
30
|
+
;
|
|
31
|
+
checkFileExist(filePath) {
|
|
32
|
+
const publicPath = path.resolve(this.publicPath);
|
|
33
|
+
const absolutePath = path.join(this.publicPath, filePath);
|
|
34
|
+
console.log("checkFileExist", { filePath, publicPath, absolutePath });
|
|
35
|
+
if (!absolutePath.includes(publicPath)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
|
|
39
|
+
}
|
|
40
|
+
;
|
|
41
|
+
raw(param) {
|
|
42
|
+
return param;
|
|
43
|
+
}
|
|
44
|
+
;
|
|
45
|
+
json(data, headers) {
|
|
46
|
+
return this.raw({
|
|
47
|
+
statusCode: 200,
|
|
48
|
+
multiValueHeaders: {
|
|
49
|
+
"Content-Type": ["application/json; charset=utf-8"],
|
|
50
|
+
...(this.isCorsEnabled ? CORS_HEADERS : {}),
|
|
51
|
+
...convertToMultiHeader(headers)
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify(data),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
xml(data) {
|
|
57
|
+
return this.raw({
|
|
58
|
+
statusCode: 200,
|
|
59
|
+
isBase64Encoded: true,
|
|
60
|
+
multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"] },
|
|
61
|
+
body: Buffer.from(data).toString("base64"),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
;
|
|
65
|
+
html(data, headers) {
|
|
66
|
+
return this.raw({
|
|
67
|
+
statusCode: 200,
|
|
68
|
+
isBase64Encoded: true,
|
|
69
|
+
multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
|
|
70
|
+
body: Buffer.from(data).toString("base64"),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
;
|
|
74
|
+
status301(url, headers) {
|
|
75
|
+
return this.raw({
|
|
76
|
+
statusCode: 301,
|
|
77
|
+
multiValueHeaders: { "Location": [url], ...convertToMultiHeader(headers) },
|
|
78
|
+
body: null
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
;
|
|
82
|
+
status404(data, headers) {
|
|
83
|
+
return this.raw({
|
|
84
|
+
statusCode: 404,
|
|
85
|
+
isBase64Encoded: true,
|
|
86
|
+
multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
|
|
87
|
+
body: Buffer.from(data).toString("base64"),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
;
|
|
91
|
+
cors() {
|
|
92
|
+
return this.raw({
|
|
93
|
+
statusCode: 200,
|
|
94
|
+
multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS : {},
|
|
95
|
+
body: JSON.stringify(""),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
;
|
|
99
|
+
fileBase64(fileBase64, mimeType, headers) {
|
|
100
|
+
return this.raw({
|
|
101
|
+
statusCode: 200,
|
|
102
|
+
isBase64Encoded: true,
|
|
103
|
+
multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
|
|
104
|
+
body: fileBase64,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
;
|
|
108
|
+
file(filePath, headers, fallbackFilePath) {
|
|
109
|
+
if (!this.checkFileExist(filePath)) {
|
|
110
|
+
if (fallbackFilePath && this.checkFileExist(fallbackFilePath)) {
|
|
111
|
+
return this.file(fallbackFilePath, headers);
|
|
112
|
+
}
|
|
113
|
+
return this.json({ error: "File not found: " + filePath });
|
|
114
|
+
}
|
|
115
|
+
const mimeType = mimeTypeResolver.lookup(filePath);
|
|
116
|
+
const body = this.readFileSync(filePath);
|
|
117
|
+
const bodyBase64 = Buffer.from(body).toString("base64");
|
|
118
|
+
console.log("bodyBase64.length", bodyBase64.length);
|
|
119
|
+
return this.fileBase64(bodyBase64, mimeType || "", headers);
|
|
120
|
+
}
|
|
121
|
+
;
|
|
122
|
+
api({ payload, versionExpired, sessionExpired, notAuthorized, message = null, errorMessage = null, ...other }, headers) {
|
|
123
|
+
if (Object.keys(other).length)
|
|
124
|
+
console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
|
|
125
|
+
return this.json({
|
|
126
|
+
apiVersion: this.apiVersion,
|
|
127
|
+
payload,
|
|
128
|
+
...(versionExpired ? { versionExpired } : {}),
|
|
129
|
+
...(sessionExpired ? { sessionExpired } : {}),
|
|
130
|
+
...(notAuthorized ? { notAuthorized } : {}),
|
|
131
|
+
...(message ? { message } : {}),
|
|
132
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
133
|
+
}, headers);
|
|
134
|
+
}
|
|
135
|
+
;
|
|
136
|
+
}
|
|
137
|
+
;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
package/src/Lambder.ts
CHANGED
|
@@ -2,7 +2,8 @@ import querystring from "querystring";
|
|
|
2
2
|
import cookieParser from "cookie";
|
|
3
3
|
|
|
4
4
|
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
5
|
-
import Resolver
|
|
5
|
+
import Resolver from "./Resolver.js";
|
|
6
|
+
import ResolverLight, { ResolverResponse } from "./ResolverLight.js";
|
|
6
7
|
|
|
7
8
|
type DDBSession = {
|
|
8
9
|
userId: string,
|
|
@@ -36,6 +37,10 @@ type HookAfterRenderFunction = (renderContext: RenderContext, resolver: Resolver
|
|
|
36
37
|
type HookFallbackFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse|Promise<ResolverResponse>;
|
|
37
38
|
type HookCompletedFunction = (renderResult: ResolverResponse) => ResolverResponse|Promise<ResolverResponse>;
|
|
38
39
|
|
|
40
|
+
type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext|null, resolver: ResolverLight) => ResolverResponse|Promise<ResolverResponse>;
|
|
41
|
+
type RouteFallbackHandlerFunction = (renderContext:RenderContext, resolver: Resolver) => ResolverResponse;
|
|
42
|
+
type ApiFallbackHandlerFunction = (renderContext:RenderContext, resolver: Resolver) => ResolverResponse;
|
|
43
|
+
|
|
39
44
|
type HookEventType = "created"|"beforeRender"|"afterRender"|"fallback"|"completed";
|
|
40
45
|
type HookListType = {
|
|
41
46
|
priority: number,
|
|
@@ -77,9 +82,9 @@ export default class Lambder {
|
|
|
77
82
|
"fallback": { priority: number, hookFn: HookFallbackFunction }[],
|
|
78
83
|
"completed": { priority: number, hookFn: HookCompletedFunction }[]
|
|
79
84
|
};
|
|
80
|
-
private globalErrorHandler:
|
|
81
|
-
private routeFallbackHandler:
|
|
82
|
-
private apiFallbackHandler:
|
|
85
|
+
private globalErrorHandler: GlobalErrorHandlerFunction|null = null;
|
|
86
|
+
private routeFallbackHandler: RouteFallbackHandlerFunction|null = null;
|
|
87
|
+
private apiFallbackHandler: ApiFallbackHandlerFunction|null = null;
|
|
83
88
|
|
|
84
89
|
constructor(
|
|
85
90
|
{ publicPath, apiPath, apiVersion, isCorsEnabled, }:
|
|
@@ -99,13 +104,13 @@ export default class Lambder {
|
|
|
99
104
|
};
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
setRouteFallbackHandler(routeFallbackHandler:
|
|
107
|
+
setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction){
|
|
103
108
|
this.routeFallbackHandler = routeFallbackHandler;
|
|
104
109
|
}
|
|
105
|
-
setApiFallbackHandler(apiFallbackHandler:
|
|
110
|
+
setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction){
|
|
106
111
|
this.apiFallbackHandler = apiFallbackHandler;
|
|
107
112
|
}
|
|
108
|
-
setGlobalErrorHandler(globalErrorHandler:
|
|
113
|
+
setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction){
|
|
109
114
|
this.globalErrorHandler = globalErrorHandler;
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -222,18 +227,26 @@ export default class Lambder {
|
|
|
222
227
|
event: APIGatewayProxyEvent,
|
|
223
228
|
lambdaContext: Context
|
|
224
229
|
): Promise<ResolverResponse>{
|
|
230
|
+
let eventRenderContext:RenderContext|null = null;
|
|
231
|
+
const resolverLight = new ResolverLight({
|
|
232
|
+
isCorsEnabled: this.isCorsEnabled,
|
|
233
|
+
publicPath: this.publicPath,
|
|
234
|
+
apiVersion: this.apiVersion,
|
|
235
|
+
})
|
|
236
|
+
|
|
225
237
|
try {
|
|
226
|
-
let renderContext
|
|
238
|
+
let renderContext = createContext(event, lambdaContext);
|
|
239
|
+
eventRenderContext = renderContext;
|
|
227
240
|
|
|
228
241
|
return await new Promise(async (
|
|
229
242
|
resolve:(renderResult: ResolverResponse)=>void,
|
|
230
243
|
reject:(err: Error)=>void
|
|
231
244
|
)=> {
|
|
232
|
-
const resolver = new Resolver({
|
|
245
|
+
const resolver = new Resolver({
|
|
233
246
|
isCorsEnabled: this.isCorsEnabled,
|
|
234
247
|
publicPath: this.publicPath,
|
|
235
248
|
apiVersion: this.apiVersion,
|
|
236
|
-
resolve, reject
|
|
249
|
+
resolve, reject
|
|
237
250
|
});
|
|
238
251
|
if(renderContext.method === "OPTIONS") return resolver.cors();
|
|
239
252
|
|
|
@@ -263,9 +276,8 @@ export default class Lambder {
|
|
|
263
276
|
});
|
|
264
277
|
}catch(err){
|
|
265
278
|
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
266
|
-
|
|
267
279
|
if(this.globalErrorHandler){
|
|
268
|
-
return this.globalErrorHandler(wrappedError);
|
|
280
|
+
return this.globalErrorHandler(wrappedError, eventRenderContext, resolverLight);
|
|
269
281
|
}
|
|
270
282
|
return { statusCode: 500, body: "Internal Server Error.", }
|
|
271
283
|
|
package/src/Resolver.ts
CHANGED
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import mimeTypeResolver from "mime-types";
|
|
4
|
-
|
|
5
|
-
const convertToMultiHeader = (
|
|
6
|
-
headers: { [key:string]: string|string[] } | undefined
|
|
7
|
-
):{ [key:string]: string[] } =>
|
|
8
|
-
Object.fromEntries(Object.entries(headers||{}).map(([k,v])=>[ k, Array.isArray(v) ? v : [v] ]));
|
|
9
|
-
|
|
10
|
-
const CORS_HEADERS = convertToMultiHeader({
|
|
11
|
-
"Access-Control-Allow-Credentials": "true",
|
|
12
|
-
"Access-Control-Allow-Headers" : "Origin, X-Requested-With, Content-Type, Accept",
|
|
13
|
-
"Access-Control-Allow-Origin": "*",
|
|
14
|
-
"Access-Control-Allow-Methods": "OPTIONS,POST",
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export type ResolverResponse = {
|
|
19
|
-
statusCode: number,
|
|
20
|
-
multiValueHeaders?: { [key:string]: string[] },
|
|
21
|
-
body: string | null,
|
|
22
|
-
isBase64Encoded?: boolean,
|
|
23
|
-
[key:string]: any,
|
|
24
|
-
};
|
|
1
|
+
import ResolverLight, { ResolverResponse } from "./ResolverLight";
|
|
25
2
|
|
|
26
3
|
|
|
27
4
|
interface IResolverMethods {
|
|
@@ -30,15 +7,15 @@ interface IResolverMethods {
|
|
|
30
7
|
isBase64Encoded?: boolean,
|
|
31
8
|
body: null | string,
|
|
32
9
|
multiValueHeaders: { [key: string]: string[] },
|
|
33
|
-
}):
|
|
34
|
-
json(data: { [key: string]: any }, headers?: { [key: string]: string | string[] }):
|
|
35
|
-
xml(data: string):
|
|
36
|
-
html(data: string, headers?: { [key: string]: string | string[] }):
|
|
37
|
-
status301(url: string, headers?: { [key: string]: string | string[] }):
|
|
38
|
-
status404(data: string, headers?: { [key: string]: string | string[] }):
|
|
39
|
-
cors():
|
|
40
|
-
fileBase64(fileBase64: string, mimeType: string, headers?: { [key: string]: string | string[] }):
|
|
41
|
-
file(filePath: string, headers?: { [key: string]: string | string[] }):
|
|
10
|
+
}): ResolverResponse;
|
|
11
|
+
json(data: { [key: string]: any }, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
12
|
+
xml(data: string): ResolverResponse;
|
|
13
|
+
html(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
14
|
+
status301(url: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
15
|
+
status404(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
16
|
+
cors(): ResolverResponse;
|
|
17
|
+
fileBase64(fileBase64: string, mimeType: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
18
|
+
file(filePath: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
42
19
|
api({
|
|
43
20
|
payload,
|
|
44
21
|
versionExpired, sessionExpired, notAuthorized,
|
|
@@ -53,17 +30,14 @@ interface IResolverMethods {
|
|
|
53
30
|
errorMessage?: any;
|
|
54
31
|
},
|
|
55
32
|
headers?: { [key:string]: string|string[] }
|
|
56
|
-
):
|
|
33
|
+
): ResolverResponse;
|
|
57
34
|
}
|
|
58
35
|
|
|
59
36
|
|
|
60
|
-
export default class Resolver {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
resolve: Function;
|
|
65
|
-
reject: Function;
|
|
66
|
-
die: IResolverMethods;
|
|
37
|
+
export default class Resolver extends ResolverLight {
|
|
38
|
+
public resolve: (renderResult: ResolverResponse) => void;
|
|
39
|
+
public reject: (err: Error) => void;
|
|
40
|
+
public die: IResolverMethods;
|
|
67
41
|
|
|
68
42
|
constructor(
|
|
69
43
|
{ isCorsEnabled, publicPath, apiVersion, resolve, reject }:
|
|
@@ -71,173 +45,37 @@ export default class Resolver {
|
|
|
71
45
|
isCorsEnabled: boolean,
|
|
72
46
|
publicPath: string,
|
|
73
47
|
apiVersion?: string|null,
|
|
74
|
-
resolve:(renderResult: ResolverResponse)=>void,
|
|
75
|
-
reject:(err: Error)=>void
|
|
48
|
+
resolve: (renderResult: ResolverResponse) => void,
|
|
49
|
+
reject: (err: Error) => void,
|
|
76
50
|
}
|
|
77
51
|
){
|
|
78
|
-
|
|
79
|
-
this.publicPath = publicPath;
|
|
80
|
-
this.apiVersion = apiVersion ?? null;
|
|
52
|
+
super({ isCorsEnabled, publicPath, apiVersion });
|
|
81
53
|
this.resolve = resolve;
|
|
82
54
|
this.reject = reject;
|
|
83
|
-
this.die = {
|
|
84
|
-
raw: this.raw,
|
|
85
|
-
json: this.json,
|
|
86
|
-
xml: this.xml,
|
|
87
|
-
html: this.html,
|
|
88
|
-
status301: this.status301,
|
|
89
|
-
status404: this.status404,
|
|
90
|
-
cors: this.cors,
|
|
91
|
-
fileBase64: this.fileBase64,
|
|
92
|
-
file: this.file,
|
|
93
|
-
api: this.api,
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
private readFileSync(filePath: string){
|
|
98
|
-
const publicPath = path.resolve(this.publicPath);
|
|
99
|
-
const absolutePath = path.join(publicPath, filePath);
|
|
100
|
-
console.log("readFileSync", { filePath, publicPath, absolutePath });
|
|
101
|
-
if(!absolutePath.includes(publicPath)){ return "forbiddenpath"; }
|
|
102
|
-
return fs.readFileSync(absolutePath);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
private checkFileExist(filePath: string){
|
|
106
|
-
const publicPath = path.resolve(this.publicPath);
|
|
107
|
-
const absolutePath = path.join(this.publicPath, filePath);
|
|
108
|
-
console.log("checkFileExist", { filePath, publicPath, absolutePath });
|
|
109
|
-
if(!absolutePath.includes(publicPath)){ return false; }
|
|
110
|
-
return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
|
|
111
|
-
};
|
|
112
55
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
...convertToMultiHeader(headers)
|
|
127
|
-
},
|
|
128
|
-
body: JSON.stringify(data),
|
|
129
|
-
});
|
|
56
|
+
// Bind ResolverLight methods to `this` for `die` and automatically resolve
|
|
57
|
+
this.die = {
|
|
58
|
+
raw: this.autoResolve(this.raw),
|
|
59
|
+
json: this.autoResolve(this.json),
|
|
60
|
+
xml: this.autoResolve(this.xml),
|
|
61
|
+
html: this.autoResolve(this.html),
|
|
62
|
+
status301: this.autoResolve(this.status301),
|
|
63
|
+
status404: this.autoResolve(this.status404),
|
|
64
|
+
cors: this.autoResolve(this.cors),
|
|
65
|
+
fileBase64: this.autoResolve(this.fileBase64),
|
|
66
|
+
file: this.autoResolve(this.file),
|
|
67
|
+
api: this.autoResolve(this.api),
|
|
68
|
+
};
|
|
130
69
|
}
|
|
131
70
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
html(
|
|
142
|
-
data: string,
|
|
143
|
-
headers?: { [key:string]: string|string[] },
|
|
144
|
-
):ResolverResponse{
|
|
145
|
-
return this.raw({
|
|
146
|
-
statusCode: 200,
|
|
147
|
-
isBase64Encoded: true,
|
|
148
|
-
multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
|
|
149
|
-
body: Buffer.from(data).toString("base64"),
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
status301(
|
|
154
|
-
url: string,
|
|
155
|
-
headers?: { [key:string]: string|string[] },
|
|
156
|
-
):ResolverResponse{
|
|
157
|
-
return this.raw({
|
|
158
|
-
statusCode: 301,
|
|
159
|
-
multiValueHeaders: { "Location" : [url], ...convertToMultiHeader(headers) },
|
|
160
|
-
body:null
|
|
161
|
-
});
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
status404(
|
|
165
|
-
data: string,
|
|
166
|
-
headers?: { [key:string]: string|string[] },
|
|
167
|
-
):ResolverResponse{
|
|
168
|
-
return this.raw({
|
|
169
|
-
statusCode: 404,
|
|
170
|
-
isBase64Encoded: true,
|
|
171
|
-
multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
|
|
172
|
-
body: Buffer.from(data).toString("base64"),
|
|
173
|
-
});
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
cors():ResolverResponse{
|
|
177
|
-
return this.raw({
|
|
178
|
-
statusCode: 200,
|
|
179
|
-
multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS: {},
|
|
180
|
-
body: JSON.stringify(""),
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
fileBase64 (
|
|
185
|
-
fileBase64: string,
|
|
186
|
-
mimeType: string,
|
|
187
|
-
headers?: { [key:string]: string|string[] },
|
|
188
|
-
):ResolverResponse{
|
|
189
|
-
return this.raw({
|
|
190
|
-
statusCode: 200,
|
|
191
|
-
isBase64Encoded: true,
|
|
192
|
-
multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
|
|
193
|
-
body: fileBase64,
|
|
194
|
-
});
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
file(
|
|
198
|
-
filePath: string,
|
|
199
|
-
headers?: { [key:string]: string|string[] },
|
|
200
|
-
fallbackFilePath?: string,
|
|
201
|
-
):ResolverResponse{
|
|
202
|
-
if(!this.checkFileExist(filePath)){
|
|
203
|
-
if(fallbackFilePath && this.checkFileExist(fallbackFilePath)){
|
|
204
|
-
return this.file(fallbackFilePath, headers);
|
|
205
|
-
}
|
|
206
|
-
return this.json({ error: "File not found: " + filePath })
|
|
207
|
-
}
|
|
208
|
-
const mimeType = mimeTypeResolver.lookup(filePath);
|
|
209
|
-
const body = this.readFileSync(filePath);
|
|
210
|
-
const bodyBase64 = Buffer.from(body).toString("base64");
|
|
211
|
-
console.log("bodyBase64.length",bodyBase64.length);
|
|
212
|
-
return this.fileBase64(bodyBase64, mimeType || "", headers);
|
|
213
|
-
};
|
|
71
|
+
private autoResolve<
|
|
72
|
+
T extends (...args: any[]) => ResolverResponse
|
|
73
|
+
>(method: T): (...funcArgs: Parameters<T>) => ResolverResponse {
|
|
74
|
+
return (...args: Parameters<T>): ResolverResponse => {
|
|
75
|
+
const result: ResolverResponse = method.apply(this, args);
|
|
76
|
+
this.resolve(result);
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
214
80
|
|
|
215
|
-
|
|
216
|
-
{
|
|
217
|
-
payload,
|
|
218
|
-
versionExpired, sessionExpired, notAuthorized,
|
|
219
|
-
message = null, errorMessage = null,
|
|
220
|
-
...other
|
|
221
|
-
}: {
|
|
222
|
-
payload?: any;
|
|
223
|
-
versionExpired?: boolean;
|
|
224
|
-
sessionExpired?: boolean;
|
|
225
|
-
notAuthorized?: boolean;
|
|
226
|
-
message?: any;
|
|
227
|
-
errorMessage?: any;
|
|
228
|
-
},
|
|
229
|
-
headers?: { [key:string]: string|string[] },
|
|
230
|
-
): ResolverResponse {
|
|
231
|
-
if(Object.keys(other).length) console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
|
|
232
|
-
return this.json({
|
|
233
|
-
apiVersion: this.apiVersion,
|
|
234
|
-
payload,
|
|
235
|
-
...(versionExpired ? {versionExpired} : {}),
|
|
236
|
-
...(sessionExpired ? {sessionExpired} : {}),
|
|
237
|
-
...(notAuthorized ? {notAuthorized} : {}),
|
|
238
|
-
...(message ? {message} : {}),
|
|
239
|
-
...(errorMessage ? {errorMessage} : {}),
|
|
240
|
-
}, headers);
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
}
|
|
81
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import mimeTypeResolver from "mime-types";
|
|
4
|
+
|
|
5
|
+
const convertToMultiHeader = (
|
|
6
|
+
headers: { [key:string]: string|string[] } | undefined
|
|
7
|
+
):{ [key:string]: string[] } =>
|
|
8
|
+
Object.fromEntries(Object.entries(headers||{}).map(([k,v])=>[ k, Array.isArray(v) ? v : [v] ]));
|
|
9
|
+
|
|
10
|
+
const CORS_HEADERS = convertToMultiHeader({
|
|
11
|
+
"Access-Control-Allow-Credentials": "true",
|
|
12
|
+
"Access-Control-Allow-Headers" : "Origin, X-Requested-With, Content-Type, Accept",
|
|
13
|
+
"Access-Control-Allow-Origin": "*",
|
|
14
|
+
"Access-Control-Allow-Methods": "OPTIONS,POST",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type ResolverResponse = {
|
|
18
|
+
statusCode: number,
|
|
19
|
+
multiValueHeaders?: { [key:string]: string[] },
|
|
20
|
+
body: string | null,
|
|
21
|
+
isBase64Encoded?: boolean,
|
|
22
|
+
[key:string]: any,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export default class ResolverLight {
|
|
27
|
+
private isCorsEnabled: boolean;
|
|
28
|
+
private publicPath: string;
|
|
29
|
+
private apiVersion: string|null;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
{ isCorsEnabled, publicPath, apiVersion }:
|
|
33
|
+
{
|
|
34
|
+
isCorsEnabled: boolean,
|
|
35
|
+
publicPath: string,
|
|
36
|
+
apiVersion?: string|null,
|
|
37
|
+
}
|
|
38
|
+
){
|
|
39
|
+
this.isCorsEnabled = isCorsEnabled;
|
|
40
|
+
this.publicPath = publicPath;
|
|
41
|
+
this.apiVersion = apiVersion ?? null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
private readFileSync(filePath: string){
|
|
45
|
+
const publicPath = path.resolve(this.publicPath);
|
|
46
|
+
const absolutePath = path.join(publicPath, filePath);
|
|
47
|
+
console.log("readFileSync", { filePath, publicPath, absolutePath });
|
|
48
|
+
if(!absolutePath.includes(publicPath)){ return "forbiddenpath"; }
|
|
49
|
+
return fs.readFileSync(absolutePath);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
private checkFileExist(filePath: string){
|
|
53
|
+
const publicPath = path.resolve(this.publicPath);
|
|
54
|
+
const absolutePath = path.join(this.publicPath, filePath);
|
|
55
|
+
console.log("checkFileExist", { filePath, publicPath, absolutePath });
|
|
56
|
+
if(!absolutePath.includes(publicPath)){ return false; }
|
|
57
|
+
return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
raw(param: ResolverResponse){
|
|
61
|
+
return param;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
json(
|
|
65
|
+
data: { [key:string]: any },
|
|
66
|
+
headers?: { [key:string]: string|string[] }
|
|
67
|
+
):ResolverResponse{
|
|
68
|
+
return this.raw({
|
|
69
|
+
statusCode: 200,
|
|
70
|
+
multiValueHeaders: {
|
|
71
|
+
"Content-Type": ["application/json; charset=utf-8"],
|
|
72
|
+
...(this.isCorsEnabled ? CORS_HEADERS : {}),
|
|
73
|
+
...convertToMultiHeader(headers)
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify(data),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
xml(data: string):ResolverResponse{
|
|
80
|
+
return this.raw({
|
|
81
|
+
statusCode: 200,
|
|
82
|
+
isBase64Encoded: true,
|
|
83
|
+
multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"]},
|
|
84
|
+
body: Buffer.from(data).toString("base64"),
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
html(
|
|
89
|
+
data: string,
|
|
90
|
+
headers?: { [key:string]: string|string[] },
|
|
91
|
+
):ResolverResponse{
|
|
92
|
+
return this.raw({
|
|
93
|
+
statusCode: 200,
|
|
94
|
+
isBase64Encoded: true,
|
|
95
|
+
multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
|
|
96
|
+
body: Buffer.from(data).toString("base64"),
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
status301(
|
|
101
|
+
url: string,
|
|
102
|
+
headers?: { [key:string]: string|string[] },
|
|
103
|
+
):ResolverResponse{
|
|
104
|
+
return this.raw({
|
|
105
|
+
statusCode: 301,
|
|
106
|
+
multiValueHeaders: { "Location" : [url], ...convertToMultiHeader(headers) },
|
|
107
|
+
body:null
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
status404(
|
|
112
|
+
data: string,
|
|
113
|
+
headers?: { [key:string]: string|string[] },
|
|
114
|
+
):ResolverResponse{
|
|
115
|
+
return this.raw({
|
|
116
|
+
statusCode: 404,
|
|
117
|
+
isBase64Encoded: true,
|
|
118
|
+
multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
|
|
119
|
+
body: Buffer.from(data).toString("base64"),
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
cors():ResolverResponse{
|
|
124
|
+
return this.raw({
|
|
125
|
+
statusCode: 200,
|
|
126
|
+
multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS: {},
|
|
127
|
+
body: JSON.stringify(""),
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
fileBase64 (
|
|
132
|
+
fileBase64: string,
|
|
133
|
+
mimeType: string,
|
|
134
|
+
headers?: { [key:string]: string|string[] },
|
|
135
|
+
):ResolverResponse{
|
|
136
|
+
return this.raw({
|
|
137
|
+
statusCode: 200,
|
|
138
|
+
isBase64Encoded: true,
|
|
139
|
+
multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
|
|
140
|
+
body: fileBase64,
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
file(
|
|
145
|
+
filePath: string,
|
|
146
|
+
headers?: { [key:string]: string|string[] },
|
|
147
|
+
fallbackFilePath?: string,
|
|
148
|
+
):ResolverResponse{
|
|
149
|
+
if(!this.checkFileExist(filePath)){
|
|
150
|
+
if(fallbackFilePath && this.checkFileExist(fallbackFilePath)){
|
|
151
|
+
return this.file(fallbackFilePath, headers);
|
|
152
|
+
}
|
|
153
|
+
return this.json({ error: "File not found: " + filePath })
|
|
154
|
+
}
|
|
155
|
+
const mimeType = mimeTypeResolver.lookup(filePath);
|
|
156
|
+
const body = this.readFileSync(filePath);
|
|
157
|
+
const bodyBase64 = Buffer.from(body).toString("base64");
|
|
158
|
+
console.log("bodyBase64.length",bodyBase64.length);
|
|
159
|
+
return this.fileBase64(bodyBase64, mimeType || "", headers);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
api(
|
|
163
|
+
{
|
|
164
|
+
payload,
|
|
165
|
+
versionExpired, sessionExpired, notAuthorized,
|
|
166
|
+
message = null, errorMessage = null,
|
|
167
|
+
...other
|
|
168
|
+
}: {
|
|
169
|
+
payload?: any;
|
|
170
|
+
versionExpired?: boolean;
|
|
171
|
+
sessionExpired?: boolean;
|
|
172
|
+
notAuthorized?: boolean;
|
|
173
|
+
message?: any;
|
|
174
|
+
errorMessage?: any;
|
|
175
|
+
},
|
|
176
|
+
headers?: { [key:string]: string|string[] },
|
|
177
|
+
): ResolverResponse {
|
|
178
|
+
if(Object.keys(other).length) console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
|
|
179
|
+
return this.json({
|
|
180
|
+
apiVersion: this.apiVersion,
|
|
181
|
+
payload,
|
|
182
|
+
...(versionExpired ? {versionExpired} : {}),
|
|
183
|
+
...(sessionExpired ? {sessionExpired} : {}),
|
|
184
|
+
...(notAuthorized ? {notAuthorized} : {}),
|
|
185
|
+
...(message ? {message} : {}),
|
|
186
|
+
...(errorMessage ? {errorMessage} : {}),
|
|
187
|
+
}, headers);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
interface IResolverMethods {
|
|
195
|
+
raw(param: {
|
|
196
|
+
statusCode: number,
|
|
197
|
+
isBase64Encoded?: boolean,
|
|
198
|
+
body: null | string,
|
|
199
|
+
multiValueHeaders: { [key: string]: string[] },
|
|
200
|
+
}): ResolverResponse;
|
|
201
|
+
json(data: { [key: string]: any }, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
202
|
+
xml(data: string): ResolverResponse;
|
|
203
|
+
html(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
204
|
+
status301(url: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
205
|
+
status404(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
206
|
+
cors(): ResolverResponse;
|
|
207
|
+
fileBase64(fileBase64: string, mimeType: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
208
|
+
file(filePath: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
|
|
209
|
+
api({
|
|
210
|
+
payload,
|
|
211
|
+
versionExpired, sessionExpired, notAuthorized,
|
|
212
|
+
message, errorMessage,
|
|
213
|
+
...other
|
|
214
|
+
}: {
|
|
215
|
+
payload?: any;
|
|
216
|
+
versionExpired?: boolean;
|
|
217
|
+
sessionExpired?: boolean;
|
|
218
|
+
notAuthorized?: boolean;
|
|
219
|
+
message?: any;
|
|
220
|
+
errorMessage?: any;
|
|
221
|
+
},
|
|
222
|
+
headers?: { [key:string]: string|string[] }
|
|
223
|
+
): ResolverResponse;
|
|
224
|
+
}
|
package/src/index.ts
CHANGED