aws-classify-server 0.1.1 → 0.1.2

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
@@ -5,7 +5,7 @@ A library for calling AWS lambda functions from a browser or react-native app wh
5
5
  The reverse is also true in that class members implemented in the browser can be called from within a Lambda server. The latter uses Web Sockets in the AWS gateway. aws-classify also provides for a static website from which everything can be executed to comply with same-origin policy.
6
6
 
7
7
  * Complex data with classes and cyclic structures can be passed and returned
8
- * Execptions are passed back to the caller of the request method
8
+ * Exceptions are passed back to the caller of the request method
9
9
  * back-end session data is simply a matter of defining fields in the response class
10
10
  * Configuration and deployment via a simple serverless.yml file
11
11
 
@@ -17,7 +17,7 @@ These AWS resources are automatically configured and deployment is fully automat
17
17
  * S3 for a static website
18
18
  * Cloudfront with a custom domain name as a CDN
19
19
 
20
- All of these resources are configured by the Serverless Framework and deployed by running a script. You only need to login to AWS in order toc reate credentials for the Serverless Framework and to register your domain name and create an SSL certificate.
20
+ All of these resources are configured by the Serverless Framework and deployed by running a script. You only need to login to AWS in order to create credentials for the Serverless Framework and to register your domain name and create an SSL certificate for it.
21
21
 
22
22
  ### Use Cases
23
23
  aws-classify harnesses AWS to make it easy to leverage a scalable cloud-based infrastructure with virtually no learning curve. From there you can leverage all of the other AWS resources your application may need. The primary use-case is startups and projects that want a complete "app in a box" solution.
@@ -197,18 +197,32 @@ You would then configure bisync.json to keep the shared code in sync
197
197
  ```
198
198
  Implementing shared code this way avoid restrictions that packaging schemes have that effectively require code to be present in project tree
199
199
  ## Deployment
200
- This serverless.yml file is all you need to configure AWS:
200
+ You will need a serverless.yml file for automatic deployment in your cloud folder
201
+ In the first two sections you need to setup these custom variables
202
+ * **service:** - A unique name for your project within your AWS account
203
+ * **domain:** - The domain name your will use(even it is not yet registered)
204
+ * **certificateARN:** - An AWS certificate which you can [generate on the AWS
205
+ console](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html)
206
+ * **prefixes** - The sub-domain to be used for the development stage. You may, of course, define other stages (e.g. staging, qa etc)
207
+ * **directories**
208
+ * **responseHandlers:** - A file that has the exports for the handlers aws-classify needs. This file should also register your responses. It looks like this:
209
+ ```
210
+ export {responseHandler, webSocketConnect, webSocketDisconnect} from "../aws-classify-server"
211
+ import {classifyServerless} from "aws-classify-server";
212
+
213
+ import {ServerResponse} from "./ServerResponse"; // Your server responses
214
+ classifyServerless.registerResponse(ServerResponse);
201
215
  ```
202
- service: 'aws-classify-tests'
216
+ * **staticWebsite:** - The directory where your static website will be built
217
+ * **includeFiles:** - Normally this just points to the yml directory in aws-classify
218
+ ```
219
+ service: 'my-fantastic-project'
203
220
 
204
221
  custom:
205
222
 
206
- #
207
- # You need to fill these out
208
- #
209
-
210
223
  # Specify this even if you don't have a custom domain at this time
211
224
  domain: awsclassify.com
225
+
212
226
  # Get your AWS SSL Certificate and put it here if you have a custom domain
213
227
  certificateARN: "arn:aws:acm:us-east-1:############:certificate/########-####-####-####-############"
214
228
 
@@ -219,19 +233,21 @@ custom:
219
233
 
220
234
  directories:
221
235
  responseHandlers: "src/server-responses/index"
