lambder 1.0.121 → 1.0.123
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/Readme.md +21 -17
- package/{deploy.sh → deploy} +5 -0
- package/dist/Lambder.d.ts +10 -2
- package/dist/Lambder.js +18 -11
- package/dist/LambderCaller.js +5 -0
- package/dist/LambderResolver.d.ts +3 -1
- package/dist/LambderResolver.js +2 -2
- package/dist/LambderResponseBuilder.d.ts +8 -1
- package/dist/LambderResponseBuilder.js +31 -1
- package/dist/LambderSessionController.js +6 -18
- package/package.json +1 -1
- package/src/Lambder.ts +31 -14
- package/src/LambderCaller.ts +5 -0
- package/src/LambderResolver.ts +5 -3
- package/src/LambderResponseBuilder.ts +32 -2
- package/src/LambderSessionController.ts +6 -18
package/Readme.md
CHANGED
|
@@ -37,8 +37,8 @@ const lambder = new Lambder({
|
|
|
37
37
|
|
|
38
38
|
// Enable session
|
|
39
39
|
lambder.enableDdbSession({
|
|
40
|
-
tableName: "website-session",
|
|
41
|
-
tableRegion: "us-east-1",
|
|
40
|
+
tableName: "website-session", // DynamoDB Table Name
|
|
41
|
+
tableRegion: "us-east-1", // DynamoDB Table Region
|
|
42
42
|
sessionSalt: "8p6Vt+4b1w3N8d/dcJ47QF3DRkp9koFg0G" // Change salt
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -221,13 +221,22 @@ lambder.addModule(async (lambder: Lambder): Promise<void> => {
|
|
|
221
221
|
You can enable session tracking by:
|
|
222
222
|
|
|
223
223
|
```typescript
|
|
224
|
-
// Enable session
|
|
224
|
+
// Enable sessions using a dynamodb session table
|
|
225
225
|
lambder.enableDdbSession({
|
|
226
|
-
tableName: "website-session",
|
|
227
|
-
tableRegion: "us-east-1",
|
|
226
|
+
tableName: "website-session", // DynamoDB Table Name
|
|
227
|
+
tableRegion: "us-east-1", // DynamoDB Table Region
|
|
228
228
|
sessionSalt: "8p6Vt+4b1w3N8d/dcJ47QF3DRkp9koFg0G" // Change salt
|
|
229
229
|
});
|
|
230
230
|
```
|
|
231
|
+
#### DynamoDB Session Table Structure:
|
|
232
|
+
|
|
233
|
+
Session system is enabled by storing data in a DynamoDB session table.
|
|
234
|
+
|
|
235
|
+
- Primary Key: "pk"
|
|
236
|
+
- Sort Key: "sk"
|
|
237
|
+
- TTL Key: "expiresAt" (optional)
|
|
238
|
+
|
|
239
|
+
#### Session Controller
|
|
231
240
|
|
|
232
241
|
After you enable the session, you can access to the session controller:
|
|
233
242
|
|
|
@@ -235,26 +244,26 @@ After you enable the session, you can access to the session controller:
|
|
|
235
244
|
// Create session controller:
|
|
236
245
|
const sessionController = lambder.getSessionController(ctx);
|
|
237
246
|
|
|
238
|
-
|
|
247
|
+
// Type for sessionController
|
|
239
248
|
sessionController: {
|
|
240
|
-
async createSession(sessionKey, data, ttlInSeconds):
|
|
249
|
+
async createSession(sessionKey, data, ttlInSeconds): session
|
|
241
250
|
// Starts a new session and persists the session data to DDB.
|
|
242
251
|
|
|
243
|
-
async fetchSession():
|
|
252
|
+
async fetchSession(): session
|
|
244
253
|
// Fetch and validate if there is an existing session
|
|
245
254
|
// This is automatically done for addSessionRoute and addSessionApi
|
|
246
255
|
// Throws if session not found
|
|
247
256
|
|
|
248
|
-
async fetchSessionIfExists():
|
|
257
|
+
async fetchSessionIfExists(): session|null
|
|
249
258
|
// Runs fetchSession and returns the session if found. Otherwise return null
|
|
250
259
|
|
|
251
|
-
async updateSessionData(updatedData):
|
|
260
|
+
async updateSessionData(updatedData): updatedSession
|
|
252
261
|
// Updates the active sessions data and persist it to ddb.
|
|
253
262
|
|
|
254
|
-
async endSession():
|
|
263
|
+
async endSession():
|
|
255
264
|
// End session and delete from DDB.
|
|
256
265
|
|
|
257
|
-
async endSessionAll():
|
|
266
|
+
async endSessionAll():
|
|
258
267
|
// Ends and deletes all registered sessions for this sessionKey across all devices
|
|
259
268
|
}
|
|
260
269
|
*/
|
|
@@ -474,13 +483,8 @@ const loadPageData = async () => {
|
|
|
474
483
|
console.error("Failed to fetch page data:", error);
|
|
475
484
|
}
|
|
476
485
|
};
|
|
477
|
-
|
|
478
486
|
```
|
|
479
487
|
|
|
480
|
-
## Advanced Configuration
|
|
481
|
-
|
|
482
|
-
Lambder is designed to be flexible and extensible, allowing for customized behaviors through hooks and custom error handling mechanisms.
|
|
483
|
-
|
|
484
488
|
## Contributing
|
|
485
489
|
|
|
486
490
|
Contributions are welcome! Especially for documentation. If you have an idea for an improvement or have found a bug, please open an issue or submit a pull request.
|
package/{deploy.sh → deploy}
RENAMED
package/dist/Lambder.d.ts
CHANGED
|
@@ -20,7 +20,15 @@ export type LambderRenderContext = {
|
|
|
20
20
|
lambdaContext: Context;
|
|
21
21
|
_otherInternal: {
|
|
22
22
|
isApiCall: boolean;
|
|
23
|
-
|
|
23
|
+
setHeaderFnAccumulator: {
|
|
24
|
+
key: string;
|
|
25
|
+
value: string | string[];
|
|
26
|
+
}[];
|
|
27
|
+
addHeaderFnAccumulator: {
|
|
28
|
+
key: string;
|
|
29
|
+
value: string;
|
|
30
|
+
}[];
|
|
31
|
+
logToApiResponseAccumulator: any[];
|
|
24
32
|
};
|
|
25
33
|
};
|
|
26
34
|
type LambderModuleFunction = (lambderInstance: Lambder) => void | Promise<void>;
|
|
@@ -30,7 +38,7 @@ type HookCreatedFunction = (lambderInstance: Lambder) => Promise<void>;
|
|
|
30
38
|
type HookBeforeRenderFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => LambderRenderContext | Error | Promise<LambderRenderContext | Error>;
|
|
31
39
|
type HookAfterRenderFunction = (ctx: LambderRenderContext, resolver: LambderResolver, response: LambderResolverResponse) => LambderResolverResponse | Error | Promise<LambderResolverResponse | Error>;
|
|
32
40
|
type HookFallbackFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => void | Promise<void>;
|
|
33
|
-
type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext | null, response: LambderResponseBuilder) => LambderResolverResponse | Promise<LambderResolverResponse>;
|
|
41
|
+
type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext | null, response: LambderResponseBuilder, logListToApiResponse?: any[]) => LambderResolverResponse | Promise<LambderResolverResponse>;
|
|
34
42
|
type RouteFallbackHandlerFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
|
|
35
43
|
type ApiFallbackHandlerFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
|
|
36
44
|
export declare const createContext: (event: APIGatewayProxyEvent, lambdaContext: Context, apiPath: string) => LambderRenderContext;
|
package/dist/Lambder.js
CHANGED
|
@@ -36,7 +36,12 @@ export const createContext = (event, lambdaContext, apiPath) => {
|
|
|
36
36
|
get, post, cookie,
|
|
37
37
|
apiName, apiPayload,
|
|
38
38
|
headers, session, lambdaContext,
|
|
39
|
-
_otherInternal: {
|
|
39
|
+
_otherInternal: {
|
|
40
|
+
isApiCall,
|
|
41
|
+
setHeaderFnAccumulator: [],
|
|
42
|
+
addHeaderFnAccumulator: [],
|
|
43
|
+
logToApiResponseAccumulator: [],
|
|
44
|
+
}
|
|
40
45
|
};
|
|
41
46
|
};
|
|
42
47
|
export default class Lambder {
|
|
@@ -207,13 +212,13 @@ export default class Lambder {
|
|
|
207
212
|
});
|
|
208
213
|
}
|
|
209
214
|
;
|
|
210
|
-
getResolver(resolve, reject) {
|
|
215
|
+
getResolver(ctx, resolve, reject) {
|
|
211
216
|
return new LambderResolver({
|
|
212
217
|
isCorsEnabled: this.isCorsEnabled,
|
|
213
218
|
publicPath: this.publicPath,
|
|
214
219
|
apiVersion: this.apiVersion,
|
|
215
220
|
lambderUtils: this.utils,
|
|
216
|
-
resolve, reject
|
|
221
|
+
ctx, resolve, reject
|
|
217
222
|
});
|
|
218
223
|
}
|
|
219
224
|
;
|
|
@@ -224,7 +229,7 @@ export default class Lambder {
|
|
|
224
229
|
eventRenderContext = ctx;
|
|
225
230
|
return await new Promise(async (resolve, reject) => {
|
|
226
231
|
try {
|
|
227
|
-
const resolver = this.getResolver(resolve, reject);
|
|
232
|
+
const resolver = this.getResolver(ctx, resolve, reject);
|
|
228
233
|
if (ctx.method === "OPTIONS")
|
|
229
234
|
return resolver.cors();
|
|
230
235
|
const firstMatchedAction = this.actionList.find(action => action.conditionFn(ctx));
|
|
@@ -247,12 +252,14 @@ export default class Lambder {
|
|
|
247
252
|
}
|
|
248
253
|
response = hookResponse;
|
|
249
254
|
}
|
|
250
|
-
// Apply
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
// Apply setHeader, addHeader values.
|
|
256
|
+
response.multiValueHeaders = response.multiValueHeaders || {};
|
|
257
|
+
for (const header of ctx._otherInternal.setHeaderFnAccumulator) {
|
|
258
|
+
response.multiValueHeaders[header.key] = Array.isArray(header.value) ? header.value : [header.value];
|
|
259
|
+
}
|
|
260
|
+
for (const header of ctx._otherInternal.addHeaderFnAccumulator) {
|
|
261
|
+
response.multiValueHeaders[header.key] = response.multiValueHeaders[header.key] || [];
|
|
262
|
+
response.multiValueHeaders[header.key].push(header.value);
|
|
256
263
|
}
|
|
257
264
|
resolve(response);
|
|
258
265
|
}
|
|
@@ -270,7 +277,7 @@ export default class Lambder {
|
|
|
270
277
|
if (this.globalErrorHandler) {
|
|
271
278
|
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
272
279
|
const responseBuilder = this.getResponseBuilder();
|
|
273
|
-
return this.globalErrorHandler(wrappedError, eventRenderContext, responseBuilder);
|
|
280
|
+
return this.globalErrorHandler(wrappedError, eventRenderContext, responseBuilder, eventRenderContext?._otherInternal.logToApiResponseAccumulator);
|
|
274
281
|
}
|
|
275
282
|
return { statusCode: 500, body: "Internal Server Error.", };
|
|
276
283
|
}
|
package/dist/LambderCaller.js
CHANGED
|
@@ -65,6 +65,11 @@ export default class LambderCaller {
|
|
|
65
65
|
activeFetchList: this.fetchTrackerList.filter(v => !v.done),
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
|
+
if (data && data.logList?.length) {
|
|
69
|
+
for (const record of data.logList) {
|
|
70
|
+
console.log("LogToApiResponse:", record);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
68
73
|
if (data && data.versionExpired) {
|
|
69
74
|
if (this.versionExpiredHandler) {
|
|
70
75
|
await this.versionExpiredHandler();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LambderRenderContext } from "./Lambder.js";
|
|
1
2
|
import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
|
|
2
3
|
import LambderUtils from "./LambderUtils.js";
|
|
3
4
|
type MethodType<T, M extends keyof T> = T[M] extends (...args: any[]) => any ? T[M] : never;
|
|
@@ -19,11 +20,12 @@ export default class LambderResolver extends LambderResponseBuilder {
|
|
|
19
20
|
resolve: (response: LambderResolverResponse) => void;
|
|
20
21
|
reject: (err: Error) => void;
|
|
21
22
|
die: DieResolverMethods;
|
|
22
|
-
constructor({ isCorsEnabled, publicPath, apiVersion, lambderUtils, resolve, reject }: {
|
|
23
|
+
constructor({ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, resolve, reject }: {
|
|
23
24
|
isCorsEnabled: boolean;
|
|
24
25
|
publicPath: string;
|
|
25
26
|
apiVersion?: string | null;
|
|
26
27
|
lambderUtils: LambderUtils;
|
|
28
|
+
ctx: LambderRenderContext;
|
|
27
29
|
resolve: (response: LambderResolverResponse) => void;
|
|
28
30
|
reject: (err: Error) => void;
|
|
29
31
|
});
|
package/dist/LambderResolver.js
CHANGED
|
@@ -3,8 +3,8 @@ export default class LambderResolver extends LambderResponseBuilder {
|
|
|
3
3
|
resolve;
|
|
4
4
|
reject;
|
|
5
5
|
die;
|
|
6
|
-
constructor({ isCorsEnabled, publicPath, apiVersion, lambderUtils, resolve, reject }) {
|
|
7
|
-
super({ isCorsEnabled, publicPath, apiVersion, lambderUtils });
|
|
6
|
+
constructor({ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, resolve, reject }) {
|
|
7
|
+
super({ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, });
|
|
8
8
|
this.resolve = resolve;
|
|
9
9
|
this.reject = reject;
|
|
10
10
|
this.die = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import LambderUtils from "./LambderUtils.js";
|
|
2
|
+
import { LambderRenderContext } from "./Lambder.js";
|
|
2
3
|
export type LambderResolverResponse = {
|
|
3
4
|
statusCode: number;
|
|
4
5
|
multiValueHeaders?: Record<string, string[]>;
|
|
@@ -11,6 +12,7 @@ export type LambderApiResponseConfig = {
|
|
|
11
12
|
notAuthorized?: boolean;
|
|
12
13
|
message?: any;
|
|
13
14
|
errorMessage?: any;
|
|
15
|
+
logList?: any[];
|
|
14
16
|
};
|
|
15
17
|
export type LambderApiResponse<T> = LambderApiResponseConfig & {
|
|
16
18
|
payload?: T | null;
|
|
@@ -20,14 +22,19 @@ export default class LambderResponseBuilder {
|
|
|
20
22
|
private publicPath;
|
|
21
23
|
private apiVersion;
|
|
22
24
|
private lambderUtils;
|
|
23
|
-
|
|
25
|
+
private ctx?;
|
|
26
|
+
constructor({ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx }: {
|
|
24
27
|
isCorsEnabled: boolean;
|
|
25
28
|
publicPath: string;
|
|
26
29
|
apiVersion?: string | null;
|
|
27
30
|
lambderUtils: LambderUtils;
|
|
31
|
+
ctx?: LambderRenderContext;
|
|
28
32
|
});
|
|
29
33
|
private readPublicFileSync;
|
|
30
34
|
private checkPublicFileExist;
|
|
35
|
+
addHeader(key: string, value: string): void;
|
|
36
|
+
setHeader(key: string, value: string | string[]): void;
|
|
37
|
+
logToApiResponse(input: any): void;
|
|
31
38
|
raw(param: LambderResolverResponse): LambderResolverResponse;
|
|
32
39
|
json(data: Record<string, any>, headers?: Record<string, string | string[]>): LambderResolverResponse;
|
|
33
40
|
xml(data: string): LambderResolverResponse;
|
|
@@ -13,11 +13,13 @@ export default class LambderResponseBuilder {
|
|
|
13
13
|
publicPath;
|
|
14
14
|
apiVersion;
|
|
15
15
|
lambderUtils;
|
|
16
|
-
|
|
16
|
+
ctx;
|
|
17
|
+
constructor({ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx }) {
|
|
17
18
|
this.isCorsEnabled = isCorsEnabled;
|
|
18
19
|
this.publicPath = publicPath;
|
|
19
20
|
this.apiVersion = apiVersion ?? null;
|
|
20
21
|
this.lambderUtils = lambderUtils;
|
|
22
|
+
this.ctx = ctx;
|
|
21
23
|
}
|
|
22
24
|
;
|
|
23
25
|
readPublicFileSync(filePath) {
|
|
@@ -40,6 +42,32 @@ export default class LambderResponseBuilder {
|
|
|
40
42
|
return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
|
|
41
43
|
}
|
|
42
44
|
;
|
|
45
|
+
addHeader(key, value) {
|
|
46
|
+
if (!this.ctx)
|
|
47
|
+
throw new Error(".addHeader function is not available within this hook");
|
|
48
|
+
else {
|
|
49
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key, value });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
;
|
|
53
|
+
setHeader(key, value) {
|
|
54
|
+
if (!this.ctx)
|
|
55
|
+
throw new Error(".setHeader function is not available within this hook");
|
|
56
|
+
else {
|
|
57
|
+
this.ctx._otherInternal.addHeaderFnAccumulator = this.ctx._otherInternal.addHeaderFnAccumulator
|
|
58
|
+
.filter(header => header.key !== key);
|
|
59
|
+
this.ctx._otherInternal.setHeaderFnAccumulator.push({ key, value });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
;
|
|
63
|
+
logToApiResponse(input) {
|
|
64
|
+
if (!this.ctx)
|
|
65
|
+
throw new Error(".logToResponse function is not available within this hook");
|
|
66
|
+
else {
|
|
67
|
+
this.ctx._otherInternal.logToApiResponseAccumulator.push(input);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
;
|
|
43
71
|
raw(param) {
|
|
44
72
|
return param;
|
|
45
73
|
}
|
|
@@ -145,6 +173,7 @@ export default class LambderResponseBuilder {
|
|
|
145
173
|
versionExpired: undefined, sessionExpired: undefined, notAuthorized: undefined,
|
|
146
174
|
message: null, errorMessage: null,
|
|
147
175
|
}, headers) {
|
|
176
|
+
const logList = this.ctx?._otherInternal?.logToApiResponseAccumulator;
|
|
148
177
|
return this.json({
|
|
149
178
|
apiVersion: this.apiVersion,
|
|
150
179
|
payload,
|
|
@@ -153,6 +182,7 @@ export default class LambderResponseBuilder {
|
|
|
153
182
|
...(notAuthorized ? { notAuthorized } : {}),
|
|
154
183
|
...(message ? { message } : {}),
|
|
155
184
|
...(errorMessage ? { errorMessage } : {}),
|
|
185
|
+
...(logList?.length ? { logList } : {}),
|
|
156
186
|
}, headers);
|
|
157
187
|
}
|
|
158
188
|
;
|
|
@@ -25,12 +25,8 @@ export default class LambderSessionController {
|
|
|
25
25
|
;
|
|
26
26
|
async createSession(sessionKey, data, ttlInSeconds) {
|
|
27
27
|
const session = await this.lambderSessionManager.createSession(sessionKey, data, ttlInSeconds);
|
|
28
|
-
this.ctx._otherInternal.
|
|
29
|
-
|
|
30
|
-
`${this.sessionTokenCookieKey}=${session.sessionToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure`,
|
|
31
|
-
`${this.sessionCsrfCookieKey}=${session.csrfToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure`,
|
|
32
|
-
],
|
|
33
|
-
};
|
|
28
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=${session.sessionToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
29
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=${session.csrfToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
34
30
|
this.ctx.session = session;
|
|
35
31
|
return this.ctx.session;
|
|
36
32
|
}
|
|
@@ -83,12 +79,8 @@ export default class LambderSessionController {
|
|
|
83
79
|
if (!this.ctx.session)
|
|
84
80
|
throw new Error("Session not found.");
|
|
85
81
|
await this.lambderSessionManager.deleteSession(this.ctx.session);
|
|
86
|
-
this.ctx._otherInternal.
|
|
87
|
-
|
|
88
|
-
`${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure`,
|
|
89
|
-
`${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure`,
|
|
90
|
-
],
|
|
91
|
-
};
|
|
82
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
83
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
92
84
|
this.ctx.session = null;
|
|
93
85
|
}
|
|
94
86
|
;
|
|
@@ -96,12 +88,8 @@ export default class LambderSessionController {
|
|
|
96
88
|
if (!this.ctx.session)
|
|
97
89
|
throw new Error("Session not found.");
|
|
98
90
|
await this.lambderSessionManager.deleteSessionAll(this.ctx.session);
|
|
99
|
-
this.ctx._otherInternal.
|
|
100
|
-
|
|
101
|
-
`${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure`,
|
|
102
|
-
`${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure`,
|
|
103
|
-
],
|
|
104
|
-
};
|
|
91
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
92
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
105
93
|
this.ctx.session = null;
|
|
106
94
|
}
|
|
107
95
|
;
|
package/package.json
CHANGED
package/src/Lambder.ts
CHANGED
|
@@ -26,7 +26,9 @@ export type LambderRenderContext = {
|
|
|
26
26
|
lambdaContext: Context;
|
|
27
27
|
_otherInternal: {
|
|
28
28
|
isApiCall: boolean,
|
|
29
|
-
|
|
29
|
+
setHeaderFnAccumulator: { key:string, value:string|string[] }[];
|
|
30
|
+
addHeaderFnAccumulator: { key:string, value:string }[];
|
|
31
|
+
logToApiResponseAccumulator: any[];
|
|
30
32
|
};
|
|
31
33
|
};
|
|
32
34
|
|
|
@@ -42,7 +44,7 @@ type HookBeforeRenderFunction = (ctx: LambderRenderContext, resolver: LambderRes
|
|
|
42
44
|
type HookAfterRenderFunction = (ctx: LambderRenderContext, resolver: LambderResolver, response: LambderResolverResponse) => LambderResolverResponse|Error|Promise<LambderResolverResponse|Error>;
|
|
43
45
|
type HookFallbackFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => void|Promise<void>;
|
|
44
46
|
|
|
45
|
-
type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext|null, response: LambderResponseBuilder) => LambderResolverResponse|Promise<LambderResolverResponse>;
|
|
47
|
+
type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext|null, response: LambderResponseBuilder, logListToApiResponse?: any[]) => LambderResolverResponse|Promise<LambderResolverResponse>;
|
|
46
48
|
type RouteFallbackHandlerFunction = (ctx:LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
|
|
47
49
|
type ApiFallbackHandlerFunction = (ctx:LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
|
|
48
50
|
|
|
@@ -79,7 +81,12 @@ export const createContext = (
|
|
|
79
81
|
get, post, cookie,
|
|
80
82
|
apiName, apiPayload,
|
|
81
83
|
headers, session, lambdaContext,
|
|
82
|
-
_otherInternal: {
|
|
84
|
+
_otherInternal: {
|
|
85
|
+
isApiCall,
|
|
86
|
+
setHeaderFnAccumulator: [],
|
|
87
|
+
addHeaderFnAccumulator: [],
|
|
88
|
+
logToApiResponseAccumulator: [],
|
|
89
|
+
}
|
|
83
90
|
};
|
|
84
91
|
}
|
|
85
92
|
|
|
@@ -296,13 +303,17 @@ export default class Lambder {
|
|
|
296
303
|
});
|
|
297
304
|
};
|
|
298
305
|
|
|
299
|
-
private getResolver(
|
|
306
|
+
private getResolver(
|
|
307
|
+
ctx: LambderRenderContext,
|
|
308
|
+
resolve: (response: LambderResolverResponse) => void,
|
|
309
|
+
reject: (err: Error) => void
|
|
310
|
+
){
|
|
300
311
|
return new LambderResolver({
|
|
301
312
|
isCorsEnabled: this.isCorsEnabled,
|
|
302
313
|
publicPath: this.publicPath,
|
|
303
314
|
apiVersion: this.apiVersion,
|
|
304
315
|
lambderUtils: this.utils,
|
|
305
|
-
resolve, reject
|
|
316
|
+
ctx, resolve, reject
|
|
306
317
|
});
|
|
307
318
|
};
|
|
308
319
|
|
|
@@ -313,7 +324,6 @@ export default class Lambder {
|
|
|
313
324
|
let eventRenderContext:LambderRenderContext|null = null;
|
|
314
325
|
|
|
315
326
|
try {
|
|
316
|
-
|
|
317
327
|
let ctx = createContext(event, lambdaContext, this.apiPath);
|
|
318
328
|
eventRenderContext = ctx;
|
|
319
329
|
|
|
@@ -322,7 +332,7 @@ export default class Lambder {
|
|
|
322
332
|
reject:(err: Error)=>void
|
|
323
333
|
)=> {
|
|
324
334
|
try{
|
|
325
|
-
const resolver = this.getResolver(resolve, reject);
|
|
335
|
+
const resolver = this.getResolver(ctx, resolve, reject);
|
|
326
336
|
if(ctx.method === "OPTIONS") return resolver.cors();
|
|
327
337
|
|
|
328
338
|
const firstMatchedAction = this.actionList.find(action => action.conditionFn(ctx));
|
|
@@ -341,12 +351,14 @@ export default class Lambder {
|
|
|
341
351
|
if(hookResponse instanceof Error){ throw hookResponse; }
|
|
342
352
|
response = hookResponse;
|
|
343
353
|
}
|
|
344
|
-
// Apply
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
// Apply setHeader, addHeader values.
|
|
355
|
+
response.multiValueHeaders = response.multiValueHeaders || {};
|
|
356
|
+
for(const header of ctx._otherInternal.setHeaderFnAccumulator){
|
|
357
|
+
response.multiValueHeaders[header.key] = Array.isArray(header.value) ? header.value: [header.value];
|
|
358
|
+
}
|
|
359
|
+
for(const header of ctx._otherInternal.addHeaderFnAccumulator){
|
|
360
|
+
response.multiValueHeaders[header.key] = response.multiValueHeaders[header.key] || [];
|
|
361
|
+
response.multiValueHeaders[header.key].push(header.value);
|
|
350
362
|
}
|
|
351
363
|
resolve(response);
|
|
352
364
|
}else{
|
|
@@ -362,7 +374,12 @@ export default class Lambder {
|
|
|
362
374
|
if(this.globalErrorHandler){
|
|
363
375
|
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
364
376
|
const responseBuilder = this.getResponseBuilder();
|
|
365
|
-
return this.globalErrorHandler(
|
|
377
|
+
return this.globalErrorHandler(
|
|
378
|
+
wrappedError,
|
|
379
|
+
eventRenderContext,
|
|
380
|
+
responseBuilder,
|
|
381
|
+
eventRenderContext?._otherInternal.logToApiResponseAccumulator
|
|
382
|
+
);
|
|
366
383
|
}
|
|
367
384
|
return { statusCode: 500, body: "Internal Server Error.", }
|
|
368
385
|
|
package/src/LambderCaller.ts
CHANGED
|
@@ -130,6 +130,11 @@ export default class LambderCaller {
|
|
|
130
130
|
activeFetchList: this.fetchTrackerList.filter(v=>!v.done),
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
|
+
if(data && data.logList?.length){
|
|
134
|
+
for(const record of data.logList){
|
|
135
|
+
console.log("LogToApiResponse:", record);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
133
138
|
if(data && data.versionExpired){
|
|
134
139
|
if(this.versionExpiredHandler){
|
|
135
140
|
await this.versionExpiredHandler();
|
package/src/LambderResolver.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LambderRenderContext } from "./Lambder.js";
|
|
1
2
|
import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
|
|
2
3
|
import LambderUtils from "./LambderUtils.js";
|
|
3
4
|
|
|
@@ -24,17 +25,18 @@ export default class LambderResolver extends LambderResponseBuilder {
|
|
|
24
25
|
public die: DieResolverMethods;
|
|
25
26
|
|
|
26
27
|
constructor(
|
|
27
|
-
{ isCorsEnabled, publicPath, apiVersion, lambderUtils, resolve, reject }:
|
|
28
|
+
{ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, resolve, reject }:
|
|
28
29
|
{
|
|
29
30
|
isCorsEnabled: boolean,
|
|
30
31
|
publicPath: string,
|
|
31
32
|
apiVersion?: string|null,
|
|
32
|
-
lambderUtils: LambderUtils
|
|
33
|
+
lambderUtils: LambderUtils,
|
|
34
|
+
ctx: LambderRenderContext,
|
|
33
35
|
resolve: (response: LambderResolverResponse) => void,
|
|
34
36
|
reject: (err: Error) => void,
|
|
35
37
|
}
|
|
36
38
|
){
|
|
37
|
-
super({ isCorsEnabled, publicPath, apiVersion, lambderUtils });
|
|
39
|
+
super({ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, });
|
|
38
40
|
this.resolve = resolve;
|
|
39
41
|
this.reject = reject;
|
|
40
42
|
|
|
@@ -3,6 +3,7 @@ import * as path from "path";
|
|
|
3
3
|
import ejs from "ejs";
|
|
4
4
|
import mimeTypeResolver from "mime-types";
|
|
5
5
|
import LambderUtils from "./LambderUtils.js";
|
|
6
|
+
import { LambderRenderContext } from "./Lambder.js";
|
|
6
7
|
|
|
7
8
|
const convertToMultiHeader = (
|
|
8
9
|
headers: Record<string, string|string[]> | undefined
|
|
@@ -29,6 +30,7 @@ export type LambderApiResponseConfig = {
|
|
|
29
30
|
notAuthorized?: boolean;
|
|
30
31
|
message?: any;
|
|
31
32
|
errorMessage?: any;
|
|
33
|
+
logList?: any[];
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
|
|
@@ -41,20 +43,23 @@ export default class LambderResponseBuilder {
|
|
|
41
43
|
private publicPath: string;
|
|
42
44
|
private apiVersion: string|null;
|
|
43
45
|
private lambderUtils: LambderUtils;
|
|
46
|
+
private ctx?: LambderRenderContext;
|
|
44
47
|
|
|
45
48
|
constructor(
|
|
46
|
-
{ isCorsEnabled, publicPath, apiVersion, lambderUtils }:
|
|
49
|
+
{ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx }:
|
|
47
50
|
{
|
|
48
51
|
isCorsEnabled: boolean,
|
|
49
52
|
publicPath: string,
|
|
50
53
|
apiVersion?: string|null,
|
|
51
|
-
lambderUtils: LambderUtils
|
|
54
|
+
lambderUtils: LambderUtils,
|
|
55
|
+
ctx?: LambderRenderContext,
|
|
52
56
|
}
|
|
53
57
|
){
|
|
54
58
|
this.isCorsEnabled = isCorsEnabled;
|
|
55
59
|
this.publicPath = publicPath;
|
|
56
60
|
this.apiVersion = apiVersion ?? null;
|
|
57
61
|
this.lambderUtils = lambderUtils;
|
|
62
|
+
this.ctx = ctx;
|
|
58
63
|
};
|
|
59
64
|
|
|
60
65
|
private readPublicFileSync(filePath: string){
|
|
@@ -73,6 +78,29 @@ export default class LambderResponseBuilder {
|
|
|
73
78
|
return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
|
|
74
79
|
};
|
|
75
80
|
|
|
81
|
+
addHeader(key: string, value: string){
|
|
82
|
+
if(!this.ctx) throw new Error(".addHeader function is not available within this hook");
|
|
83
|
+
else{
|
|
84
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key, value });
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
setHeader(key: string, value: string|string[]){
|
|
89
|
+
if(!this.ctx) throw new Error(".setHeader function is not available within this hook");
|
|
90
|
+
else{
|
|
91
|
+
this.ctx._otherInternal.addHeaderFnAccumulator = this.ctx._otherInternal.addHeaderFnAccumulator
|
|
92
|
+
.filter(header=>header.key !== key);
|
|
93
|
+
this.ctx._otherInternal.setHeaderFnAccumulator.push({ key, value });
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
logToApiResponse(input:any){
|
|
98
|
+
if(!this.ctx) throw new Error(".logToResponse function is not available within this hook");
|
|
99
|
+
else{
|
|
100
|
+
this.ctx._otherInternal.logToApiResponseAccumulator.push(input);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
76
104
|
|
|
77
105
|
raw(param: LambderResolverResponse){
|
|
78
106
|
return param;
|
|
@@ -217,6 +245,7 @@ export default class LambderResponseBuilder {
|
|
|
217
245
|
},
|
|
218
246
|
headers?: Record<string, string|string[]>,
|
|
219
247
|
): LambderResolverResponse {
|
|
248
|
+
const logList = this.ctx?._otherInternal?.logToApiResponseAccumulator;
|
|
220
249
|
return this.json({
|
|
221
250
|
apiVersion: this.apiVersion,
|
|
222
251
|
payload,
|
|
@@ -225,6 +254,7 @@ export default class LambderResponseBuilder {
|
|
|
225
254
|
...(notAuthorized ? {notAuthorized} : {}),
|
|
226
255
|
...(message ? {message} : {}),
|
|
227
256
|
...(errorMessage ? {errorMessage} : {}),
|
|
257
|
+
...(logList?.length ? {logList} : {}),
|
|
228
258
|
}, headers);
|
|
229
259
|
};
|
|
230
260
|
|
|
@@ -42,12 +42,8 @@ export default class LambderSessionController {
|
|
|
42
42
|
|
|
43
43
|
async createSession (sessionKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext> {
|
|
44
44
|
const session = await this.lambderSessionManager.createSession(sessionKey, data, ttlInSeconds);
|
|
45
|
-
this.ctx._otherInternal.
|
|
46
|
-
|
|
47
|
-
`${this.sessionTokenCookieKey}=${session.sessionToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure`,
|
|
48
|
-
`${this.sessionCsrfCookieKey}=${session.csrfToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure`,
|
|
49
|
-
],
|
|
50
|
-
};
|
|
45
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=${session.sessionToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
46
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=${session.csrfToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
51
47
|
this.ctx.session = session;
|
|
52
48
|
return this.ctx.session;
|
|
53
49
|
};
|
|
@@ -94,24 +90,16 @@ export default class LambderSessionController {
|
|
|
94
90
|
async endSession (){
|
|
95
91
|
if(!this.ctx.session) throw new Error("Session not found.");
|
|
96
92
|
await this.lambderSessionManager.deleteSession(this.ctx.session);
|
|
97
|
-
this.ctx._otherInternal.
|
|
98
|
-
|
|
99
|
-
`${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure`,
|
|
100
|
-
`${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure`,
|
|
101
|
-
],
|
|
102
|
-
};
|
|
93
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
94
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
103
95
|
this.ctx.session = null
|
|
104
96
|
};
|
|
105
97
|
|
|
106
98
|
async endSessionAll (){
|
|
107
99
|
if(!this.ctx.session) throw new Error("Session not found.");
|
|
108
100
|
await this.lambderSessionManager.deleteSessionAll(this.ctx.session);
|
|
109
|
-
this.ctx._otherInternal.
|
|
110
|
-
|
|
111
|
-
`${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure`,
|
|
112
|
-
`${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure`,
|
|
113
|
-
],
|
|
114
|
-
};
|
|
101
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
102
|
+
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
115
103
|
this.ctx.session = null
|
|
116
104
|
};
|
|
117
105
|
};
|