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 resiliant to change
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-classify-server",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "main": "./lib/cjs/index.js",
5
5
  "module": "./lib/esm/index.js",
6
6
  "types": "./lib/esm/index.d.ts",