222
- staticWebsite: "./static"
223
- includeFiles: "node_modules/aws-classify/yml"
224
-
225
- #Include any scripts needed to build your static site
226
- #scripts:
227
- #hooks:
228
- #'before:deploy:deploy': npm run build:static
229
-
230
- #
231
- # Boiler plate
232
- #
233
- yml: "../../yml/"
234
- #yml ${self:custom.directories.includeFiles}
236
+ staticWebsite: "../web/build"
237
+ includeFiles: "node_modules/aws-classify-serverless/yml"
238
+ ```
239
+ If you are using a web site that requires building you will want to force it to be built as part of the deployment by adding the script in the **scripts** custom variable:
240
+ ```
241
+ scripts:
242
+ hooks:
243
+ 'before:deploy:deploy': npm run build:static
244
+ ```
245
+ The remaining custom variables just need to be copied as is. They will:
246
+ * set up the detailed names for your domain and buckets
247
+ * configure the s3Sync plugin. Note that noSync: true is specified here so you will have to run ```sls s3Sync``` to push our your site. Doing this way avoids s3Sync deploying your site when you use offline mode
248
+ * Configures other custom variables needed
249
+ ```
250
+ yml: ${self:custom.directories.includeFiles}
235
251
  stage: ${opt:stage,'dev'}
236
252
  hostedZoneName: ${self:custom.domain}.
237
253
  domainName: ${self:custom.prefixes.${self:custom.stage}}{$self:custome.domain}
@@ -246,16 +262,20 @@ custom:
246
262
  serverless-offline:
247
263
  httpPort: 4000
248
264
  dynamodb: ${file(${self:custom.yml}/custom-dynamodb.yml)}
249
-
250
-
265
+ ```
266
+ The deployment is dependent on a number plugs which must be installed with npm install
267
+ ```
251
268
  plugins:
252
269
  - serverless-plugin-scripts
253
270
  - serverless-s3-sync
271
+ - "@serverless-aws/serverless-s3-remover"
254
272
  - serverless-cloudfront-invalidate
255
273
  - serverless-plugin-typescript
256
274
  - serverless-dynamodb-local
257
275
  - serverless-offline
258
-
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
278
+ ```
259
279
  provider:
260
280
  name: aws
261
281
  runtime: nodejs16.x
@@ -267,16 +287,19 @@ provider:
267
287
  APIG_ENDPOINT: ${file(${self:custom.yml}/apig.yml)}
268
288
  iam:
269
289
  role:
270
- - statements:
290
+ statements:
271
291
  - ${file(${self:custom.yml}/provider-iam.yml)}
272
-
292
+ ```
293
+ The functions details each of the Lamda handlers. The details are in an include file
294
+ ```
273
295
  functions:
274
296
  - ${file(${self:custom.yml}/functions.yml)}
275
-
297
+ ```
298
+ Finally resources contains all of the resource definitions most of which are in include files. Important: If you don't yet have a custom domain use ```{file(${self:custom.yml}/resources-website.yml)}``` instead of ```{file(${self:custom.yml}/resources-custom-website.yml)}```
299
+ ```
276
300
  resources:
277
- - ${file(${self:custom.yml}/resources-website.yml)}
278
- # {file(${self:custom.yml}/resources-custom-website.yml)} if you have a custom domain
279
-
301
+ - ${file(${self:custom.yml}/resources-custom-website.yml)}
302
+ - ${file(${self:custom.yml}/resources-dynamodb.yml)}
280
303
  ```
281
304
 
282
305
  To deploy you need to:
@@ -291,13 +314,14 @@ serverless config credentials \
291
314
  - Obtain a no-cost SSL certificate on the AWS console and put it in the serverless.yml file
292
315
  - You can deploy a test version with
293
316
  ```
294
- serverless deploy stage dev
317
+ sls deploy --verbose --stage dev
318
+ sls s3sync
295
319
  ```
296
- and a production version with
320
+ - Deploy production version with
297
321
  ```
298
322
  serverless deploy stage prod
323
+ sls s3sync
299
324
  ```
300
-
301
325
  ## Roadmap
302
326
  * Create some sample applications
303
327
  * Continued shakeout with real-world applications
@@ -2,5 +2,6 @@ export declare class ClassifyResponse {
2
2
  __sessionId__: string | undefined;
3
3
  __connectionId__: string | undefined;
4
4
  __interfaceName__: string | undefined;
5
+ __userId__: string | undefined;
5
6
  constructor(interfaceName: string, sessionId: string, connectionId: string);
6
7
  }
