somod-chat-service 0.4.3 → 0.5.0

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.
@@ -0,0 +1,2 @@
1
+ import { APIGatewayProxyEventV2, APIGatewayProxyWebsocketEventV2 } from "aws-lambda";
2
+ export declare const getUserIdFromEvent: (event: APIGatewayProxyEventV2 | APIGatewayProxyWebsocketEventV2) => string;
@@ -0,0 +1,5 @@
1
+ import { UserProviderMiddlewareKey } from "./constants";
2
+ // get User Id from Event
3
+ export var getUserIdFromEvent = function (event) {
4
+ return event.somodMiddlewareContext.get(UserProviderMiddlewareKey);
5
+ };
@@ -52,7 +52,7 @@ export var validateIncomingMessage = function (message, userId) { return __await
52
52
  if (errorMessage) {
53
53
  return [2 /*return*/, {
54
54
  statusCode: 400,
55
- headers: { "Content-Type": "application/json" },
55
+ headers: { "Content-Type": "application/json; charset=utf-8" },
56
56
  body: JSON.stringify({
57
57
  message: errorMessage
58
58
  })
@@ -6,5 +6,5 @@ export declare const Error: {
6
6
  required: string;
7
7
  invalid: string;
8
8
  };
9
- export declare const handleSessionToken: (userId: string, sessionToken?: string) => SessionIdResult;
9
+ export declare const handleSessionToken: (userId: string, threadId: string, sessionToken?: string) => Promise<SessionIdResult>;
10
10
  export {};
@@ -1,44 +1,59 @@
1
1
  var _a, _b;
2
+ import { __awaiter, __generator } from "tslib";
2
3
  import { verify } from "jsonwebtoken";
4
+ import { threadCache } from "./threadCache";
3
5
  var sessionJwtSecret = (_a = process.env.SESSION_SECRET) !== null && _a !== void 0 ? _a : "";
4
6
  var sessionForce = (_b = process.env.SESSION_FORCE) !== null && _b !== void 0 ? _b : "";
5
7
  export var Error = {
6
8
  required: "missing required field: sessionToken",
7
9
  invalid: "invalid field: sessionToken"
8
10
  };
9
- export var handleSessionToken = function (userId, sessionToken) {
10
- var result = { sessionId: "" };
11
- if (sessionJwtSecret) {
12
- if (!sessionToken) {
13
- if (sessionForce == "true") {
11
+ export var handleSessionToken = function (userId, threadId, sessionToken) { return __awaiter(void 0, void 0, void 0, function () {
12
+ var result, thread, session, now;
13
+ var _a;
14
+ return __generator(this, function (_b) {
15
+ switch (_b.label) {
16
+ case 0:
17
+ result = { sessionId: "" };
18
+ if (!sessionJwtSecret) return [3 /*break*/, 5];
19
+ if (!!sessionToken) return [3 /*break*/, 4];
20
+ if (!(sessionForce == "true")) return [3 /*break*/, 1];
14
21
  result.error = Error.required;
15
- }
16
- }
17
- else {
18
- try {
19
- var session = verify(sessionToken, sessionJwtSecret + "", {
20
- algorithms: ["HS512"],
21
- ignoreExpiration: true
22
- });
23
- var now = Date.now();
24
- if (session.participants.includes(userId) &&
25
- session.startTime <= now &&
26
- now <= session.endTime) {
27
- result.sessionId = session.id;
22
+ return [3 /*break*/, 3];
23
+ case 1: return [4 /*yield*/, threadCache.get(threadId, 5000)];
24
+ case 2:
25
+ thread = _b.sent();
26
+ if ((_a = thread === null || thread === void 0 ? void 0 : thread.sessionRequired) === null || _a === void 0 ? void 0 : _a.includes(userId)) {
27
+ result.error = Error.required;
28
+ }
29
+ _b.label = 3;
30
+ case 3: return [3 /*break*/, 5];
31
+ case 4:
32
+ try {
33
+ session = verify(sessionToken, sessionJwtSecret + "", {
34
+ algorithms: ["HS512"],
35
+ ignoreExpiration: true
36
+ });
37
+ now = Date.now();
38
+ if (session.participants.includes(userId) &&
39
+ session.startTime <= now &&
40
+ now <= session.endTime) {
41
+ result.sessionId = session.id;
42
+ }
43
+ else {
44
+ result.error = Error.invalid;
45
+ }
28
46
  }
29
- else {
47
+ catch (err) {
48
+ // eslint-disable-next-line no-console
49
+ console.error("session token verification failed. userId=" +
50
+ userId +
51
+ ", error=" +
52
+ err);
30
53
  result.error = Error.invalid;
31
54
  }
32
- }
33
- catch (err) {
34
- // eslint-disable-next-line no-console
35
- console.error("session token verification failed. userId=" +
36
- userId +
37
- ", error=" +
38
- err);
39
- result.error = Error.invalid;
40
- }
55
+ _b.label = 5;
56
+ case 5: return [2 /*return*/, result];
41
57
  }
42
- }
43
- return result;
44
- };
58
+ });
59
+ }); };
@@ -1,4 +1,4 @@
1
1
  import { Thread } from "./types";
2
2
  export declare const threadCache: {
3
- get: (threadId: string) => Promise<Thread>;
3
+ get: (threadId: string, ttl?: number) => Promise<Thread>;
4
4
  };
@@ -5,7 +5,7 @@ import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
5
5
  var dynamodb = new DynamoDBClient();
6
6
  var _threadCache = cache(100, 60 * 60000);
7
7
  export var threadCache = {
8
- get: function (threadId) { return __awaiter(void 0, void 0, void 0, function () {
8
+ get: function (threadId, ttl) { return __awaiter(void 0, void 0, void 0, function () {
9
9
  return __generator(this, function (_a) {
10
10
  switch (_a.label) {
11
11
  case 0: return [4 /*yield*/, _threadCache.get(threadId, function () { return __awaiter(void 0, void 0, void 0, function () {
@@ -23,7 +23,7 @@ export var threadCache = {
23
23
  : undefined];
24
24
  }
25
25
  });
26
- }); })];
26
+ }); }, ttl)];
27
27
  case 1: return [2 /*return*/, _a.sent()];
28
28
  }
29
29
  });
@@ -1,13 +1,16 @@
1
1
  export type ThreadInput = {
2
2
  participants: string[];
3
3
  };
4
+ export type ThreadSessionRequired = {
5
+ sessionRequired?: string[];
6
+ };
4
7
  export type Thread = {
5
8
  id: string;
6
- } & ThreadInput;
9
+ } & ThreadInput & ThreadSessionRequired;
7
10
  export type MessageInput = {
8
11
  threadId: string;
9
12
  type: "text" | "image" | "control";
10
- action: "new" | "edit" | "delete" | "sessionStart" | "sessionExtend" | "sessionEnd";
13
+ action: "new" | "edit" | "delete" | "sessionStart" | "sessionExtend" | "sessionEnd" | "sessionRequirementChange";
11
14
  message: string;
12
15
  sessionToken?: string;
13
16
  };
@@ -1,5 +1,11 @@
1
1
  export var typeToAllowedActionsMap = {
2
2
  text: ["new", "edit"],
3
3
  image: ["new", "edit"],
4
- control: ["delete", "sessionStart", "sessionExtend", "sessionEnd"]
4
+ control: [
5
+ "delete",
6
+ "sessionStart",
7
+ "sessionExtend",
8
+ "sessionEnd",
9
+ "sessionRequirementChange"
10
+ ]
5
11
  };
@@ -6,6 +6,7 @@ import { v1 as v1uuid } from "uuid";
6
6
  import { QueryCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
7
7
  import { convertToAttr, unmarshall } from "@aws-sdk/util-dynamodb";
8
8
  import { handleSessionToken } from "../../lib/sessionUtil";
9
+ import { getUserIdFromEvent } from "../../lib/getUserIdFromEvent";
9
10
  var dynamodb = new DynamoDBClient();
10
11
  var builder = new RouteBuilder();
11
12
  var postMessageHandler = function (request, event) { return __awaiter(void 0, void 0, void 0, function () {
@@ -20,11 +21,13 @@ var postMessageHandler = function (request, event) { return __awaiter(void 0, vo
20
21
  if (messageValidationError) {
21
22
  return [2 /*return*/, messageValidationError];
22
23
  }
23
- sessionIdResult = handleSessionToken(userId, request.body.sessionToken);
24
+ return [4 /*yield*/, handleSessionToken(userId, request.body.threadId, request.body.sessionToken)];
25
+ case 2:
26
+ sessionIdResult = _b.sent();
24
27
  if (sessionIdResult.error) {
25
28
  return [2 /*return*/, {
26
29
  statusCode: 400,
27
- headers: { "Content-Type": "application/json" },
30
+ headers: { "Content-Type": "application/json; charset=utf-8" },
28
31
  body: JSON.stringify({
29
32
  message: sessionIdResult.error
30
33
  })
@@ -35,11 +38,11 @@ var postMessageHandler = function (request, event) { return __awaiter(void 0, vo
35
38
  return [4 /*yield*/, putMessage(process.env.MESSAGE_BOX_TABLE_NAME + "", userId, __assign(__assign(__assign({}, msg), { sessionId: sessionIdResult.sessionId, id: v1uuid().split("-").join(""), from: userId, sentAt: Date.now() }), (actions.includes(request.body.action)
36
39
  ? { sessionToken: sessionToken }
37
40
  : {})))];
38
- case 2:
41
+ case 3:
39
42
  message = _b.sent();
40
43
  return [2 /*return*/, {
41
44
  statusCode: 200,
42
- headers: { "Content-Type": "application/json" },
45
+ headers: { "Content-Type": "application/json; charset=utf-8" },
43
46
  body: JSON.stringify({
44
47
  id: message.id,
45
48
  seqNo: message.seqNo,
@@ -55,7 +58,7 @@ var syncMessagesHandler = function (request, event) { return __awaiter(void 0, v
55
58
  return __generator(this, function (_a) {
56
59
  switch (_a.label) {
57
60
  case 0:
58
- userId = event.somodMiddlewareContext.get(UserProviderMiddlewareKey);
61
+ userId = getUserIdFromEvent(event);
59
62
  queryCommandInput = {
60
63
  TableName: process.env.MESSAGE_BOX_TABLE_NAME + "",
61
64
  KeyConditionExpression: "#userId = :userId",
@@ -85,7 +88,7 @@ var syncMessagesHandler = function (request, event) { return __awaiter(void 0, v
85
88
  return [2 /*return*/, {
86
89
  statusCode: 200,
87
90
  headers: {
88
- "Content-Type": "application/json"
91
+ "Content-Type": "application/json; charset=utf-8"
89
92
  },
90
93
  body: JSON.stringify(messages)
91
94
  }];
@@ -24,7 +24,7 @@ var userProviderMiddleware = function (next, event) { return __awaiter(void 0, v
24
24
  if (key === undefined) {
25
25
  return [2 /*return*/, {
26
26
  statusCode: 400,
27
- headers: { "Content-Type": "application/json" },
27
+ headers: { "Content-Type": "application/json; charset=utf-8" },
28
28
  body: JSON.stringify({
29
29
  message: "Expected ".concat(UserProviderMiddlewareKey, " in the request headers or message body")
30
30
  })
@@ -1 +1 @@
1
- {"/thread":{"POST":{"body":{"parser":"json","schema":{"type":"object","additionalProperties":false,"required":["participants"],"properties":{"participants":{"type":"array","items":{"type":"string","pattern":"^[a-zA-Z0-9-_]{1,50}$"},"minItems":2,"maxItems":2}}}}}},"/thread/{id}":{"GET":{"parameters":[{"name":"id","in":"path","schema":{"type":"string","pattern":"^[a-f0-9]{32}$"},"required":true}]}}}
1
+ {"/thread":{"POST":{"body":{"parser":"json","schema":{"type":"object","additionalProperties":false,"required":["participants"],"properties":{"participants":{"type":"array","items":{"type":"string","pattern":"^[a-zA-Z0-9-_]{1,50}$"},"minItems":2,"maxItems":2}}}}}},"/thread/{id}":{"GET":{"parameters":[{"name":"id","in":"path","schema":{"type":"string","pattern":"^[a-f0-9]{32}$"},"required":true}]}},"/thread/{id}/session":{"POST":{"parameters":[{"name":"id","in":"path","schema":{"type":"string","pattern":"^[a-f0-9]{32}$"},"required":true}],"body":{"parser":"json","schema":{"type":"object","required":["sessionRequired"],"properties":{"sessionRequired":{"type":"array","items":{"type":"string","pattern":"^[a-zA-Z0-9-_]{1,50}$"},"minItems":2,"maxItems":2}}}}}}}
@@ -1,8 +1,10 @@
1
1
  import { __assign, __awaiter, __generator } from "tslib";
2
2
  import { RouteBuilder } from "somod-http-extension";
3
- import { DynamoDBClient, GetItemCommand, PutItemCommand } from "@aws-sdk/client-dynamodb";
4
- import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
5
- import { v1 as v1uuid } from "uuid";
3
+ import { DynamoDBClient, GetItemCommand, PutItemCommand, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
4
+ import { convertToAttr, marshall, unmarshall } from "@aws-sdk/util-dynamodb";
5
+ import { v1, v1 as v1uuid } from "uuid";
6
+ import { putMessage } from "../../lib/message";
7
+ import { getUserIdFromEvent } from "../../lib/getUserIdFromEvent";
6
8
  var builder = new RouteBuilder();
7
9
  var dynamodb = new DynamoDBClient();
8
10
  var createThread = function (request) { return __awaiter(void 0, void 0, void 0, function () {
@@ -24,7 +26,7 @@ var createThread = function (request) { return __awaiter(void 0, void 0, void 0,
24
26
  _a.sent();
25
27
  return [2 /*return*/, {
26
28
  statusCode: 200,
27
- headers: { "Content-Type": "application/json" },
29
+ headers: { "Content-Type": "application/json; charset=utf-8" },
28
30
  body: JSON.stringify(thread)
29
31
  }];
30
32
  }
@@ -45,13 +47,63 @@ var getThread = function (request) { return __awaiter(void 0, void 0, void 0, fu
45
47
  return [2 /*return*/, result.Item
46
48
  ? {
47
49
  statusCode: 200,
48
- headers: { "Content-Type": "application/json" },
50
+ headers: { "Content-Type": "application/json; charset=utf-8" },
49
51
  body: JSON.stringify(unmarshall(result.Item))
50
52
  }
51
53
  : { statusCode: 404 }];
52
54
  }
53
55
  });
54
56
  }); };
57
+ var updateSessionRequired = function (request, event) { return __awaiter(void 0, void 0, void 0, function () {
58
+ var userId, updateItemCommand, e_1;
59
+ var _a;
60
+ return __generator(this, function (_b) {
61
+ switch (_b.label) {
62
+ case 0:
63
+ userId = getUserIdFromEvent(event);
64
+ updateItemCommand = new UpdateItemCommand({
65
+ TableName: process.env.THREAD_TABLE_NAME + "",
66
+ Key: marshall({ id: request.parameters.path.id }),
67
+ UpdateExpression: "SET #sessionRequired = :sessionRequired",
68
+ ExpressionAttributeNames: {
69
+ "#sessionRequired": "sessionRequired"
70
+ },
71
+ ExpressionAttributeValues: {
72
+ ":sessionRequired": convertToAttr(request.body.sessionRequired)
73
+ }
74
+ });
75
+ return [4 /*yield*/, dynamodb.send(updateItemCommand)];
76
+ case 1:
77
+ _b.sent();
78
+ _b.label = 2;
79
+ case 2:
80
+ _b.trys.push([2, 4, , 5]);
81
+ return [4 /*yield*/, putMessage((_a = process.env.MESSAGE_BOX_TABLE_NAME) !== null && _a !== void 0 ? _a : "", userId, {
82
+ id: v1().split("-").join(""),
83
+ type: "control",
84
+ action: "sessionRequirementChange",
85
+ from: userId,
86
+ message: JSON.stringify(request.body.sessionRequired),
87
+ sentAt: Date.now(),
88
+ threadId: request.parameters.path.id
89
+ })];
90
+ case 3:
91
+ _b.sent();
92
+ return [3 /*break*/, 5];
93
+ case 4:
94
+ e_1 = _b.sent();
95
+ // eslint-disable-next-line no-console
96
+ console.error(e_1);
97
+ return [3 /*break*/, 5];
98
+ case 5: return [2 /*return*/, {
99
+ statusCode: 200,
100
+ headers: { "Content-Type": "application/json; charset=utf-8" },
101
+ body: JSON.stringify({ message: "Thread updated successfully" })
102
+ }];
103
+ }
104
+ });
105
+ }); };
55
106
  builder.add("/thread", "POST", createThread);
56
107
  builder.add("/thread/{id}", "GET", getThread);
108
+ builder.add("/thread/{id}/session", "POST", updateSessionRequired);
57
109
  export default builder.getHandler();
@@ -2,26 +2,28 @@ import { __assign, __awaiter, __generator, __rest } from "tslib";
2
2
  import { RouteBuilder } from "somod-websocket-extension";
3
3
  import { putMessage, validateIncomingMessage } from "../../lib/message";
4
4
  import { v1 as v1uuid } from "uuid";
5
- import { UserProviderMiddlewareKey } from "../../lib";
6
5
  import { handleSessionToken } from "../../lib/sessionUtil";
6
+ import { getUserIdFromEvent } from "../../lib/getUserIdFromEvent";
7
7
  var builder = new RouteBuilder();
8
8
  builder.add("$default", function (message, event) { return __awaiter(void 0, void 0, void 0, function () {
9
9
  var userId, messageValidationError, sessionIdResult, _a, wsMsgId, sessionToken, msg, actions, messageResult;
10
10
  return __generator(this, function (_b) {
11
11
  switch (_b.label) {
12
12
  case 0:
13
- userId = event.somodMiddlewareContext.get(UserProviderMiddlewareKey);
13
+ userId = getUserIdFromEvent(event);
14
14
  return [4 /*yield*/, validateIncomingMessage(message.body, userId)];
15
15
  case 1:
16
16
  messageValidationError = _b.sent();
17
17
  if (messageValidationError) {
18
18
  return [2 /*return*/, messageValidationError];
19
19
  }
20
- sessionIdResult = handleSessionToken(userId, message.body.sessionToken);
20
+ return [4 /*yield*/, handleSessionToken(userId, message.body.threadId, message.body.sessionToken)];
21
+ case 2:
22
+ sessionIdResult = _b.sent();
21
23
  if (sessionIdResult.error) {
22
24
  return [2 /*return*/, {
23
25
  statusCode: 400,
24
- headers: { "Content-Type": "application/json" },
26
+ headers: { "Content-Type": "application/json; charset=utf-8" },
25
27
  body: JSON.stringify({
26
28
  message: sessionIdResult.error
27
29
  })
@@ -32,11 +34,11 @@ builder.add("$default", function (message, event) { return __awaiter(void 0, voi
32
34
  return [4 /*yield*/, putMessage(process.env.MESSAGE_BOX_TABLE_NAME + "", userId, __assign(__assign(__assign({}, msg), { sessionId: sessionIdResult.sessionId, from: userId, id: v1uuid().split("-").join(""), sentAt: Date.now() }), (actions.includes(message.body.action)
33
35
  ? { sessionToken: sessionToken }
34
36
  : {})))];
35
- case 2:
37
+ case 3:
36
38
  messageResult = _b.sent();
37
39
  return [2 /*return*/, {
38
40
  statusCode: 200,
39
- headers: { "Content-Type": "application/json" },
41
+ headers: { "Content-Type": "application/json; charset=utf-8" },
40
42
  body: JSON.stringify({
41
43
  wsMsgId: wsMsgId,
42
44
  type: "ack",
@@ -1 +1 @@
1
- {"Resources":{"ChatApi":{"Type":"AWS::Serverless::HttpApi","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{}},"ThreadTable":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"Thread"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"}]}},"MessageBox":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn","StreamArn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"MessageBox"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"userId","KeyType":"HASH"},{"AttributeName":"seqNo","KeyType":"RANGE"}],"AttributeDefinitions":[{"AttributeName":"userId","AttributeType":"S"},{"AttributeName":"seqNo","AttributeType":"N"}],"StreamSpecification":{"StreamViewType":"NEW_IMAGE"}}},"MessageStreamHandlerDLQ":{"Type":"AWS::SQS::Queue","SOMOD::Output":{"default":true,"attributes":["Arn","QueueName","QueueUrl"]},"Properties":{"QueueName":{"SOMOD::ResourceName":"MessageStreamHandlerDLQ"}}},"MessageStreamHandler":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MsgStrmHandler"},"CodeUri":{"SOMOD::Function":{"name":"messageStreamHandler","type":"DynamoDB"}},"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"MSG_NOTIFICATION_TOPIC":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}],"Action":["sns:Publish"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}],"Action":["sqs:SendMessage"]}]}],"Events":{"Stream":{"Type":"DynamoDB","Properties":{"Stream":{"SOMOD::Ref":{"resource":"MessageBox","attribute":"StreamArn"}},"BatchSize":1,"MaximumRetryAttempts":3,"StartingPosition":"LATEST","DestinationConfig":{"OnFailure":{"Destination":{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}}}}}}}},"UserProviderMiddleware":{"Type":"SOMOD::Serverless::FunctionMiddleware","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{"CodeUri":{"SOMOD::FunctionMiddleware":{"name":"userProvider"}}}},"CommonLibsLayer":{"Type":"AWS::Serverless::LayerVersion","SOMOD::Output":{"default":true},"Properties":{"RetentionPolicy":"Delete","ContentUri":{"SOMOD::FunctionLayer":{"name":"commonlibs","libraries":["decorated-ajv"]}}}},"Message":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MessageApi"},"CodeUri":{"SOMOD::Function":{"name":"message","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"SESSION_SECRET":{"SOMOD::Parameter":"chat.session.jwt.secret"},"SESSION_FORCE":{"SOMOD::Parameter":"chat.session.force"}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:Query"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}],"Events":{"PostMessage":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/post-message"}},"SyncMessages":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/sync-messages"}}}}},"Thread":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"ThreadApi"},"CodeUri":{"SOMOD::Function":{"name":"thread","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:GetItem"]}]}],"Events":{"CreateThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/thread"}},"GetThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/thread/{id}"}}}}},"MessageNotificationTopic":{"Type":"AWS::SNS::Topic","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["TopicName"]},"Properties":{"TopicName":{"SOMOD::ResourceName":"MsgNotificationTopic"}}},"WebSocketApi":{"Type":"AWS::ApiGatewayV2::Api","SOMOD::Access":"public","Properties":{"Name":{"SOMOD::ResourceName":"ChatWebsocketApi"},"ProtocolType":"WEBSOCKET","RouteSelectionExpression":"$request.body.action"}},"ConnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::Access":"public","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$connect","OperationName":"ConnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"ConnectInteg"}}]]}}},"ConnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Connect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunctionArn}/invocations",{"OnConnectFunctionArn":{"SOMOD::Ref":{"resource":"OnConnectFunction","attribute":"Arn"}}}]}}},"DisconnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$disconnect","OperationName":"DisconnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DisconnectInteg"}}]]}}},"DisconnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Disconnect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunctionArn}/invocations",{"OnDisconnectFunctionArn":{"SOMOD::Ref":{"resource":"OnDisconnectFunction","attribute":"Arn"}}}]}}},"DefaultRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$default","OperationName":"DefaultRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DefaultInteg"}}]]}}},"DefaultRouteResponse":{"Type":"AWS::ApiGatewayV2::RouteResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"RouteId":{"SOMOD::Ref":{"resource":"DefaultRoute"}},"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteResponseKey":"$default"}},"DefaultInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Default Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnMessageFunctionArn}/invocations",{"OnMessageFunctionArn":{"SOMOD::Ref":{"resource":"OnMessageFunction","attribute":"Arn"}}}]}}},"DefaultIntegResponse":{"Type":"AWS::ApiGatewayV2::IntegrationResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"IntegrationId":{"SOMOD::Ref":{"resource":"DefaultInteg"}},"IntegrationResponseKey":"$default"}},"Stage":{"Type":"AWS::ApiGatewayV2::Stage","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"StageName":"$default","ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"AutoDeploy":true}},"ConnectionsTable":{"Type":"AWS::DynamoDB::Table","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"},{"AttributeName":"userId","AttributeType":"S"}],"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"GlobalSecondaryIndexes":[{"IndexName":"ByUserId","KeySchema":[{"AttributeName":"userId","KeyType":"HASH"}],"Projection":{"ProjectionType":"KEYS_ONLY"}}]}},"OnConnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnConnect","middlewares":[{"resource":"UserProviderMiddleware"}]}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]}]}]}},"OnConnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnConnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$connect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnDisconnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnDisconnect"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:DeleteItem"]}]}]}},"OnDisconnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnDisconnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$disconnect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnMessage","middlewares":[{"module":"somod-websocket-extension","resource":"SomodWebSocketMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"SESSION_SECRET":{"SOMOD::Parameter":"chat.session.jwt.secret"},"SESSION_FORCE":{"SOMOD::Parameter":"chat.session.force"}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}]}},"OnMessagePermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnMessageFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$default",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"NotifyMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"CodeUri":{"SOMOD::Function":{"type":"SNS","name":"wsNotifyMessage"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}},"CONNECTIONS_ENDPOINT":{"Fn::Sub":["https://${WebSocketApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Stage":{"SOMOD::Ref":{"resource":"Stage"}}}]}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"Fn::Sub":["${ConnectionsTableArn}/index/ByUserId",{"ConnectionsTableArn":{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}}]}],"Action":["dynamodb:Query"]}]},{"Statement":[{"Effect":"Allow","Action":["execute-api:ManageConnections"],"Resource":[{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}]}]}],"Events":{"NotifyFromSNS":{"Type":"SNS","Properties":{"Topic":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}}},"Timeout":300}}},"Outputs":{"chat.http-api.id":{"Fn::Sub":["https://${apiId}.execute-api.${AWS::Region}.amazonaws.com/",{"apiId":{"SOMOD::Ref":{"resource":"ChatApi"}}}]},"chat.websocket-api.id":{"SOMOD::If":[{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},{"Fn::Sub":["wss://${apiId}.execute-api.${AWS::Region}.amazonaws.com/$default",{"apiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]},null]}}}
1
+ {"Resources":{"ChatApi":{"Type":"AWS::Serverless::HttpApi","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{}},"ThreadTable":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"Thread"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"}]}},"MessageBox":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn","StreamArn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"MessageBox"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"userId","KeyType":"HASH"},{"AttributeName":"seqNo","KeyType":"RANGE"}],"AttributeDefinitions":[{"AttributeName":"userId","AttributeType":"S"},{"AttributeName":"seqNo","AttributeType":"N"}],"StreamSpecification":{"StreamViewType":"NEW_IMAGE"}}},"MessageStreamHandlerDLQ":{"Type":"AWS::SQS::Queue","SOMOD::Output":{"default":true,"attributes":["Arn","QueueName","QueueUrl"]},"Properties":{"QueueName":{"SOMOD::ResourceName":"MessageStreamHandlerDLQ"}}},"MessageStreamHandler":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MsgStrmHandler"},"CodeUri":{"SOMOD::Function":{"name":"messageStreamHandler","type":"DynamoDB"}},"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"MSG_NOTIFICATION_TOPIC":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}],"Action":["sns:Publish"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}],"Action":["sqs:SendMessage"]}]}],"Events":{"Stream":{"Type":"DynamoDB","Properties":{"Stream":{"SOMOD::Ref":{"resource":"MessageBox","attribute":"StreamArn"}},"BatchSize":1,"MaximumRetryAttempts":3,"StartingPosition":"LATEST","DestinationConfig":{"OnFailure":{"Destination":{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}}}}}}}},"UserProviderMiddleware":{"Type":"SOMOD::Serverless::FunctionMiddleware","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{"CodeUri":{"SOMOD::FunctionMiddleware":{"name":"userProvider"}}}},"CommonLibsLayer":{"Type":"AWS::Serverless::LayerVersion","SOMOD::Output":{"default":true},"Properties":{"RetentionPolicy":"Delete","ContentUri":{"SOMOD::FunctionLayer":{"name":"commonlibs","libraries":["decorated-ajv"]}}}},"Message":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MessageApi"},"CodeUri":{"SOMOD::Function":{"name":"message","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"SESSION_SECRET":{"SOMOD::Parameter":"chat.session.jwt.secret"},"SESSION_FORCE":{"SOMOD::Parameter":"chat.session.force"}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:Query"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}],"Events":{"PostMessage":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/post-message"}},"SyncMessages":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/sync-messages"}}}}},"Thread":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"ThreadApi"},"CodeUri":{"SOMOD::Function":{"name":"thread","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:GetItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]}]}],"Events":{"CreateThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/thread"}},"GetThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/thread/{id}"}},"UpdateSessionRequireInThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/thread/{id}/session"}}}}},"MessageNotificationTopic":{"Type":"AWS::SNS::Topic","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["TopicName"]},"Properties":{"TopicName":{"SOMOD::ResourceName":"MsgNotificationTopic"}}},"WebSocketApi":{"Type":"AWS::ApiGatewayV2::Api","SOMOD::Access":"public","Properties":{"Name":{"SOMOD::ResourceName":"ChatWebsocketApi"},"ProtocolType":"WEBSOCKET","RouteSelectionExpression":"$request.body.action"}},"ConnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::Access":"public","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$connect","OperationName":"ConnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"ConnectInteg"}}]]}}},"ConnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Connect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunctionArn}/invocations",{"OnConnectFunctionArn":{"SOMOD::Ref":{"resource":"OnConnectFunction","attribute":"Arn"}}}]}}},"DisconnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$disconnect","OperationName":"DisconnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DisconnectInteg"}}]]}}},"DisconnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Disconnect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunctionArn}/invocations",{"OnDisconnectFunctionArn":{"SOMOD::Ref":{"resource":"OnDisconnectFunction","attribute":"Arn"}}}]}}},"DefaultRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$default","OperationName":"DefaultRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DefaultInteg"}}]]}}},"DefaultRouteResponse":{"Type":"AWS::ApiGatewayV2::RouteResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"RouteId":{"SOMOD::Ref":{"resource":"DefaultRoute"}},"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteResponseKey":"$default"}},"DefaultInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Default Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnMessageFunctionArn}/invocations",{"OnMessageFunctionArn":{"SOMOD::Ref":{"resource":"OnMessageFunction","attribute":"Arn"}}}]}}},"DefaultIntegResponse":{"Type":"AWS::ApiGatewayV2::IntegrationResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"IntegrationId":{"SOMOD::Ref":{"resource":"DefaultInteg"}},"IntegrationResponseKey":"$default"}},"Stage":{"Type":"AWS::ApiGatewayV2::Stage","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"StageName":"$default","ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"AutoDeploy":true}},"ConnectionsTable":{"Type":"AWS::DynamoDB::Table","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"},{"AttributeName":"userId","AttributeType":"S"}],"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"GlobalSecondaryIndexes":[{"IndexName":"ByUserId","KeySchema":[{"AttributeName":"userId","KeyType":"HASH"}],"Projection":{"ProjectionType":"KEYS_ONLY"}}]}},"OnConnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnConnect","middlewares":[{"resource":"UserProviderMiddleware"}]}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]}]}]}},"OnConnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnConnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$connect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnDisconnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnDisconnect"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:DeleteItem"]}]}]}},"OnDisconnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnDisconnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$disconnect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnMessage","middlewares":[{"module":"somod-websocket-extension","resource":"SomodWebSocketMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"SESSION_SECRET":{"SOMOD::Parameter":"chat.session.jwt.secret"},"SESSION_FORCE":{"SOMOD::Parameter":"chat.session.force"}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}]}},"OnMessagePermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnMessageFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$default",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"NotifyMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"CodeUri":{"SOMOD::Function":{"type":"SNS","name":"wsNotifyMessage"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}},"CONNECTIONS_ENDPOINT":{"Fn::Sub":["https://${WebSocketApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Stage":{"SOMOD::Ref":{"resource":"Stage"}}}]}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"Fn::Sub":["${ConnectionsTableArn}/index/ByUserId",{"ConnectionsTableArn":{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}}]}],"Action":["dynamodb:Query"]}]},{"Statement":[{"Effect":"Allow","Action":["execute-api:ManageConnections"],"Resource":[{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}]}]}],"Events":{"NotifyFromSNS":{"Type":"SNS","Properties":{"Topic":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}}},"Timeout":300}}},"Outputs":{"chat.http-api.id":{"Fn::Sub":["https://${apiId}.execute-api.${AWS::Region}.amazonaws.com/",{"apiId":{"SOMOD::Ref":{"resource":"ChatApi"}}}]},"chat.websocket-api.id":{"SOMOD::If":[{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},{"Fn::Sub":["wss://${apiId}.execute-api.${AWS::Region}.amazonaws.com/$default",{"apiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]},null]}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somod-chat-service",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Serverless Chat Service from SOMOD",
5
5
  "scripts": {
6
6
  "prettier": "npx prettier --check --ignore-unknown --no-error-on-unmatched-pattern ./**/*",