Merge branch 'feat-lobby' into develop
commit
51e4d6664d
|
|
@ -110,3 +110,8 @@ This started as a fork of the [work](https://github.com/versatica/mediasoup-demo
|
|||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
Contributions to this work were made on behalf of the GÉANT project, a project that has received funding from the European Union’s Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2). On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material which was developed by a member of the GÉANT project.
|
||||
|
||||
GÉANT Vereniging (Association) is registered with the Chamber of Commerce in Amsterdam with registration number 40535155 and operates in the UK as a branch of GÉANT Vereniging. Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK.
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"import",
|
||||
"react"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"react-app"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"array-bracket-spacing": [
|
||||
2,
|
||||
"always",
|
||||
{
|
||||
"objectsInArrays": true,
|
||||
"arraysInArrays": true
|
||||
}
|
||||
],
|
||||
"arrow-parens": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"arrow-spacing": 2,
|
||||
"block-spacing": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"brace-style": [
|
||||
2,
|
||||
"allman",
|
||||
{
|
||||
"allowSingleLine": true
|
||||
}
|
||||
],
|
||||
"camelcase": 2,
|
||||
"comma-dangle": 2,
|
||||
"comma-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"comma-style": 2,
|
||||
"computed-property-spacing": 2,
|
||||
"constructor-super": 2,
|
||||
"func-call-spacing": 2,
|
||||
"generator-star-spacing": 2,
|
||||
"guard-for-in": 2,
|
||||
"indent": [
|
||||
2,
|
||||
"tab",
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"key-spacing": [
|
||||
2,
|
||||
{
|
||||
"singleLine": {
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
},
|
||||
"multiLine": {
|
||||
"beforeColon": true,
|
||||
"afterColon": true,
|
||||
"align": "colon"
|
||||
}
|
||||
}
|
||||
],
|
||||
"keyword-spacing": 2,
|
||||
"linebreak-style": [
|
||||
2,
|
||||
"unix"
|
||||
],
|
||||
"lines-around-comment": [
|
||||
2,
|
||||
{
|
||||
"allowBlockStart": true,
|
||||
"allowObjectStart": true,
|
||||
"beforeBlockComment": true,
|
||||
"beforeLineComment": false
|
||||
}
|
||||
],
|
||||
"max-len": [
|
||||
2,
|
||||
90,
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"comments": 110,
|
||||
"ignoreUrls": true,
|
||||
"ignoreStrings": true,
|
||||
"ignoreTemplateLiterals": true,
|
||||
"ignoreRegExpLiterals": true
|
||||
}
|
||||
],
|
||||
"newline-after-var": 2,
|
||||
"newline-before-return": 2,
|
||||
"newline-per-chained-call": 2,
|
||||
"no-alert": 2,
|
||||
"no-caller": 2,
|
||||
"no-case-declarations": 2,
|
||||
"no-catch-shadow": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-confusing-arrow": [
|
||||
"error",
|
||||
{
|
||||
"allowParens": true
|
||||
}
|
||||
],
|
||||
"no-console": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-debugger": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-div-regex": 2,
|
||||
"no-empty": [
|
||||
2,
|
||||
{
|
||||
"allowEmptyCatch": true
|
||||
}
|
||||
],
|
||||
"no-empty-pattern": 2,
|
||||
"no-else-return": 0,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-label": 2,
|
||||
"no-extra-semi": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-global-assign": 2,
|
||||
"no-implicit-coercion": 2,
|
||||
"no-implicit-globals": 2,
|
||||
"no-inner-declarations": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-operators": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [
|
||||
2,
|
||||
{
|
||||
"max": 1,
|
||||
"maxEOF": 0,
|
||||
"maxBOF": 0
|
||||
}
|
||||
],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-proto": 2,
|
||||
"no-prototype-builtins": 0,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-restricted-imports": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-self-assign": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-undef": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unmodified-loop-condition": 2,
|
||||
"no-unreachable": 2,
|
||||
"no-unused-vars": [
|
||||
1,
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "after-used"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": [
|
||||
2,
|
||||
{
|
||||
"functions": false
|
||||
}
|
||||
],
|
||||
"no-useless-call": 2,
|
||||
"no-useless-computed-key": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-useless-rename": 2,
|
||||
"no-var": 2,
|
||||
"no-whitespace-before-property": 2,
|
||||
"object-curly-newline": 0,
|
||||
"object-curly-spacing": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"object-property-newline": [
|
||||
2,
|
||||
{
|
||||
"allowMultiplePropertiesPerLine": true
|
||||
}
|
||||
],
|
||||
"prefer-const": 2,
|
||||
"prefer-rest-params": 2,
|
||||
"prefer-spread": 2,
|
||||
"prefer-template": 2,
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"semi-spacing": 2,
|
||||
"space-before-blocks": 2,
|
||||
"space-before-function-paren": [
|
||||
2,
|
||||
{
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}
|
||||
],
|
||||
"space-in-parens": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"spaced-comment": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"strict": 2,
|
||||
"valid-typeof": 2,
|
||||
"eol-last": 0,
|
||||
"yoda": 2,
|
||||
"import/extensions": 2,
|
||||
"import/no-duplicates": 2,
|
||||
"jsx-quotes": [
|
||||
2,
|
||||
"prefer-single"
|
||||
],
|
||||
"react/display-name": [
|
||||
2,
|
||||
{
|
||||
"ignoreTranspilerName": false
|
||||
}
|
||||
],
|
||||
"react/forbid-prop-types": 0,
|
||||
"react/jsx-boolean-value": 2,
|
||||
"react/jsx-closing-bracket-location": 2,
|
||||
"react/jsx-curly-spacing": 2,
|
||||
"react/jsx-equals-spacing": 2,
|
||||
"react/jsx-handler-names": 2,
|
||||
"react/jsx-indent-props": [
|
||||
2,
|
||||
"tab"
|
||||
],
|
||||
"react/jsx-indent": [
|
||||
2,
|
||||
"tab"
|
||||
],
|
||||
"react/jsx-key": 2,
|
||||
"react/jsx-max-props-per-line": 0,
|
||||
"react/jsx-no-bind": 0,
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/jsx-no-literals": 0,
|
||||
"react/jsx-no-undef": 0,
|
||||
"react/jsx-pascal-case": 2,
|
||||
"react/jsx-sort-prop-types": 0,
|
||||
"react/jsx-sort-props": 0,
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/no-danger": 2,
|
||||
"react/no-deprecated": 2,
|
||||
"react/no-did-mount-set-state": 2,
|
||||
"react/no-did-update-set-state": 2,
|
||||
"react/no-direct-mutation-state": 2,
|
||||
"react/no-is-mounted": 2,
|
||||
"react/no-multi-comp": 0,
|
||||
"react/no-set-state": 0,
|
||||
"react/no-string-refs": 0,
|
||||
"react/no-unknown-property": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/prop-types": [
|
||||
2,
|
||||
{
|
||||
"skipUndeclared": true
|
||||
}
|
||||
],
|
||||
"react/react-in-jsx-scope": 2,
|
||||
"react/self-closing-comp": 2,
|
||||
"react/sort-comp": 0,
|
||||
"react/jsx-wrap-multilines": [
|
||||
2,
|
||||
{
|
||||
"declaration": false,
|
||||
"assignment": false,
|
||||
"return": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
375
app/package.json
375
app/package.json
|
|
@ -6,32 +6,32 @@
|
|||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.1.2",
|
||||
"@material-ui/icons": "^4.2.1",
|
||||
"bowser": "^2.4.0",
|
||||
"create-torrent": "^3.33.0",
|
||||
"@material-ui/core": "^4.5.1",
|
||||
"@material-ui/icons": "^4.5.1",
|
||||
"bowser": "^2.7.0",
|
||||
"create-torrent": "^4.4.1",
|
||||
"domready": "^1.0.8",
|
||||
"file-saver": "^2.0.1",
|
||||
"file-saver": "^2.0.2",
|
||||
"hark": "^1.2.3",
|
||||
"marked": "^0.6.1",
|
||||
"mediasoup-client": "^3.0.6",
|
||||
"notistack": "^0.5.1",
|
||||
"marked": "^0.7.0",
|
||||
"mediasoup-client": "^3.2.7",
|
||||
"notistack": "^0.9.5",
|
||||
"prop-types": "^15.7.2",
|
||||
"random-string": "^0.2.0",
|
||||
"react": "^16.8.5",
|
||||
"react-cookie-consent": "^2.2.2",
|
||||
"react-dom": "^16.8.5",
|
||||
"react-redux": "^6.0.1",
|
||||
"react-scripts": "2.1.8",
|
||||
"redux": "^4.0.1",
|
||||
"react": "^16.10.2",
|
||||
"react-cookie-consent": "^2.5.0",
|
||||
"react-dom": "^16.10.2",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-scripts": "3.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-persist": "^5.10.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"riek": "^1.1.0",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"source-map-explorer": "^1.8.0",
|
||||
"webtorrent": "^0.103.1"
|
||||
"socket.io-client": "^2.3.0",
|
||||
"source-map-explorer": "^2.1.0",
|
||||
"webtorrent": "^0.107.16"
|
||||
},
|
||||
"scripts": {
|
||||
"analyze-main": "source-map-explorer build/static/js/main.*",
|
||||
|
|
@ -41,342 +41,15 @@
|
|||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"import",
|
||||
"react"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"array-bracket-spacing": [
|
||||
2,
|
||||
"always",
|
||||
{
|
||||
"objectsInArrays": true,
|
||||
"arraysInArrays": true
|
||||
}
|
||||
],
|
||||
"arrow-parens": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"arrow-spacing": 2,
|
||||
"block-spacing": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"brace-style": [
|
||||
2,
|
||||
"allman",
|
||||
{
|
||||
"allowSingleLine": true
|
||||
}
|
||||
],
|
||||
"camelcase": 2,
|
||||
"comma-dangle": 2,
|
||||
"comma-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"comma-style": 2,
|
||||
"computed-property-spacing": 2,
|
||||
"constructor-super": 2,
|
||||
"func-call-spacing": 2,
|
||||
"generator-star-spacing": 2,
|
||||
"guard-for-in": 2,
|
||||
"indent": [
|
||||
2,
|
||||
"tab",
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"key-spacing": [
|
||||
2,
|
||||
{
|
||||
"singleLine": {
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
},
|
||||
"multiLine": {
|
||||
"beforeColon": true,
|
||||
"afterColon": true,
|
||||
"align": "colon"
|
||||
}
|
||||
}
|
||||
],
|
||||
"keyword-spacing": 2,
|
||||
"linebreak-style": [
|
||||
2,
|
||||
"unix"
|
||||
],
|
||||
"lines-around-comment": [
|
||||
2,
|
||||
{
|
||||
"allowBlockStart": true,
|
||||
"allowObjectStart": true,
|
||||
"beforeBlockComment": true,
|
||||
"beforeLineComment": false
|
||||
}
|
||||
],
|
||||
"max-len": [
|
||||
2,
|
||||
90,
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"comments": 110,
|
||||
"ignoreUrls": true,
|
||||
"ignoreStrings": true,
|
||||
"ignoreTemplateLiterals": true,
|
||||
"ignoreRegExpLiterals": true
|
||||
}
|
||||
],
|
||||
"newline-after-var": 2,
|
||||
"newline-before-return": 2,
|
||||
"newline-per-chained-call": 2,
|
||||
"no-alert": 2,
|
||||
"no-caller": 2,
|
||||
"no-case-declarations": 2,
|
||||
"no-catch-shadow": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-confusing-arrow": [
|
||||
"error",
|
||||
{
|
||||
"allowParens": true
|
||||
}
|
||||
],
|
||||
"no-console": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-debugger": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-div-regex": 2,
|
||||
"no-empty": [
|
||||
2,
|
||||
{
|
||||
"allowEmptyCatch": true
|
||||
}
|
||||
],
|
||||
"no-empty-pattern": 2,
|
||||
"no-else-return": 0,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-label": 2,
|
||||
"no-extra-semi": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-global-assign": 2,
|
||||
"no-implicit-coercion": 2,
|
||||
"no-implicit-globals": 2,
|
||||
"no-inner-declarations": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-operators": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [
|
||||
2,
|
||||
{
|
||||
"max": 1,
|
||||
"maxEOF": 0,
|
||||
"maxBOF": 0
|
||||
}
|
||||
],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-proto": 2,
|
||||
"no-prototype-builtins": 0,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-restricted-imports": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-self-assign": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-undef": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unmodified-loop-condition": 2,
|
||||
"no-unreachable": 2,
|
||||
"no-unused-vars": [
|
||||
1,
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "after-used"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": [
|
||||
2,
|
||||
{
|
||||
"functions": false
|
||||
}
|
||||
],
|
||||
"no-useless-call": 2,
|
||||
"no-useless-computed-key": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-useless-rename": 2,
|
||||
"no-var": 2,
|
||||
"no-whitespace-before-property": 2,
|
||||
"object-curly-newline": 0,
|
||||
"object-curly-spacing": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"object-property-newline": [
|
||||
2,
|
||||
{
|
||||
"allowMultiplePropertiesPerLine": true
|
||||
}
|
||||
],
|
||||
"prefer-const": 2,
|
||||
"prefer-rest-params": 2,
|
||||
"prefer-spread": 2,
|
||||
"prefer-template": 2,
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"semi-spacing": 2,
|
||||
"space-before-blocks": 2,
|
||||
"space-before-function-paren": [
|
||||
2,
|
||||
{
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}
|
||||
],
|
||||
"space-in-parens": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"spaced-comment": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"strict": 2,
|
||||
"valid-typeof": 2,
|
||||
"eol-last": 0,
|
||||
"yoda": 2,
|
||||
"import/extensions": 2,
|
||||
"import/no-duplicates": 2,
|
||||
"jsx-quotes": [
|
||||
2,
|
||||
"prefer-single"
|
||||
],
|
||||
"react/display-name": [
|
||||
2,
|
||||
{
|
||||
"ignoreTranspilerName": false
|
||||
}
|
||||
],
|
||||
"react/forbid-prop-types": 0,
|
||||
"react/jsx-boolean-value": 2,
|
||||
"react/jsx-closing-bracket-location": 2,
|
||||
"react/jsx-curly-spacing": 2,
|
||||
"react/jsx-equals-spacing": 2,
|
||||
"react/jsx-handler-names": 2,
|
||||
"react/jsx-indent-props": [
|
||||
2,
|
||||
"tab"
|
||||
],
|
||||
"react/jsx-indent": [
|
||||
2,
|
||||
"tab"
|
||||
],
|
||||
"react/jsx-key": 2,
|
||||
"react/jsx-max-props-per-line": 0,
|
||||
"react/jsx-no-bind": 0,
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/jsx-no-literals": 0,
|
||||
"react/jsx-no-undef": 0,
|
||||
"react/jsx-pascal-case": 2,
|
||||
"react/jsx-sort-prop-types": 0,
|
||||
"react/jsx-sort-props": 0,
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/no-danger": 2,
|
||||
"react/no-deprecated": 2,
|
||||
"react/no-did-mount-set-state": 2,
|
||||
"react/no-did-update-set-state": 2,
|
||||
"react/no-direct-mutation-state": 2,
|
||||
"react/no-is-mounted": 2,
|
||||
"react/no-multi-comp": 0,
|
||||
"react/no-set-state": 0,
|
||||
"react/no-string-refs": 0,
|
||||
"react/no-unknown-property": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/prop-types": [
|
||||
2,
|
||||
{
|
||||
"skipUndeclared": true
|
||||
}
|
||||
],
|
||||
"react/react-in-jsx-scope": 2,
|
||||
"react/self-closing-comp": 2,
|
||||
"react/sort-comp": 0,
|
||||
"react/jsx-wrap-multilines": [
|
||||
2,
|
||||
{
|
||||
"declaration": false,
|
||||
"assignment": false,
|
||||
"return": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie > 0",
|
||||
"not op_mini all"
|
||||
]
|
||||
],
|
||||
"devDependencies": {
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-react": "^7.16.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,30 @@
|
|||
import io from 'socket.io-client';
|
||||
import * as mediasoupClient from 'mediasoup-client';
|
||||
import WebTorrent from 'webtorrent';
|
||||
import createTorrent from 'create-torrent';
|
||||
import saveAs from 'file-saver';
|
||||
// import io from 'socket.io-client';
|
||||
// import * as mediasoupClient from 'mediasoup-client';
|
||||
// import WebTorrent from 'webtorrent';
|
||||
// import createTorrent from 'create-torrent';
|
||||
// import saveAs from 'file-saver';
|
||||
import Logger from './Logger';
|
||||
import hark from 'hark';
|
||||
import ScreenShare from './ScreenShare';
|
||||
import Spotlights from './Spotlights';
|
||||
// import ScreenShare from './ScreenShare';
|
||||
// import Spotlights from './Spotlights';
|
||||
import { getSignalingUrl } from './urlFactory';
|
||||
import * as requestActions from './actions/requestActions';
|
||||
import * as stateActions from './actions/stateActions';
|
||||
|
||||
let WebTorrent;
|
||||
|
||||
let createTorrent;
|
||||
|
||||
let saveAs;
|
||||
|
||||
let mediasoupClient;
|
||||
|
||||
let io;
|
||||
|
||||
let ScreenShare;
|
||||
|
||||
let Spotlights;
|
||||
|
||||
const {
|
||||
turnServers,
|
||||
requestTimeout,
|
||||
|
|
@ -77,31 +92,25 @@ export default class RoomClient
|
|||
}
|
||||
|
||||
constructor(
|
||||
{ roomId, peerId, device, useSimulcast, produce, consume, forceTcp })
|
||||
{ roomId, peerId, accessCode, device, useSimulcast, produce, forceTcp })
|
||||
{
|
||||
logger.debug(
|
||||
'constructor() [roomId: "%s", peerId: "%s", device: "%s", useSimulcast: "%s", produce: "%s", consume: "%s", forceTcp: "%s"]',
|
||||
roomId, peerId, device.flag, useSimulcast, produce, consume, forceTcp);
|
||||
'constructor() [roomId: "%s", peerId: "%s", device: "%s", useSimulcast: "%s", produce: "%s", forceTcp: "%s"]',
|
||||
roomId, peerId, device.flag, useSimulcast, produce, forceTcp);
|
||||
|
||||
this._signalingUrl = getSignalingUrl(peerId, roomId);
|
||||
|
||||
// window element to external login site
|
||||
this._loginWindow = null;
|
||||
|
||||
// Closed flag.
|
||||
this._closed = false;
|
||||
|
||||
// Whether we should produce.
|
||||
this._produce = produce;
|
||||
|
||||
// Whether we should consume.
|
||||
this._consume = consume;
|
||||
|
||||
// Wheter we force TCP
|
||||
this._forceTcp = forceTcp;
|
||||
|
||||
// Torrent support
|
||||
this._torrentSupport = WebTorrent.WEBRTC_SUPPORT;
|
||||
this._torrentSupport = null;
|
||||
|
||||
// Whether simulcast should be used.
|
||||
this._useSimulcast = useSimulcast;
|
||||
|
|
@ -112,6 +121,9 @@ export default class RoomClient
|
|||
// My peer name.
|
||||
this._peerId = peerId;
|
||||
|
||||
// Access code
|
||||
this._accessCode = accessCode;
|
||||
|
||||
// Alert sound
|
||||
this._soundAlert = new Audio('/sounds/notify.mp3');
|
||||
|
||||
|
|
@ -120,21 +132,14 @@ export default class RoomClient
|
|||
|
||||
// The room ID
|
||||
this._roomId = roomId;
|
||||
store.dispatch(stateActions.setRoomName(roomId));
|
||||
|
||||
// mediasoup-client Device instance.
|
||||
// @type {mediasoupClient.Device}
|
||||
this._mediasoupDevice = null;
|
||||
|
||||
this._doneJoining = false;
|
||||
|
||||
// Our WebTorrent client
|
||||
this._webTorrent = this._torrentSupport && new WebTorrent({
|
||||
tracker : {
|
||||
rtcConfig : {
|
||||
iceServers : ROOM_OPTIONS.turnServers
|
||||
}
|
||||
}
|
||||
});
|
||||
this._webTorrent = null;
|
||||
|
||||
// Max spotlights
|
||||
if (device.bowser.ios || device.bowser.mobile || device.bowser.android)
|
||||
|
|
@ -170,7 +175,7 @@ export default class RoomClient
|
|||
// @type {Map<String, mediasoupClient.Consumer>}
|
||||
this._consumers = new Map();
|
||||
|
||||
this._screenSharing = ScreenShare.create(device);
|
||||
this._screenSharing = null;
|
||||
|
||||
this._screenSharingProducer = null;
|
||||
|
||||
|
|
@ -198,6 +203,8 @@ export default class RoomClient
|
|||
this._recvTransport.close();
|
||||
|
||||
store.dispatch(stateActions.setRoomState('closed'));
|
||||
|
||||
window.location = '/';
|
||||
}
|
||||
|
||||
_startKeyListener()
|
||||
|
|
@ -305,19 +312,51 @@ export default class RoomClient
|
|||
|
||||
login()
|
||||
{
|
||||
const url = `/auth/login?roomId=${this._roomId}&peerId=${this._peerId}`;
|
||||
const url = `/auth/login?id=${this._peerId}`;
|
||||
|
||||
this._loginWindow = window.open(url, 'loginWindow');
|
||||
window.open(url, 'loginWindow');
|
||||
}
|
||||
|
||||
logout()
|
||||
{
|
||||
window.location = '/auth/logout';
|
||||
window.open('/auth/logout', 'logoutWindow');
|
||||
}
|
||||
|
||||
closeLoginWindow()
|
||||
receiveLoginChildWindow(data)
|
||||
{
|
||||
this._loginWindow.close();
|
||||
logger.debug('receiveFromChildWindow() | [data:"%o"]', data);
|
||||
|
||||
const { displayName, picture } = data;
|
||||
|
||||
if (store.getState().room.state === 'connected')
|
||||
{
|
||||
this.changeDisplayName(displayName);
|
||||
this.changePicture(picture);
|
||||
}
|
||||
else
|
||||
{
|
||||
store.dispatch(stateActions.setDisplayName(displayName));
|
||||
store.dispatch(stateActions.setPicture(picture));
|
||||
}
|
||||
|
||||
store.dispatch(stateActions.loggedIn(true));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'You are logged in.'
|
||||
}));
|
||||
}
|
||||
|
||||
receiveLogoutChildWindow()
|
||||
{
|
||||
logger.debug('receiveLogoutChildWindow()');
|
||||
|
||||
store.dispatch(stateActions.loggedIn(false));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'You are logged out.'
|
||||
}));
|
||||
}
|
||||
|
||||
_soundNotification()
|
||||
|
|
@ -399,6 +438,12 @@ export default class RoomClient
|
|||
{
|
||||
logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
|
||||
|
||||
if (!displayName)
|
||||
displayName = 'Guest';
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setDisplayNameInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('changeDisplayName', { displayName });
|
||||
|
|
@ -419,24 +464,23 @@ export default class RoomClient
|
|||
type : 'error',
|
||||
text : 'An error occured while changing your display name.'
|
||||
}));
|
||||
|
||||
// We need to refresh the component for it to render the previous
|
||||
// displayName again.
|
||||
store.dispatch(stateActions.setDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
async changeProfilePicture(picture)
|
||||
store.dispatch(
|
||||
stateActions.setDisplayNameInProgress(false));
|
||||
}
|
||||
|
||||
async changePicture(picture)
|
||||
{
|
||||
logger.debug('changeProfilePicture() [picture: "%s"]', picture);
|
||||
logger.debug('changePicture() [picture: "%s"]', picture);
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('changeProfilePicture', { picture });
|
||||
await this.sendRequest('changePicture', { picture });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('shareProfilePicure() | failed: %o', error);
|
||||
logger.error('changePicture() | failed: %o', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -607,34 +651,46 @@ export default class RoomClient
|
|||
const {
|
||||
chatHistory,
|
||||
fileHistory,
|
||||
lastN
|
||||
lastNHistory,
|
||||
locked,
|
||||
lobbyPeers,
|
||||
accessCode
|
||||
} = await this.sendRequest('serverHistory');
|
||||
|
||||
if (chatHistory.length > 0)
|
||||
{
|
||||
logger.debug('Got chat history');
|
||||
store.dispatch(
|
||||
(chatHistory.length > 0) && store.dispatch(
|
||||
stateActions.addChatHistory(chatHistory));
|
||||
}
|
||||
|
||||
if (fileHistory.length > 0)
|
||||
(fileHistory.length > 0) && store.dispatch(
|
||||
stateActions.addFileHistory(fileHistory));
|
||||
|
||||
if (lastNHistory.length > 0)
|
||||
{
|
||||
logger.debug('Got files history');
|
||||
|
||||
store.dispatch(stateActions.addFileHistory(fileHistory));
|
||||
}
|
||||
|
||||
if (lastN.length > 0)
|
||||
{
|
||||
logger.debug('Got lastN');
|
||||
logger.debug('Got lastNHistory');
|
||||
|
||||
// Remove our self from list
|
||||
const index = lastN.indexOf(this._peerId);
|
||||
const index = lastNHistory.indexOf(this._peerId);
|
||||
|
||||
lastN.splice(index, 1);
|
||||
lastNHistory.splice(index, 1);
|
||||
|
||||
this._spotlights.addSpeakerList(lastN);
|
||||
this._spotlights.addSpeakerList(lastNHistory);
|
||||
}
|
||||
|
||||
locked ?
|
||||
store.dispatch(stateActions.setRoomLocked()) :
|
||||
store.dispatch(stateActions.setRoomUnLocked());
|
||||
|
||||
(lobbyPeers.length > 0) && lobbyPeers.forEach((peer) =>
|
||||
{
|
||||
store.dispatch(
|
||||
stateActions.addLobbyPeer(peer.peerId));
|
||||
store.dispatch(
|
||||
stateActions.setLobbyPeerDisplayName(peer.displayName));
|
||||
store.dispatch(
|
||||
stateActions.setLobbyPeerPicture(peer.picture));
|
||||
});
|
||||
|
||||
(accessCode != null) && store.dispatch(
|
||||
stateActions.setAccessCode(accessCode));
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
|
|
@ -735,6 +791,22 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async getAudioTrack()
|
||||
{
|
||||
await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio : true, video : false
|
||||
});
|
||||
}
|
||||
|
||||
async getVideoTrack()
|
||||
{
|
||||
await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio : false, video : true
|
||||
});
|
||||
}
|
||||
|
||||
async changeAudioDevice(deviceId)
|
||||
{
|
||||
logger.debug('changeAudioDevice() [deviceId: %s]', deviceId);
|
||||
|
|
@ -934,6 +1006,26 @@ export default class RoomClient
|
|||
stateActions.setSelectedPeer(peerId));
|
||||
}
|
||||
|
||||
async promoteLobbyPeer(peerId)
|
||||
{
|
||||
logger.debug('promoteLobbyPeer() [peerId:"%s"]', peerId);
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setLobbyPeerPromotionInProgress(peerId, true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('promotePeer', { peerId });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('promoteLobbyPeer() failed: %o', error);
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setLobbyPeerPromotionInProgress(peerId, false));
|
||||
}
|
||||
|
||||
// type: mic/webcam/screen
|
||||
// mute: true/false
|
||||
async modifyPeerConsumer(peerId, type, mute)
|
||||
|
|
@ -1061,13 +1153,78 @@ export default class RoomClient
|
|||
stateActions.setMyRaiseHandStateInProgress(false));
|
||||
}
|
||||
|
||||
async _loadDynamicImports()
|
||||
{
|
||||
({ default: WebTorrent } = await import(
|
||||
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackChunkName: "webtorrent" */
|
||||
'webtorrent'
|
||||
));
|
||||
|
||||
({ default: createTorrent } = await import(
|
||||
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackChunkName: "create-torrent" */
|
||||
'create-torrent'
|
||||
));
|
||||
|
||||
({ default: saveAs } = await import(
|
||||
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackChunkName: "file-saver" */
|
||||
'file-saver'
|
||||
));
|
||||
|
||||
({ default: ScreenShare } = await import(
|
||||
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackChunkName: "screensharing" */
|
||||
'./ScreenShare'
|
||||
));
|
||||
|
||||
({ default: Spotlights } = await import(
|
||||
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackChunkName: "spotlights" */
|
||||
'./Spotlights'
|
||||
));
|
||||
|
||||
mediasoupClient = await import(
|
||||
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackChunkName: "mediasoup" */
|
||||
'mediasoup-client'
|
||||
);
|
||||
|
||||
({ default: io } = await import(
|
||||
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackChunkName: "socket.io" */
|
||||
'socket.io-client'
|
||||
));
|
||||
}
|
||||
|
||||
async join({ joinVideo })
|
||||
{
|
||||
await this._loadDynamicImports();
|
||||
|
||||
this._torrentSupport = WebTorrent.WEBRTC_SUPPORT;
|
||||
|
||||
this._webTorrent = this._torrentSupport && new WebTorrent({
|
||||
tracker : {
|
||||
rtcConfig : {
|
||||
iceServers : ROOM_OPTIONS.turnServers
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._screenSharing = ScreenShare.create(this._device);
|
||||
|
||||
this._signalingSocket = io(this._signalingUrl);
|
||||
|
||||
this._spotlights = new Spotlights(this._maxSpotlights, this._signalingSocket);
|
||||
|
||||
store.dispatch(stateActions.toggleJoined());
|
||||
store.dispatch(stateActions.setRoomState('connecting'));
|
||||
|
||||
this._signalingSocket.on('connect', () =>
|
||||
|
|
@ -1075,39 +1232,53 @@ export default class RoomClient
|
|||
logger.debug('signaling Peer "connect" event');
|
||||
});
|
||||
|
||||
this._signalingSocket.on('disconnect', () =>
|
||||
this._signalingSocket.on('disconnect', (reason) =>
|
||||
{
|
||||
logger.warn('signaling Peer "disconnect" event');
|
||||
logger.warn('signaling Peer "disconnect" event [reason:"%s"]', reason);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
if (reason === 'io server disconnect')
|
||||
{
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'You are disconnected.'
|
||||
}));
|
||||
|
||||
this.close();
|
||||
}
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'You are disconnected, attempting to reconnect.'
|
||||
}));
|
||||
|
||||
store.dispatch(stateActions.setRoomState('connecting'));
|
||||
});
|
||||
|
||||
this._signalingSocket.on('reconnect_failed', () =>
|
||||
{
|
||||
logger.warn('signaling Peer "reconnect_failed" event');
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'You are disconnected.'
|
||||
}));
|
||||
|
||||
// Close mediasoup Transports.
|
||||
if (this._sendTransport)
|
||||
{
|
||||
this._sendTransport.close();
|
||||
this._sendTransport = null;
|
||||
}
|
||||
|
||||
if (this._recvTransport)
|
||||
{
|
||||
this._recvTransport.close();
|
||||
this._recvTransport = null;
|
||||
}
|
||||
|
||||
store.dispatch(stateActions.setRoomState('closed'));
|
||||
this.close();
|
||||
});
|
||||
|
||||
this._signalingSocket.on('close', () =>
|
||||
this._signalingSocket.on('reconnect', (attemptNumber) =>
|
||||
{
|
||||
if (this._closed)
|
||||
return;
|
||||
logger.debug('signaling Peer "reconnect" event [attempts:"%s"]', attemptNumber);
|
||||
|
||||
logger.warn('signaling Peer "close" event');
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'You are reconnected.'
|
||||
}));
|
||||
|
||||
this.close();
|
||||
store.dispatch(stateActions.setRoomState('connected'));
|
||||
});
|
||||
|
||||
this._signalingSocket.on('request', async (request, cb) =>
|
||||
|
|
@ -1241,18 +1412,35 @@ export default class RoomClient
|
|||
'socket "notification" event [method:%s, data:%o]',
|
||||
notification.method, notification.data);
|
||||
|
||||
try
|
||||
{
|
||||
switch (notification.method)
|
||||
{
|
||||
case 'roomReady':
|
||||
case 'enteredLobby':
|
||||
{
|
||||
await this._joinRoom({ joinVideo });
|
||||
store.dispatch(stateActions.setInLobby(true));
|
||||
|
||||
const { displayName } = store.getState().settings;
|
||||
const { picture } = store.getState().me;
|
||||
|
||||
await this.sendRequest('changeDisplayName', { displayName });
|
||||
await this.sendRequest('changePicture', { picture });
|
||||
break;
|
||||
}
|
||||
|
||||
case 'signInRequired':
|
||||
{
|
||||
store.dispatch(stateActions.setSignInRequired(true));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'roomLocked':
|
||||
case 'roomReady':
|
||||
{
|
||||
store.dispatch(stateActions.setRoomLockedOut());
|
||||
store.dispatch(stateActions.toggleJoined());
|
||||
store.dispatch(stateActions.setInLobby(false));
|
||||
|
||||
await this._joinRoom({ joinVideo });
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -1283,6 +1471,118 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'parkedPeer':
|
||||
{
|
||||
const { peerId } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
stateActions.addLobbyPeer(peerId));
|
||||
store.dispatch(
|
||||
stateActions.setToolbarsVisible(true));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'New participant entered the lobby.'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lobby:peerClosed':
|
||||
{
|
||||
const { peerId } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
stateActions.removeLobbyPeer(peerId));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'Participant in lobby left.'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lobby:promotedPeer':
|
||||
{
|
||||
const { peerId } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
stateActions.removeLobbyPeer(peerId));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lobby:changeDisplayName':
|
||||
{
|
||||
const { peerId, displayName } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setLobbyPeerDisplayName(displayName, peerId));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : `Participant in lobby changed name to ${displayName}.`
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lobby:changePicture':
|
||||
{
|
||||
const { peerId, picture } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setLobbyPeerPicture(picture, peerId));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'Participant in lobby changed picture.'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'setAccessCode':
|
||||
{
|
||||
const { accessCode } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setAccessCode(accessCode));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'Access code for room updated'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'setJoinByAccessCode':
|
||||
{
|
||||
const { joinByAccessCode } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setJoinByAccessCode(joinByAccessCode));
|
||||
|
||||
if (joinByAccessCode)
|
||||
{
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'Access code for room is now activated'
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'Access code for room is now deactivated'
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'activeSpeaker':
|
||||
{
|
||||
const { peerId } = notification.data;
|
||||
|
|
@ -1311,7 +1611,7 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'changeProfilePicture':
|
||||
case 'changePicture':
|
||||
{
|
||||
const { peerId, picture } = notification.data;
|
||||
|
||||
|
|
@ -1320,26 +1620,6 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'auth':
|
||||
{
|
||||
const { displayName, picture } = notification.data;
|
||||
|
||||
this.changeDisplayName(displayName);
|
||||
|
||||
this.changeProfilePicture(picture);
|
||||
store.dispatch(stateActions.setPicture(picture));
|
||||
store.dispatch(stateActions.loggedIn());
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'You are logged in.'
|
||||
}));
|
||||
|
||||
this.closeLoginWindow();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'chatMessage':
|
||||
{
|
||||
const { peerId, chatMessage } = notification.data;
|
||||
|
|
@ -1353,6 +1633,8 @@ export default class RoomClient
|
|||
store.getState().toolarea.currentToolTab !== 'chat')
|
||||
) // Make sound
|
||||
{
|
||||
store.dispatch(
|
||||
stateActions.setToolbarsVisible(true));
|
||||
this._soundNotification();
|
||||
}
|
||||
|
||||
|
|
@ -1376,6 +1658,8 @@ export default class RoomClient
|
|||
store.getState().toolarea.currentToolTab !== 'files')
|
||||
) // Make sound
|
||||
{
|
||||
store.dispatch(
|
||||
stateActions.setToolbarsVisible(true));
|
||||
this._soundNotification();
|
||||
}
|
||||
|
||||
|
|
@ -1498,6 +1782,18 @@ export default class RoomClient
|
|||
'unknown notification.method "%s"', notification.method);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('error on socket "notification" event failed:"%o"', error);
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
type : 'error',
|
||||
text : 'Error on server request.'
|
||||
}));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1573,8 +1869,6 @@ export default class RoomClient
|
|||
});
|
||||
}
|
||||
|
||||
if (this._consume)
|
||||
{
|
||||
const transportInfo = await this.sendRequest(
|
||||
'createWebRtcTransport',
|
||||
{
|
||||
|
|
@ -1610,7 +1904,6 @@ export default class RoomClient
|
|||
.then(callback)
|
||||
.catch(errback);
|
||||
});
|
||||
}
|
||||
|
||||
// Set our media capabilities.
|
||||
store.dispatch(stateActions.setMediaCapabilities(
|
||||
|
|
@ -1628,11 +1921,11 @@ export default class RoomClient
|
|||
displayName : displayName,
|
||||
picture : picture,
|
||||
device : this._device,
|
||||
rtpCapabilities : this._consume
|
||||
? this._mediasoupDevice.rtpCapabilities
|
||||
: undefined
|
||||
rtpCapabilities : this._mediasoupDevice.rtpCapabilities
|
||||
});
|
||||
|
||||
logger.debug('_joinRoom() joined, got peers [peers:"%o"]', peers);
|
||||
|
||||
for (const peer of peers)
|
||||
{
|
||||
store.dispatch(
|
||||
|
|
@ -1741,6 +2034,60 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async setAccessCode(code)
|
||||
{
|
||||
logger.debug('setAccessCode()');
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('setAccessCode', { accessCode: code });
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setAccessCode(code));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : 'Access code saved.'
|
||||
}));
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('setAccessCode() | failed: %o', error);
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
type : 'error',
|
||||
text : 'Unable to set access code.'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async setJoinByAccessCode(value)
|
||||
{
|
||||
logger.debug('setJoinByAccessCode()');
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('setJoinByAccessCode', { joinByAccessCode: value });
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setJoinByAccessCode(value));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : `You switched Join by access-code to ${value}`
|
||||
}));
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('setAccessCode() | failed: %o', error);
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
type : 'error',
|
||||
text : 'Unable to set join by access code.'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async enableMic()
|
||||
{
|
||||
if (this._micProducer)
|
||||
|
|
@ -2278,7 +2625,7 @@ export default class RoomClient
|
|||
|
||||
try
|
||||
{
|
||||
logger.debug('_getAudioDeviceId() | calling _updateWebcams()');
|
||||
logger.debug('_getAudioDeviceId() | calling _updateAudioDeviceId()');
|
||||
|
||||
await this._updateAudioDevices();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ export const setRoomUrl = (url) =>
|
|||
};
|
||||
};
|
||||
|
||||
export const setRoomName = (name) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_NAME',
|
||||
payload : { name }
|
||||
};
|
||||
};
|
||||
|
||||
export const setRoomState = (state) =>
|
||||
{
|
||||
return {
|
||||
|
|
@ -36,10 +44,35 @@ export const setRoomUnLocked = () =>
|
|||
};
|
||||
};
|
||||
|
||||
export const setRoomLockedOut = () =>
|
||||
export const setInLobby = (inLobby) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_LOCKED_OUT'
|
||||
type : 'SET_IN_LOBBY',
|
||||
payload : { inLobby }
|
||||
};
|
||||
};
|
||||
|
||||
export const setSignInRequired = (signInRequired) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_SIGN_IN_REQUIRED',
|
||||
payload : { signInRequired }
|
||||
};
|
||||
};
|
||||
|
||||
export const setAccessCode = (accessCode) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ACCESS_CODE',
|
||||
payload : { accessCode }
|
||||
};
|
||||
};
|
||||
|
||||
export const setJoinByAccessCode = (joinByAccessCode) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_JOIN_BY_ACCESS_CODE',
|
||||
payload : { joinByAccessCode }
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -49,6 +82,12 @@ export const setSettingsOpen = ({ settingsOpen }) =>
|
|||
payload : { settingsOpen }
|
||||
});
|
||||
|
||||
export const setLockDialogOpen = ({ lockDialogOpen }) =>
|
||||
({
|
||||
type : 'SET_LOCK_DIALOG_OPEN',
|
||||
payload : { lockDialogOpen }
|
||||
});
|
||||
|
||||
export const setMe = ({ peerId, device, loginEnabled }) =>
|
||||
{
|
||||
return {
|
||||
|
|
@ -126,6 +165,14 @@ export const setDisplayName = (displayName) =>
|
|||
};
|
||||
};
|
||||
|
||||
export const setDisplayNameInProgress = (flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_DISPLAY_NAME_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleAdvancedMode = () =>
|
||||
{
|
||||
return {
|
||||
|
|
@ -178,6 +225,13 @@ export const toggleSettings = () =>
|
|||
};
|
||||
};
|
||||
|
||||
export const toggleLockDialog = () =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_LOCK_DIALOG'
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleToolArea = () =>
|
||||
{
|
||||
return {
|
||||
|
|
@ -391,6 +445,46 @@ export const setPeerVolume = (peerId, volume) =>
|
|||
};
|
||||
};
|
||||
|
||||
export const addLobbyPeer = (peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_LOBBY_PEER',
|
||||
payload : { peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const removeLobbyPeer = (peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'REMOVE_LOBBY_PEER',
|
||||
payload : { peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setLobbyPeerDisplayName = (displayName, peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_LOBBY_PEER_DISPLAY_NAME',
|
||||
payload : { displayName, peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setLobbyPeerPicture = (picture, peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_LOBBY_PEER_PICTURE',
|
||||
payload : { picture, peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setLobbyPeerPromotionInProgress = (peerId, flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const addNotification = (notification) =>
|
||||
{
|
||||
return {
|
||||
|
|
@ -555,9 +649,10 @@ export const setPeerPicture = (peerId, picture) =>
|
|||
payload : { peerId, picture }
|
||||
});
|
||||
|
||||
export const loggedIn = () =>
|
||||
export const loggedIn = (flag) =>
|
||||
({
|
||||
type : 'LOGGED_IN'
|
||||
type : 'LOGGED_IN',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const toggleJoined = () =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import PromoteIcon from '@material-ui/icons/OpenInBrowser';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
padding : theme.spacing(1),
|
||||
width : '100%',
|
||||
overflow : 'hidden',
|
||||
cursor : 'auto',
|
||||
display : 'flex'
|
||||
},
|
||||
avatar :
|
||||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem'
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
fontSize : '1rem',
|
||||
border : 'none',
|
||||
display : 'flex',
|
||||
paddingLeft : theme.spacing(1),
|
||||
flexGrow : 1,
|
||||
alignItems : 'center'
|
||||
},
|
||||
controls :
|
||||
{
|
||||
float : 'right',
|
||||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'center'
|
||||
},
|
||||
button :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
margin : '0.3rem',
|
||||
borderRadius : 2,
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.5)',
|
||||
cursor : 'pointer',
|
||||
transitionProperty : 'opacity, background-color',
|
||||
transitionDuration : '0.15s',
|
||||
width : 'var(--media-control-button-size)',
|
||||
height : 'var(--media-control-button-size)',
|
||||
opacity : 0.85,
|
||||
'&:hover' :
|
||||
{
|
||||
opacity : 1
|
||||
},
|
||||
'&.disabled' :
|
||||
{
|
||||
pointerEvents : 'none',
|
||||
backgroundColor : 'var(--media-control-botton-disabled)'
|
||||
},
|
||||
'&.promote' :
|
||||
{
|
||||
backgroundColor : 'var(--media-control-botton-on)'
|
||||
}
|
||||
},
|
||||
ListItem :
|
||||
{
|
||||
alignItems : 'center'
|
||||
}
|
||||
});
|
||||
|
||||
const ListLobbyPeer = (props) =>
|
||||
{
|
||||
const {
|
||||
roomClient,
|
||||
peer,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
const picture = peer.picture || EmptyAvatar;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
className={classnames(classes.ListItem)}
|
||||
key={peer.peerId}
|
||||
button
|
||||
alignItems='flex-start'
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar alt='Peer avatar' src={picture} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={peer.displayName}
|
||||
/>
|
||||
<Tooltip title='Click to let them in'>
|
||||
<ListItemIcon
|
||||
className={classnames(classes.button, 'promote', {
|
||||
disabled : peer.promotionInProgress
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
roomClient.promoteLobbyPeer(peer.id);
|
||||
}}
|
||||
>
|
||||
<PromoteIcon />
|
||||
</ListItemIcon>
|
||||
</Tooltip>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
ListLobbyPeer.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
peer : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { id }) =>
|
||||
{
|
||||
return {
|
||||
peer : state.lobbyPeers[id]
|
||||
};
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.lobbyPeers === next.lobbyPeers
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(ListLobbyPeer)));
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
lobbyPeersKeySelector
|
||||
} from '../../Selectors';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import * as stateActions from '../../../actions/stateActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import Button from '@material-ui/core/Button';
|
||||
// import FormLabel from '@material-ui/core/FormLabel';
|
||||
// import FormControl from '@material-ui/core/FormControl';
|
||||
// import FormGroup from '@material-ui/core/FormGroup';
|
||||
// import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
// import Checkbox from '@material-ui/core/Checkbox';
|
||||
// import InputLabel from '@material-ui/core/InputLabel';
|
||||
// import OutlinedInput from '@material-ui/core/OutlinedInput';
|
||||
// import Switch from '@material-ui/core/Switch';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListSubheader from '@material-ui/core/ListSubheader';
|
||||
import ListLobbyPeer from './ListLobbyPeer';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
},
|
||||
dialogPaper :
|
||||
{
|
||||
width : '30vw',
|
||||
[theme.breakpoints.down('lg')] :
|
||||
{
|
||||
width : '40vw'
|
||||
},
|
||||
[theme.breakpoints.down('md')] :
|
||||
{
|
||||
width : '50vw'
|
||||
},
|
||||
[theme.breakpoints.down('sm')] :
|
||||
{
|
||||
width : '70vw'
|
||||
},
|
||||
[theme.breakpoints.down('xs')] :
|
||||
{
|
||||
width : '90vw'
|
||||
}
|
||||
},
|
||||
lock :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
}
|
||||
});
|
||||
|
||||
const LockDialog = ({
|
||||
// roomClient,
|
||||
room,
|
||||
handleCloseLockDialog,
|
||||
// handleAccessCode,
|
||||
lobbyPeers,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
return (
|
||||
<Dialog
|
||||
className={classes.root}
|
||||
open={room.lockDialogOpen}
|
||||
onClose={() => handleCloseLockDialog({ lockDialogOpen: false })}
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>Lobby administration</DialogTitle>
|
||||
{/*
|
||||
<FormControl component='fieldset' className={classes.formControl}>
|
||||
<FormLabel component='legend'>Room lock</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={room.locked} onChange={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
label='Lock'
|
||||
/>
|
||||
TODO: access code
|
||||
<FormControlLabel disabled={ room.locked ? false : true }
|
||||
control={
|
||||
<Checkbox checked={room.joinByAccessCode}
|
||||
onChange={
|
||||
(event) => roomClient.setJoinByAccessCode(event.target.checked)
|
||||
}
|
||||
/>}
|
||||
label='Join by Access code'
|
||||
/>
|
||||
<InputLabel htmlFor='access-code-input' />
|
||||
<OutlinedInput
|
||||
disabled={ room.locked ? false : true }
|
||||
id='acces-code-input'
|
||||
label='Access code'
|
||||
labelWidth={0}
|
||||
variant='outlined'
|
||||
value={room.accessCode}
|
||||
onChange={(event) => handleAccessCode(event.target.value)}
|
||||
>
|
||||
</OutlinedInput>
|
||||
<Button onClick={() => roomClient.setAccessCode(room.accessCode)} color='primary'>
|
||||
save
|
||||
</Button>
|
||||
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
*/}
|
||||
{ lobbyPeers.length > 0 ?
|
||||
<List
|
||||
dense
|
||||
subheader={
|
||||
<ListSubheader component='div'>
|
||||
Participants in Lobby
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
{
|
||||
lobbyPeers.map((peerId) =>
|
||||
{
|
||||
return (<ListLobbyPeer key={peerId} id={peerId} />);
|
||||
})
|
||||
}
|
||||
</List>
|
||||
:
|
||||
<DialogContent>
|
||||
<DialogContentText gutterBottom>
|
||||
There are currently no one in the lobby.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
}
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleCloseLockDialog({ lockDialogOpen: false })} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
LockDialog.propTypes =
|
||||
{
|
||||
// roomClient : PropTypes.any.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
handleCloseLockDialog : PropTypes.func.isRequired,
|
||||
handleAccessCode : PropTypes.func.isRequired,
|
||||
lobbyPeers : PropTypes.array,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
room : state.room,
|
||||
lobbyPeers : lobbyPeersKeySelector(state)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
handleCloseLockDialog : stateActions.setLockDialogOpen,
|
||||
handleAccessCode : stateActions.setAccessCode
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.locked === next.room.locked &&
|
||||
prev.room.joinByAccessCode === next.room.joinByAccessCode &&
|
||||
prev.room.accessCode === next.room.accessCode &&
|
||||
prev.room.code === next.room.code &&
|
||||
prev.room.lockDialogOpen === next.room.lockDialogOpen &&
|
||||
prev.room.codeHidden === next.room.codeHidden &&
|
||||
prev.lobbyPeers === next.lobbyPeers
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(LockDialog)));
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useEffect, Suspense } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import JoinDialog from './JoinDialog';
|
||||
import LoadingView from './LoadingView';
|
||||
import { ReactLazyPreload } from './ReactLazyPreload';
|
||||
|
||||
const Room = ReactLazyPreload(() => import(/* webpackChunkName: "room" */ './Room'));
|
||||
|
||||
const App = (props) =>
|
||||
{
|
||||
const {
|
||||
room
|
||||
} = props;
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
Room.preload();
|
||||
|
||||
return;
|
||||
}, []);
|
||||
|
||||
if (!room.joined)
|
||||
{
|
||||
return (
|
||||
<JoinDialog />
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
<Suspense fallback={<LoadingView />}>
|
||||
<Room />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
App.propTypes =
|
||||
{
|
||||
room : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
room : state.room
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room === next.room
|
||||
);
|
||||
}
|
||||
}
|
||||
)(App);
|
||||
|
|
@ -96,7 +96,7 @@ class HiddenPeers extends React.PureComponent
|
|||
onClick={() => openUsersTab()}
|
||||
>
|
||||
<p>+{hiddenPeersCount} <br /> participant
|
||||
{(hiddenPeersCount === 1) ? null : 's'}
|
||||
{(hiddenPeersCount > 1) && 's'}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -332,13 +332,11 @@ const Me = (props) =>
|
|||
}
|
||||
}}
|
||||
>
|
||||
{ screenState === 'on' || screenState === 'unsupported' ?
|
||||
{ (screenState === 'on' || screenState === 'unsupported') &&
|
||||
<ScreenOffIcon/>
|
||||
:null
|
||||
}
|
||||
{ screenState === 'off' ?
|
||||
{ screenState === 'off' &&
|
||||
<ScreenIcon/>
|
||||
:null
|
||||
}
|
||||
</Fab>
|
||||
</div>
|
||||
|
|
@ -351,10 +349,10 @@ const Me = (props) =>
|
|||
peer={me}
|
||||
displayName={settings.displayName}
|
||||
showPeerInfo
|
||||
videoTrack={webcamProducer ? webcamProducer.track : null}
|
||||
videoTrack={webcamProducer && webcamProducer.track}
|
||||
videoVisible={videoVisible}
|
||||
audioCodec={micProducer ? micProducer.codec : null}
|
||||
videoCodec={webcamProducer ? webcamProducer.codec : null}
|
||||
audioCodec={micProducer && micProducer.codec}
|
||||
videoCodec={webcamProducer && webcamProducer.codec}
|
||||
onChangeDisplayName={(displayName) =>
|
||||
{
|
||||
roomClient.changeDisplayName(displayName);
|
||||
|
|
@ -364,9 +362,9 @@ const Me = (props) =>
|
|||
</VideoView>
|
||||
</div>
|
||||
</div>
|
||||
{ screenProducer ?
|
||||
{ screenProducer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
className={classnames(classes.root, 'screen', hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -390,7 +388,7 @@ const Me = (props) =>
|
|||
>
|
||||
<div className={classnames(classes.viewContainer)} style={style}>
|
||||
<div
|
||||
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||
className={classnames(classes.controls, hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -420,13 +418,12 @@ const Me = (props) =>
|
|||
isScreen
|
||||
advancedMode={advancedMode}
|
||||
videoContain
|
||||
videoTrack={screenProducer ? screenProducer.track : null}
|
||||
videoTrack={screenProducer && screenProducer.track}
|
||||
videoVisible={screenVisible}
|
||||
videoCodec={screenProducer ? screenProducer.codec : null}
|
||||
videoCodec={screenProducer && screenProducer.codec}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -166,8 +166,8 @@ const Peer = (props) =>
|
|||
classnames(
|
||||
classes.root,
|
||||
'webcam',
|
||||
hover ? 'hover' : null,
|
||||
activeSpeaker ? 'active-speaker' : null
|
||||
hover && 'hover',
|
||||
activeSpeaker && 'active-speaker'
|
||||
)
|
||||
}
|
||||
onMouseOver={() => setHover(true)}
|
||||
|
|
@ -192,15 +192,14 @@ const Peer = (props) =>
|
|||
style={rootStyle}
|
||||
>
|
||||
<div className={classnames(classes.viewContainer)}>
|
||||
{ !videoVisible ?
|
||||
{ !videoVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>this video is paused</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
<div
|
||||
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||
className={classnames(classes.controls, hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -241,7 +240,7 @@ const Peer = (props) =>
|
|||
}
|
||||
</Fab>
|
||||
|
||||
{ !smallScreen ?
|
||||
{ !smallScreen &&
|
||||
<Fab
|
||||
aria-label='New window'
|
||||
className={classes.fab}
|
||||
|
|
@ -257,7 +256,6 @@ const Peer = (props) =>
|
|||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
:null
|
||||
}
|
||||
|
||||
<Fab
|
||||
|
|
@ -279,20 +277,20 @@ const Peer = (props) =>
|
|||
peer={peer}
|
||||
displayName={peer.displayName}
|
||||
showPeerInfo
|
||||
videoTrack={webcamConsumer ? webcamConsumer.track : null}
|
||||
videoTrack={webcamConsumer && webcamConsumer.track}
|
||||
videoVisible={videoVisible}
|
||||
videoProfile={videoProfile}
|
||||
audioCodec={micConsumer ? micConsumer.codec : null}
|
||||
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
||||
audioCodec={micConsumer && micConsumer.codec}
|
||||
videoCodec={webcamConsumer && webcamConsumer.codec}
|
||||
>
|
||||
<Volume id={peer.id} />
|
||||
</VideoView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ screenConsumer ?
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
className={classnames(classes.root, 'screen', hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -314,17 +312,16 @@ const Peer = (props) =>
|
|||
}}
|
||||
style={rootStyle}
|
||||
>
|
||||
{ !screenVisible ?
|
||||
{ !screenVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>this video is paused</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
{ screenVisible ?
|
||||
{ screenVisible &&
|
||||
<div className={classnames(classes.viewContainer)}>
|
||||
<div
|
||||
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||
className={classnames(classes.controls, hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -346,7 +343,7 @@ const Peer = (props) =>
|
|||
}, 2000);
|
||||
}}
|
||||
>
|
||||
{ !smallScreen ?
|
||||
{ !smallScreen &&
|
||||
<Fab
|
||||
aria-label='New window'
|
||||
className={classes.fab}
|
||||
|
|
@ -362,7 +359,6 @@ const Peer = (props) =>
|
|||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
:null
|
||||
}
|
||||
|
||||
<Fab
|
||||
|
|
@ -381,16 +377,14 @@ const Peer = (props) =>
|
|||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
videoContain
|
||||
videoTrack={screenConsumer ? screenConsumer.track : null}
|
||||
videoTrack={screenConsumer && screenConsumer.track}
|
||||
videoVisible={screenVisible}
|
||||
videoProfile={screenProfile}
|
||||
videoCodec={screenConsumer ? screenConsumer.codec : null}
|
||||
videoCodec={screenConsumer && screenConsumer.codec}
|
||||
/>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -117,11 +117,10 @@ const SpeakerPeer = (props) =>
|
|||
style={spacingStyle}
|
||||
>
|
||||
<div className={classnames(classes.viewContainer)} style={style}>
|
||||
{ !videoVisible ?
|
||||
{ !videoVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>this video is paused</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
<VideoView
|
||||
|
|
@ -140,18 +139,17 @@ const SpeakerPeer = (props) =>
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{ screenConsumer ?
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen')}
|
||||
>
|
||||
{ !screenVisible ?
|
||||
{ !screenVisible &&
|
||||
<div className={classes.videoInfo} style={style}>
|
||||
<p>this video is paused</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
{ screenVisible ?
|
||||
{ screenVisible &&
|
||||
<div className={classnames(classes.viewContainer)} style={style}>
|
||||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
|
|
@ -162,10 +160,8 @@ const SpeakerPeer = (props) =>
|
|||
videoCodec={screenConsumer ? screenConsumer.codec : null}
|
||||
/>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,62 +1,251 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../RoomContext';
|
||||
import * as stateActions from '../actions/stateActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import CookieConsent from 'react-cookie-consent';
|
||||
import MuiDialogTitle from '@material-ui/core/DialogTitle';
|
||||
import MuiDialogContent from '@material-ui/core/DialogContent';
|
||||
import MuiDialogActions from '@material-ui/core/DialogActions';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
display : 'flex',
|
||||
width : '100%',
|
||||
height : '100%',
|
||||
backgroundColor : 'var(--background-color)',
|
||||
backgroundImage : `url(${window.config.background})`,
|
||||
backgroundAttachment : 'fixed',
|
||||
backgroundPosition : 'center',
|
||||
backgroundSize : 'cover',
|
||||
backgroundRepeat : 'no-repeat'
|
||||
},
|
||||
dialogTitle :
|
||||
{
|
||||
},
|
||||
dialogPaper :
|
||||
{
|
||||
width : '20vw',
|
||||
width : '30vw',
|
||||
padding : theme.spacing(2),
|
||||
[theme.breakpoints.down('lg')] :
|
||||
{
|
||||
width : '30vw'
|
||||
width : '40vw'
|
||||
},
|
||||
[theme.breakpoints.down('md')] :
|
||||
{
|
||||
width : '40vw'
|
||||
width : '50vw'
|
||||
},
|
||||
[theme.breakpoints.down('sm')] :
|
||||
{
|
||||
width : '60vw'
|
||||
width : '70vw'
|
||||
},
|
||||
[theme.breakpoints.down('xs')] :
|
||||
{
|
||||
width : '80vw'
|
||||
width : '90vw'
|
||||
}
|
||||
},
|
||||
logo :
|
||||
{
|
||||
display : 'block'
|
||||
display : 'block',
|
||||
paddingBottom : '1vh'
|
||||
},
|
||||
loginButton :
|
||||
{
|
||||
position : 'absolute',
|
||||
right : theme.spacing(2),
|
||||
top : theme.spacing(2),
|
||||
padding : 0
|
||||
},
|
||||
largeIcon :
|
||||
{
|
||||
fontSize : '2em'
|
||||
},
|
||||
largeAvatar :
|
||||
{
|
||||
width : 50,
|
||||
height : 50
|
||||
},
|
||||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
}
|
||||
});
|
||||
|
||||
const DialogTitle = withStyles(styles)((props) =>
|
||||
{
|
||||
const [ open, setOpen ] = useState(false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const openTimer = setTimeout(() => setOpen(true), 1000);
|
||||
const closeTimer = setTimeout(() => setOpen(false), 4000);
|
||||
|
||||
return () =>
|
||||
{
|
||||
clearTimeout(openTimer);
|
||||
clearTimeout(closeTimer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { children, classes, myPicture, onLogin, ...other } = props;
|
||||
|
||||
const handleTooltipClose = () =>
|
||||
{
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleTooltipOpen = () =>
|
||||
{
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography variant='h5'>{children}</Typography>
|
||||
{ window.config.loginEnabled &&
|
||||
<Tooltip
|
||||
onClose={handleTooltipClose}
|
||||
onOpen={handleTooltipOpen}
|
||||
open={open}
|
||||
title='Click to log in'
|
||||
placement='left'
|
||||
>
|
||||
<IconButton
|
||||
aria-label='Account'
|
||||
className={classes.loginButton}
|
||||
color='inherit'
|
||||
onClick={onLogin}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} className={classes.largeAvatar} />
|
||||
:
|
||||
<AccountCircle className={classes.largeIcon} />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
</MuiDialogTitle>
|
||||
);
|
||||
});
|
||||
|
||||
const DialogContent = withStyles((theme) => ({
|
||||
root :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
}
|
||||
}))(MuiDialogContent);
|
||||
|
||||
const DialogActions = withStyles((theme) => ({
|
||||
root :
|
||||
{
|
||||
margin : 0,
|
||||
padding : theme.spacing(1)
|
||||
}
|
||||
}))(MuiDialogActions);
|
||||
|
||||
const JoinDialog = ({
|
||||
roomClient,
|
||||
room,
|
||||
displayName,
|
||||
displayNameInProgress,
|
||||
loggedIn,
|
||||
myPicture,
|
||||
changeDisplayName,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
const handleKeyDown = (event) =>
|
||||
{
|
||||
const { key } = event;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case 'Enter':
|
||||
case 'Escape':
|
||||
{
|
||||
if (displayName === '')
|
||||
changeDisplayName('Guest');
|
||||
if (room.inLobby)
|
||||
roomClient.changeDisplayName(displayName);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Dialog
|
||||
className={classes.root}
|
||||
open
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
{ window.config.logo ?
|
||||
<img alt='Logo' className={classes.logo} src={window.config.logo} />
|
||||
:null
|
||||
}
|
||||
<Typography variant='subtitle1'>You are about to join a meeting, how would you like to join?</Typography>
|
||||
<DialogTitle
|
||||
myPicture={myPicture}
|
||||
onLogin={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
>
|
||||
{ window.config.title }
|
||||
<hr />
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText gutterBottom>
|
||||
You are about to join a meeting.
|
||||
</DialogContentText>
|
||||
|
||||
<DialogContentText variant='h6' gutterBottom align='center'>
|
||||
Room ID: { room.name }
|
||||
</DialogContentText>
|
||||
|
||||
<DialogContentText gutterBottom>
|
||||
Set your name for participation,
|
||||
and choose how you want to join:
|
||||
</DialogContentText>
|
||||
|
||||
<TextField
|
||||
id='displayname'
|
||||
label='Your name'
|
||||
value={displayName}
|
||||
variant='outlined'
|
||||
margin='normal'
|
||||
disabled={displayNameInProgress}
|
||||
onChange={(event) =>
|
||||
{
|
||||
const { value } = event.target;
|
||||
|
||||
changeDisplayName(value);
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={() =>
|
||||
{
|
||||
if (displayName === '')
|
||||
changeDisplayName('Guest');
|
||||
if (room.inLobby)
|
||||
roomClient.changeDisplayName(displayName);
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
</DialogContent>
|
||||
|
||||
{ !room.inLobby ?
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() =>
|
||||
|
|
@ -64,6 +253,7 @@ const JoinDialog = ({
|
|||
roomClient.join({ joinVideo: false });
|
||||
}}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
>
|
||||
Audio only
|
||||
</Button>
|
||||
|
|
@ -73,18 +263,93 @@ const JoinDialog = ({
|
|||
roomClient.join({ joinVideo: true });
|
||||
}}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
>
|
||||
Audio and Video
|
||||
</Button>
|
||||
</DialogActions>
|
||||
:
|
||||
<DialogContent>
|
||||
<DialogContentText
|
||||
className={classes.green}
|
||||
gutterBottom
|
||||
variant='h6'
|
||||
align='center'
|
||||
>
|
||||
Ok, you are ready
|
||||
</DialogContentText>
|
||||
{ room.signInRequired ?
|
||||
<DialogContentText gutterBottom>
|
||||
The room is empty!
|
||||
You can Log In to start the meeting or wait until the host joins.
|
||||
</DialogContentText>
|
||||
:
|
||||
<DialogContentText gutterBottom>
|
||||
The room is locked - hang on until somebody lets you in ...
|
||||
</DialogContentText>
|
||||
}
|
||||
</DialogContent>
|
||||
}
|
||||
|
||||
<CookieConsent>
|
||||
This website uses cookies to enhance the user experience.
|
||||
</CookieConsent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
JoinDialog.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
room : PropTypes.object.isRequired,
|
||||
displayName : PropTypes.string.isRequired,
|
||||
displayNameInProgress : PropTypes.bool.isRequired,
|
||||
loginEnabled : PropTypes.bool.isRequired,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
myPicture : PropTypes.string,
|
||||
changeDisplayName : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withRoomContext(withStyles(styles)(JoinDialog));
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
room : state.room,
|
||||
displayName : state.settings.displayName,
|
||||
displayNameInProgress : state.me.displayNameInProgress,
|
||||
loginEnabled : state.me.loginEnabled,
|
||||
loggedIn : state.me.loggedIn,
|
||||
myPicture : state.me.picture
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
changeDisplayName : (displayName) =>
|
||||
{
|
||||
dispatch(stateActions.setDisplayName(displayName));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.inLobby === next.room.inLobby &&
|
||||
prev.room.signInRequired === next.room.signInRequired &&
|
||||
prev.settings.displayName === next.settings.displayName &&
|
||||
prev.me.displayNameInProgress === next.me.displayNameInProgress &&
|
||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||
prev.me.loggedIn === next.me.loggedIn &&
|
||||
prev.me.picture === next.me.picture
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(JoinDialog)));
|
||||
|
|
@ -123,7 +123,7 @@ ChatInput.propTypes =
|
|||
const mapStateToProps = (state) =>
|
||||
({
|
||||
displayName : state.settings.displayName,
|
||||
picture : state.settings.picture
|
||||
picture : state.me.picture
|
||||
});
|
||||
|
||||
export default withRoomContext(
|
||||
|
|
@ -136,7 +136,7 @@ export default withRoomContext(
|
|||
{
|
||||
return (
|
||||
prev.settings.displayName === next.settings.displayName &&
|
||||
prev.settings.picture === next.settings.picture
|
||||
prev.me.picture === next.me.picture
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ MessageList.propTypes =
|
|||
const mapStateToProps = (state) =>
|
||||
({
|
||||
chatmessages : state.chatmessages,
|
||||
myPicture : state.settings.picture
|
||||
myPicture : state.me.picture
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
@ -109,7 +109,7 @@ export default connect(
|
|||
{
|
||||
return (
|
||||
prev.chatmessages === next.chatmessages &&
|
||||
prev.settings.picture === next.settings.picture
|
||||
prev.me.picture === next.me.picture
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class File extends React.PureComponent
|
|||
<img alt='Avatar' className={classes.avatar} src={picture} />
|
||||
|
||||
<div className={classes.fileContent}>
|
||||
{ file.files ?
|
||||
{ file.files &&
|
||||
<Fragment>
|
||||
<Typography className={classes.text}>
|
||||
File finished downloading
|
||||
|
|
@ -92,13 +92,12 @@ class File extends React.PureComponent
|
|||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
:null
|
||||
}
|
||||
<Typography className={classes.text}>
|
||||
{ `${displayName} shared a file` }
|
||||
</Typography>
|
||||
|
||||
{ !file.active && !file.files ?
|
||||
{ (!file.active && !file.files) &&
|
||||
<div className={classes.fileInfo}>
|
||||
<Typography className={classes.text}>
|
||||
{ magnet.decode(magnetUri).dn }
|
||||
|
|
@ -121,20 +120,17 @@ class File extends React.PureComponent
|
|||
</Typography>
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
{ file.timeout ?
|
||||
{ file.timeout &&
|
||||
<Typography className={classes.text}>
|
||||
If this process takes a long time, there might not be anyone seeding
|
||||
this torrent. Try asking someone to reupload the file that you want.
|
||||
</Typography>
|
||||
:null
|
||||
}
|
||||
|
||||
{ file.active ?
|
||||
{ file.active &&
|
||||
<progress value={file.progress} />
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ class FileList extends React.PureComponent
|
|||
const {
|
||||
files,
|
||||
me,
|
||||
picture,
|
||||
peers,
|
||||
classes
|
||||
} = this.props;
|
||||
|
|
@ -61,7 +60,7 @@ class FileList extends React.PureComponent
|
|||
if (me.id === file.peerId)
|
||||
{
|
||||
displayName = 'You';
|
||||
filePicture = picture;
|
||||
filePicture = me.picture;
|
||||
}
|
||||
else if (peers[file.peerId])
|
||||
{
|
||||
|
|
@ -91,7 +90,6 @@ FileList.propTypes =
|
|||
{
|
||||
files : PropTypes.object.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
picture : PropTypes.string,
|
||||
peers : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
|
@ -101,7 +99,6 @@ const mapStateToProps = (state) =>
|
|||
return {
|
||||
files : state.files,
|
||||
me : state.me,
|
||||
picture : state.settings.picture,
|
||||
peers : state.peers
|
||||
};
|
||||
};
|
||||
|
|
@ -116,7 +113,6 @@ export default connect(
|
|||
return (
|
||||
prev.files === next.files &&
|
||||
prev.me === next.me &&
|
||||
prev.settings.picture === next.settings.picture &&
|
||||
prev.peers === next.peers
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ const ListMe = (props) =>
|
|||
classes
|
||||
} = props;
|
||||
|
||||
const picture = settings.picture || EmptyAvatar;
|
||||
const picture = me.picture || EmptyAvatar;
|
||||
|
||||
return (
|
||||
<li className={classes.root}>
|
||||
|
|
@ -91,9 +91,8 @@ const ListMe = (props) =>
|
|||
</div>
|
||||
|
||||
<div className={classes.indicators}>
|
||||
{ me.raisedHand ?
|
||||
{ me.raisedHand &&
|
||||
<div className={classnames(classes.icon, 'raise-hand')} />
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ const ListPeer = (props) =>
|
|||
{peer.displayName}
|
||||
</div>
|
||||
<div className={classes.indicators}>
|
||||
{ peer.raiseHandState ?
|
||||
{ peer.raiseHandState &&
|
||||
<div className={
|
||||
classnames(
|
||||
classes.icon, 'raise-hand', {
|
||||
|
|
@ -169,12 +169,11 @@ const ListPeer = (props) =>
|
|||
)
|
||||
}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
{children}
|
||||
<div className={classes.controls}>
|
||||
{ screenConsumer ?
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.button, 'screen', {
|
||||
on : screenVisible,
|
||||
|
|
@ -195,7 +194,6 @@ const ListPeer = (props) =>
|
|||
<ScreenOffIcon />
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
<div
|
||||
className={classnames(classes.button, 'mic', {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ class ParticipantList extends React.PureComponent
|
|||
<li className={classes.listheader}>Me:</li>
|
||||
<ListMe />
|
||||
</ul>
|
||||
<br />
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>Participants in Spotlight:</li>
|
||||
{ spotlightPeers.map((peer) => (
|
||||
|
|
@ -104,7 +103,6 @@ class ParticipantList extends React.PureComponent
|
|||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<br />
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>Passive Participants:</li>
|
||||
{ passivePeers.map((peerId) => (
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
videoBoxesSelector,
|
||||
spotlightsLengthSelector
|
||||
} from '../Selectors';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Peer from '../Containers/Peer';
|
||||
|
|
@ -14,7 +15,7 @@ import HiddenPeers from '../Containers/HiddenPeers';
|
|||
|
||||
const RATIO = 1.334;
|
||||
const PADDING_V = 50;
|
||||
const PADDING_H = 20;
|
||||
const PADDING_H = 0;
|
||||
|
||||
const styles = () =>
|
||||
({
|
||||
|
|
@ -27,11 +28,17 @@ const styles = () =>
|
|||
flexWrap : 'wrap',
|
||||
justifyContent : 'center',
|
||||
alignItems : 'center',
|
||||
alignContent : 'center',
|
||||
paddingTop : 40,
|
||||
paddingBottom : 10,
|
||||
paddingLeft : 10,
|
||||
paddingRight : 10
|
||||
alignContent : 'center'
|
||||
},
|
||||
hiddenToolBar :
|
||||
{
|
||||
paddingTop : 0,
|
||||
transition : 'padding .5s'
|
||||
},
|
||||
showingToolBar :
|
||||
{
|
||||
paddingTop : 60,
|
||||
transition : 'padding .5s'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -63,7 +70,8 @@ class Democratic extends React.PureComponent
|
|||
}
|
||||
|
||||
const width = this.peersRef.current.clientWidth - PADDING_H;
|
||||
const height = this.peersRef.current.clientHeight - PADDING_V;
|
||||
const height = this.peersRef.current.clientHeight -
|
||||
(this.props.toolbarsVisible ? PADDING_V : PADDING_H);
|
||||
|
||||
let x, y, space;
|
||||
|
||||
|
|
@ -86,8 +94,8 @@ class Democratic extends React.PureComponent
|
|||
if (Math.ceil(this.state.peerWidth) !== Math.ceil(0.9 * x))
|
||||
{
|
||||
this.setState({
|
||||
peerWidth : 0.9 * x,
|
||||
peerHeight : 0.9 * y
|
||||
peerWidth : 0.95 * x,
|
||||
peerHeight : 0.95 * y
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -125,6 +133,7 @@ class Democratic extends React.PureComponent
|
|||
peersLength,
|
||||
spotlightsPeers,
|
||||
spotlightsLength,
|
||||
toolbarsVisible,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -135,7 +144,13 @@ class Democratic extends React.PureComponent
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root} ref={this.peersRef}>
|
||||
<div
|
||||
className={classnames(
|
||||
classes.root,
|
||||
toolbarsVisible ? classes.showingToolBar : classes.hiddenToolBar
|
||||
)}
|
||||
ref={this.peersRef}
|
||||
>
|
||||
<Me
|
||||
advancedMode={advancedMode}
|
||||
spacing={6}
|
||||
|
|
@ -153,11 +168,10 @@ class Democratic extends React.PureComponent
|
|||
/>
|
||||
);
|
||||
})}
|
||||
{ spotlightsLength < peersLength ?
|
||||
{ spotlightsLength < peersLength &&
|
||||
<HiddenPeers
|
||||
hiddenPeersCount={peersLength - spotlightsLength}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -171,6 +185,7 @@ Democratic.propTypes =
|
|||
boxes : PropTypes.number,
|
||||
spotlightsLength : PropTypes.number,
|
||||
spotlightsPeers : PropTypes.array.isRequired,
|
||||
toolbarsVisible : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -180,7 +195,8 @@ const mapStateToProps = (state) =>
|
|||
peersLength : peersLengthSelector(state),
|
||||
boxes : videoBoxesSelector(state),
|
||||
spotlightsPeers : spotlightPeersSelector(state),
|
||||
spotlightsLength : spotlightsLengthSelector(state)
|
||||
spotlightsLength : spotlightsLengthSelector(state),
|
||||
toolbarsVisible : state.room.toolbarsVisible
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -195,7 +211,8 @@ export default connect(
|
|||
prev.peers === next.peers &&
|
||||
prev.producers === next.producers &&
|
||||
prev.consumers === next.consumers &&
|
||||
prev.room.spotlights === next.room.spotlights
|
||||
prev.room.spotlights === next.room.spotlights &&
|
||||
prev.room.toolbarsVisible === next.room.toolbarsVisible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,13 +228,12 @@ class Filmstrip extends React.PureComponent
|
|||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.speaker} ref={this.activePeerContainer}>
|
||||
{ peers[activePeerId] ?
|
||||
{ peers[activePeerId] &&
|
||||
<SpeakerPeer
|
||||
advancedMode={advancedMode}
|
||||
id={activePeerId}
|
||||
style={speakerStyle}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
@ -286,11 +285,10 @@ class Filmstrip extends React.PureComponent
|
|||
</Grid>
|
||||
</div>
|
||||
|
||||
{ spotlightsLength<Object.keys(peers).length ?
|
||||
{ spotlightsLength<Object.keys(peers).length &&
|
||||
<HiddenPeers
|
||||
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ export default class PeerAudio extends React.PureComponent
|
|||
this._setTrack(audioTrack);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps)
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(nextProps)
|
||||
{
|
||||
const { audioTrack } = nextProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
|
||||
export const ReactLazyPreload = (importStatement) =>
|
||||
{
|
||||
const Component = React.lazy(importStatement);
|
||||
|
||||
Component.preload = importStatement;
|
||||
|
||||
return Component;
|
||||
};
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
lobbyPeersKeySelector
|
||||
} from './Selectors';
|
||||
import * as appPropTypes from './appPropTypes';
|
||||
import { withRoomContext } from '../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
|
@ -13,7 +16,6 @@ import AppBar from '@material-ui/core/AppBar';
|
|||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
|
|
@ -30,11 +32,13 @@ import VideoWindow from './VideoWindow/VideoWindow';
|
|||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import SecurityIcon from '@material-ui/icons/Security';
|
||||
import LockDialog from './AccessControl/LockDialog/LockDialog';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Settings from './Settings/Settings';
|
||||
import JoinDialog from './JoinDialog';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
const TIMEOUT = 10 * 1000;
|
||||
|
||||
|
|
@ -150,6 +154,38 @@ const styles = (theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
const PulsingBadge = withStyles((theme) =>
|
||||
({
|
||||
badge :
|
||||
{
|
||||
backgroundColor : theme.palette.secondary.main,
|
||||
// boxShadow : `0 0 0 2px ${theme.palette.secondary.main}`,
|
||||
'&::after' :
|
||||
{
|
||||
position : 'absolute',
|
||||
width : '100%',
|
||||
height : '100%',
|
||||
borderRadius : '50%',
|
||||
animation : '$ripple 1.2s infinite ease-in-out',
|
||||
border : `3px solid ${theme.palette.secondary.main}`,
|
||||
content : '""'
|
||||
}
|
||||
},
|
||||
'@keyframes ripple' :
|
||||
{
|
||||
'0%' :
|
||||
{
|
||||
transform : 'scale(.8)',
|
||||
opacity : 1
|
||||
},
|
||||
'100%' :
|
||||
{
|
||||
transform : 'scale(2.4)',
|
||||
opacity : 0
|
||||
}
|
||||
}
|
||||
}))(Badge);
|
||||
|
||||
class Room extends React.PureComponent
|
||||
{
|
||||
constructor(props)
|
||||
|
|
@ -227,11 +263,13 @@ class Room extends React.PureComponent
|
|||
const {
|
||||
roomClient,
|
||||
room,
|
||||
lobbyPeers,
|
||||
advancedMode,
|
||||
myPicture,
|
||||
loggedIn,
|
||||
loginEnabled,
|
||||
setSettingsOpen,
|
||||
setLockDialogOpen,
|
||||
toolAreaOpen,
|
||||
toggleToolArea,
|
||||
unread,
|
||||
|
|
@ -245,26 +283,6 @@ class Room extends React.PureComponent
|
|||
democratic : Democratic
|
||||
}[room.mode];
|
||||
|
||||
if (room.lockedOut)
|
||||
{
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Paper className={classes.message}>
|
||||
<Typography variant='h2'>This room is locked at the moment, try again later.</Typography>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if (!room.joined)
|
||||
{
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<JoinDialog />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<CookieConsent>
|
||||
|
|
@ -286,7 +304,7 @@ class Room extends React.PureComponent
|
|||
className={room.toolbarsVisible ? classes.show : classes.hide}
|
||||
>
|
||||
<Toolbar>
|
||||
<Badge
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={unread}
|
||||
>
|
||||
|
|
@ -298,11 +316,8 @@ class Room extends React.PureComponent
|
|||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Badge>
|
||||
{ window.config.logo ?
|
||||
<img alt='Logo' className={classes.logo} src={window.config.logo} />
|
||||
:null
|
||||
}
|
||||
</PulsingBadge>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography
|
||||
className={classes.title}
|
||||
variant='h6'
|
||||
|
|
@ -313,6 +328,7 @@ class Room extends React.PureComponent
|
|||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
<Tooltip title={`${room.locked ? 'Unlock' : 'Lock'} room`}>
|
||||
<IconButton
|
||||
aria-label='Lock room'
|
||||
className={classes.actionButton}
|
||||
|
|
@ -335,7 +351,25 @@ class Room extends React.PureComponent
|
|||
<LockOpenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
{ this.fullscreen.fullscreenEnabled ?
|
||||
</Tooltip>
|
||||
{ lobbyPeers.length > 0 &&
|
||||
<Tooltip title='Show lobby'>
|
||||
<IconButton
|
||||
aria-label='Lobby'
|
||||
color='inherit'
|
||||
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
|
||||
>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={lobbyPeers.length}
|
||||
>
|
||||
<SecurityIcon />
|
||||
</PulsingBadge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{ this.fullscreen.fullscreenEnabled &&
|
||||
<Tooltip title={`${this.state.fullscreen ? 'Leave' : 'Enter'} fullscreen`}>
|
||||
<IconButton
|
||||
aria-label='Fullscreen'
|
||||
className={classes.actionButton}
|
||||
|
|
@ -348,8 +382,9 @@ class Room extends React.PureComponent
|
|||
<FullScreenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
:null
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip title='Show settings'>
|
||||
<IconButton
|
||||
aria-label='Settings'
|
||||
className={classes.actionButton}
|
||||
|
|
@ -358,7 +393,9 @@ class Room extends React.PureComponent
|
|||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
{ loginEnabled ?
|
||||
</Tooltip>
|
||||
{ loginEnabled &&
|
||||
<Tooltip title={`Log ${loggedIn ? 'out' : 'in'}`}>
|
||||
<IconButton
|
||||
aria-label='Account'
|
||||
className={classes.actionButton}
|
||||
|
|
@ -374,7 +411,7 @@ class Room extends React.PureComponent
|
|||
<AccountCircle />
|
||||
}
|
||||
</IconButton>
|
||||
:null
|
||||
</Tooltip>
|
||||
}
|
||||
<Button
|
||||
aria-label='Leave meeting'
|
||||
|
|
@ -407,17 +444,19 @@ class Room extends React.PureComponent
|
|||
|
||||
<View advancedMode={advancedMode} />
|
||||
|
||||
<LockDialog />
|
||||
|
||||
<Settings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Room.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
lobbyPeers : PropTypes.array,
|
||||
advancedMode : PropTypes.bool.isRequired,
|
||||
myPicture : PropTypes.string,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
|
|
@ -425,6 +464,7 @@ Room.propTypes =
|
|||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
setSettingsOpen : PropTypes.func.isRequired,
|
||||
setLockDialogOpen : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
unread : PropTypes.number.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
|
|
@ -434,10 +474,11 @@ Room.propTypes =
|
|||
const mapStateToProps = (state) =>
|
||||
({
|
||||
room : state.room,
|
||||
lobbyPeers : lobbyPeersKeySelector(state),
|
||||
advancedMode : state.settings.advancedMode,
|
||||
loggedIn : state.me.loggedIn,
|
||||
loginEnabled : state.me.loginEnabled,
|
||||
myPicture : state.settings.picture,
|
||||
myPicture : state.me.picture,
|
||||
toolAreaOpen : state.toolarea.toolAreaOpen,
|
||||
unread : state.toolarea.unreadMessages +
|
||||
state.toolarea.unreadFiles
|
||||
|
|
@ -453,6 +494,10 @@ const mapDispatchToProps = (dispatch) =>
|
|||
{
|
||||
dispatch(stateActions.setSettingsOpen({ settingsOpen }));
|
||||
},
|
||||
setLockDialogOpen : (lockDialogOpen) =>
|
||||
{
|
||||
dispatch(stateActions.setLockDialogOpen({ lockDialogOpen }));
|
||||
},
|
||||
toggleToolArea : () =>
|
||||
{
|
||||
dispatch(stateActions.toggleToolArea());
|
||||
|
|
@ -468,9 +513,10 @@ export default withRoomContext(connect(
|
|||
{
|
||||
return (
|
||||
prev.room === next.room &&
|
||||
prev.lobbyPeers === next.lobbyPeers &&
|
||||
prev.me.loggedIn === next.me.loggedIn &&
|
||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||
prev.settings.picture === next.settings.picture &&
|
||||
prev.me.picture === next.me.picture &&
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
|
||||
prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const producersSelect = (state) => state.producers;
|
|||
const consumersSelect = (state) => state.consumers;
|
||||
const spotlightsSelector = (state) => state.room.spotlights;
|
||||
const peersSelector = (state) => state.peers;
|
||||
const lobbyPeersSelector = (state) => state.lobbyPeers;
|
||||
const getPeerConsumers = (state, props) =>
|
||||
(state.peers[props.id] ? state.peers[props.id].consumers : null);
|
||||
const getAllConsumers = (state) => state.consumers;
|
||||
|
|
@ -12,6 +13,11 @@ const peersKeySelector = createSelector(
|
|||
(peers) => Object.keys(peers)
|
||||
);
|
||||
|
||||
export const lobbyPeersKeySelector = createSelector(
|
||||
lobbyPeersSelector,
|
||||
(lobbyPeers) => Object.keys(lobbyPeers)
|
||||
);
|
||||
|
||||
export const micProducersSelector = createSelector(
|
||||
producersSelect,
|
||||
(producers) => Object.values(producers).filter((producer) => producer.source === 'mic')
|
||||
|
|
|
|||
|
|
@ -171,24 +171,17 @@ class VideoView extends React.PureComponent
|
|||
})}
|
||||
>
|
||||
<div className={classes.box}>
|
||||
{ audioCodec ?
|
||||
<p>{audioCodec}</p>
|
||||
:null
|
||||
}
|
||||
{ audioCodec && <p>{audioCodec}</p> }
|
||||
|
||||
{ videoCodec ?
|
||||
<p>{videoCodec} {videoProfile}</p>
|
||||
:null
|
||||
}
|
||||
{ videoCodec && <p>{videoCodec} {videoProfile}</p> }
|
||||
|
||||
{ (videoVisible && videoWidth !== null) ?
|
||||
{ (videoVisible && videoWidth !== null) &&
|
||||
<p>{videoWidth}x{videoHeight}</p>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ showPeerInfo ?
|
||||
{ showPeerInfo &&
|
||||
<div className={classes.peer}>
|
||||
<div className={classes.box}>
|
||||
{ isMe ?
|
||||
|
|
@ -212,17 +205,15 @@ class VideoView extends React.PureComponent
|
|||
</span>
|
||||
}
|
||||
|
||||
{ advancedMode ?
|
||||
{ advancedMode &&
|
||||
<div className={classes.deviceInfo}>
|
||||
<span>
|
||||
{peer.device.name} {Math.floor(peer.device.version) || null}
|
||||
</span>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
@ -256,7 +247,8 @@ class VideoView extends React.PureComponent
|
|||
clearInterval(this._videoResolutionTimer);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps)
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(nextProps)
|
||||
{
|
||||
const { videoTrack } = nextProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class NewWindow extends React.PureComponent
|
|||
return ReactDOM.createPortal([
|
||||
<div key='newwindow' className={classes.root}>
|
||||
<div className={classes.controls}>
|
||||
{this.fullscreen.fullscreenEnabled && (
|
||||
{ this.fullscreen.fullscreenEnabled &&
|
||||
<div
|
||||
className={classes.button}
|
||||
onClick={this.handleToggleFullscreen}
|
||||
|
|
@ -144,7 +144,7 @@ class NewWindow extends React.PureComponent
|
|||
<FullScreenIcon className={classes.icon} />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
</div>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ html
|
|||
font-family: 'Roboto';
|
||||
font-weight: 300;
|
||||
margin : 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import RoomClient from './RoomClient';
|
|||
import RoomContext from './RoomContext';
|
||||
import deviceInfo from './deviceInfo';
|
||||
import * as stateActions from './actions/stateActions';
|
||||
import Room from './components/Room';
|
||||
import App from './components/App';
|
||||
import LoadingView from './components/LoadingView';
|
||||
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
|
||||
import { PersistGate } from 'redux-persist/lib/integration/react';
|
||||
|
|
@ -19,7 +19,7 @@ import * as serviceWorker from './serviceWorker';
|
|||
|
||||
import './index.css';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production')
|
||||
if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production')
|
||||
{
|
||||
debug.enable('* -engine* -socket* -RIE* *WARN* *ERROR*');
|
||||
}
|
||||
|
|
@ -62,8 +62,8 @@ function run()
|
|||
window.history.pushState('', '', urlParser.toString());
|
||||
}
|
||||
|
||||
const accessCode = parameters.get('code');
|
||||
const produce = parameters.get('produce') !== 'false';
|
||||
const consume = parameters.get('consume') !== 'false';
|
||||
const useSimulcast = parameters.get('simulcast') === 'true';
|
||||
const forceTcp = parameters.get('forceTcp') === 'true';
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ function run()
|
|||
);
|
||||
|
||||
roomClient = new RoomClient(
|
||||
{ roomId, peerId, device, useSimulcast, produce, consume, forceTcp });
|
||||
{ roomId, peerId, accessCode, device, useSimulcast, produce, forceTcp });
|
||||
|
||||
global.CLIENT = roomClient;
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ function run()
|
|||
<PersistGate loading={<LoadingView />} persistor={persistor}>
|
||||
<RoomContext.Provider value={roomClient}>
|
||||
<SnackbarProvider>
|
||||
<Room />
|
||||
<App />
|
||||
</SnackbarProvider>
|
||||
</RoomContext.Provider>
|
||||
</PersistGate>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
const lobbyPeer = (state = {}, action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
case 'ADD_LOBBY_PEER':
|
||||
return { id: action.payload.peerId };
|
||||
|
||||
case 'SET_LOBBY_PEER_DISPLAY_NAME':
|
||||
return { ...state, displayName: action.payload.displayName };
|
||||
case 'SET_LOBBY_PEER_PICTURE':
|
||||
return { ...state, picture: action.payload.picture };
|
||||
case 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS':
|
||||
return { ...state, promotionInProgress: action.payload.flag };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const lobbyPeers = (state = {}, action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
case 'ADD_LOBBY_PEER':
|
||||
{
|
||||
return { ...state, [action.payload.peerId]: lobbyPeer(undefined, action) };
|
||||
}
|
||||
|
||||
case 'REMOVE_LOBBY_PEER':
|
||||
{
|
||||
const { peerId } = action.payload;
|
||||
const newState = { ...state };
|
||||
|
||||
delete newState[peerId];
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
case 'SET_LOBBY_PEER_DISPLAY_NAME':
|
||||
case 'SET_LOBBY_PEER_PICTURE':
|
||||
case 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS':
|
||||
{
|
||||
const oldLobbyPeer = state[action.payload.peerId];
|
||||
|
||||
if (!oldLobbyPeer)
|
||||
{
|
||||
// Tried to update non-existant lobbyPeer. Has probably been promoted, or left.
|
||||
return state;
|
||||
}
|
||||
|
||||
return { ...state, [oldLobbyPeer.id]: lobbyPeer(oldLobbyPeer, action) };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default lobbyPeers;
|
||||
|
|
@ -2,6 +2,7 @@ const initialState =
|
|||
{
|
||||
id : null,
|
||||
device : null,
|
||||
picture : null,
|
||||
canSendMic : false,
|
||||
canSendWebcam : false,
|
||||
canShareScreen : false,
|
||||
|
|
@ -11,6 +12,7 @@ const initialState =
|
|||
webcamInProgress : false,
|
||||
audioInProgress : false,
|
||||
screenShareInProgress : false,
|
||||
displayNameInProgress : false,
|
||||
loginEnabled : false,
|
||||
raiseHand : false,
|
||||
raiseHandInProgress : false,
|
||||
|
|
@ -38,11 +40,18 @@ const me = (state = initialState, action) =>
|
|||
}
|
||||
|
||||
case 'LOGGED_IN':
|
||||
return { ...state, loggedIn: true };
|
||||
{
|
||||
const { flag } = action.payload;
|
||||
|
||||
return { ...state, loggedIn: flag };
|
||||
}
|
||||
|
||||
case 'USER_LOGOUT':
|
||||
return { ...state, loggedIn: false };
|
||||
|
||||
case 'SET_PICTURE':
|
||||
return { ...state, picture: action.payload.picture };
|
||||
|
||||
case 'SET_MEDIA_CAPABILITIES':
|
||||
{
|
||||
const {
|
||||
|
|
@ -110,6 +119,13 @@ const me = (state = initialState, action) =>
|
|||
return { ...state, raiseHandInProgress: flag };
|
||||
}
|
||||
|
||||
case 'SET_DISPLAY_NAME_IN_PROGRESS':
|
||||
{
|
||||
const { flag } = action.payload;
|
||||
|
||||
return { ...state, displayNameInProgress: flag };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
const initialState =
|
||||
{
|
||||
url : null,
|
||||
name : '',
|
||||
state : 'new', // new/connecting/connected/disconnected/closed,
|
||||
locked : false,
|
||||
lockedOut : false,
|
||||
inLobby : false,
|
||||
signInRequired : false,
|
||||
accessCode : '', // access code to the room if locked and joinByAccessCode == true
|
||||
joinByAccessCode : true, // if true: accessCode is a possibility to open the room
|
||||
activeSpeakerId : null,
|
||||
torrentSupport : false,
|
||||
showSettings : false,
|
||||
|
|
@ -14,6 +18,7 @@ const initialState =
|
|||
selectedPeerId : null,
|
||||
spotlights : [],
|
||||
settingsOpen : false,
|
||||
lockDialogOpen : false,
|
||||
joined : false
|
||||
};
|
||||
|
||||
|
|
@ -28,6 +33,13 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, url };
|
||||
}
|
||||
|
||||
case 'SET_ROOM_NAME':
|
||||
{
|
||||
const { name } = action.payload;
|
||||
|
||||
return { ...state, name };
|
||||
}
|
||||
|
||||
case 'SET_ROOM_STATE':
|
||||
{
|
||||
const roomState = action.payload.state;
|
||||
|
|
@ -48,9 +60,39 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, locked: false };
|
||||
}
|
||||
|
||||
case 'SET_ROOM_LOCKED_OUT':
|
||||
case 'SET_IN_LOBBY':
|
||||
{
|
||||
return { ...state, lockedOut: true };
|
||||
const { inLobby } = action.payload;
|
||||
|
||||
return { ...state, inLobby };
|
||||
}
|
||||
|
||||
case 'SET_SIGN_IN_REQUIRED':
|
||||
{
|
||||
const { signInRequired } = action.payload;
|
||||
|
||||
return { ...state, signInRequired };
|
||||
}
|
||||
|
||||
case 'SET_ACCESS_CODE':
|
||||
{
|
||||
const { accessCode } = action.payload;
|
||||
|
||||
return { ...state, accessCode };
|
||||
}
|
||||
|
||||
case 'SET_JOIN_BY_ACCESS_CODE':
|
||||
{
|
||||
const { joinByAccessCode } = action.payload;
|
||||
|
||||
return { ...state, joinByAccessCode };
|
||||
}
|
||||
|
||||
case 'SET_LOCK_DIALOG_OPEN':
|
||||
{
|
||||
const { lockDialogOpen } = action.payload;
|
||||
|
||||
return { ...state, lockDialogOpen };
|
||||
}
|
||||
|
||||
case 'SET_SETTINGS_OPEN':
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import room from './room';
|
|||
import me from './me';
|
||||
import producers from './producers';
|
||||
import peers from './peers';
|
||||
import lobbyPeers from './lobbyPeers';
|
||||
import consumers from './consumers';
|
||||
import peerVolumes from './peerVolumes';
|
||||
import notifications from './notifications';
|
||||
|
|
@ -16,6 +17,7 @@ export default combineReducers({
|
|||
me,
|
||||
producers,
|
||||
peers,
|
||||
lobbyPeers,
|
||||
consumers,
|
||||
peerVolumes,
|
||||
notifications,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const initialState =
|
||||
{
|
||||
displayName : 'Guest',
|
||||
picture : null,
|
||||
selectedWebcam : null,
|
||||
selectedAudioDevice : null,
|
||||
advancedMode : false,
|
||||
|
|
@ -24,20 +23,11 @@ const settings = (state = initialState, action) =>
|
|||
|
||||
case 'SET_DISPLAY_NAME':
|
||||
{
|
||||
let { displayName } = action.payload;
|
||||
|
||||
// Be ready for undefined displayName (so keep previous one).
|
||||
if (!displayName)
|
||||
displayName = state.displayName;
|
||||
const { displayName } = action.payload;
|
||||
|
||||
return { ...state, displayName };
|
||||
}
|
||||
|
||||
case 'SET_PICTURE':
|
||||
{
|
||||
return { ...state, picture: action.payload.picture };
|
||||
}
|
||||
|
||||
case 'TOGGLE_ADVANCED_MODE':
|
||||
{
|
||||
const advancedMode = !state.advancedMode;
|
||||
|
|
|
|||
|
|
@ -1,168 +0,0 @@
|
|||
module.exports =
|
||||
{
|
||||
env:
|
||||
{
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
extends:
|
||||
[
|
||||
'eslint:recommended'
|
||||
],
|
||||
settings: {},
|
||||
parserOptions:
|
||||
{
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures:
|
||||
{
|
||||
impliedStrict: true
|
||||
}
|
||||
},
|
||||
rules:
|
||||
{
|
||||
'array-bracket-spacing': [ 2, 'always',
|
||||
{
|
||||
objectsInArrays: true,
|
||||
arraysInArrays: true
|
||||
}],
|
||||
'arrow-parens': [ 2, 'always' ],
|
||||
'arrow-spacing': 2,
|
||||
'block-spacing': [ 2, 'always' ],
|
||||
'brace-style': [ 2, 'allman', { allowSingleLine: true } ],
|
||||
'camelcase': 2,
|
||||
'comma-dangle': 2,
|
||||
'comma-spacing': [ 2, { before: false, after: true } ],
|
||||
'comma-style': 2,
|
||||
'computed-property-spacing': 2,
|
||||
'constructor-super': 2,
|
||||
'func-call-spacing': 2,
|
||||
'generator-star-spacing': 2,
|
||||
'guard-for-in': 2,
|
||||
'indent': [ 2, 'tab', { 'SwitchCase': 1 } ],
|
||||
'key-spacing': [ 2,
|
||||
{
|
||||
singleLine:
|
||||
{
|
||||
beforeColon: false,
|
||||
afterColon: true
|
||||
},
|
||||
multiLine:
|
||||
{
|
||||
beforeColon: true,
|
||||
afterColon: true,
|
||||
align: 'colon'
|
||||
}
|
||||
}],
|
||||
'keyword-spacing': 2,
|
||||
'linebreak-style': [ 2, 'unix' ],
|
||||
'lines-around-comment': [ 2,
|
||||
{
|
||||
allowBlockStart: true,
|
||||
allowObjectStart: true,
|
||||
beforeBlockComment: true,
|
||||
beforeLineComment: false
|
||||
}],
|
||||
'max-len': [ 2, 90,
|
||||
{
|
||||
tabWidth: 2,
|
||||
comments: 110,
|
||||
ignoreUrls: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreRegExpLiterals: true
|
||||
}],
|
||||
'newline-after-var': 2,
|
||||
'newline-before-return': 2,
|
||||
'newline-per-chained-call': 2,
|
||||
'no-alert': 2,
|
||||
'no-caller': 2,
|
||||
'no-case-declarations': 2,
|
||||
'no-catch-shadow': 2,
|
||||
'no-class-assign': 2,
|
||||
'no-confusing-arrow': 2,
|
||||
'no-console': 2,
|
||||
'no-const-assign': 2,
|
||||
'no-debugger': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-div-regex': 2,
|
||||
'no-empty': [ 2, { allowEmptyCatch: true } ],
|
||||
'no-empty-pattern': 2,
|
||||
'no-else-return': 0,
|
||||
'no-eval': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-ex-assign': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-label': 2,
|
||||
'no-extra-semi': 2,
|
||||
'no-fallthrough': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-global-assign': 2,
|
||||
'no-implicit-coercion': 2,
|
||||
'no-implicit-globals': 2,
|
||||
'no-inner-declarations': 2,
|
||||
'no-invalid-regexp': 2,
|
||||
'no-invalid-this': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-lonely-if': 2,
|
||||
'no-mixed-operators': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multi-spaces': 2,
|
||||
'no-multi-str': 2,
|
||||
'no-multiple-empty-lines': [ 2, { max: 1, maxEOF: 0, maxBOF: 0 } ],
|
||||
'no-native-reassign': 2,
|
||||
'no-negated-in-lhs': 2,
|
||||
'no-new': 2,
|
||||
'no-new-func': 2,
|
||||
'no-new-wrappers': 2,
|
||||
'no-obj-calls': 2,
|
||||
'no-proto': 2,
|
||||
'no-prototype-builtins': 0,
|
||||
'no-redeclare': 2,
|
||||
'no-regex-spaces': 2,
|
||||
'no-restricted-imports': 2,
|
||||
'no-return-assign': 2,
|
||||
'no-self-assign': 2,
|
||||
'no-self-compare': 2,
|
||||
'no-sequences': 2,
|
||||
'no-shadow': 2,
|
||||
'no-shadow-restricted-names': 2,
|
||||
'no-spaced-func': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-this-before-super': 2,
|
||||
'no-throw-literal': 2,
|
||||
'no-undef': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unmodified-loop-condition': 2,
|
||||
'no-unreachable': 2,
|
||||
'no-unused-vars': [ 1, { vars: 'all', args: 'after-used' }],
|
||||
'no-use-before-define': [ 2, { functions: false } ],
|
||||
'no-useless-call': 2,
|
||||
'no-useless-computed-key': 2,
|
||||
'no-useless-concat': 2,
|
||||
'no-useless-rename': 2,
|
||||
'no-var': 2,
|
||||
'no-whitespace-before-property': 2,
|
||||
'object-curly-newline': 0,
|
||||
'object-curly-spacing': [ 2, 'always' ],
|
||||
'object-property-newline': [ 2, { allowMultiplePropertiesPerLine: true } ],
|
||||
'prefer-const': 2,
|
||||
'prefer-rest-params': 2,
|
||||
'prefer-spread': 2,
|
||||
'prefer-template': 2,
|
||||
'quotes': [ 2, 'single', { avoidEscape: true } ],
|
||||
'semi': [ 2, 'always' ],
|
||||
'semi-spacing': 2,
|
||||
'space-before-blocks': 2,
|
||||
'space-before-function-paren': [ 2, 'never' ],
|
||||
'space-in-parens': [ 2, 'never' ],
|
||||
'spaced-comment': [ 2, 'always' ],
|
||||
'strict': 0,
|
||||
'valid-typeof': 2,
|
||||
'yoda': 2
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
{
|
||||
"env":
|
||||
{
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends":
|
||||
[
|
||||
"eslint:recommended"
|
||||
],
|
||||
"settings": {},
|
||||
"parserOptions":
|
||||
{
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures":
|
||||
{
|
||||
"impliedStrict": true
|
||||
}
|
||||
},
|
||||
"rules":
|
||||
{
|
||||
"array-bracket-spacing": [ 2, "always",
|
||||
{
|
||||
"objectsInArrays": true,
|
||||
"arraysInArrays": true
|
||||
}],
|
||||
"arrow-parens": [ 2, "always" ],
|
||||
"arrow-spacing": 2,
|
||||
"block-spacing": [ 2, "always" ],
|
||||
"brace-style": [ 2, "allman", { "allowSingleLine": true } ],
|
||||
"camelcase": 2,
|
||||
"comma-dangle": 2,
|
||||
"comma-spacing": [ 2, { "before": false, "after": true } ],
|
||||
"comma-style": 2,
|
||||
"computed-property-spacing": 2,
|
||||
"constructor-super": 2,
|
||||
"func-call-spacing": 2,
|
||||
"generator-star-spacing": 2,
|
||||
"guard-for-in": 2,
|
||||
"indent": [ 2, "tab", { "SwitchCase": 1 } ],
|
||||
"key-spacing": [ 2,
|
||||
{
|
||||
"singleLine":
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
},
|
||||
"multiLine":
|
||||
{
|
||||
"beforeColon": true,
|
||||
"afterColon": true,
|
||||
"align": "colon"
|
||||
}
|
||||
}],
|
||||
"keyword-spacing": 2,
|
||||
"linebreak-style": [ 2, "unix" ],
|
||||
"lines-around-comment": [ 2,
|
||||
{
|
||||
"allowBlockStart": true,
|
||||
"allowObjectStart": true,
|
||||
"beforeBlockComment": true,
|
||||
"beforeLineComment": false
|
||||
}],
|
||||
"max-len": [ 2, 90,
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"comments": 90,
|
||||
"ignoreUrls": true,
|
||||
"ignoreStrings": true,
|
||||
"ignoreTemplateLiterals": true,
|
||||
"ignoreRegExpLiterals": true
|
||||
}],
|
||||
"newline-after-var": 2,
|
||||
"newline-before-return": 2,
|
||||
"newline-per-chained-call": 2,
|
||||
"no-alert": 2,
|
||||
"no-caller": 2,
|
||||
"no-case-declarations": 2,
|
||||
"no-catch-shadow": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-confusing-arrow": 2,
|
||||
"no-console": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-debugger": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-div-regex": 2,
|
||||
"no-empty": [ 2, { "allowEmptyCatch": true } ],
|
||||
"no-empty-pattern": 2,
|
||||
"no-else-return": 0,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-label": 2,
|
||||
"no-extra-semi": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-global-assign": 2,
|
||||
"no-implicit-coercion": 2,
|
||||
"no-implicit-globals": 2,
|
||||
"no-inner-declarations": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-invalid-this": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-operators": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [ 1, { "max": 1, "maxEOF": 0, "maxBOF": 0 } ],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-proto": 2,
|
||||
"no-prototype-builtins": 0,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-restricted-imports": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-self-assign": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-undef": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unmodified-loop-condition": 2,
|
||||
"no-unreachable": 2,
|
||||
"no-unused-vars": [ 1, { "vars": "all", "args": "after-used" }],
|
||||
"no-use-before-define": [ 2, { "functions": false } ],
|
||||
"no-useless-call": 2,
|
||||
"no-useless-computed-key": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-useless-rename": 2,
|
||||
"no-var": 2,
|
||||
"no-whitespace-before-property": 2,
|
||||
"object-curly-newline": 0,
|
||||
"object-curly-spacing": [ 2, "always" ],
|
||||
"object-property-newline": [ 2, { "allowMultiplePropertiesPerLine": true } ],
|
||||
"prefer-const": 2,
|
||||
"prefer-rest-params": 2,
|
||||
"prefer-spread": 2,
|
||||
"prefer-template": 2,
|
||||
"quotes": [ 2, "single", { "avoidEscape": true } ],
|
||||
"semi": [ 2, "always" ],
|
||||
"semi-spacing": 2,
|
||||
"space-before-blocks": 2,
|
||||
"space-before-function-paren": [ 2,
|
||||
{
|
||||
"anonymous" : "never",
|
||||
"named" : "never",
|
||||
"asyncArrow" : "always"
|
||||
}],
|
||||
"space-in-parens": [ 2, "never" ],
|
||||
"spaced-comment": [ 2, "always" ],
|
||||
"strict": 2,
|
||||
"valid-typeof": 2,
|
||||
"yoda": 2
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ module.exports =
|
|||
},*/
|
||||
// session cookie secret
|
||||
cookieSecret : 'T0P-S3cR3t_cook!e',
|
||||
cookieName : 'multiparty-meeting.sid',
|
||||
tls :
|
||||
{
|
||||
cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`,
|
||||
|
|
@ -33,6 +34,16 @@ module.exports =
|
|||
// Any http request is redirected to https.
|
||||
// Listening port for http server.
|
||||
listeningRedirectPort : 80,
|
||||
// If this is set to true, only signed-in users will be able
|
||||
// to join a room directly. Non-signed-in users (guests) will
|
||||
// always be put in the lobby regardless of room lock status.
|
||||
// If false, there is no difference between guests and signed-in
|
||||
// users when joining.
|
||||
requireSignInToAccess : true,
|
||||
// This flag has no effect when requireSignInToAccess is false
|
||||
// When truthy, the room will be open to all users when the first
|
||||
// authenticated user has already joined the room.
|
||||
activateOnHostJoin : true,
|
||||
// Mediasoup settings
|
||||
mediasoup :
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Tasks:
|
||||
*
|
||||
* gulp lint
|
||||
* Checks source code
|
||||
*
|
||||
* gulp watch
|
||||
* Observes changes in the code
|
||||
*
|
||||
* gulp
|
||||
* Invokes both `lint` and `watch` tasks
|
||||
*/
|
||||
|
||||
const gulp = require('gulp');
|
||||
const plumber = require('gulp-plumber');
|
||||
const eslint = require('gulp-eslint');
|
||||
|
||||
const LINTING_FILES =
|
||||
[
|
||||
'gulpfile.js',
|
||||
'server.js',
|
||||
'config/config.example.js',
|
||||
'lib/**/*.js'
|
||||
];
|
||||
|
||||
gulp.task('lint', () =>
|
||||
{
|
||||
|
||||
return gulp.src(LINTING_FILES)
|
||||
.pipe(plumber())
|
||||
.pipe(eslint())
|
||||
.pipe(eslint.format());
|
||||
});
|
||||
|
||||
gulp.task('lint-fix', function()
|
||||
{
|
||||
return gulp.src(LINTING_FILES)
|
||||
.pipe(plumber())
|
||||
.pipe(eslint({ fix: true }))
|
||||
.pipe(eslint.format())
|
||||
.pipe(gulp.dest((file) => file.base));
|
||||
});
|
||||
|
||||
gulp.task('default', gulp.series('lint'));
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const headers = {
|
||||
'access-control-allow-origin': '*',
|
||||
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'access-control-allow-headers': 'content-type, accept',
|
||||
'access-control-max-age': 10,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
exports.prepareResponse = (req, cb) =>
|
||||
{
|
||||
let data = '';
|
||||
|
||||
req.on('data', (chunk) =>
|
||||
{
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
req.on('end', () =>
|
||||
{
|
||||
cb(data);
|
||||
});
|
||||
};
|
||||
|
||||
exports.respond = (res, data, status) =>
|
||||
{
|
||||
status = status || 200;
|
||||
res.writeHead(status, headers);
|
||||
res.end(data);
|
||||
};
|
||||
|
||||
exports.send404 = (res) =>
|
||||
{
|
||||
exports.respond(res, 'Not Found', 404);
|
||||
};
|
||||
|
||||
exports.redirector = (res, loc, status) =>
|
||||
{
|
||||
status = status || 302;
|
||||
res.writeHead(status, { Location: loc });
|
||||
res.end();
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
exports.loginHelper = function(data)
|
||||
{
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Multiparty Meeting</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
let data = ${JSON.stringify(data)};
|
||||
|
||||
window.opener.CLIENT.receiveLoginChildWindow(data);
|
||||
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
exports.logoutHelper = function()
|
||||
{
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Multiparty Meeting</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
window.opener.CLIENT.receiveLogoutChildWindow();
|
||||
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return html;
|
||||
};
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
const EventEmitter = require('events').EventEmitter;
|
||||
const Logger = require('./Logger');
|
||||
|
||||
const logger = new Logger('Lobby');
|
||||
|
||||
class Lobby extends EventEmitter
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
logger.info('constructor()');
|
||||
|
||||
super();
|
||||
|
||||
// Closed flag.
|
||||
this._closed = false;
|
||||
|
||||
this._peers = new Map();
|
||||
}
|
||||
|
||||
close()
|
||||
{
|
||||
logger.info('close()');
|
||||
|
||||
this._closed = true;
|
||||
|
||||
this._peers.forEach((peer) =>
|
||||
{
|
||||
if (!peer.closed)
|
||||
peer.close();
|
||||
});
|
||||
|
||||
this._peers.clear();
|
||||
}
|
||||
|
||||
checkEmpty()
|
||||
{
|
||||
logger.info('checkEmpty()');
|
||||
|
||||
return this._peers.size === 0;
|
||||
}
|
||||
|
||||
peerList()
|
||||
{
|
||||
logger.info('peerList()');
|
||||
|
||||
return Array.from(this._peers.values()).map((peer) =>
|
||||
({
|
||||
peerId : peer.id,
|
||||
displayName : peer.displayName
|
||||
}));
|
||||
}
|
||||
|
||||
hasPeer(peerId)
|
||||
{
|
||||
return this._peers.has(peerId);
|
||||
}
|
||||
|
||||
promoteAllPeers()
|
||||
{
|
||||
logger.info('promoteAllPeers()');
|
||||
|
||||
this._peers.forEach((peer) =>
|
||||
{
|
||||
if (peer.socket)
|
||||
this.promotePeer(peer.id);
|
||||
});
|
||||
}
|
||||
|
||||
promotePeer(peerId)
|
||||
{
|
||||
logger.info('promotePeer() [peer:"%s"]', peerId);
|
||||
|
||||
const peer = this._peers.get(peerId);
|
||||
|
||||
if (peer)
|
||||
{
|
||||
peer.socket.removeListener('request', peer.socketRequestHandler);
|
||||
peer.removeListener('authenticationChanged', peer.authenticationHandler);
|
||||
peer.removeListener('close', peer.closeHandler);
|
||||
|
||||
peer.socketRequestHandler = null;
|
||||
peer.authenticationHandler = null;
|
||||
peer.closeHandler = null;
|
||||
|
||||
this.emit('promotePeer', peer);
|
||||
this._peers.delete(peerId);
|
||||
}
|
||||
}
|
||||
|
||||
parkPeer(peer)
|
||||
{
|
||||
logger.info('parkPeer() [peer:"%s"]', peer.id);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
peer.socketRequestHandler = (request, cb) =>
|
||||
{
|
||||
logger.debug(
|
||||
'Peer "request" event [method:"%s", peer:"%s"]',
|
||||
request.method, peer.id);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
this._handleSocketRequest(peer, request, cb)
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('request failed [error:"%o"]', error);
|
||||
|
||||
cb(error);
|
||||
});
|
||||
};
|
||||
|
||||
peer.authenticationHandler = () =>
|
||||
{
|
||||
logger.info('parkPeer() | authenticationChange [peer:"%s"]', peer.id);
|
||||
|
||||
peer.authenticated && this.emit('peerAuthenticated', peer);
|
||||
};
|
||||
|
||||
peer.closeHandler = () =>
|
||||
{
|
||||
logger.debug('Peer "close" event [peer:"%s"]', peer.id);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
this.emit('peerClosed', peer);
|
||||
|
||||
this._peers.delete(peer.id);
|
||||
|
||||
if (this.checkEmpty())
|
||||
this.emit('lobbyEmpty');
|
||||
};
|
||||
|
||||
this._notification(peer.socket, 'enteredLobby');
|
||||
|
||||
this._peers.set(peer.id, peer);
|
||||
|
||||
peer.on('authenticationChanged', peer.authenticationHandler);
|
||||
|
||||
peer.socket.on('request', peer.socketRequestHandler);
|
||||
|
||||
peer.on('close', peer.closeHandler);
|
||||
}
|
||||
|
||||
async _handleSocketRequest(peer, request, cb)
|
||||
{
|
||||
logger.debug(
|
||||
'_handleSocketRequest [peer:"%s"], [request:"%s"]',
|
||||
peer.id,
|
||||
request.method
|
||||
);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
switch (request.method)
|
||||
{
|
||||
case 'changeDisplayName':
|
||||
{
|
||||
const { displayName } = request.data;
|
||||
|
||||
peer.displayName = displayName;
|
||||
|
||||
this.emit('changeDisplayName', peer);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
case 'changePicture':
|
||||
{
|
||||
const { picture } = request.data;
|
||||
|
||||
peer.picture = picture;
|
||||
|
||||
this.emit('changePicture', peer);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_notification(socket, method, data = {}, broadcast = false)
|
||||
{
|
||||
if (broadcast)
|
||||
{
|
||||
socket.broadcast.to(this._roomId).emit(
|
||||
'notification', { method, data }
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
socket.emit('notification', { method, data });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Lobby;
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
const debug = require('debug');
|
||||
|
||||
const APP_NAME = 'multiparty-meeting-server';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,342 @@
|
|||
const EventEmitter = require('events').EventEmitter;
|
||||
const Logger = require('./Logger');
|
||||
|
||||
const logger = new Logger('Peer');
|
||||
|
||||
class Peer extends EventEmitter
|
||||
{
|
||||
constructor({ id, socket })
|
||||
{
|
||||
logger.info('constructor() [id:"%s", socket:"%s"]', id, socket.id);
|
||||
super();
|
||||
|
||||
this._id = id;
|
||||
|
||||
this._authId = null;
|
||||
|
||||
this._socket = socket;
|
||||
|
||||
this._closed = false;
|
||||
|
||||
this._joined = false;
|
||||
|
||||
this._inLobby = false;
|
||||
|
||||
this._authenticated = false;
|
||||
|
||||
this._displayName = false;
|
||||
|
||||
this._picture = null;
|
||||
|
||||
this._email = null;
|
||||
|
||||
this._device = null;
|
||||
|
||||
this._rtpCapabilities = null;
|
||||
|
||||
this._raisedHand = false;
|
||||
|
||||
this._transports = new Map();
|
||||
|
||||
this._producers = new Map();
|
||||
|
||||
this._consumers = new Map();
|
||||
|
||||
this._checkAuthentication();
|
||||
|
||||
this._handlePeer();
|
||||
}
|
||||
|
||||
close()
|
||||
{
|
||||
logger.info('close()');
|
||||
|
||||
this._closed = true;
|
||||
|
||||
// Iterate and close all mediasoup Transport associated to this Peer, so all
|
||||
// its Producers and Consumers will also be closed.
|
||||
this.transports.forEach((transport) =>
|
||||
{
|
||||
transport.close();
|
||||
});
|
||||
|
||||
if (this._socket)
|
||||
this._socket.disconnect(true);
|
||||
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
_handlePeer()
|
||||
{
|
||||
this.socket.use((packet, next) =>
|
||||
{
|
||||
this._checkAuthentication();
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', () =>
|
||||
{
|
||||
if (this.closed)
|
||||
return;
|
||||
|
||||
logger.debug('"disconnect" event [id:%s]', this.id);
|
||||
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
_checkAuthentication()
|
||||
{
|
||||
if (
|
||||
Boolean(this.socket.handshake.session.passport) &&
|
||||
Boolean(this.socket.handshake.session.passport.user)
|
||||
)
|
||||
{
|
||||
const {
|
||||
id,
|
||||
displayName,
|
||||
picture,
|
||||
email
|
||||
} = this.socket.handshake.session.passport.user;
|
||||
|
||||
id && (this.authId = id);
|
||||
displayName && (this.displayName = displayName);
|
||||
picture && (this.picture = picture);
|
||||
email && (this.email = email);
|
||||
|
||||
this.authenticated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.authenticated = false;
|
||||
}
|
||||
}
|
||||
|
||||
get id()
|
||||
{
|
||||
return this._id;
|
||||
}
|
||||
|
||||
set id(id)
|
||||
{
|
||||
this._id = id;
|
||||
}
|
||||
|
||||
get authId()
|
||||
{
|
||||
return this._authId;
|
||||
}
|
||||
|
||||
set authId(authId)
|
||||
{
|
||||
this._authId = authId;
|
||||
}
|
||||
|
||||
get socket()
|
||||
{
|
||||
return this._socket;
|
||||
}
|
||||
|
||||
set socket(socket)
|
||||
{
|
||||
this._socket = socket;
|
||||
}
|
||||
|
||||
get closed()
|
||||
{
|
||||
return this._closed;
|
||||
}
|
||||
|
||||
get joined()
|
||||
{
|
||||
return this._joined;
|
||||
}
|
||||
|
||||
set joined(joined)
|
||||
{
|
||||
this._joined = joined;
|
||||
}
|
||||
|
||||
get inLobby()
|
||||
{
|
||||
return this._inLobby;
|
||||
}
|
||||
|
||||
set inLobby(inLobby)
|
||||
{
|
||||
this._inLobby = inLobby;
|
||||
}
|
||||
|
||||
get authenticated()
|
||||
{
|
||||
return this._authenticated;
|
||||
}
|
||||
|
||||
set authenticated(authenticated)
|
||||
{
|
||||
if (authenticated !== this._authenticated)
|
||||
{
|
||||
const oldAuthenticated = this._authenticated;
|
||||
|
||||
this._authenticated = authenticated;
|
||||
|
||||
this.emit('authenticationChanged', { oldAuthenticated });
|
||||
}
|
||||
}
|
||||
|
||||
get displayName()
|
||||
{
|
||||
return this._displayName;
|
||||
}
|
||||
|
||||
set displayName(displayName)
|
||||
{
|
||||
if (displayName !== this._displayName)
|
||||
{
|
||||
const oldDisplayName = this._displayName;
|
||||
|
||||
this._displayName = displayName;
|
||||
|
||||
this.emit('displayNameChanged', { oldDisplayName });
|
||||
}
|
||||
}
|
||||
|
||||
get picture()
|
||||
{
|
||||
return this._picture;
|
||||
}
|
||||
|
||||
set picture(picture)
|
||||
{
|
||||
if (picture !== this._picture)
|
||||
{
|
||||
const oldPicture = this._picture;
|
||||
|
||||
this._picture = picture;
|
||||
|
||||
this.emit('pictureChanged', { oldPicture });
|
||||
}
|
||||
}
|
||||
|
||||
get email()
|
||||
{
|
||||
return this._email;
|
||||
}
|
||||
|
||||
set email(email)
|
||||
{
|
||||
this._email = email;
|
||||
}
|
||||
|
||||
get device()
|
||||
{
|
||||
return this._device;
|
||||
}
|
||||
|
||||
set device(device)
|
||||
{
|
||||
this._device = device;
|
||||
}
|
||||
|
||||
get rtpCapabilities()
|
||||
{
|
||||
return this._rtpCapabilities;
|
||||
}
|
||||
|
||||
set rtpCapabilities(rtpCapabilities)
|
||||
{
|
||||
this._rtpCapabilities = rtpCapabilities;
|
||||
}
|
||||
|
||||
get raisedHand()
|
||||
{
|
||||
return this._raisedHand;
|
||||
}
|
||||
|
||||
set raisedHand(raisedHand)
|
||||
{
|
||||
this._raisedHand = raisedHand;
|
||||
}
|
||||
|
||||
get transports()
|
||||
{
|
||||
return this._transports;
|
||||
}
|
||||
|
||||
get producers()
|
||||
{
|
||||
return this._producers;
|
||||
}
|
||||
|
||||
get consumers()
|
||||
{
|
||||
return this._consumers;
|
||||
}
|
||||
|
||||
addTransport(id, transport)
|
||||
{
|
||||
this.transports.set(id, transport);
|
||||
}
|
||||
|
||||
getTransport(id)
|
||||
{
|
||||
return this.transports.get(id);
|
||||
}
|
||||
|
||||
getConsumerTransport()
|
||||
{
|
||||
return Array.from(this.transports.values())
|
||||
.find((t) => t.appData.consuming);
|
||||
}
|
||||
|
||||
removeTransport(id)
|
||||
{
|
||||
this.transports.delete(id);
|
||||
}
|
||||
|
||||
addProducer(id, producer)
|
||||
{
|
||||
this.producers.set(id, producer);
|
||||
}
|
||||
|
||||
getProducer(id)
|
||||
{
|
||||
return this.producers.get(id);
|
||||
}
|
||||
|
||||
removeProducer(id)
|
||||
{
|
||||
this.producers.delete(id);
|
||||
}
|
||||
|
||||
addConsumer(id, consumer)
|
||||
{
|
||||
this.consumers.set(id, consumer);
|
||||
}
|
||||
|
||||
getConsumer(id)
|
||||
{
|
||||
return this.consumers.get(id);
|
||||
}
|
||||
|
||||
removeConsumer(id)
|
||||
{
|
||||
this.consumers.delete(id);
|
||||
}
|
||||
|
||||
get peerInfo()
|
||||
{
|
||||
const peerInfo =
|
||||
{
|
||||
id : this.id,
|
||||
displayName : this.displayName,
|
||||
picture : this.picture,
|
||||
device : this.device
|
||||
};
|
||||
|
||||
return peerInfo;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Peer;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,321 +0,0 @@
|
|||
const mediasoup = require('mediasoup');
|
||||
const readline = require('readline');
|
||||
const colors = require('colors/safe');
|
||||
const repl = require('repl');
|
||||
const homer = require('./lib/homer');
|
||||
const config = require('./config/config');
|
||||
|
||||
// mediasoup server.
|
||||
const mediaServer = mediasoup.Server(
|
||||
{
|
||||
numWorkers : null,
|
||||
logLevel : config.mediasoup.logLevel,
|
||||
logTags : config.mediasoup.logTags,
|
||||
rtcIPv4 : config.mediasoup.rtcIPv4,
|
||||
rtcIPv6 : config.mediasoup.rtcIPv6,
|
||||
rtcAnnouncedIPv4 : config.mediasoup.rtcAnnouncedIPv4,
|
||||
rtcAnnouncedIPv6 : config.mediasoup.rtcAnnouncedIPv6,
|
||||
rtcMinPort : config.mediasoup.rtcMinPort,
|
||||
rtcMaxPort : config.mediasoup.rtcMaxPort
|
||||
});
|
||||
|
||||
// Do Homer stuff.
|
||||
if (process.env.MEDIASOUP_HOMER_OUTPUT)
|
||||
homer(mediaServer);
|
||||
|
||||
global.SERVER = mediaServer;
|
||||
|
||||
mediaServer.on('newroom', (room) =>
|
||||
{
|
||||
global.ROOM = room;
|
||||
|
||||
room.on('newpeer', (peer) =>
|
||||
{
|
||||
global.PEER = peer;
|
||||
|
||||
if (peer.consumers.length > 0)
|
||||
global.CONSUMER = peer.consumers[peer.consumers.length - 1];
|
||||
|
||||
peer.on('newtransport', (transport) =>
|
||||
{
|
||||
global.TRANSPORT = transport;
|
||||
});
|
||||
|
||||
peer.on('newproducer', (producer) =>
|
||||
{
|
||||
global.PRODUCER = producer;
|
||||
});
|
||||
|
||||
peer.on('newconsumer', (consumer) =>
|
||||
{
|
||||
global.CONSUMER = consumer;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for keyboard input.
|
||||
|
||||
let cmd;
|
||||
let terminal;
|
||||
|
||||
openCommandConsole();
|
||||
|
||||
function openCommandConsole()
|
||||
{
|
||||
stdinLog('[opening Readline Command Console...]');
|
||||
|
||||
closeCommandConsole();
|
||||
closeTerminal();
|
||||
|
||||
cmd = readline.createInterface(
|
||||
{
|
||||
input : process.stdin,
|
||||
output : process.stdout
|
||||
});
|
||||
|
||||
cmd.on('SIGINT', () =>
|
||||
{
|
||||
process.exit();
|
||||
});
|
||||
|
||||
readStdin();
|
||||
|
||||
function readStdin()
|
||||
{
|
||||
cmd.question('cmd> ', (answer) =>
|
||||
{
|
||||
switch (answer)
|
||||
{
|
||||
case '':
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'h':
|
||||
case 'help':
|
||||
{
|
||||
stdinLog('');
|
||||
stdinLog('available commands:');
|
||||
stdinLog('- h, help : show this message');
|
||||
stdinLog('- sd, serverdump : execute server.dump()');
|
||||
stdinLog('- rd, roomdump : execute room.dump() for the latest created mediasoup Room');
|
||||
stdinLog('- pd, peerdump : execute peer.dump() for the latest created mediasoup Peer');
|
||||
stdinLog('- td, transportdump : execute transport.dump() for the latest created mediasoup Transport');
|
||||
stdinLog('- prd, producerdump : execute producer.dump() for the latest created mediasoup Producer');
|
||||
stdinLog('- cd, consumerdump : execute consumer.dump() for the latest created mediasoup Consumer');
|
||||
stdinLog('- t, terminal : open REPL Terminal');
|
||||
stdinLog('');
|
||||
readStdin();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'sd':
|
||||
case 'serverdump':
|
||||
{
|
||||
mediaServer.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`server.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`mediaServer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'rd':
|
||||
case 'roomdump':
|
||||
{
|
||||
if (!global.ROOM)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.ROOM.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`room.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`room.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pd':
|
||||
case 'peerdump':
|
||||
{
|
||||
if (!global.PEER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.PEER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`peer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`peer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'td':
|
||||
case 'transportdump':
|
||||
{
|
||||
if (!global.TRANSPORT)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.TRANSPORT.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`transport.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`transport.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'prd':
|
||||
case 'producerdump':
|
||||
{
|
||||
if (!global.PRODUCER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.PRODUCER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`producer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`producer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'cd':
|
||||
case 'consumerdump':
|
||||
{
|
||||
if (!global.CONSUMER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.CONSUMER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`consumer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`consumer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 't':
|
||||
case 'terminal':
|
||||
{
|
||||
openTerminal();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
stdinError(`unknown command: ${answer}`);
|
||||
stdinLog('press \'h\' or \'help\' to get the list of available commands');
|
||||
|
||||
readStdin();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function openTerminal()
|
||||
{
|
||||
stdinLog('[opening REPL Terminal...]');
|
||||
|
||||
closeCommandConsole();
|
||||
closeTerminal();
|
||||
|
||||
terminal = repl.start(
|
||||
{
|
||||
prompt : 'terminal> ',
|
||||
useColors : true,
|
||||
useGlobal : true,
|
||||
ignoreUndefined : false
|
||||
});
|
||||
|
||||
terminal.on('exit', () => openCommandConsole());
|
||||
}
|
||||
|
||||
function closeCommandConsole()
|
||||
{
|
||||
if (cmd)
|
||||
{
|
||||
cmd.close();
|
||||
cmd = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function closeTerminal()
|
||||
{
|
||||
if (terminal)
|
||||
{
|
||||
terminal.removeAllListeners('exit');
|
||||
terminal.close();
|
||||
terminal = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function stdinLog(msg)
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(colors.green(msg));
|
||||
}
|
||||
|
||||
function stdinError(msg)
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(colors.red.bold('ERROR: ') + colors.red(msg));
|
||||
}
|
||||
|
||||
module.exports = mediaServer;
|
||||
|
|
@ -9,20 +9,21 @@
|
|||
"dependencies": {
|
||||
"awaitqueue": "^1.0.0",
|
||||
"base-64": "^0.1.0",
|
||||
"colors": "^1.1.2",
|
||||
"compression": "^1.7.3",
|
||||
"debug": "^4.1.0",
|
||||
"express": "^4.16.3",
|
||||
"express-session": "^1.16.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"colors": "^1.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"connect-redis": "^4.0.3",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"debug": "^4.1.1",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.0",
|
||||
"express-socket.io-session": "^1.3.5",
|
||||
"helmet": "^3.21.2",
|
||||
"mediasoup": "^3.0.12",
|
||||
"openid-client": "^2.5.0",
|
||||
"openid-client": "^3.7.3",
|
||||
"passport": "^0.4.0",
|
||||
"socket.io": "^2.1.1",
|
||||
"spdy": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-eslint": "^5.0.0",
|
||||
"gulp-plumber": "^1.2.0"
|
||||
"redis": "^2.8.0",
|
||||
"socket.io": "^2.3.0",
|
||||
"spdy": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
159
server/server.js
159
server/server.js
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
process.title = 'multiparty-meeting-server';
|
||||
|
||||
const config = require('./config/config');
|
||||
|
|
@ -9,17 +7,28 @@ const fs = require('fs');
|
|||
const http = require('http');
|
||||
const spdy = require('spdy');
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const compression = require('compression');
|
||||
const mediasoup = require('mediasoup');
|
||||
const AwaitQueue = require('awaitqueue');
|
||||
const Logger = require('./lib/Logger');
|
||||
const Room = require('./lib/Room');
|
||||
const utils = require('./util');
|
||||
const Peer = require('./lib/Peer');
|
||||
const base64 = require('base-64');
|
||||
const helmet = require('helmet');
|
||||
const {
|
||||
loginHelper,
|
||||
logoutHelper
|
||||
} = require('./httpHelper');
|
||||
// auth
|
||||
const passport = require('passport');
|
||||
const redis = require('redis');
|
||||
const client = redis.createClient();
|
||||
const { Issuer, Strategy } = require('openid-client');
|
||||
const session = require('express-session');
|
||||
const expressSession = require('express-session');
|
||||
const RedisStore = require('connect-redis')(expressSession);
|
||||
const sharedSession = require('express-socket.io-session');
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
||||
|
|
@ -42,17 +51,51 @@ let nextMediasoupWorkerIdx = 0;
|
|||
// Map of Room instances indexed by roomId.
|
||||
const rooms = new Map();
|
||||
|
||||
// Map of Peer instances indexed by peerId.
|
||||
const peers = new Map();
|
||||
|
||||
// TLS server configuration.
|
||||
const tls =
|
||||
{
|
||||
cert : fs.readFileSync(config.tls.cert),
|
||||
key : fs.readFileSync(config.tls.key)
|
||||
key : fs.readFileSync(config.tls.key),
|
||||
secureOptions : 'tlsv12',
|
||||
ciphers :
|
||||
[
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
||||
'ECDHE-RSA-CHACHA20-POLY1305',
|
||||
'DHE-RSA-AES128-GCM-SHA256',
|
||||
'DHE-RSA-AES256-GCM-SHA384'
|
||||
].join(':'),
|
||||
honorCipherOrder : true
|
||||
};
|
||||
|
||||
const app = express();
|
||||
let httpsServer;
|
||||
let oidcClient;
|
||||
let oidcStrategy;
|
||||
|
||||
app.use(helmet.hsts());
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
const session = expressSession({
|
||||
secret : config.cookieSecret,
|
||||
name : config.cookieName,
|
||||
resave : true,
|
||||
saveUninitialized : true,
|
||||
store : new RedisStore({ client }),
|
||||
cookie : {
|
||||
secure : true,
|
||||
httpOnly : true,
|
||||
maxAge : 60 * 60 * 1000 // Expire after 1 hour since last request from user
|
||||
}
|
||||
});
|
||||
|
||||
app.use(session);
|
||||
|
||||
passport.serializeUser((user, done) =>
|
||||
{
|
||||
|
|
@ -64,6 +107,11 @@ passport.deserializeUser((user, done) =>
|
|||
done(null, user);
|
||||
});
|
||||
|
||||
let httpsServer;
|
||||
let io;
|
||||
let oidcClient;
|
||||
let oidcStrategy;
|
||||
|
||||
const auth = config.auth;
|
||||
|
||||
async function run()
|
||||
|
|
@ -115,6 +163,15 @@ async function run()
|
|||
room.logStatus();
|
||||
}
|
||||
}, 120000);
|
||||
|
||||
// check for deserted rooms
|
||||
setInterval(() =>
|
||||
{
|
||||
for (const room of rooms.values())
|
||||
{
|
||||
room.checkEmpty();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
async function setupAuth(oidcIssuer)
|
||||
|
|
@ -136,10 +193,9 @@ async function setupAuth(oidcIssuer)
|
|||
// resolved from the issuer configuration, instead of true you may provide
|
||||
// any of the supported values directly, i.e. "S256" (recommended) or "plain"
|
||||
const usePKCE = false;
|
||||
const client = oidcClient;
|
||||
|
||||
oidcStrategy = new Strategy(
|
||||
{ client, params, passReqToCallback, usePKCE },
|
||||
{ client: oidcClient, params, passReqToCallback, usePKCE },
|
||||
(tokenset, userinfo, done) =>
|
||||
{
|
||||
const user =
|
||||
|
|
@ -150,15 +206,15 @@ async function setupAuth(oidcIssuer)
|
|||
_claims : tokenset.claims
|
||||
};
|
||||
|
||||
if (typeof(userinfo.picture) !== 'undefined')
|
||||
if (userinfo.picture != null)
|
||||
{
|
||||
if (!userinfo.picture.match(/^http/g))
|
||||
{
|
||||
user.Photos = [ { value: `data:image/jpeg;base64, ${userinfo.picture}` } ];
|
||||
user.picture = `data:image/jpeg;base64, ${userinfo.picture}`;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.Photos = [ { value: userinfo.picture } ];
|
||||
user.picture = userinfo.picture;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,22 +230,22 @@ async function setupAuth(oidcIssuer)
|
|||
|
||||
if (userinfo.email != null)
|
||||
{
|
||||
user.emails = [ { value: userinfo.email } ];
|
||||
user.email = userinfo.email;
|
||||
}
|
||||
|
||||
if (userinfo.given_name != null)
|
||||
{
|
||||
user.name = { givenName: userinfo.given_name };
|
||||
user.name.givenName = userinfo.given_name;
|
||||
}
|
||||
|
||||
if (userinfo.family_name != null)
|
||||
{
|
||||
user.name = { familyName: userinfo.family_name };
|
||||
user.name.familyName = userinfo.family_name;
|
||||
}
|
||||
|
||||
if (userinfo.middle_name != null)
|
||||
{
|
||||
user.name = { middleName: userinfo.middle_name };
|
||||
user.name.middleName = userinfo.middle_name;
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
|
|
@ -198,24 +254,15 @@ async function setupAuth(oidcIssuer)
|
|||
|
||||
passport.use('oidc', oidcStrategy);
|
||||
|
||||
app.use(session({
|
||||
secret : config.cookieSecret,
|
||||
resave : true,
|
||||
saveUninitialized : true,
|
||||
cookie : { secure: true }
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// login
|
||||
// loginparams
|
||||
app.get('/auth/login', (req, res, next) =>
|
||||
{
|
||||
passport.authenticate('oidc', {
|
||||
state : base64.encode(JSON.stringify({
|
||||
roomId : req.query.roomId,
|
||||
peerId : req.query.peerId,
|
||||
code : utils.random(10)
|
||||
id : req.query.id
|
||||
}))
|
||||
})(req, res, next);
|
||||
});
|
||||
|
|
@ -224,7 +271,7 @@ async function setupAuth(oidcIssuer)
|
|||
app.get('/auth/logout', (req, res) =>
|
||||
{
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
res.send(logoutHelper());
|
||||
});
|
||||
|
||||
// callback
|
||||
|
|
@ -235,10 +282,8 @@ async function setupAuth(oidcIssuer)
|
|||
{
|
||||
const state = JSON.parse(base64.decode(req.query.state));
|
||||
|
||||
if (rooms.has(state.roomId))
|
||||
{
|
||||
let displayName;
|
||||
let photo;
|
||||
let picture;
|
||||
|
||||
if (req.user != null)
|
||||
{
|
||||
|
|
@ -247,29 +292,22 @@ async function setupAuth(oidcIssuer)
|
|||
else
|
||||
displayName = '';
|
||||
|
||||
if (
|
||||
req.user.Photos != null &&
|
||||
req.user.Photos[0] != null &&
|
||||
req.user.Photos[0].value != null
|
||||
)
|
||||
photo = req.user.Photos[0].value;
|
||||
if (req.user.picture != null)
|
||||
picture = req.user.picture;
|
||||
else
|
||||
photo = '/static/media/buddy.403cb9f6.svg';
|
||||
picture = '/static/media/buddy.403cb9f6.svg';
|
||||
}
|
||||
|
||||
const data =
|
||||
{
|
||||
peerId : state.peerId,
|
||||
displayName : displayName,
|
||||
picture : photo
|
||||
};
|
||||
const peer = peers.get(state.id);
|
||||
|
||||
const room = rooms.get(state.roomId);
|
||||
peer && (peer.authenticated = true);
|
||||
peer && (peer.displayName = displayName);
|
||||
peer && (peer.picture = picture);
|
||||
|
||||
room.authCallback(data);
|
||||
}
|
||||
|
||||
res.send('');
|
||||
res.send(loginHelper({
|
||||
displayName,
|
||||
picture
|
||||
}));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -316,11 +354,17 @@ async function runHttpsServer()
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a protoo WebSocketServer to allow WebSocket connections from browsers.
|
||||
* Create a WebSocketServer to allow WebSocket connections from browsers.
|
||||
*/
|
||||
async function runWebSocketServer()
|
||||
{
|
||||
const io = require('socket.io')(httpsServer);
|
||||
io = require('socket.io')(httpsServer);
|
||||
|
||||
io.use(
|
||||
sharedSession(session, {
|
||||
autoSave : true
|
||||
})
|
||||
);
|
||||
|
||||
// Handle connections from clients.
|
||||
io.on('connection', (socket) =>
|
||||
|
|
@ -342,12 +386,17 @@ async function runWebSocketServer()
|
|||
queue.push(async () =>
|
||||
{
|
||||
const room = await getOrCreateRoom({ roomId });
|
||||
const peer = new Peer({ id: peerId, socket });
|
||||
|
||||
room.handleConnection({ peerId, socket });
|
||||
peers.set(peerId, peer);
|
||||
|
||||
peer.on('close', () => peers.delete(peerId));
|
||||
|
||||
room.handlePeer(peer);
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('room creation or room joining failed:%o', error);
|
||||
logger.error('room creation or room joining failed [error:"%o"]', error);
|
||||
|
||||
socket.disconnect(true);
|
||||
|
||||
|
|
@ -410,7 +459,7 @@ async function getOrCreateRoom({ roomId })
|
|||
// If the Room does not exist create a new one.
|
||||
if (!room)
|
||||
{
|
||||
logger.info('creating a new Room [roomId:%s]', roomId);
|
||||
logger.info('creating a new Room [roomId:"%s"]', roomId);
|
||||
|
||||
const mediasoupWorker = getMediasoupWorker();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var crypto = require('crypto');
|
||||
|
||||
exports.random = function (howMany, chars) {
|
||||
chars = chars
|
||||
|| "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
|
||||
var rnd = crypto.randomBytes(howMany)
|
||||
, value = new Array(howMany)
|
||||
, len = len = Math.min(256, chars.length)
|
||||
, d = 256 / len
|
||||
|
||||
for (var i = 0; i < howMany; i++) {
|
||||
value[i] = chars[Math.floor(rnd[i] / d)]
|
||||
};
|
||||
|
||||
return value.join('');
|
||||
}
|
||||
Loading…
Reference in New Issue