@@ -11,12 +11,15 @@ export declare class ClassifyServerless {
11
11
  dispatch(ev: APIGatewayProxyEvent, context: Context, classes?: {}): Promise<string>;
12
12
  registerRequest<T>(requestClass: new () => T, classes?: any): new () => T;
13
13
  getSessionId<T>(responseObj: T): any;
14
+ getUserId<T>(responseObj: T): any;
15
+ setUserId<T>(responseObj: T, userId: string): void;
14
16
  createRequest<T, U>(responseObj: U, requestClass: new () => T): T;
15
17
  createRequestForSession<T>(sessionId: string, requestClass: new () => T): Promise<T>;
16
18
  createResponse<T, R>(responseClass: new () => T, sessionId: string, callback: (_obj: T) => Promise<R>, classes?: any): Promise<R>;
19
+ getSessionsForUserId(userId: string): Promise<any[]>;
17
20
  }
18
21
  export declare function getSessionData(sessionId: string, interfaceName?: string): Promise<Record<string, any> | undefined>;
19
- export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string): Promise<string>;
22
+ export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string, userId?: string): Promise<string>;
20
23
  export declare const EndPointsLogging: {
21
24
  create: boolean;
22
25
  connect: boolean;
@@ -106,7 +106,7 @@ class ClassifyServerless {
106
106
  }
107
107
  const updatedSessionData = (0, js_freeze_dry_1.serialize)(obj, classes);
108
108
  if (!result || updatedSessionData !== result.sessionData)
109
- sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined);
109
+ sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined, obj.__userId__);
110
110
  // Formulate response
111
111
  const lambdaResponse = {
112
112
  data: fnResponse,
@@ -189,6 +189,13 @@ class ClassifyServerless {
189
189
  throw Error('ClassifyServerless.getSessionId: no session id');
190
190
  return sessionId;
191
191
  }
192
+ getUserId(responseObj) {
193
+ const userId = responseObj['__userId__'];
194
+ return userId;
195
+ }
196
+ setUserId(responseObj, userId) {
197
+ responseObj['__userId__'] = userId;
198
+ }
192
199
  createRequest(responseObj, requestClass) {
193
200
  const obj = new requestClass();
194
201
  obj['__sessionId__'] = responseObj['__sessionId__'];
@@ -226,6 +233,7 @@ class ClassifyServerless {
226
233
  Object.assign(obj, (0, js_freeze_dry_1.deserialize)(sessionData, classes));
227
234
  obj.__sessionId__ = sessionId;
228
235
  obj.__connectionId__ = result.connectionId;
236
+ obj.__userId__ = result.userId;
229
237
  if (this.logLevel.calls)
230
238
  this.log(`creating response for ${interfaceName} with sessionId=${sessionId} connectionId= ${obj.__connectionId__} sessionData=${sessionData}`);
231
239
  const ret = yield callback(obj);
@@ -238,6 +246,23 @@ class ClassifyServerless {
238
246
  return ret;
239
247
  });
240
248
  }
249
+ getSessionsForUserId(userId) {
250
+ return __awaiter(this, void 0, void 0, function* () {
251
+ if (userId) {
252
+ const data = yield ddbDocClient.query({
253
+ TableName: 'classifySessionStore',
254
+ KeyConditionExpression: 'userId = :userId',
255
+ IndexName: 'userId',
256
+ ExpressionAttributeValues: {
257
+ ':userId': userId,
258
+ },
259
+ ProjectionExpression: `sessionId`
260
+ });
261
+ return data.Items ? data.Items.map(i => i.sessionId) : [];
262
+ }
263
+ return [];
264
+ });
265
+ }
241
266
  }
242
267
  exports.ClassifyServerless = ClassifyServerless;
243
268
  function getSessionData(sessionId, interfaceName = "") {
@@ -247,7 +272,7 @@ function getSessionData(sessionId, interfaceName = "") {
247
272
  TableName: 'classifySessionStore',
248
273
  Key: { sessionId },
249
274
  ConsistentRead: true,
250
- ProjectionExpression: `interface_${interfaceName}, connectionId, updated`
275
+ ProjectionExpression: `interface_${interfaceName}, connectionId, userId, updated`
251
276
  });
