diff --git a/.eslintrc.js b/.eslintrc.js
index 1cf30e92a..2b865ae3b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -10,6 +10,7 @@ module.exports = {
'eslint:recommended',
'plugin:vue/base',
'plugin:vue/essential',
+ "@vue/typescript/recommended",
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
@@ -60,9 +61,20 @@ module.exports = {
'jest/no-truthy-falsy': 'warn',
'jest/prefer-spy-on': 'error',
'jest/require-top-level-describe': 'warn',
+
+ '@typescript-eslint/no-var-requires': 'warn',
},
parserOptions: {
- ecmaVersion: 6,
- parser: 'babel-eslint'
- }
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+ overrides: [
+ {
+ files: ["*.js"],
+ rules: {
+ "@typescript-eslint/explicit-module-boundary-types": "off",
+ "@typescript-eslint/no-var-requires": "off",
+ }
+ }
+ ]
};
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b8d0fa775..ab7cc6eed 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -3,7 +3,8 @@ name: CI/CD
on:
pull_request:
push:
- branches: master
+ branches:
+ - master
paths-ignore:
- '.vscode/**'
- 'docs/**'
@@ -53,7 +54,13 @@ jobs:
- run: npm run lint-ci
env:
NODE_ENV: production
- - run: npm test
+ - run: npm run test-unit-client
+ - run: npm run test-unit-server
+ env:
+ REDIS_HOST: localhost
+ REDIS_PORT: 6379
+ - run: npx sequelize-cli db:migrate
+ - run: npm run test-e2e
env:
REDIS_HOST: localhost
REDIS_PORT: 6379
diff --git a/.optic/api/specification.json b/.optic/api/specification.json
index 04d995e82..711ad326d 100644
--- a/.optic/api/specification.json
+++ b/.optic/api/specification.json
@@ -1,5 +1,5 @@
[
-{"BatchCommitStarted":{"batchId":"419f7dcc-9754-4a15-a745-2f703ca070f2","commitMessage":"initialize api docs","eventContext":{"clientId":"anonymous","clientSessionId":"e363bd4d-3762-4659-884c-89b42d1caba9","clientCommandBatchId":"419f7dcc-9754-4a15-a745-2f703ca070f2","createdAt":"2021-03-17T20:33:10.585Z"}}}
+{"BatchCommitStarted":{"batchId":"419f7dcc-9754-4a15-a745-2f703ca070f2","commitMessage":"initialize api docs","parentId":null,"eventContext":{"clientId":"anonymous","clientSessionId":"e363bd4d-3762-4659-884c-89b42d1caba9","clientCommandBatchId":"419f7dcc-9754-4a15-a745-2f703ca070f2","createdAt":"2021-03-17T20:33:10.585Z"}}}
,{"PathComponentAdded":{"pathId":"path_ToMAgy1O4T","parentPathId":"root","name":"api","eventContext":{"clientId":"anonymous","clientSessionId":"e363bd4d-3762-4659-884c-89b42d1caba9","clientCommandBatchId":"419f7dcc-9754-4a15-a745-2f703ca070f2","createdAt":"2021-03-17T20:33:10.592Z"}}}
,{"PathComponentAdded":{"pathId":"path_iunrqSlWx6","parentPathId":"path_ToMAgy1O4T","name":"room","eventContext":{"clientId":"anonymous","clientSessionId":"e363bd4d-3762-4659-884c-89b42d1caba9","clientCommandBatchId":"419f7dcc-9754-4a15-a745-2f703ca070f2","createdAt":"2021-03-17T20:33:10.593Z"}}}
,{"PathParameterAdded":{"pathId":"path_wYST1fWVVx","parentPathId":"path_iunrqSlWx6","name":"RoomName","eventContext":null}}
@@ -255,7 +255,7 @@
,{"ContributionAdded":{"id":"path_ueXMVJGDNT.GET","key":"purpose","value":"Get all permission grants available","eventContext":{"clientId":"anonymous","clientSessionId":"e363bd4d-3762-4659-884c-89b42d1caba9","clientCommandBatchId":"334f99c5-884e-41b1-8caf-8a9b90b42725","createdAt":"2021-03-17T20:41:03.633Z"}}}
,{"ContributionAdded":{"id":"path_emFynlFlJX.POST","key":"purpose","value":"Create a room","eventContext":{"clientId":"anonymous","clientSessionId":"e363bd4d-3762-4659-884c-89b42d1caba9","clientCommandBatchId":"704501f8-e5bc-464e-b61c-3bd1a7a2c9a1","createdAt":"2021-03-17T20:41:29.035Z"}}}
,{"ContributionAdded":{"id":"path_wYST1fWVVx.GET","key":"purpose","value":"Get current room settings","eventContext":{"clientId":"anonymous","clientSessionId":"e363bd4d-3762-4659-884c-89b42d1caba9","clientCommandBatchId":"6620778d-2b2e-4c23-9081-0bba1da68bce","createdAt":"2021-03-17T20:41:56.978Z"}}}
-,{"BatchCommitStarted":{"batchId":"64305058-e0bc-41a9-8f06-cfb2da6502d8","commitMessage":"","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"64305058-e0bc-41a9-8f06-cfb2da6502d8","createdAt":"2021-05-08T15:53:49.792Z"}}}
+,{"BatchCommitStarted":{"batchId":"64305058-e0bc-41a9-8f06-cfb2da6502d8","commitMessage":"","parentId":null,"eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"64305058-e0bc-41a9-8f06-cfb2da6502d8","createdAt":"2021-05-08T15:53:49.792Z"}}}
,{"PathComponentAdded":{"pathId":"path_H7LlnwSM6H","parentPathId":"path_vCGm854LSf","name":"logout","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"64305058-e0bc-41a9-8f06-cfb2da6502d8","createdAt":"2021-05-08T15:53:49.799Z"}}}
,{"PathComponentAdded":{"pathId":"path_lDbNkAq628","parentPathId":"path_vCGm854LSf","name":"auth","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"64305058-e0bc-41a9-8f06-cfb2da6502d8","createdAt":"2021-05-08T15:53:49.799Z"}}}
,{"PathComponentAdded":{"pathId":"path_Mfg82tCl3o","parentPathId":"path_lDbNkAq628","name":"discord","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"64305058-e0bc-41a9-8f06-cfb2da6502d8","createdAt":"2021-05-08T15:53:49.800Z"}}}
@@ -315,6 +315,118 @@
,{"ContributionAdded":{"id":"path_wYST1fWVVx.DELETE","key":"purpose","value":"Unload room","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"aa2d2bfb-2c67-4c45-a50e-acee2f38c70b","createdAt":"2021-05-08T15:55:00.063Z"}}}
,{"ContributionAdded":{"id":"field_umnMOrPKto","key":"description","value":"","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"d0ce6fcb-4d29-4bfc-a0d1-ce758f89b9ea","createdAt":"2021-05-08T15:57:06.174Z"}}}
,{"ContributionAdded":{"id":"field_umnMOrPKto","key":"description","value":"","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"fc378824-7448-411c-a25c-4dbec518d7ec","createdAt":"2021-05-08T15:57:06.248Z"}}}
-,{"BatchCommitStarted":{"batchId":"eac4e14f-2797-4005-b895-285bb6a8fd24","commitMessage":"","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"eac4e14f-2797-4005-b895-285bb6a8fd24","createdAt":"2021-05-08T16:55:54.648Z"}}}
+,{"BatchCommitStarted":{"batchId":"eac4e14f-2797-4005-b895-285bb6a8fd24","commitMessage":"","parentId":null,"eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"eac4e14f-2797-4005-b895-285bb6a8fd24","createdAt":"2021-05-08T16:55:54.648Z"}}}
,{"BatchCommitEnded":{"batchId":"eac4e14f-2797-4005-b895-285bb6a8fd24","eventContext":{"clientId":"anonymous","clientSessionId":"de12a765-eee2-43b0-b79d-9847479fdab3","clientCommandBatchId":"eac4e14f-2797-4005-b895-285bb6a8fd24","createdAt":"2021-05-08T16:55:54.654Z"}}}
+,{"BatchCommitStarted":{"batchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","commitMessage":"","parentId":null,"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.755Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_kycE8EPOxY","baseShapeId":"$boolean","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.761Z"}}}
+,{"FieldAdded":{"fieldId":"field_cVhC3ZxKJZ","shapeId":"shape_BUZVI2XJHG","name":"claim","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_cVhC3ZxKJZ","shapeId":"shape_kycE8EPOxY"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.761Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_8WbAaqH6hM","baseShapeId":"$optional","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.762Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_8WbAaqH6hM","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_kycE8EPOxY"}},"consumingParameterId":"$optionalInner"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.762Z"}}}
+,{"FieldShapeSet":{"shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_cVhC3ZxKJZ","shapeId":"shape_8WbAaqH6hM"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.763Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_5wBClimC9i","baseShapeId":"$optional","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.763Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_5wBClimC9i","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_aYKF4E2gna"}},"consumingParameterId":"$optionalInner"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.763Z"}}}
+,{"FieldShapeSet":{"shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_AfH6Vr2EZO","shapeId":"shape_5wBClimC9i"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.764Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_roRyXLAnSm","baseShapeId":"$optional","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.764Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_roRyXLAnSm","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_Jmfcu9UpJS"}},"consumingParameterId":"$optionalInner"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.764Z"}}}
+,{"FieldShapeSet":{"shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_nz6Sj1mLM8","shapeId":"shape_roRyXLAnSm"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.765Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_RpaKUo6ZcC","baseShapeId":"$optional","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.765Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_RpaKUo6ZcC","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_uX7tQsTvLu"}},"consumingParameterId":"$optionalInner"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.765Z"}}}
+,{"FieldShapeSet":{"shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_15wjTa9KKW","shapeId":"shape_RpaKUo6ZcC"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.765Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_LjPJrrdL4I","baseShapeId":"$optional","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.765Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_LjPJrrdL4I","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_F65mc0ITOk"}},"consumingParameterId":"$optionalInner"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.765Z"}}}
+,{"FieldShapeSet":{"shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_umnMOrPKto","shapeId":"shape_LjPJrrdL4I"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.766Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_k7HZ6mWde0","baseShapeId":"$optional","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.766Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_k7HZ6mWde0","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_f7sSETe2Ze"}},"consumingParameterId":"$optionalInner"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.766Z"}}}
+,{"FieldShapeSet":{"shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_PKUghwl2OF","shapeId":"shape_k7HZ6mWde0"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.766Z"}}}
+,{"BatchCommitEnded":{"batchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"32b80016-3b86-4255-8b45-c4901c4eb74c","createdAt":"2021-05-29T05:02:01.766Z"}}}
+,{"BatchCommitStarted":{"batchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","commitMessage":"","parentId":null,"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.844Z"}}}
+,{"PathComponentAdded":{"pathId":"path_BdjIsLSV84","parentPathId":"path_ToMAgy1O4T","name":"dev","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.852Z"}}}
+,{"PathComponentAdded":{"pathId":"path_30gnOABW2f","parentPathId":"path_BdjIsLSV84","name":"reset-rate-limit","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.852Z"}}}
+,{"PathComponentAdded":{"pathId":"path_BXgqi6mhE5","parentPathId":"path_vCGm854LSf","name":"register","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.852Z"}}}
+,{"PathComponentAdded":{"pathId":"path_f8bMtbUE2r","parentPathId":"path_vCGm854LSf","name":"login","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.852Z"}}}
+,{"RequestParameterAddedByPathAndMethod":{"parameterId":"request-parameter_725Rwjo56J","pathId":"path_30gnOABW2f","httpMethod":"POST","parameterLocation":"query","name":"queryString","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.853Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_eJ8OuJM4Bt","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.853Z"}}}
+,{"RequestParameterShapeSet":{"parameterId":"request-parameter_725Rwjo56J","parameterDescriptor":{"shapeId":"shape_eJ8OuJM4Bt","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.853Z"}}}
+,{"RequestAdded":{"requestId":"request_0GEvKQen8i","pathId":"path_30gnOABW2f","httpMethod":"POST","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.853Z"}}}
+,{"ResponseAddedByPathAndMethod":{"responseId":"response_d7L5HqWuW7","pathId":"path_30gnOABW2f","httpMethod":"POST","httpStatusCode":200,"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.854Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_1WBblJyyh6","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.856Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_MjxzhrAjVh","baseShapeId":"$boolean","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.856Z"}}}
+,{"FieldAdded":{"fieldId":"field_Gs73S6lS4b","shapeId":"shape_1WBblJyyh6","name":"success","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_Gs73S6lS4b","shapeId":"shape_MjxzhrAjVh"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.856Z"}}}
+,{"ResponseBodySet":{"responseId":"response_d7L5HqWuW7","bodyDescriptor":{"httpContentType":"application/json","shapeId":"shape_1WBblJyyh6","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.857Z"}}}
+,{"RequestParameterAddedByPathAndMethod":{"parameterId":"request-parameter_66pLmViYj0","pathId":"path_BXgqi6mhE5","httpMethod":"POST","parameterLocation":"query","name":"queryString","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.857Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_LDEMphaO3Z","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.857Z"}}}
+,{"RequestParameterShapeSet":{"parameterId":"request-parameter_66pLmViYj0","parameterDescriptor":{"shapeId":"shape_LDEMphaO3Z","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.857Z"}}}
+,{"RequestAdded":{"requestId":"request_sownxMvZzK","pathId":"path_BXgqi6mhE5","httpMethod":"POST","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.857Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_RUGNT5ZAL6","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.857Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_KPWZCN6aim","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.858Z"}}}
+,{"FieldAdded":{"fieldId":"field_84yl2iz81K","shapeId":"shape_RUGNT5ZAL6","name":"email","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_84yl2iz81K","shapeId":"shape_KPWZCN6aim"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.858Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_Xh8yH4l5Ra","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.858Z"}}}
+,{"FieldAdded":{"fieldId":"field_nwMZyS0shG","shapeId":"shape_RUGNT5ZAL6","name":"password","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_nwMZyS0shG","shapeId":"shape_Xh8yH4l5Ra"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.858Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_wNfWylE0rj","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.858Z"}}}
+,{"FieldAdded":{"fieldId":"field_tOhUrNawhz","shapeId":"shape_RUGNT5ZAL6","name":"username","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_tOhUrNawhz","shapeId":"shape_wNfWylE0rj"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.858Z"}}}
+,{"RequestBodySet":{"requestId":"request_sownxMvZzK","bodyDescriptor":{"httpContentType":"application/json","shapeId":"shape_RUGNT5ZAL6","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.859Z"}}}
+,{"ResponseAddedByPathAndMethod":{"responseId":"response_723HB9ay6l","pathId":"path_BXgqi6mhE5","httpMethod":"POST","httpStatusCode":200,"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.859Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_aHyBK5HEYn","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.859Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_bIc3AlaO9H","baseShapeId":"$boolean","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.859Z"}}}
+,{"FieldAdded":{"fieldId":"field_w53oSqcbQa","shapeId":"shape_aHyBK5HEYn","name":"success","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_w53oSqcbQa","shapeId":"shape_bIc3AlaO9H"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.859Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_ZEcn2QCSH6","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.860Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_BtSpMvjcMH","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.860Z"}}}
+,{"FieldAdded":{"fieldId":"field_fegM8Mj6wq","shapeId":"shape_ZEcn2QCSH6","name":"email","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_fegM8Mj6wq","shapeId":"shape_BtSpMvjcMH"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.860Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_UmnDFmikXQ","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.860Z"}}}
+,{"FieldAdded":{"fieldId":"field_WK6ngfcIR0","shapeId":"shape_ZEcn2QCSH6","name":"username","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_WK6ngfcIR0","shapeId":"shape_UmnDFmikXQ"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.860Z"}}}
+,{"FieldAdded":{"fieldId":"field_2g0HLgA7EA","shapeId":"shape_aHyBK5HEYn","name":"user","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_2g0HLgA7EA","shapeId":"shape_ZEcn2QCSH6"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.860Z"}}}
+,{"ResponseBodySet":{"responseId":"response_723HB9ay6l","bodyDescriptor":{"httpContentType":"application/json","shapeId":"shape_aHyBK5HEYn","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.860Z"}}}
+,{"ResponseAddedByPathAndMethod":{"responseId":"response_Imybtl7zVh","pathId":"path_BXgqi6mhE5","httpMethod":"POST","httpStatusCode":400,"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_9SO9J0jGXf","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_dYJU6Wce1w","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_5i75FYhq1o","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_F4QDGkbmBO","baseShapeId":"$list","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"FieldAdded":{"fieldId":"field_CbQrT8VNEr","shapeId":"shape_dYJU6Wce1w","name":"fields","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_CbQrT8VNEr","shapeId":"shape_F4QDGkbmBO"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_3Yd1PIJjpj","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"FieldAdded":{"fieldId":"field_8a8e1Y6OLI","shapeId":"shape_dYJU6Wce1w","name":"message","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_8a8e1Y6OLI","shapeId":"shape_3Yd1PIJjpj"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_IhmgItUcRl","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"FieldAdded":{"fieldId":"field_z1hG61na9k","shapeId":"shape_dYJU6Wce1w","name":"name","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_z1hG61na9k","shapeId":"shape_IhmgItUcRl"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"FieldAdded":{"fieldId":"field_X0EBFiwwok","shapeId":"shape_9SO9J0jGXf","name":"error","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_X0EBFiwwok","shapeId":"shape_dYJU6Wce1w"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.861Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_aUhpEW4PdS","baseShapeId":"$boolean","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"FieldAdded":{"fieldId":"field_qYoeJqmo4W","shapeId":"shape_9SO9J0jGXf","name":"success","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_qYoeJqmo4W","shapeId":"shape_aUhpEW4PdS"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_F4QDGkbmBO","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_5i75FYhq1o"}},"consumingParameterId":"$listItem"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"ResponseBodySet":{"responseId":"response_Imybtl7zVh","bodyDescriptor":{"httpContentType":"application/json","shapeId":"shape_9SO9J0jGXf","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"RequestParameterAddedByPathAndMethod":{"parameterId":"request-parameter_UJL8uHk0S3","pathId":"path_f8bMtbUE2r","httpMethod":"POST","parameterLocation":"query","name":"queryString","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_i5Ko2MHWsf","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"RequestParameterShapeSet":{"parameterId":"request-parameter_UJL8uHk0S3","parameterDescriptor":{"shapeId":"shape_i5Ko2MHWsf","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"RequestAdded":{"requestId":"request_Eb0Dcr224F","pathId":"path_f8bMtbUE2r","httpMethod":"POST","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.862Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_wjc4D3IumU","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.863Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_7Y4R7spMgt","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.863Z"}}}
+,{"FieldAdded":{"fieldId":"field_nYrsGhISNV","shapeId":"shape_wjc4D3IumU","name":"email","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_nYrsGhISNV","shapeId":"shape_7Y4R7spMgt"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.863Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_lKy5y9w321","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.864Z"}}}
+,{"FieldAdded":{"fieldId":"field_uZbLBHSwUz","shapeId":"shape_wjc4D3IumU","name":"password","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_uZbLBHSwUz","shapeId":"shape_lKy5y9w321"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.864Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_uoZ3mg9rAd","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.864Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_PSYBAjdJ7S","baseShapeId":"$optional","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.864Z"}}}
+,{"FieldAdded":{"fieldId":"field_fp8sRjLgAt","shapeId":"shape_wjc4D3IumU","name":"username","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_fp8sRjLgAt","shapeId":"shape_PSYBAjdJ7S"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.864Z"}}}
+,{"ShapeParameterShapeSet":{"shapeDescriptor":{"ProviderInShape":{"shapeId":"shape_PSYBAjdJ7S","providerDescriptor":{"ShapeProvider":{"shapeId":"shape_uoZ3mg9rAd"}},"consumingParameterId":"$optionalInner"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.864Z"}}}
+,{"RequestBodySet":{"requestId":"request_Eb0Dcr224F","bodyDescriptor":{"httpContentType":"application/json","shapeId":"shape_wjc4D3IumU","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.864Z"}}}
+,{"ResponseAddedByPathAndMethod":{"responseId":"response_3rpcg4K0KL","pathId":"path_f8bMtbUE2r","httpMethod":"POST","httpStatusCode":200,"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_6qv5qFNh1Q","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_DhX6OHMveS","baseShapeId":"$boolean","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"FieldAdded":{"fieldId":"field_Kpsyyckay1","shapeId":"shape_6qv5qFNh1Q","name":"success","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_Kpsyyckay1","shapeId":"shape_DhX6OHMveS"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_4FSV4CuvpY","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_PI6aNXuzlM","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"FieldAdded":{"fieldId":"field_xB1TpwzhQW","shapeId":"shape_4FSV4CuvpY","name":"email","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_xB1TpwzhQW","shapeId":"shape_PI6aNXuzlM"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_00HXTfyHII","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"FieldAdded":{"fieldId":"field_YeMOwl9F24","shapeId":"shape_4FSV4CuvpY","name":"username","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_YeMOwl9F24","shapeId":"shape_00HXTfyHII"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.865Z"}}}
+,{"FieldAdded":{"fieldId":"field_QpNxEDi4SU","shapeId":"shape_6qv5qFNh1Q","name":"user","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_QpNxEDi4SU","shapeId":"shape_4FSV4CuvpY"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.866Z"}}}
+,{"ResponseBodySet":{"responseId":"response_3rpcg4K0KL","bodyDescriptor":{"httpContentType":"application/json","shapeId":"shape_6qv5qFNh1Q","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.866Z"}}}
+,{"ResponseAddedByPathAndMethod":{"responseId":"response_YFRtIAYLeN","pathId":"path_f8bMtbUE2r","httpMethod":"POST","httpStatusCode":401,"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.866Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_Fcn2uIGpAR","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.866Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_218g9bDu88","baseShapeId":"$object","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.866Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_umCUaIKH5t","baseShapeId":"$string","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.866Z"}}}
+,{"FieldAdded":{"fieldId":"field_ZR2z39iDBg","shapeId":"shape_218g9bDu88","name":"message","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_ZR2z39iDBg","shapeId":"shape_umCUaIKH5t"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.866Z"}}}
+,{"FieldAdded":{"fieldId":"field_4aigeHkQTz","shapeId":"shape_Fcn2uIGpAR","name":"error","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_4aigeHkQTz","shapeId":"shape_218g9bDu88"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.867Z"}}}
+,{"ShapeAdded":{"shapeId":"shape_W7ub87jNhL","baseShapeId":"$boolean","parameters":{"DynamicParameterList":{"shapeParameterIds":[]}},"name":"","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.867Z"}}}
+,{"FieldAdded":{"fieldId":"field_P3tr3xyYjx","shapeId":"shape_Fcn2uIGpAR","name":"success","shapeDescriptor":{"FieldShapeFromShape":{"fieldId":"field_P3tr3xyYjx","shapeId":"shape_W7ub87jNhL"}},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.867Z"}}}
+,{"ResponseBodySet":{"responseId":"response_YFRtIAYLeN","bodyDescriptor":{"httpContentType":"application/json","shapeId":"shape_Fcn2uIGpAR","isRemoved":false},"eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.867Z"}}}
+,{"BatchCommitEnded":{"batchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"1ba3957d-ec6a-46b4-8d68-d3344fa98ee7","createdAt":"2021-05-29T05:02:27.867Z"}}}
+,{"ContributionAdded":{"id":"path_30gnOABW2f.POST","key":"purpose","value":"Reset the rate limit for the requester","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"3cfd69a4-f883-4726-8687-8439931e0f55","createdAt":"2021-05-29T05:02:47.540Z"}}}
+,{"ContributionAdded":{"id":"path_30gnOABW2f.POST","key":"description","value":"Only available in development mode.","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"de12b48c-5e03-4115-aece-35d021c37032","createdAt":"2021-05-29T05:02:57.930Z"}}}
+,{"ContributionAdded":{"id":"path_BXgqi6mhE5.POST","key":"purpose","value":"Register a new user","eventContext":{"clientId":"anonymous","clientSessionId":"050b3a77-b7aa-4fa2-bc31-e0d61be00d75","clientCommandBatchId":"508a1b18-8b14-4069-9c59-d1a6faa82889","createdAt":"2021-05-29T05:03:12.333Z"}}}
]
\ No newline at end of file
diff --git a/README.md b/README.md
index 4dd5f9149..bb8f18822 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
# OpenTogetherTube
-[![Build Status](https://travis-ci.com/dyc3/opentogethertube.svg?branch=master)](https://travis-ci.com/dyc3/opentogethertube)
+[![CI/CD](https://github.com/dyc3/opentogethertube/actions/workflows/main.yml/badge.svg)](https://github.com/dyc3/opentogethertube/actions/workflows/main.yml)
[![codecov](https://codecov.io/gh/dyc3/opentogethertube/branch/master/graph/badge.svg)](https://codecov.io/gh/dyc3/opentogethertube)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dyc3_opentogethertube&metric=alert_status)](https://sonarcloud.io/dashboard?id=dyc3_opentogethertube)
[![Docker size](https://img.shields.io/docker/image-size/dyc3/opentogethertube)](https://hub.docker.com/r/dyc3/opentogethertube)
The easy way to watch videos with your friends.
-http://opentogethertube.com/
+https://opentogethertube.com/
# Deployment
diff --git a/api.js b/api.js
index a38a6e873..7b5d76b92 100644
--- a/api.js
+++ b/api.js
@@ -1,31 +1,32 @@
const express = require('express');
const uuid = require("uuid/v4");
const _ = require("lodash");
-const InfoExtract = require("./server/infoextractor");
+import InfoExtract from "./server/infoextractor";
+import { RoomRequestType } from "./common/models/messages";
const { getLogger } = require('./logger.js');
const permissions = require("./server/permissions.js");
const storage = require("./storage.js");
-const roommanager = require("./roommanager.js");
-const { rateLimiter, handleRateLimit, setRateLimitHeaders } = require("./server/rate-limit.js");
+import roommanager from "./server/roommanager";
+const { rateLimiter, handleRateLimit, setRateLimitHeaders } = require("./server/rate-limit");
+import { QueueMode, Role, Visibility } from "./common/models/types";
+import roomapi from "./server/api/room";
+import clientmanager from "./server/clientmanager";
+import { redisClient } from "./redisclient";
+import { ANNOUNCEMENT_CHANNEL } from "./common/constants";
const log = getLogger("api");
-// These strings are not allowed to be used as room names.
-const RESERVED_ROOM_NAMES = [
- "list",
- "create",
- "generate",
-];
-
const VALID_ROOM_VISIBILITY = [
- "public",
- "unlisted",
- "private",
+ Visibility.Public,
+ Visibility.Unlisted,
+ Visibility.Private,
];
const VALID_ROOM_QUEUE_MODE = [
- "manual",
- "vote",
+ QueueMode.Manual,
+ QueueMode.Vote,
+ QueueMode.Loop,
+ QueueMode.Dj,
];
function handleGetRoomFailure(res, err) {
@@ -83,52 +84,16 @@ function handlePostVideoFailure(res, err) {
const router = express.Router();
-router.get("/room/list", (req, res) => {
- let isAuthorized = req.get("apikey") === process.env.OPENTOGETHERTUBE_API_KEY;
- if (req.get("apikey") && !isAuthorized) {
- res.status(400).json({
- success: false,
- error: "apikey is invalid",
- });
- return;
- }
- let rooms = [];
- for (const room of roommanager.rooms) {
- if (room.visibility !== "public" && !isAuthorized) {
- continue;
- }
- let obj = {
- name: room.name,
- title: room.title,
- description: room.description,
- isTemporary: room.isTemporary,
- visibility: room.visibility,
- queueMode: room.queueMode,
- currentSource: room.currentSource,
- users: room.clients.length,
- };
- if (isAuthorized) {
- obj.queueLength = room.queue.length;
- obj.isPlaying = room.isPlaying;
- obj.playbackPosition = room.playbackPosition;
- obj.clients = room.clients.map(client => {
- return {
- username: client.username,
- isLoggedIn: client.isLoggedIn,
- ip: client.req_ip,
- forward_ip: client.req_forward_ip,
- };
- });
- }
- rooms.push(obj);
- }
- rooms = _.orderBy(rooms, ["users", "name"], ["desc", "asc"]);
- res.json(rooms);
-});
+router.use("/room", roomapi);
+if (process.env.NODE_ENV === "development") {
+ (async () => {
+ router.use("/dev", (await import("./server/api/dev")).default);
+ })();
+}
router.get("/room/:name", async (req, res) => {
try {
- let room = await roommanager.getOrLoadRoom(req.params.name);
+ let room = await roommanager.GetRoom(req.params.name);
let hasOwner = !!room.owner;
room = _.cloneDeep(_.pick(room, [
"name",
@@ -138,17 +103,18 @@ router.get("/room/:name", async (req, res) => {
"visibility",
"queueMode",
"queue",
- "clients",
- "permissions",
+ "users",
+ "grants",
]));
+ room.permissions = room.grants;
room.hasOwner = hasOwner;
- let clients = [];
- for (let c of room.clients) {
+ let users = [];
+ for (let c of room.users) {
let client = _.pick(c, ["username", "isLoggedIn"]);
client.name = client.username;
- clients.push(client);
+ users.push(client);
}
- room.clients = clients;
+ room.clients = users;
for (let video of room.queue) {
delete video._lastVotesChanged;
if (room.queueMode === "vote") {
@@ -165,118 +131,6 @@ router.get("/room/:name", async (req, res) => {
}
});
-router.post("/room/create", async (req, res) => {
- if (!req.body.name) {
- log.info(req.body);
- res.status(400).json({
- success: false,
- error: {
- message: "Missing argument (name)",
- },
- });
- return;
- }
- if (RESERVED_ROOM_NAMES.includes(req.body.name)) {
- res.status(400).json({
- success: false,
- error: {
- message: "Room name not allowed (reserved)",
- },
- });
- return;
- }
- if (req.body.name.length < 3) {
- res.status(400).json({
- success: false,
- error: {
- message: "Room name not allowed (too short, must be at least 3 characters)",
- },
- });
- return;
- }
- if (req.body.name.length > 32) {
- res.status(400).json({
- success: false,
- error: {
- message: "Room name not allowed (too long, must be at most 32 characters)",
- },
- });
- return;
- }
- if (!(/^[A-za-z0-9_-]+$/).exec(req.body.name)) {
- res.status(400).json({
- success: false,
- error: {
- message: "Room name not allowed (invalid characters)",
- },
- });
- return;
- }
- if (req.body.visibility && !VALID_ROOM_VISIBILITY.includes(req.body.visibility)) {
- res.status(400).json({
- success: false,
- error: {
- message: "Invalid value for room visibility",
- },
- });
- return;
- }
- let points = 50;
- if (!req.body.temporary) {
- req.body.temporary = false;
- points *= 4;
- }
- if (!req.body.visibility) {
- req.body.visibility = "public";
- }
- try {
- try {
- let info = await rateLimiter.consume(req.ip, points);
- setRateLimitHeaders(res, info);
- }
- catch (e) {
- if (e instanceof Error) {
- throw e;
- }
- else {
- handleRateLimit(res, e);
- return;
- }
- }
- if (req.user) {
- await roommanager.createRoom({ ...req.body, owner: req.user });
- }
- else {
- await roommanager.createRoom(req.body);
- }
- log.info(`${req.body.temporary ? "Temporary" : "Permanent"} room created: name=${req.body.name} ip=${req.ip} user-agent=${req.headers["user-agent"]}`);
- res.json({
- success: true,
- });
- }
- catch (e) {
- if (e.name === "RoomNameTakenException") {
- res.status(400).json({
- success: false,
- error: {
- name: e.name,
- message: "Room with that name already exists",
- },
- });
- }
- else {
- log.error(`Unable to create room: ${e} ${e.message}`);
- res.status(500).json({
- success: false,
- error: {
- name: "Unknown",
- message: "An unknown error occured when creating this room. Try again later.",
- },
- });
- }
- }
-});
-
router.post("/room/generate", async (req, res) => {
try {
let info = await rateLimiter.consume(req.ip, 50);
@@ -301,7 +155,10 @@ router.post("/room/generate", async (req, res) => {
}
let roomName = uuid();
log.debug(`Generating room: ${roomName}`);
- await roommanager.createRoom(roomName, true);
+ await roommanager.CreateRoom({
+ name: roomName,
+ isTemporary: true,
+ });
log.info(`room generated: ip=${req.ip} user-agent=${req.headers["user-agent"]}`);
res.json({
success: true,
@@ -310,9 +167,10 @@ router.post("/room/generate", async (req, res) => {
});
router.patch("/room/:name", async (req, res) => {
+ // FIXME: should send a room request to update the room settings
let room;
try {
- room = await roommanager.getOrLoadRoom(req.params.name);
+ room = await roommanager.GetRoom(req.params.name);
}
catch (err) {
handleGetRoomFailure(res, err);
@@ -347,6 +205,13 @@ router.patch("/room/:name", async (req, res) => {
if (req.body.claim && !room.owner) {
if (req.user) {
room.owner = req.user;
+ // HACK: force the room to send the updated user info to the client
+ for (let user of room.realusers) {
+ if (user.user_id === room.owner.id) {
+ room.syncUser(room.getUserInfo(user.id));
+ break;
+ }
+ }
}
else {
res.status(401).json({
@@ -366,7 +231,7 @@ router.patch("/room/:name", async (req, res) => {
});
}
catch (err) {
- log.error(`Failed to update room: ${err} ${err.message}`);
+ log.error(`Failed to update room: ${err} ${err.stack}`);
res.status(500).json({
success: false,
});
@@ -409,7 +274,7 @@ router.post("/room/:name/queue", async (req, res) => {
return;
}
}
- room = await roommanager.getOrLoadRoom(req.params.name);
+ room = await roommanager.GetRoom(req.params.name);
}
catch (err) {
handleGetRoomFailure(res, err);
@@ -417,15 +282,17 @@ router.post("/room/:name/queue", async (req, res) => {
}
try {
- let success;
+ let client = clientmanager.getClient(req.session, req.params.name);
+ // FIXME: what if the client is not connected to this node?
+ let roomRequest = { type: RoomRequestType.AddRequest, client: client.id };
if (req.body.videos) {
- success = await room.addManyToQueue(req.body.videos, req.session);
+ roomRequest.videos = req.body.videos;
}
else if (req.body.url) {
- success = await room.addToQueue({ url: req.body.url }, req.session);
+ roomRequest.url = req.body.url;
}
else if (req.body.service && req.body.id) {
- success = await room.addToQueue({ service: req.body.service, id: req.body.id }, req.session);
+ roomRequest.video = { service: req.body.service, id: req.body.id };
}
else {
res.status(400).json({
@@ -434,8 +301,9 @@ router.post("/room/:name/queue", async (req, res) => {
});
return;
}
+ await room.processRequest(roomRequest);
res.json({
- success,
+ success: true,
});
}
catch (err) {
@@ -460,7 +328,7 @@ router.delete("/room/:name/queue", async (req, res) => {
return;
}
}
- room = await roommanager.getOrLoadRoom(req.params.name);
+ room = await roommanager.GetRoom(req.params.name);
}
catch (err) {
handleGetRoomFailure(res, err);
@@ -468,69 +336,26 @@ router.delete("/room/:name/queue", async (req, res) => {
}
try {
- let success;
+ let client = clientmanager.getClient(req.session, req.params.name);
+ // FIXME: what if the client is not connected to this node?
if (req.body.service && req.body.id) {
- success = room.removeFromQueue({ service: req.body.service, id: req.body.id }, req.session);
- }
- else if (req.body.url) {
- success = room.removeFromQueue({ url: req.body.url }, req.session);
+ await room.processRequest({ type: RoomRequestType.RemoveRequest, client: client.id, video: {service: req.body.service, id: req.body.id} });
+ res.json({
+ success: true,
+ });
}
else {
res.status(400).json({
success: false,
error: "Invalid parameters",
});
- return;
}
- res.json({
- success,
- });
}
catch (err) {
handlePostVideoFailure(res, err);
}
});
-router.post("/room/:name/vote", (req, res) => {
- roommanager.getOrLoadRoom(req.params.name).then(room => {
- if (req.body.service && req.body.id) {
- let success = room.voteVideo({ service: req.body.service, id: req.body.id }, req.session);
- res.json({
- success,
- });
- }
- else {
- res.status(400).json({
- success: false,
- error: "Invalid parameters",
- });
- }
- }).catch(err => handleGetRoomFailure(res, err));
-});
-
-router.delete("/room/:name/vote", (req, res) => {
- roommanager.getOrLoadRoom(req.params.name).then(room => {
- if (req.body.service && req.body.id) {
- let success = room.removeVoteVideo({ service: req.body.service, id: req.body.id }, req.session);
- res.json({
- success,
- });
- }
- else {
- res.status(400).json({
- success: false,
- error: "Invalid parameters",
- });
- }
- }).catch(err => handleGetRoomFailure(res, err));
-});
-
-router.post("/room/:name/undo", (req, res) => {
- roommanager.getOrLoadRoom(req.params.name).then(room => {
- room.undoEvent(req.body.event);
- }).catch(err => handleGetRoomFailure(res, err));
-});
-
router.get("/data/previewAdd", async (req, res) => {
let points = 5;
try {
@@ -580,8 +405,15 @@ router.get("/data/previewAdd", async (req, res) => {
});
router.get("/data/permissions", (req, res) => {
- const { ROLES, ROLE_NAMES, ROLE_DISPLAY_NAMES, PERMISSIONS } = permissions;
- let roles = _.values(ROLES).map(i => {
+ const { ROLE_NAMES, ROLE_DISPLAY_NAMES, PERMISSIONS } = permissions;
+ let roles = [
+ Role.Owner,
+ Role.Administrator,
+ Role.Moderator,
+ Role.TrustedUser,
+ Role.RegisteredUser,
+ Role.UnregisteredUser,
+ ].map(i => {
return {
id: i,
name: ROLE_NAMES[i],
@@ -595,8 +427,8 @@ router.get("/data/permissions", (req, res) => {
});
router.post("/announce", (req, res) => {
- if (req.body.apikey) {
- if (req.body.apikey !== process.env.OPENTOGETHERTUBE_API_KEY) {
+ if (req.get("apikey")) {
+ if (req.get("apikey") !== process.env.OPENTOGETHERTUBE_API_KEY) {
res.status(400).json({
success: false,
error: "apikey is invalid",
@@ -620,13 +452,19 @@ router.post("/announce", (req, res) => {
}
try {
- roommanager.sendAnnouncement(req.body.text);
+ redisClient.publish(ANNOUNCEMENT_CHANNEL, JSON.stringify({
+ action: "announcement",
+ text: req.body.text,
+ }));
}
catch (error) {
log.error(`An unknown error occurred while sending an announcement: ${error}`);
res.status(500).json({
success: false,
- error: "Unknown, check logs",
+ error: {
+ name: "Unknown",
+ message: "Unknown, check logs",
+ },
});
return;
}
diff --git a/app.js b/app.js
index ae994f2a6..1cc23f119 100644
--- a/app.js
+++ b/app.js
@@ -1,3 +1,4 @@
+require('ts-node').register();
const express = require('express');
const http = require('http');
const fs = require('fs');
@@ -95,7 +96,7 @@ log.info(`Search provider: ${process.env.SEARCH_PROVIDER}`);
const app = express();
const server = http.createServer(app);
-const { redisClient } = require('./redisclient.js');
+const { redisClient } = require('./redisclient');
function checkRedis() {
let start = new Date();
@@ -132,9 +133,14 @@ let sessionOpts = {
},
};
if (process.env.NODE_ENV === "production" && !process.env.OTT_HOSTNAME.includes("localhost")) {
- app.set('trust proxy', 1);
+ log.warn("Trusting proxy, X-Forwarded-* headers will be trusted.");
+ app.set('trust proxy', process.env["TRUST_PROXY"] || 1);
sessionOpts.cookie.secure = true;
}
+if (process.env.FORCE_INSECURE_COOKIES) {
+ log.warn("FORCE_INSECURE_COOKIES found, cookies will only be set on http, not https");
+ sessionOpts.cookie.secure = false;
+}
const sessions = session(sessionOpts);
app.use(sessions);
@@ -157,6 +163,10 @@ app.use((req, res, next) => {
if (!req.user && !req.session.username) {
let username = uniqueNamesGenerator();
log.debug(`Generated name for new user (on request): ${username}`);
+ log.debug(`headers: x-forwarded-proto=${req.headers["x-forwarded-proto"]} x-forwarded-for=${req.headers["x-forwarded-for"]} x-forwarded-host=${req.headers["x-forwarded-host"]}`);
+ if (req.protocol === "http" && sessionOpts.cookie.secure) {
+ log.error(`found protocol ${req.protocol} and secure cookies. cookies will not be set`);
+ }
req.session.username = username;
req.session.save((err) => {
if (err) {
@@ -174,9 +184,14 @@ app.use((req, res, next) => {
next();
});
-const roommanager = require("./roommanager");
const api = require("./api");
-roommanager.start(server, sessions);
+
+const websockets = require("./server/websockets.js");
+websockets.Setup(server, sessions);
+const clientmanager = require("./server/clientmanager.ts");
+clientmanager.Setup();
+const roommanager = require("./server/roommanager");
+roommanager.start();
const bodyParser = require('body-parser');
app.use(bodyParser.json()); // to support JSON-encoded bodies
diff --git a/benchmarks/clients/environment.yml b/benchmarks/clients/environment.yml
new file mode 100644
index 000000000..d8b87c4dc
--- /dev/null
+++ b/benchmarks/clients/environment.yml
@@ -0,0 +1,10 @@
+name: ott
+channels:
+ - conda-forge
+ - defaults
+dependencies:
+ - python=3.9
+ - pip
+ - pip:
+ - websockets==9.0.2
+ - tqdm
diff --git a/benchmarks/clients/stress.py b/benchmarks/clients/stress.py
new file mode 100644
index 000000000..4a5240fd6
--- /dev/null
+++ b/benchmarks/clients/stress.py
@@ -0,0 +1,35 @@
+import asyncio
+import websockets
+import time
+from tqdm import tqdm
+import os
+
+port = os.environ.get("PORT") or "8080"
+
+url = f"ws://localhost:{port}/echo"
+
+async def main():
+ conns: websockets.ClientConnection = await asyncio.gather(*[websockets.connect(url) for _ in range(2000)])
+ print("all clients connected")
+ while True:
+
+ # start = None
+ # for conn in tqdm(conns):
+ # await conn.recv()
+ # if not start:
+ # print("start")
+ # start = time.time()
+ # end = time.time()
+ # print(f"time to receive all messages: {end - start}s")
+
+ start = None
+ for coro in asyncio.as_completed([conn.recv() for conn in conns]):
+ await coro
+ if not start:
+ print("start")
+ start = time.time()
+ end = time.time()
+ print(f"time to receive all messages: {end - start}s")
+
+
+asyncio.get_event_loop().run_until_complete(main())
diff --git a/benchmarks/golang/go.mod b/benchmarks/golang/go.mod
new file mode 100644
index 000000000..39a3e1ade
--- /dev/null
+++ b/benchmarks/golang/go.mod
@@ -0,0 +1,5 @@
+module test
+
+go 1.16
+
+require github.com/gorilla/websocket v1.4.2 // indirect
diff --git a/benchmarks/golang/go.sum b/benchmarks/golang/go.sum
new file mode 100644
index 000000000..85efffd99
--- /dev/null
+++ b/benchmarks/golang/go.sum
@@ -0,0 +1,2 @@
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
diff --git a/benchmarks/golang/main.go b/benchmarks/golang/main.go
new file mode 100644
index 000000000..acdddd966
--- /dev/null
+++ b/benchmarks/golang/main.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "flag"
+ "html/template"
+ "log"
+ "net/http"
+
+ "github.com/gorilla/websocket"
+)
+
+var addr = flag.String("addr", "localhost:8081", "http service address")
+
+var upgrader = websocket.Upgrader{} // use default options
+
+var connections []*websocket.Conn
+
+func echo(w http.ResponseWriter, r *http.Request) {
+ c, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Print("upgrade:", err)
+ return
+ }
+ connections = append(connections, c)
+ defer c.Close()
+ for {
+ mt, message, err := c.ReadMessage()
+ if err != nil {
+ log.Println("read:", err)
+ break
+ }
+ log.Printf("recv: %s", message)
+ for i := 0; i < len(connections); i++ {
+ connections[i].WriteMessage(mt, message)
+ }
+ // err = c.WriteMessage(mt, message)
+ // if err != nil {
+ // log.Println("write:", err)
+ // break
+ // }
+ }
+}
+
+func home(w http.ResponseWriter, r *http.Request) {
+ homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
+}
+
+func main() {
+ flag.Parse()
+ log.SetFlags(0)
+ http.HandleFunc("/echo", echo)
+ http.HandleFunc("/", home)
+ log.Fatal(http.ListenAndServe(*addr, nil))
+}
+
+var homeTemplate = template.Must(template.New("").Parse(`
+
+
+
+
+
+
+
+
+
+ Click "Open" to create a connection to the server,
+"Send" to send a message to the server and "Close" to close the connection.
+You can change the message and send multiple times.
+
+
+ |
+
+ |
+
+
+`))
diff --git a/benchmarks/rust/.gitignore b/benchmarks/rust/.gitignore
new file mode 100644
index 000000000..9f970225a
--- /dev/null
+++ b/benchmarks/rust/.gitignore
@@ -0,0 +1 @@
+target/
\ No newline at end of file
diff --git a/benchmarks/rust/Cargo.lock b/benchmarks/rust/Cargo.lock
new file mode 100644
index 000000000..4ec830b27
--- /dev/null
+++ b/benchmarks/rust/Cargo.lock
@@ -0,0 +1,1977 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "actix"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1be241f88f3b1e7e9a3fbe3b5a8a0f6915b5a1d7ee0d9a248d3376d01068cc60"
+dependencies = [
+ "actix-rt",
+ "actix_derive",
+ "bitflags",
+ "bytes 0.5.6",
+ "crossbeam-channel",
+ "derive_more",
+ "futures-channel",
+ "futures-util",
+ "log",
+ "once_cell",
+ "parking_lot",
+ "pin-project 0.4.28",
+ "smallvec",
+ "tokio",
+ "tokio-util",
+ "trust-dns-proto",
+ "trust-dns-resolver",
+]
+
+[[package]]
+name = "actix-codec"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570"
+dependencies = [
+ "bitflags",
+ "bytes 0.5.6",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project 0.4.28",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "actix-connect"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc"
+dependencies = [
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "actix-utils",
+ "derive_more",
+ "either",
+ "futures-util",
+ "http",
+ "log",
+ "trust-dns-proto",
+ "trust-dns-resolver",
+]
+
+[[package]]
+name = "actix-files"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8035f08f194893b199f4928b40425bd727c0257cf0fcf36f4ac214968d649ec7"
+dependencies = [
+ "actix-http",
+ "actix-service",
+ "actix-web",
+ "bitflags",
+ "bytes 0.5.6",
+ "derive_more",
+ "futures-core",
+ "futures-util",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "v_htmlescape",
+]
+
+[[package]]
+name = "actix-http"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874"
+dependencies = [
+ "actix-codec",
+ "actix-connect",
+ "actix-rt",
+ "actix-service",
+ "actix-threadpool",
+ "actix-utils",
+ "base64",
+ "bitflags",
+ "brotli2",
+ "bytes 0.5.6",
+ "cookie",
+ "copyless",
+ "derive_more",
+ "either",
+ "encoding_rs",
+ "flate2",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "fxhash",
+ "h2",
+ "http",
+ "httparse",
+ "indexmap",
+ "itoa",
+ "language-tags",
+ "lazy_static",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project 1.0.7",
+ "rand",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sha-1",
+ "slab",
+ "time",
+]
+
+[[package]]
+name = "actix-macros"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "actix-router"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c"
+dependencies = [
+ "bytestring",
+ "http",
+ "log",
+ "regex",
+ "serde",
+]
+
+[[package]]
+name = "actix-rt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227"
+dependencies = [
+ "actix-macros",
+ "actix-threadpool",
+ "copyless",
+ "futures-channel",
+ "futures-util",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "actix-server"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e"
+dependencies = [
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "actix-utils",
+ "futures-channel",
+ "futures-util",
+ "log",
+ "mio",
+ "mio-uds",
+ "num_cpus",
+ "slab",
+ "socket2",
+]
+
+[[package]]
+name = "actix-service"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb"
+dependencies = [
+ "futures-util",
+ "pin-project 0.4.28",
+]
+
+[[package]]
+name = "actix-testing"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c"
+dependencies = [
+ "actix-macros",
+ "actix-rt",
+ "actix-server",
+ "actix-service",
+ "log",
+ "socket2",
+]
+
+[[package]]
+name = "actix-threadpool"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30"
+dependencies = [
+ "derive_more",
+ "futures-channel",
+ "lazy_static",
+ "log",
+ "num_cpus",
+ "parking_lot",
+ "threadpool",
+]
+
+[[package]]
+name = "actix-tls"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb"
+dependencies = [
+ "actix-codec",
+ "actix-service",
+ "actix-utils",
+ "futures-util",
+]
+
+[[package]]
+name = "actix-utils"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a"
+dependencies = [
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "bitflags",
+ "bytes 0.5.6",
+ "either",
+ "futures-channel",
+ "futures-sink",
+ "futures-util",
+ "log",
+ "pin-project 0.4.28",
+ "slab",
+]
+
+[[package]]
+name = "actix-web"
+version = "3.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86"
+dependencies = [
+ "actix-codec",
+ "actix-http",
+ "actix-macros",
+ "actix-router",
+ "actix-rt",
+ "actix-server",
+ "actix-service",
+ "actix-testing",
+ "actix-threadpool",
+ "actix-tls",
+ "actix-utils",
+ "actix-web-codegen",
+ "awc",
+ "bytes 0.5.6",
+ "derive_more",
+ "encoding_rs",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "fxhash",
+ "log",
+ "mime",
+ "pin-project 1.0.7",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "socket2",
+ "time",
+ "tinyvec",
+ "url",
+]
+
+[[package]]
+name = "actix-web-actors"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6edf3c2693e2a8c422800c87ee89a6a4eac7dd01109bc172a1093ce1f4f001"
+dependencies = [
+ "actix",
+ "actix-codec",
+ "actix-http",
+ "actix-web",
+ "bytes 0.5.6",
+ "futures-channel",
+ "futures-core",
+ "pin-project 0.4.28",
+]
+
+[[package]]
+name = "actix-web-codegen"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "actix_derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95aceadaf327f18f0df5962fedc1bde2f870566a0b9f65c89508a3b1f79334c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "awc"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691"
+dependencies = [
+ "actix-codec",
+ "actix-http",
+ "actix-rt",
+ "actix-service",
+ "base64",
+ "bytes 0.5.6",
+ "cfg-if 1.0.0",
+ "derive_more",
+ "futures-core",
+ "log",
+ "mime",
+ "percent-encoding",
+ "rand",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+]
+
+[[package]]
+name = "base-x"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "brotli-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "brotli2"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
+dependencies = [
+ "brotli-sys",
+ "libc",
+]
+
+[[package]]
+name = "buf-min"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6ae7069aad07c7cdefe6a22a671f00650728bd2331a4cc62e1e5d0becdf9ca4"
+dependencies = [
+ "bytes 0.5.6",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "bytestring"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d"
+dependencies = [
+ "bytes 1.0.1",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "const_fn"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "cookie"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
+dependencies = [
+ "percent-encoding",
+ "time",
+ "version_check 0.9.3",
+]
+
+[[package]]
+name = "copyless"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
+
+[[package]]
+name = "cpufeatures"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
+dependencies = [
+ "crossbeam-utils",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
+dependencies = [
+ "autocfg",
+ "cfg-if 0.1.10",
+ "lazy_static",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "discard"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "enum-as-inner"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crc32fast",
+ "libc",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "futures"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
+
+[[package]]
+name = "futures-io"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121"
+dependencies = [
+ "autocfg",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282"
+
+[[package]]
+name = "futures-task"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
+
+[[package]]
+name = "futures-util"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
+dependencies = [
+ "autocfg",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite 0.2.6",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check 0.9.3",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "h2"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "tracing-futures",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "heck"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "http"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "httparse"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ipconfig"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
+dependencies = [
+ "socket2",
+ "widestring",
+ "winapi 0.3.9",
+ "winreg",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "language-tags"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "lock_api"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+
+[[package]]
+name = "maybe-uninit"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
+
+[[package]]
+name = "memchr"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio-uds"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
+dependencies = [
+ "iovec",
+ "libc",
+ "mio",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "nom"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
+dependencies = [
+ "memchr",
+ "version_check 0.1.5",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pin-project"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "918192b5c59119d51e0cd221f4d49dde9112824ba717369e903c97d076083d0f"
+dependencies = [
+ "pin-project-internal 0.4.28",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
+dependencies = [
+ "pin-project-internal 1.0.7",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be26700300be6d9d23264c73211d8190e755b6b5ca7a1b28230025511b52a5e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "resolv-conf"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
+dependencies = [
+ "hostname",
+ "quick-error",
+]
+
+[[package]]
+name = "rust"
+version = "0.1.0"
+dependencies = [
+ "actix",
+ "actix-files",
+ "actix-web",
+ "actix-web-actors",
+ "awc",
+ "env_logger",
+ "rand",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "serde"
+version = "1.0.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
+dependencies = [
+ "block-buffer",
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
+
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+
+[[package]]
+name = "socket2"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "standback"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
+dependencies = [
+ "version_check 0.9.3",
+]
+
+[[package]]
+name = "stdweb"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
+dependencies = [
+ "discard",
+ "rustc_version",
+ "stdweb-derive",
+ "stdweb-internal-macros",
+ "stdweb-internal-runtime",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "stdweb-derive"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-macros"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
+dependencies = [
+ "base-x",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha1",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-runtime"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
+
+[[package]]
+name = "syn"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "time"
+version = "0.2.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372"
+dependencies = [
+ "const_fn",
+ "libc",
+ "standback",
+ "stdweb",
+ "time-macros",
+ "version_check 0.9.3",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "time-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
+dependencies = [
+ "proc-macro-hack",
+ "time-macros-impl",
+]
+
+[[package]]
+name = "time-macros-impl"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "standback",
+ "syn",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "futures-core",
+ "iovec",
+ "lazy_static",
+ "libc",
+ "memchr",
+ "mio",
+ "mio-uds",
+ "pin-project-lite 0.1.12",
+ "signal-hook-registry",
+ "slab",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "log",
+ "pin-project-lite 0.1.12",
+ "tokio",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
+dependencies = [
+ "cfg-if 1.0.0",
+ "log",
+ "pin-project-lite 0.2.6",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project 1.0.7",
+ "tracing",
+]
+
+[[package]]
+name = "trust-dns-proto"
+version = "0.19.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9"
+dependencies = [
+ "async-trait",
+ "cfg-if 1.0.0",
+ "enum-as-inner",
+ "futures",
+ "idna",
+ "lazy_static",
+ "log",
+ "rand",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "trust-dns-resolver"
+version = "0.19.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb"
+dependencies = [
+ "cfg-if 0.1.10",
+ "futures",
+ "ipconfig",
+ "lazy_static",
+ "log",
+ "lru-cache",
+ "resolv-conf",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "trust-dns-proto",
+]
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check 0.9.3",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
+dependencies = [
+ "matches",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "v_escape"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "039a44473286eb84e4e74f90165feff67c802dbeced7ee4c5b00d719b0d0475e"
+dependencies = [
+ "buf-min",
+ "v_escape_derive",
+]
+
+[[package]]
+name = "v_escape_derive"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668"
+dependencies = [
+ "nom",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "v_htmlescape"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d7c2a33ed7cf0dc1b42bcf39e01b6512f9df08f09e1cd8a49d9dc49a6a9482"
+dependencies = [
+ "cfg-if 1.0.0",
+ "v_escape",
+]
+
+[[package]]
+name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
+
+[[package]]
+name = "widestring"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "winreg"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
diff --git a/benchmarks/rust/Cargo.toml b/benchmarks/rust/Cargo.toml
new file mode 100644
index 000000000..9db6783c6
--- /dev/null
+++ b/benchmarks/rust/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "rust"
+version = "0.1.0"
+authors = ["Carson McManus "]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[profile.release]
+codegen-units = 1
+lto = true
+
+[profile.release.package."*"]
+codegen-units = 1
+
+[dependencies]
+actix = "0.10"
+actix-web = "3"
+actix-web-actors = "3.0.0"
+actix-files = "0.3"
+awc = "2"
+env_logger = "0.8"
+rand = "0.7"
diff --git a/benchmarks/rust/src/main.rs b/benchmarks/rust/src/main.rs
new file mode 100644
index 000000000..8f3167a63
--- /dev/null
+++ b/benchmarks/rust/src/main.rs
@@ -0,0 +1,255 @@
+//! Simple echo websocket server.
+//! Open `http://localhost:8080/ws/index.html` in browser
+//! or [python console client](https://github.com/actix/examples/blob/master/websocket/websocket-client.py)
+//! could be used for testing.
+
+use std::{collections::HashMap, time::{Duration, Instant}};
+
+use actix::prelude::*;
+use actix_files as fs;
+use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
+use actix_web_actors::ws;
+use rand::{rngs::ThreadRng, Rng};
+
+// How often heartbeat pings are sent
+const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
+/// How long before lack of client response causes a timeout
+const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
+
+
+/// do websocket handshake and start `MyWebSocket` actor
+async fn ws_index(r: HttpRequest, stream: web::Payload, room: web::Data>) -> Result {
+ let res = ws::start(
+ Client {
+ id: 0,
+ hb: Instant::now(),
+ addr: room.get_ref().clone()
+ },
+ &r,
+ stream,
+ );
+ res
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct Message(pub String);
+
+/// New chat session is created
+#[derive(Message)]
+#[rtype(usize)]
+pub struct Connect {
+ pub addr: Recipient,
+}
+
+/// Session is disconnected
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct Disconnect {
+ pub id: usize,
+}
+
+/// Send message to specific room
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct ClientMessage {
+ /// Id of the client session
+ pub id: usize,
+ /// Peer message
+ pub msg: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct Room {
+ clients: HashMap>,
+ rng: ThreadRng,
+}
+
+impl Room {
+ pub fn new() -> Room {
+ Room {
+ clients: HashMap::new(),
+ rng: rand::thread_rng(),
+ }
+ }
+
+ fn send_message(&self, message: &str) {
+ for client in &self.clients {
+ let _ = client.1.do_send(Message(message.to_owned()));
+ }
+ }
+}
+
+impl Actor for Room {
+ type Context = Context;
+}
+
+impl Handler for Room {
+ type Result = usize;
+
+ fn handle(&mut self, msg: Connect, _: &mut Context) -> Self::Result {
+ println!("client connected");
+ let id = self.rng.gen::();
+ self.clients.insert(id, msg.addr);
+ id
+ }
+}
+
+impl Handler for Room {
+ type Result = ();
+
+ fn handle(&mut self, msg: Disconnect, _: &mut Context) -> Self::Result {
+ println!("client disconnected");
+ self.clients.remove(&msg.id);
+ }
+}
+
+impl Handler for Room {
+ type Result = ();
+
+ fn handle(&mut self, msg: ClientMessage, _: &mut Context) {
+ self.send_message(msg.msg.as_str());
+ }
+}
+
+struct Client {
+ /// unique session id
+ id: usize,
+ /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
+ /// otherwise we drop connection.
+ hb: Instant,
+ /// Chat server
+ addr: Addr,
+}
+
+impl Actor for Client {
+ type Context = ws::WebsocketContext;
+
+ /// Method is called on actor start.
+ /// We register ws session with ChatServer
+ fn started(&mut self, ctx: &mut Self::Context) {
+ // we'll start heartbeat process on session start.
+ self.hb(ctx);
+
+ // register self in chat server. `AsyncContext::wait` register
+ // future within context, but context waits until this future resolves
+ // before processing any other events.
+ // HttpContext::state() is instance of WsChatSessionState, state is shared
+ // across all routes within application
+ let addr = ctx.address();
+ self.addr
+ .send(Connect {
+ addr: addr.recipient(),
+ })
+ .into_actor(self)
+ .then(|res, act, ctx| {
+ match res {
+ Ok(res) => act.id = res,
+ // something is wrong with chat server
+ _ => ctx.stop(),
+ }
+ fut::ready(())
+ })
+ .wait(ctx);
+ }
+
+ fn stopping(&mut self, _: &mut Self::Context) -> Running {
+ // notify chat server
+ self.addr.do_send(Disconnect { id: self.id });
+ Running::Stop
+ }
+}
+
+impl Client {
+ fn hb(&self, ctx: &mut ::Context) {
+ ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
+ // check client heartbeats
+ if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
+ // heartbeat timed out
+ println!("Websocket Client heartbeat failed, disconnecting!");
+
+ // stop actor
+ ctx.stop();
+
+ // don't try to send a ping
+ return;
+ }
+
+ ctx.ping(b"");
+ });
+ }
+}
+
+impl Handler for Client {
+ type Result = ();
+
+ fn handle(&mut self, msg: Message, ctx: &mut Self::Context) {
+ ctx.text(msg.0);
+ }
+}
+
+impl StreamHandler> for Client {
+ fn handle(
+ &mut self,
+ msg: Result,
+ ctx: &mut Self::Context,
+ ) {
+ let msg = match msg {
+ Err(_) => {
+ ctx.stop();
+ return;
+ }
+ Ok(msg) => msg,
+ };
+
+ // println!("WEBSOCKET MESSAGE: {:?}", msg);
+ match msg {
+ ws::Message::Ping(msg) => {
+ self.hb = Instant::now();
+ ctx.pong(&msg);
+ }
+ ws::Message::Pong(_) => {
+ self.hb = Instant::now();
+ }
+ ws::Message::Text(text) => {
+ let m = text.trim();
+ self.addr.do_send(ClientMessage {
+ id: self.id,
+ msg: m.to_owned(),
+ })
+ }
+ ws::Message::Binary(_) => println!("Unexpected binary"),
+ ws::Message::Close(reason) => {
+ ctx.close(reason);
+ ctx.stop();
+ }
+ ws::Message::Continuation(_) => {
+ ctx.stop();
+ }
+ ws::Message::Nop => (),
+ }
+ }
+}
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+ std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
+ env_logger::init();
+
+ let server = Room::new().start();
+
+ HttpServer::new(move || {
+ App::new()
+ .data(server.clone())
+ // enable logger
+ .wrap(middleware::Logger::default())
+ // websocket route
+ .service(web::resource("/echo").route(web::get().to(ws_index)))
+ // static files
+ .service(fs::Files::new("/", "static/").index_file("index.html"))
+ })
+ // start http server on 127.0.0.1:8080
+ .bind("127.0.0.1:8080")?
+ .run()
+ .await
+}
diff --git a/common/constants.ts b/common/constants.ts
new file mode 100644
index 000000000..542a3670b
--- /dev/null
+++ b/common/constants.ts
@@ -0,0 +1 @@
+export const ANNOUNCEMENT_CHANNEL = "announcement";
diff --git a/common/models/messages.ts b/common/models/messages.ts
new file mode 100644
index 000000000..68512c66d
--- /dev/null
+++ b/common/models/messages.ts
@@ -0,0 +1,196 @@
+/* eslint-disable no-unused-vars */
+import { ClientId, ClientInfo, QueueMode, RoomUserInfo, Visibility, Grants, PlayerStatus, Role, RoomEventContext } from "./types";
+import { Video, VideoId } from "./video";
+
+export type ServerMessage = ServerMessageSync | ServerMessageUnload | ServerMessageChat | ServerMessageEvent | ServerMessageAnnouncement | ServerMessageUser
+
+interface ServerMessageBase {
+ action: string
+}
+
+export interface ServerMessageSync extends ServerMessageBase {
+ action: "sync"
+ name?: string
+ title?: string,
+ description?: string,
+ isTemporary?: boolean,
+ visibility?: Visibility,
+ queueMode?: QueueMode,
+ isPlaying?: boolean,
+ playbackPosition?: number,
+}
+
+export interface ServerMessageUnload extends ServerMessageBase {
+ action: "unload"
+}
+
+export interface ServerMessageChat extends ServerMessageBase {
+ action: "chat"
+ from: RoomUserInfo
+ text: string
+}
+
+export interface ServerMessageEvent extends ServerMessageBase {
+ action: "event"
+ request: RoomRequest
+ user: RoomUserInfo
+ additional: RoomEventContext
+}
+
+export interface ServerMessageAnnouncement extends ServerMessageBase {
+ action: "announcement"
+ text: string
+}
+
+export interface ServerMessageUser extends ServerMessageBase {
+ action: "user"
+ user: UserInfo
+}
+
+export interface UserInfo extends Omit {
+ isYou?: boolean
+ grants: number
+}
+
+export type ClientMessage = ClientMessagePlay | ClientMessagePause | ClientMessageSkip | ClientMessageSeek | ClientMessageOrder | ClientMessageChat | ClientMessageKickMe | ClientMessagePlayerStatus | ClientMessagePromote;
+
+interface ClientMessageBase {
+ action: string
+}
+
+export interface ClientMessagePlay extends ClientMessageBase {
+ action: "play"
+}
+
+export interface ClientMessagePause extends ClientMessageBase {
+ action: "pause"
+}
+
+export interface ClientMessageSkip extends ClientMessageBase {
+ action: "skip"
+}
+
+export interface ClientMessageSeek extends ClientMessageBase {
+ action: "seek"
+ position: number
+}
+
+export interface ClientMessageOrder extends ClientMessageBase {
+ action: "queue-move"
+ currentIdx: number
+ targetIdx: number
+}
+
+export interface ClientMessageChat extends ClientMessageBase {
+ action: "chat"
+ text: string
+}
+
+export interface ClientMessageKickMe extends ClientMessageBase {
+ action: "kickme"
+}
+
+export interface ClientMessagePlayerStatus extends ClientMessageBase {
+ action: "status"
+ status: PlayerStatus
+}
+
+export interface ClientMessagePromote extends ClientMessageBase {
+ action: "set-role"
+ clientId: ClientId
+ role: Role
+}
+
+export type RoomRequest = JoinRequest | LeaveRequest | PlaybackRequest | SkipRequest | SeekRequest | AddRequest | RemoveRequest | OrderRequest | VoteRequest | PromoteRequest | UpdateUser | ChatRequest | UndoRequest
+
+export enum RoomRequestType {
+ JoinRequest,
+ LeaveRequest,
+ PlaybackRequest,
+ SkipRequest,
+ SeekRequest,
+ AddRequest,
+ RemoveRequest,
+ OrderRequest,
+ VoteRequest,
+ PromoteRequest,
+ UpdateUser,
+ ChatRequest,
+ UndoRequest,
+}
+
+export interface RoomRequestBase {
+ type: RoomRequestType
+ client: ClientId
+}
+
+export interface JoinRequest extends RoomRequestBase {
+ type: RoomRequestType.JoinRequest
+ info: ClientInfo
+}
+
+export interface LeaveRequest extends RoomRequestBase {
+ type: RoomRequestType.LeaveRequest
+}
+
+export interface PlaybackRequest extends RoomRequestBase {
+ type: RoomRequestType.PlaybackRequest
+ state: boolean
+}
+
+export interface SkipRequest extends RoomRequestBase {
+ type: RoomRequestType.SkipRequest
+}
+
+export interface SeekRequest extends RoomRequestBase {
+ type: RoomRequestType.SeekRequest
+ value: number
+}
+
+export interface AddRequest extends RoomRequestBase {
+ type: RoomRequestType.AddRequest
+ video?: VideoId
+ videos?: VideoId[]
+ url? :string
+}
+
+export interface RemoveRequest extends RoomRequestBase {
+ type: RoomRequestType.RemoveRequest
+ video: VideoId
+}
+
+export interface OrderRequest extends RoomRequestBase {
+ type: RoomRequestType.OrderRequest
+ fromIdx: number
+ toIdx: number
+}
+
+export interface VoteRequest extends RoomRequestBase {
+ type: RoomRequestType.VoteRequest
+ video: VideoId,
+ add: boolean
+}
+
+export interface PromoteRequest extends RoomRequestBase {
+ type: RoomRequestType.PromoteRequest
+ targetClientId: ClientId
+ role: Role
+}
+
+/**
+ * Request that the room pull new information about the user.
+ */
+export interface UpdateUser extends RoomRequestBase {
+ type: RoomRequestType.UpdateUser
+ info: ClientInfo
+}
+
+export interface ChatRequest extends RoomRequestBase {
+ type: RoomRequestType.ChatRequest
+ text: string
+}
+
+export interface UndoRequest extends RoomRequestBase {
+ type: RoomRequestType.UndoRequest
+ event: ServerMessageEvent
+}
diff --git a/common/models/types.ts b/common/models/types.ts
new file mode 100644
index 000000000..1a9df81c7
--- /dev/null
+++ b/common/models/types.ts
@@ -0,0 +1,99 @@
+/* eslint-disable no-unused-vars */
+import { Session } from "express-session";
+import { User } from "models/user";
+import { Video } from "./video";
+
+export enum Visibility {
+ Public = "public",
+ Unlisted = "unlisted",
+ Private = "private",
+}
+
+export enum QueueMode {
+ Manual = "manual",
+ Vote = "vote",
+ Loop = "loop",
+ Dj = "dj",
+}
+
+export enum OttWebsocketError {
+ UNKNOWN = 4000,
+ INVALID_CONNECTION_URL = 4001,
+ ROOM_NOT_FOUND = 4002,
+ ROOM_UNLOADED = 4003,
+}
+
+export enum PlayerStatus {
+ none = "none",
+ ready = "ready",
+ buffering = "buffering",
+ error = "error",
+}
+
+export type MySession = Session & { username?: string, passport?: { user?: number } }
+
+export type ClientInfo = { id: ClientId, username?: string, user_id?: number, status?: PlayerStatus }
+
+export interface RoomOptions {
+ name: string
+ title: string
+ description: string
+ visibility: Visibility
+ queueMode: QueueMode
+ isTemporary: boolean
+ owner: User | null,
+ grants: Grants
+ userRoles: Map>
+}
+
+export interface RoomState extends RoomOptions, RoomStateComputed {
+ currentSource: Video | null
+ queue: Video[]
+ isPlaying: boolean
+ playbackPosition: number
+ users: RoomUserInfo[]
+ votes: Map>
+}
+
+export interface RoomStateComputed {
+ hasOwner: boolean
+ voteCounts: Map
+}
+
+// Only these should be sent to clients, all others should be considered unsafe
+export type RoomStateSyncable = Omit
+
+// Only these should be stored in redis
+export type RoomStateStorable = Omit
+
+export type RoomUserInfo = {
+ id: ClientId
+ name: string
+ isLoggedIn: boolean
+ status: PlayerStatus
+ role: Role
+}
+
+export enum Role {
+ Administrator = 4,
+ Moderator = 3,
+ TrustedUser = 2,
+ RegisteredUser = 1,
+ UnregisteredUser = 0,
+ Owner = -1,
+}
+
+export type ClientId = string
+
+export declare class Grants {
+ masks: any
+ constructor(grants?: Grants | any);
+}
+
+export interface RoomEventContext {
+ video?: Video
+ videos?: Video[]
+ prevPosition?: number
+ queueIdx?: number
+ user?: RoomUserInfo
+}
diff --git a/common/models/video.ts b/common/models/video.ts
new file mode 100644
index 000000000..82dfceeb3
--- /dev/null
+++ b/common/models/video.ts
@@ -0,0 +1,40 @@
+export interface VideoId {
+ service: string
+ id: string
+}
+
+// export type Video = VideoDefault | VideoGoogleDrive | VideoDirect
+
+export interface VideoDefault extends VideoId {
+ title: string | null
+ description: string | null
+ length: number | null
+ thumbnail: string | null
+}
+
+export interface VideoGoogleDrive extends VideoId {
+ service: "googledrive"
+ title: string | null
+ length: number | null
+ thumbnail: string | null
+ mime: string | null
+}
+
+export interface VideoDirect extends VideoId {
+ service: "direct"
+ title: string | null
+ description: string | null
+ length: number | null
+ mime: string | null
+}
+
+export interface Video extends VideoId {
+ title?: string | null
+ description?: string | null
+ length?: number | null
+ thumbnail?: string | null
+ mime?: string | null
+ highlight?: boolean
+}
+
+export type VideoMetadata = Omit