aws-classify-server 0.1.4 → 0.1.6
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
CHANGED
|
@@ -274,7 +274,7 @@ plugins:
|
|
|
274
274
|
- serverless-dynamodb-local
|
|
275
275
|
- serverless-offline
|
|
276
276
|
```
|
|
277
|
-
The provider section details configuration the Serverless Framework handles. Many of the details are in included files to keep this simple and
|
|
277
|
+
The provider section details configuration the Serverless Framework handles. Many of the details are in included files to keep this simple and resilient to change
|
|
278
278
|
```
|
|
279
279
|
provider:
|
|
280
280
|
name: aws
|
|
@@ -285,6 +285,7 @@ provider:
|
|
|
285
285
|
websocket: true
|
|
286
286
|
environment:
|
|
287
287
|
APIG_ENDPOINT: ${file(${self:custom.yml}/apig.yml)}
|
|
288
|
+
DOMAIN: ${self:custom.domainName}
|
|
288
289
|
iam:
|
|
289
290
|
role:
|
|
290
291
|
statements:
|
|
@@ -4,7 +4,9 @@ export declare class ClassifyServerless {
|
|
|
4
4
|
logLevel: Partial<typeof EndPointsLogging>;
|
|
5
5
|
classDefs: Map<any, ClassDef<any, any>>;
|
|
6
6
|
requests: Map<any, any>;
|
|
7
|
+
expirationMinutes: number;
|
|
7
8
|
log: (_message: string) => void;
|
|
9
|
+
setExpirationMinutes(minutes: number): void;
|
|
8
10
|
setLogger(log: (_message: string) => void): void;
|
|
9
11
|
setLogLevel(logLevel: Partial<typeof EndPointsLogging>): void;
|
|
10
12
|
registerResponse<T>(responseClass: new () => T, authorizer?: (_endPoint: T, _method: string, _args: IArguments) => Promise<boolean>): void;
|
|
@@ -17,9 +19,12 @@ export declare class ClassifyServerless {
|
|
|
17
19
|
createRequestForSession<T>(sessionId: string, requestClass: new () => T): Promise<T>;
|
|
18
20
|
createResponse<T, R>(responseClass: new () => T, sessionId: string, callback: (_obj: T) => Promise<R>, classes?: any): Promise<R>;
|
|
19
21
|
getSessionsForUserId(userId: string): Promise<any[]>;
|
|
22
|
+
getSessions(): Promise<any[]>;
|
|
23
|
+
deleteSessionsForUserId(userId: string): Promise<void>;
|
|
24
|
+
deleteSessions(): Promise<void>;
|
|
20
25
|
}
|
|
21
26
|
export declare function getSessionData(sessionId: string, interfaceName?: string): Promise<Record<string, any> | undefined>;
|
|
22
|
-
export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string, userId?: string): Promise<string>;
|
|
27
|
+
export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string, userId?: string, expirationMinutes?: number): Promise<string>;
|
|
23
28
|
export declare const EndPointsLogging: {
|
|
24
29
|
create: boolean;
|
|
25
30
|
connect: boolean;
|
|
@@ -16,13 +16,18 @@ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
|
16
16
|
const client_apigatewaymanagementapi_1 = require("@aws-sdk/client-apigatewaymanagementapi");
|
|
17
17
|
const ddbClient = new client_dynamodb_1.DynamoDBClient({ region: process.env.DD_REGION, endpoint: process.env.DD_ENDPOINT });
|
|
18
18
|
const ddbDocClient = lib_dynamodb_1.DynamoDBDocument.from(ddbClient);
|
|
19
|
+
const updateDebounceInterval = 1000 * 60 * 10; // Don't update session unless 10 minutes have passed or data changed
|
|
19
20
|
class ClassifyServerless {
|
|
20
21
|
constructor() {
|
|
21
22
|
this.logLevel = {};
|
|
22
23
|
this.classDefs = new Map();
|
|
23
24
|
this.requests = new Map();
|
|
25
|
+
this.expirationMinutes = 60 * 24;
|
|
24
26
|
this.log = msg => console.log(msg);
|
|
25
27
|
}
|
|
28
|
+
setExpirationMinutes(minutes) {
|
|
29
|
+
this.expirationMinutes = minutes;
|
|
30
|
+
}
|
|
26
31
|
setLogger(log) {
|
|
27
32
|
this.log = log;
|
|
28
33
|
}
|
|
@@ -54,7 +59,7 @@ class ClassifyServerless {
|
|
|
54
59
|
const socketResult = yield getSessionData(sessionId, "");
|
|
55
60
|
if (!socketResult) {
|
|
56
61
|
sessionId = context.awsRequestId;
|
|
57
|
-
yield saveSessionData(sessionId, undefined, undefined, undefined);
|
|
62
|
+
yield saveSessionData(sessionId, undefined, undefined, undefined, undefined, this.expirationMinutes);
|
|
58
63
|
}
|
|
59
64
|
const lambdaResponse = {
|
|
60
65
|
data: process.env.IS_OFFLINE ? 'ws://localhost:3001' : `wss://${process.env.APIG_ENDPOINT}`,
|
|
@@ -105,8 +110,8 @@ class ClassifyServerless {
|
|
|
105
110
|
return (0, js_freeze_dry_1.serialize)(lambdaResponse, classes);
|
|
106
111
|
}
|
|
107
112
|
const updatedSessionData = (0, js_freeze_dry_1.serialize)(obj, classes);
|
|
108
|
-
if (!result || updatedSessionData !== result.sessionData)
|
|
109
|
-
sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined, obj.__userId__);
|
|
113
|
+
if (!result || updatedSessionData !== result.sessionData || (Date.now() > (result.updated + updateDebounceInterval)))
|
|
114
|
+
sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined, obj.__userId__, this.expirationMinutes);
|
|
110
115
|
// Formulate response
|
|
111
116
|
const lambdaResponse = {
|
|
112
117
|
data: fnResponse,
|
|
@@ -174,7 +179,7 @@ class ClassifyServerless {
|
|
|
174
179
|
catch (e) {
|
|
175
180
|
if (expressServer.logLevel.exceptions)
|
|
176
181
|
expressServer.log(e.message);
|
|
177
|
-
throw e;
|
|
182
|
+
throw new Error(`Unable to send message via Websocket ${e.message}`);
|
|
178
183
|
}
|
|
179
184
|
});
|
|
180
185
|
};
|
|
@@ -238,10 +243,10 @@ class ClassifyServerless {
|
|
|
238
243
|
this.log(`creating response for ${interfaceName} with sessionId=${sessionId} connectionId= ${obj.__connectionId__} sessionData=${sessionData}`);
|
|
239
244
|
const ret = yield callback(obj);
|
|
240
245
|
const updatedSessionData = (0, js_freeze_dry_1.serialize)(obj, classes);
|
|
241
|
-
if (updatedSessionData !== result.sessionData) {
|
|
246
|
+
if (updatedSessionData !== result.sessionData || (Date.now() > (result.updated + updateDebounceInterval))) {
|
|
242
247
|
if (this.logLevel.calls)
|
|
243
248
|
this.log(`saving session data for ${interfaceName} with sessionId=${sessionId} connectionId= ${obj.__connectionId__} sessionData=${updatedSessionData}`);
|
|
244
|
-
yield saveSessionData(sessionId, interfaceName, updatedSessionData, undefined);
|
|
249
|
+
yield saveSessionData(sessionId, interfaceName, updatedSessionData, undefined, undefined, this.expirationMinutes);
|
|
245
250
|
}
|
|
246
251
|
return ret;
|
|
247
252
|
});
|
|
@@ -263,8 +268,54 @@ class ClassifyServerless {
|
|
|
263
268
|
return [];
|
|
264
269
|
});
|
|
265
270
|
}
|
|
271
|
+
getSessions() {
|
|
272
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
const data = yield ddbDocClient.scan({
|
|
274
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
275
|
+
ProjectionExpression: `sessionId`
|
|
276
|
+
});
|
|
277
|
+
return data.Items ? data.Items.map(i => i.sessionId) : [];
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
deleteSessionsForUserId(userId) {
|
|
281
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
282
|
+
if (userId) {
|
|
283
|
+
const data = yield ddbDocClient.query({
|
|
284
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
285
|
+
KeyConditionExpression: 'userId = :userId',
|
|
286
|
+
IndexName: 'userId',
|
|
287
|
+
ExpressionAttributeValues: {
|
|
288
|
+
':userId': userId,
|
|
289
|
+
},
|
|
290
|
+
ProjectionExpression: `sessionId`
|
|
291
|
+
});
|
|
292
|
+
const keys = data.Items ? data.Items.map(i => i.sessionId) : [];
|
|
293
|
+
yield deleteSessionByKeys(keys);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
deleteSessions() {
|
|
298
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
299
|
+
const data = yield ddbDocClient.scan({
|
|
300
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
301
|
+
ProjectionExpression: `sessionId`
|
|
302
|
+
});
|
|
303
|
+
const keys = data.Items ? data.Items.map(i => i.sessionId) : [];
|
|
304
|
+
yield deleteSessionByKeys(keys);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
266
307
|
}
|
|
267
308
|
exports.ClassifyServerless = ClassifyServerless;
|
|
309
|
+
function deleteSessionByKeys(keys) {
|
|
310
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
+
for (let ix = 0; ix < keys.length; ++ix) {
|
|
312
|
+
yield ddbDocClient.delete({
|
|
313
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
314
|
+
Key: { sessionId: keys[ix] }
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
268
319
|
function getSessionData(sessionId, interfaceName = "") {
|
|
269
320
|
return __awaiter(this, void 0, void 0, function* () {
|
|
270
321
|
if (sessionId) {
|
|
@@ -272,7 +323,7 @@ function getSessionData(sessionId, interfaceName = "") {
|
|
|
272
323
|
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
273
324
|
Key: { sessionId },
|
|
274
325
|
ConsistentRead: true,
|
|
275
|
-
ProjectionExpression: `interface_${interfaceName}, connectionId, userId, updated`
|
|
326
|
+
ProjectionExpression: `interface_${interfaceName}, connectionId, userId, updated, expires`
|
|
276
327
|
});
|
|
277
328
|
return data.Item;
|
|
278
329
|
}
|
|
@@ -280,10 +331,16 @@ function getSessionData(sessionId, interfaceName = "") {
|
|
|
280
331
|
});
|
|
281
332
|
}
|
|
282
333
|
exports.getSessionData = getSessionData;
|
|
283
|
-
function saveSessionData(sessionId, interfaceName, sessionData, connectionId, userId) {
|
|
334
|
+
function saveSessionData(sessionId, interfaceName, sessionData, connectionId, userId, expirationMinutes) {
|
|
284
335
|
return __awaiter(this, void 0, void 0, function* () {
|
|
285
336
|
const updateExpressionComponents = ['updated = :time'];
|
|
286
337
|
const expressionAttributeValues = { ':time': new Date().getTime() };
|
|
338
|
+
if (typeof expirationMinutes === 'number') {
|
|
339
|
+
const expires = new Date();
|
|
340
|
+
expires.setMinutes(expires.getMinutes() + expirationMinutes);
|
|
341
|
+
updateExpressionComponents.push('expires = :expires');
|
|
342
|
+
expressionAttributeValues[':expires'] = Math.floor(expires.getTime() / 1000);
|
|
343
|
+
}
|
|
287
344
|
if (sessionData && interfaceName) {
|
|
288
345
|
updateExpressionComponents.push(`interface_${interfaceName} = :data`);
|
|
289
346
|
expressionAttributeValues[`:data`] = sessionData;
|
|
@@ -4,7 +4,9 @@ export declare class ClassifyServerless {
|
|
|
4
4
|
logLevel: Partial<typeof EndPointsLogging>;
|
|
5
5
|
classDefs: Map<any, ClassDef<any, any>>;
|
|
6
6
|
requests: Map<any, any>;
|
|
7
|
+
expirationMinutes: number;
|
|
7
8
|
log: (_message: string) => void;
|
|
9
|
+
setExpirationMinutes(minutes: number): void;
|
|
8
10
|
setLogger(log: (_message: string) => void): void;
|
|
9
11
|
setLogLevel(logLevel: Partial<typeof EndPointsLogging>): void;
|
|
10
12
|
registerResponse<T>(responseClass: new () => T, authorizer?: (_endPoint: T, _method: string, _args: IArguments) => Promise<boolean>): void;
|
|
@@ -17,9 +19,12 @@ export declare class ClassifyServerless {
|
|
|
17
19
|
createRequestForSession<T>(sessionId: string, requestClass: new () => T): Promise<T>;
|
|
18
20
|
createResponse<T, R>(responseClass: new () => T, sessionId: string, callback: (_obj: T) => Promise<R>, classes?: any): Promise<R>;
|
|
19
21
|
getSessionsForUserId(userId: string): Promise<any[]>;
|
|
22
|
+
getSessions(): Promise<any[]>;
|
|
23
|
+
deleteSessionsForUserId(userId: string): Promise<void>;
|
|
24
|
+
deleteSessions(): Promise<void>;
|
|
20
25
|
}
|
|
21
26
|
export declare function getSessionData(sessionId: string, interfaceName?: string): Promise<Record<string, any> | undefined>;
|
|
22
|
-
export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string, userId?: string): Promise<string>;
|
|
27
|
+
export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string, userId?: string, expirationMinutes?: number): Promise<string>;
|
|
23
28
|
export declare const EndPointsLogging: {
|
|
24
29
|
create: boolean;
|
|
25
30
|
connect: boolean;
|
|
@@ -13,13 +13,18 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
|
13
13
|
import { ApiGatewayManagementApiClient, PostToConnectionCommand } from "@aws-sdk/client-apigatewaymanagementapi";
|
|
14
14
|
const ddbClient = new DynamoDBClient({ region: process.env.DD_REGION, endpoint: process.env.DD_ENDPOINT });
|
|
15
15
|
const ddbDocClient = DynamoDBDocument.from(ddbClient);
|
|
16
|
+
const updateDebounceInterval = 1000 * 60 * 10; // Don't update session unless 10 minutes have passed or data changed
|
|
16
17
|
export class ClassifyServerless {
|
|
17
18
|
constructor() {
|
|
18
19
|
this.logLevel = {};
|
|
19
20
|
this.classDefs = new Map();
|
|
20
21
|
this.requests = new Map();
|
|
22
|
+
this.expirationMinutes = 60 * 24;
|
|
21
23
|
this.log = msg => console.log(msg);
|
|
22
24
|
}
|
|
25
|
+
setExpirationMinutes(minutes) {
|
|
26
|
+
this.expirationMinutes = minutes;
|
|
27
|
+
}
|
|
23
28
|
setLogger(log) {
|
|
24
29
|
this.log = log;
|
|
25
30
|
}
|
|
@@ -51,7 +56,7 @@ export class ClassifyServerless {
|
|
|
51
56
|
const socketResult = yield getSessionData(sessionId, "");
|
|
52
57
|
if (!socketResult) {
|
|
53
58
|
sessionId = context.awsRequestId;
|
|
54
|
-
yield saveSessionData(sessionId, undefined, undefined, undefined);
|
|
59
|
+
yield saveSessionData(sessionId, undefined, undefined, undefined, undefined, this.expirationMinutes);
|
|
55
60
|
}
|
|
56
61
|
const lambdaResponse = {
|
|
57
62
|
data: process.env.IS_OFFLINE ? 'ws://localhost:3001' : `wss://${process.env.APIG_ENDPOINT}`,
|
|
@@ -102,8 +107,8 @@ export class ClassifyServerless {
|
|
|
102
107
|
return serialize(lambdaResponse, classes);
|
|
103
108
|
}
|
|
104
109
|
const updatedSessionData = serialize(obj, classes);
|
|
105
|
-
if (!result || updatedSessionData !== result.sessionData)
|
|
106
|
-
sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined, obj.__userId__);
|
|
110
|
+
if (!result || updatedSessionData !== result.sessionData || (Date.now() > (result.updated + updateDebounceInterval)))
|
|
111
|
+
sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined, obj.__userId__, this.expirationMinutes);
|
|
107
112
|
// Formulate response
|
|
108
113
|
const lambdaResponse = {
|
|
109
114
|
data: fnResponse,
|
|
@@ -171,7 +176,7 @@ export class ClassifyServerless {
|
|
|
171
176
|
catch (e) {
|
|
172
177
|
if (expressServer.logLevel.exceptions)
|
|
173
178
|
expressServer.log(e.message);
|
|
174
|
-
throw e;
|
|
179
|
+
throw new Error(`Unable to send message via Websocket ${e.message}`);
|
|
175
180
|
}
|
|
176
181
|
});
|
|
177
182
|
};
|
|
@@ -235,10 +240,10 @@ export class ClassifyServerless {
|
|
|
235
240
|
this.log(`creating response for ${interfaceName} with sessionId=${sessionId} connectionId= ${obj.__connectionId__} sessionData=${sessionData}`);
|
|
236
241
|
const ret = yield callback(obj);
|
|
237
242
|
const updatedSessionData = serialize(obj, classes);
|
|
238
|
-
if (updatedSessionData !== result.sessionData) {
|
|
243
|
+
if (updatedSessionData !== result.sessionData || (Date.now() > (result.updated + updateDebounceInterval))) {
|
|
239
244
|
if (this.logLevel.calls)
|
|
240
245
|
this.log(`saving session data for ${interfaceName} with sessionId=${sessionId} connectionId= ${obj.__connectionId__} sessionData=${updatedSessionData}`);
|
|
241
|
-
yield saveSessionData(sessionId, interfaceName, updatedSessionData, undefined);
|
|
246
|
+
yield saveSessionData(sessionId, interfaceName, updatedSessionData, undefined, undefined, this.expirationMinutes);
|
|
242
247
|
}
|
|
243
248
|
return ret;
|
|
244
249
|
});
|
|
@@ -260,6 +265,52 @@ export class ClassifyServerless {
|
|
|
260
265
|
return [];
|
|
261
266
|
});
|
|
262
267
|
}
|
|
268
|
+
getSessions() {
|
|
269
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
270
|
+
const data = yield ddbDocClient.scan({
|
|
271
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
272
|
+
ProjectionExpression: `sessionId`
|
|
273
|
+
});
|
|
274
|
+
return data.Items ? data.Items.map(i => i.sessionId) : [];
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
deleteSessionsForUserId(userId) {
|
|
278
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
279
|
+
if (userId) {
|
|
280
|
+
const data = yield ddbDocClient.query({
|
|
281
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
282
|
+
KeyConditionExpression: 'userId = :userId',
|
|
283
|
+
IndexName: 'userId',
|
|
284
|
+
ExpressionAttributeValues: {
|
|
285
|
+
':userId': userId,
|
|
286
|
+
},
|
|
287
|
+
ProjectionExpression: `sessionId`
|
|
288
|
+
});
|
|
289
|
+
const keys = data.Items ? data.Items.map(i => i.sessionId) : [];
|
|
290
|
+
yield deleteSessionByKeys(keys);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
deleteSessions() {
|
|
295
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
const data = yield ddbDocClient.scan({
|
|
297
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
298
|
+
ProjectionExpression: `sessionId`
|
|
299
|
+
});
|
|
300
|
+
const keys = data.Items ? data.Items.map(i => i.sessionId) : [];
|
|
301
|
+
yield deleteSessionByKeys(keys);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function deleteSessionByKeys(keys) {
|
|
306
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
+
for (let ix = 0; ix < keys.length; ++ix) {
|
|
308
|
+
yield ddbDocClient.delete({
|
|
309
|
+
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
310
|
+
Key: { sessionId: keys[ix] }
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
});
|
|
263
314
|
}
|
|
264
315
|
export function getSessionData(sessionId, interfaceName = "") {
|
|
265
316
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -268,17 +319,23 @@ export function getSessionData(sessionId, interfaceName = "") {
|
|
|
268
319
|
TableName: `classifySessionStore.${process.env.DOMAIN}`,
|
|
269
320
|
Key: { sessionId },
|
|
270
321
|
ConsistentRead: true,
|
|
271
|
-
ProjectionExpression: `interface_${interfaceName}, connectionId, userId, updated`
|
|
322
|
+
ProjectionExpression: `interface_${interfaceName}, connectionId, userId, updated, expires`
|
|
272
323
|
});
|
|
273
324
|
return data.Item;
|
|
274
325
|
}
|
|
275
326
|
return undefined;
|
|
276
327
|
});
|
|
277
328
|
}
|
|
278
|
-
export function saveSessionData(sessionId, interfaceName, sessionData, connectionId, userId) {
|
|
329
|
+
export function saveSessionData(sessionId, interfaceName, sessionData, connectionId, userId, expirationMinutes) {
|
|
279
330
|
return __awaiter(this, void 0, void 0, function* () {
|
|
280
331
|
const updateExpressionComponents = ['updated = :time'];
|
|
281
332
|
const expressionAttributeValues = { ':time': new Date().getTime() };
|
|
333
|
+
if (typeof expirationMinutes === 'number') {
|
|
334
|
+
const expires = new Date();
|
|
335
|
+
expires.setMinutes(expires.getMinutes() + expirationMinutes);
|
|
336
|
+
updateExpressionComponents.push('expires = :expires');
|
|
337
|
+
expressionAttributeValues[':expires'] = Math.floor(expires.getTime() / 1000);
|
|
338
|
+
}
|
|
282
339
|
if (sessionData && interfaceName) {
|
|
283
340
|
updateExpressionComponents.push(`interface_${interfaceName} = :data`);
|
|
284
341
|
expressionAttributeValues[`:data`] = sessionData;
|