252
277
  return data.Item;
253
278
  }
@@ -255,7 +280,7 @@ function getSessionData(sessionId, interfaceName = "") {
255
280
  });
256
281
  }
257
282
  exports.getSessionData = getSessionData;
258
- function saveSessionData(sessionId, interfaceName, sessionData, connectionId) {
283
+ function saveSessionData(sessionId, interfaceName, sessionData, connectionId, userId) {
259
284
  return __awaiter(this, void 0, void 0, function* () {
260
285
  const updateExpressionComponents = ['updated = :time'];
261
286
  const expressionAttributeValues = { ':time': new Date().getTime() };
@@ -267,6 +292,10 @@ function saveSessionData(sessionId, interfaceName, sessionData, connectionId) {
267
292
  updateExpressionComponents.push('connectionId = :connectionId');
268
293
  expressionAttributeValues[':connectionId'] = connectionId;
269
294
  }
295
+ if (userId) {
296
+ updateExpressionComponents.push('userId = :userId');
297
+ expressionAttributeValues[':userId'] = userId;
298
+ }
270
299
  // First try and out data only if the session id exists
271
300
  yield ddbDocClient.update({
272
301
  TableName: 'classifySessionStore',
@@ -2,5 +2,6 @@ export declare class ClassifyResponse {
2
2
  __sessionId__: string | undefined;
3
3
  __connectionId__: string | undefined;
4
4
  __interfaceName__: string | undefined;
5
+ __userId__: string | undefined;
5
6
  constructor(interfaceName: string, sessionId: string, connectionId: string);
6
7
  }
@@ -11,12 +11,15 @@ export declare class ClassifyServerless {
11
11
  dispatch(ev: APIGatewayProxyEvent, context: Context, classes?: {}): Promise<string>;
12
12
  registerRequest<T>(requestClass: new () => T, classes?: any): new () => T;
13
13
  getSessionId<T>(responseObj: T): any;
14
+ getUserId<T>(responseObj: T): any;
15
+ setUserId<T>(responseObj: T, userId: string): void;
14
16
  createRequest<T, U>(responseObj: U, requestClass: new () => T): T;
15
17
  createRequestForSession<T>(sessionId: string, requestClass: new () => T): Promise<T>;
16
18
  createResponse<T, R>(responseClass: new () => T, sessionId: string, callback: (_obj: T) => Promise<R>, classes?: any): Promise<R>;
19
+ getSessionsForUserId(userId: string): Promise<any[]>;
17
20
  }
18
21
  export declare function getSessionData(sessionId: string, interfaceName?: string): Promise<Record<string, any> | undefined>;
19
- export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string): Promise<string>;
22
+ export declare function saveSessionData(sessionId: string, interfaceName?: string, sessionData?: string, connectionId?: string, userId?: string): Promise<string>;
20
23
  export declare const EndPointsLogging: {
21
24
  create: boolean;
22
25
  connect: boolean;
@@ -103,7 +103,7 @@ export class ClassifyServerless {
103
103
  }
104
104
  const updatedSessionData = serialize(obj, classes);
105
105
  if (!result || updatedSessionData !== result.sessionData)
106
- sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined);
106
+ sessionId = yield saveSessionData(sessionId, request.interfaceName, updatedSessionData, undefined, obj.__userId__);
107
107
  // Formulate response
108
108
  const lambdaResponse = {
109
109
  data: fnResponse,
@@ -186,6 +186,13 @@ export class ClassifyServerless {
186
186
  throw Error('ClassifyServerless.getSessionId: no session id');
187
187
  return sessionId;
188
188
  }
189
+ getUserId(responseObj) {
190
+ const userId = responseObj['__userId__'];
191
+ return userId;
192
+ }
193
+ setUserId(responseObj, userId) {
194
+ responseObj['__userId__'] = userId;
195
+ }
189
196
  createRequest(responseObj, requestClass) {
190
197
  const obj = new requestClass();
191
198
  obj['__sessionId__'] = responseObj['__sessionId__'];
@@ -223,6 +230,7 @@ export class ClassifyServerless {
223
230
  Object.assign(obj, deserialize(sessionData, classes));
224
231
  obj.__sessionId__ = sessionId;
225
232
  obj.__connectionId__ = result.connectionId;
233
+ obj.__userId__ = result.userId;
226
234
  if (this.logLevel.calls)
227
235
  this.log(`creating response for ${interfaceName} with sessionId=${sessionId} connectionId= ${obj.__connectionId__} sessionData=${sessionData}`);
228
236
  const ret = yield callback(obj);
@@ -235,6 +243,23 @@ export class ClassifyServerless {
235
243
  return ret;
236
244
  });
237
245
  }
246
+ getSessionsForUserId(userId) {
247
+ return __awaiter(this, void 0, void 0, function* () {
248
+ if (userId) {
249
+ const data = yield ddbDocClient.query({
250
+ TableName: 'classifySessionStore',
251
+ KeyConditionExpression: 'userId = :userId',
252
+ IndexName: 'userId',
253
+ ExpressionAttributeValues: {
254
+ ':userId': userId,
255
+ },
256
+ ProjectionExpression: `sessionId`
257
+ });
258
+ return data.Items ? data.Items.map(i => i.sessionId) : [];
259
+ }
260
+ return [];
261
+ });
262
+ }
238
263
  }
239
264
  export function getSessionData(sessionId, interfaceName = "") {
240
265
  return __awaiter(this, void 0, void 0, function* () {
@@ -243,14 +268,14 @@ export function getSessionData(sessionId, interfaceName = "") {
243
268
  TableName: 'classifySessionStore',
244
269
  Key: { sessionId },
245
270
  ConsistentRead: true,
246
- ProjectionExpression: `interface_${interfaceName}, connectionId, updated`
271
+ ProjectionExpression: `interface_${interfaceName}, connectionId, userId, updated`
247
272
  });
248
273
  return data.Item;
249
274
  }
250
275
  return undefined;
251
276
  });
252
277
  }
253
- export function saveSessionData(sessionId, interfaceName, sessionData, connectionId) {
278
+ export function saveSessionData(sessionId, interfaceName, sessionData, connectionId, userId) {
254
279
  return __awaiter(this, void 0, void 0, function* () {
255
280
  const updateExpressionComponents = ['updated = :time'];
256
281
  const expressionAttributeValues = { ':time': new Date().getTime() };
@@ -262,6 +287,10 @@ export function saveSessionData(sessionId, interfaceName, sessionData, connectio
262
287
  updateExpressionComponents.push('connectionId = :connectionId');
263
288
  expressionAttributeValues[':connectionId'] = connectionId;
264
289
  }
290
+ if (userId) {
291
+ updateExpressionComponents.push('userId = :userId');
292
+ expressionAttributeValues[':userId'] = userId;
293
+ }
265
294
  // First try and out data only if the session id exists
266
295
  yield ddbDocClient.update({
267
296
  TableName: 'classifySessionStore',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-classify-server",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "main": "./lib/cjs/index.js",
5
5
  "module": "./lib/esm/index.js",
6
6
  "types": "./lib/esm/index.d.ts",
@@ -77,4 +77,15 @@ Resources:
77
77
  DNSName:
78
78
  Fn::GetAtt:
79
79
  - CloudFrontDistribution
80
- - DomainName
80
+ - DomainName
81
+ Outputs:
82
+ CloudFrontId:
83
+ Description: CloudFront Id
84
+ Value: !Ref CloudFrontDistribution
85
+ Export:
86
+ Name: CloudFrontId
87
+ WebsiteUrl:
88
+ Description: Cloudfront URL
89
+ Value: !GetAtt CloudFrontDistribution.DomainName
90
+ Export:
91
+ Name: CloudFrontURL
@@ -75,3 +75,14 @@ Resources:
75
75
  - CloudFrontDistribution
76
76
  - DomainName
77
77
 
78
+ Outputs:
79
+ CloudFrontId:
80
+ Description: CloudFront Id
81
+ Value: !Ref CloudFrontDistribution
82
+ Export:
83
+ Name: CloudFrontId
84
+ WebsiteUrl:
85
+ Description: Cloudfront URL
86
+ Value: !GetAtt CloudFrontDistribution.DomainName
87
+ Export:
88
+ Name: CloudFrontURL