lambder 1.0.31 → 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 +3 -2
- package/dist/Lambder.js +8 -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 +11 -5
- 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,7 +36,7 @@ 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>;
|
|
38
|
-
type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext | null) => ResolverResponse | Promise<ResolverResponse>;
|
|
39
|
+
type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext | null, resolver: ResolverLight) => ResolverResponse | Promise<ResolverResponse>;
|
|
39
40
|
type RouteFallbackHandlerFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse;
|
|
40
41
|
type ApiFallbackHandlerFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse;
|
|
41
42
|
export declare const createContext: (event: APIGatewayProxyEvent, lambdaContext: Context) => RenderContext;
|
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;
|
|
@@ -148,6 +149,11 @@ export default class Lambder {
|
|
|
148
149
|
}
|
|
149
150
|
async render(event, lambdaContext) {
|
|
150
151
|
let eventRenderContext = null;
|
|
152
|
+
const resolverLight = new ResolverLight({
|
|
153
|
+
isCorsEnabled: this.isCorsEnabled,
|
|
154
|
+
publicPath: this.publicPath,
|
|
155
|
+
apiVersion: this.apiVersion,
|
|
156
|
+
});
|
|
151
157
|
try {
|
|
152
158
|
let renderContext = createContext(event, lambdaContext);
|
|
153
159
|
eventRenderContext = renderContext;
|
|
@@ -156,7 +162,7 @@ export default class Lambder {
|
|
|
156
162
|
isCorsEnabled: this.isCorsEnabled,
|
|
157
163
|
publicPath: this.publicPath,
|
|
158
164
|
apiVersion: this.apiVersion,
|
|
159
|
-
resolve, reject
|
|
165
|
+
resolve, reject
|
|
160
166
|
});
|
|
161
167
|
if (renderContext.method === "OPTIONS")
|
|
162
168
|
return resolver.cors();
|
|
@@ -189,7 +195,7 @@ export default class Lambder {
|
|
|
189
195
|
catch (err) {
|
|
190
196
|
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
191
197
|
if (this.globalErrorHandler) {
|
|
192
|
-
return this.globalErrorHandler(wrappedError, eventRenderContext);
|
|
198
|
+
return this.globalErrorHandler(wrappedError, eventRenderContext, resolverLight);
|
|
193
199
|
}
|
|
194
200
|
return { statusCode: 500, body: "Internal Server Error.", };
|
|
195
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,7 +37,7 @@ 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
|
|
|
39
|
-
type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext|null) => ResolverResponse|Promise<ResolverResponse>;
|
|
40
|
+
type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext|null, resolver: ResolverLight) => ResolverResponse|Promise<ResolverResponse>;
|
|
40
41
|
type RouteFallbackHandlerFunction = (renderContext:RenderContext, resolver: Resolver) => ResolverResponse;
|
|
41
42
|
type ApiFallbackHandlerFunction = (renderContext:RenderContext, resolver: Resolver) => ResolverResponse;
|
|
42
43
|
|
|
@@ -227,6 +228,11 @@ export default class Lambder {
|
|
|
227
228
|
lambdaContext: Context
|
|
228
229
|
): Promise<ResolverResponse>{
|
|
229
230
|
let eventRenderContext:RenderContext|null = null;
|
|
231
|
+
const resolverLight = new ResolverLight({
|
|
232
|
+
isCorsEnabled: this.isCorsEnabled,
|
|
233
|
+
publicPath: this.publicPath,
|
|
234
|
+
apiVersion: this.apiVersion,
|
|
235
|
+
})
|
|
230
236
|
|
|
231
237
|
try {
|
|
232
238
|
let renderContext = createContext(event, lambdaContext);
|
|
@@ -236,11 +242,11 @@ export default class Lambder {
|
|
|
236
242
|
resolve:(renderResult: ResolverResponse)=>void,
|
|
237
243
|
reject:(err: Error)=>void
|
|
238
244
|
)=> {
|
|
239
|
-
const resolver = new Resolver({
|
|
245
|
+
const resolver = new Resolver({
|
|
240
246
|
isCorsEnabled: this.isCorsEnabled,
|
|
241
247
|
publicPath: this.publicPath,
|
|
242
248
|
apiVersion: this.apiVersion,
|
|
243
|
-
resolve, reject
|
|
249
|
+
resolve, reject
|
|
244
250
|
});
|
|
245
251
|
if(renderContext.method === "OPTIONS") return resolver.cors();
|
|
246
252
|
|
|
@@ -271,7 +277,7 @@ export default class Lambder {
|
|
|
271
277
|
}catch(err){
|
|
272
278
|
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
273
279
|
if(this.globalErrorHandler){
|
|
274
|
-
return this.globalErrorHandler(wrappedError, eventRenderContext);
|
|
280
|
+
return this.globalErrorHandler(wrappedError, eventRenderContext, resolverLight);
|
|
275
281
|
}
|
|
276
282
|
return { statusCode: 500, body: "Internal Server Error.", }
|
|
277
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