Compare commits

..

No commits in common. "c1e42a836e926a9b3339edbda5ff127196f33280" and "42f92dc9775fda072b022711e8a59db900a7b617" have entirely different histories.

12 changed files with 4209 additions and 2688 deletions

View File

@ -28,8 +28,7 @@ module.exports = {
'comma-dangle': ['error', 'always-multiline'], 'comma-dangle': ['error', 'always-multiline'],
'array-bracket-newline': ['error', 'consistent'], 'array-bracket-newline': ['error', 'consistent'],
'quote-props': ['error', 'as-needed'], 'quote-props': ['error', 'as-needed'],
indent: ['warn', 'tab'], 'indent': ['warn', 'tab'],
semi: ['error', 'always'], semi: ["error", "always"],
'@typescript-eslint/ban-types': 'off',
}, },
} }

View File

@ -70,12 +70,11 @@ check if those values are correct. Therefore this is not the recommended way.
The syntax to set all settings is `occ config:app:set bbb KEY --value "VALUE"`. The syntax to set all settings is `occ config:app:set bbb KEY --value "VALUE"`.
Key | Description Key | Description
--------------------------------- | ------------------------------------------------------------------------------------ --------------------- | ------------------------------------------------------------------------------------
`app.navigation` | Set to `true` to show navigation entry `app.navigation` | Set to `true` to show navigation entry
`app.navigation.name` | Defines the navigation label. Default "BigBlueButton". `app.navigation.name` | Defines the navigation label. Default "BigBlueButton".
`api.url` | URL to your BBB server. Should start with `https://` `api.url` | URL to your BBB server. Should start with `https://`
`api.secret` | Secret of your BBB server `api.secret` | Secret of your BBB server
`api.meta_analytics-callback-url` | URL which gets called after meetings ends to generate statistics. See [bbb-analytics](https://github.com/betagouv/bbb-analytics).
`app.shortener` | Value of your shortener service. Should start with `https://` and contain `{token}`. `app.shortener` | Value of your shortener service. Should start with `https://` and contain `{token}`.
`avatar.path` | Absolute path to an optional avatar cache directory. `avatar.path` | Absolute path to an optional avatar cache directory.
`avatar.url` | URL which serves `avatar.path` to be used as avatar cache. `avatar.url` | URL which serves `avatar.path` to be used as avatar cache.

View File

@ -106,9 +106,7 @@ class API {
$joinMeetingParams->setCreateTime(sprintf("%.0f", $creationTime)); $joinMeetingParams->setCreateTime(sprintf("%.0f", $creationTime));
$joinMeetingParams->setJoinViaHtml5(true); $joinMeetingParams->setJoinViaHtml5(true);
$joinMeetingParams->setRedirect(true); $joinMeetingParams->setRedirect(true);
$joinMeetingParams->setGuest($uid === null);
// set the guest parameter for everyone but moderators to send all users to the waiting room if setting is selected
$joinMeetingParams->setGuest((($room->access === Room::ACCESS_WAITING_ROOM_ALL) && !$isModerator) || $uid === null);
$joinMeetingParams->addUserData('bbb_listen_only_mode', $room->getListenOnly()); $joinMeetingParams->addUserData('bbb_listen_only_mode', $room->getListenOnly());
@ -177,13 +175,6 @@ class API {
$createMeetingParams->addMeta('bbb-origin', \method_exists($this->defaults, 'getProductName') ? $this->defaults->getProductName() : 'Nextcloud'); $createMeetingParams->addMeta('bbb-origin', \method_exists($this->defaults, 'getProductName') ? $this->defaults->getProductName() : 'Nextcloud');
$createMeetingParams->addMeta('bbb-origin-server-name', $this->request->getServerHost()); $createMeetingParams->addMeta('bbb-origin-server-name', $this->request->getServerHost());
$analyticsCallbackUrl = $this->config->getAppValue('bbb', 'api.meta_analytics-callback-url');
if (!empty($analyticsCallbackUrl)) {
// For more details: https://github.com/bigbluebutton/bigbluebutton/blob/develop/record-and-playback/core/scripts/post_events/post_events_analytics_callback.rb
$createMeetingParams->addMeta('analytics-callback-url', $analyticsCallbackUrl);
$createMeetingParams->setMeetingKeepEvents(true);
}
$mac = $this->crypto->calculateHMAC($room->uid); $mac = $this->crypto->calculateHMAC($room->uid);
$endMeetingUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.meetingEnded', ['token' => $room->uid, 'mac' => $mac]); $endMeetingUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.meetingEnded', ['token' => $room->uid, 'mac' => $mac]);
@ -208,7 +199,7 @@ class API {
$createMeetingParams->addPresentation($presentation->getUrl(), null, $presentation->getFilename()); $createMeetingParams->addPresentation($presentation->getUrl(), null, $presentation->getFilename());
} }
if ($room->access === Room::ACCESS_WAITING_ROOM || $room->access === Room::ACCESS_WAITING_ROOM_ALL) { if ($room->access === Room::ACCESS_WAITING_ROOM) {
$createMeetingParams->setGuestPolicyAskModerator(); $createMeetingParams->setGuestPolicyAskModerator();
} }

View File

@ -47,11 +47,10 @@ class Room extends Entity implements JsonSerializable {
public const ACCESS_PUBLIC = 'public'; public const ACCESS_PUBLIC = 'public';
public const ACCESS_PASSWORD = 'password'; public const ACCESS_PASSWORD = 'password';
public const ACCESS_WAITING_ROOM = 'waiting_room'; public const ACCESS_WAITING_ROOM = 'waiting_room';
public const ACCESS_WAITING_ROOM_ALL = 'waiting_room_all';
public const ACCESS_INTERNAL = 'internal'; public const ACCESS_INTERNAL = 'internal';
public const ACCESS_INTERNAL_RESTRICTED = 'internal_restricted'; public const ACCESS_INTERNAL_RESTRICTED = 'internal_restricted';
public const ACCESS = [self::ACCESS_PUBLIC, self::ACCESS_PASSWORD, self::ACCESS_WAITING_ROOM, self::ACCESS_WAITING_ROOM_ALL, self::ACCESS_INTERNAL, self::ACCESS_INTERNAL_RESTRICTED]; public const ACCESS = [self::ACCESS_PUBLIC, self::ACCESS_PASSWORD, self::ACCESS_WAITING_ROOM, self::ACCESS_INTERNAL, self::ACCESS_INTERNAL_RESTRICTED];
public $uid; public $uid;
public $name; public $name;

View File

@ -14,7 +14,7 @@
"license": "agpl", "license": "agpl",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "NODE_ENV=production webpack --progress --config webpack.prod.js", "build": "NODE_ENV=production webpack --progress --hide-modules --config webpack.prod.js",
"dev": "NODE_ENV=development webpack --progress --config webpack.dev.js", "dev": "NODE_ENV=development webpack --progress --config webpack.dev.js",
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js", "watch": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js",
"test": "run-s --continue-on-error --print-label test:**", "test": "run-s --continue-on-error --print-label test:**",
@ -34,20 +34,20 @@
"release:publish": "node scripts/publish-release.js" "release:publish": "node scripts/publish-release.js"
}, },
"dependencies": { "dependencies": {
"@commitlint/cli": "^16.2.3", "@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^16.2.1", "@commitlint/config-conventional": "^9.1.2",
"@commitlint/travis-cli": "^16.2.3", "@commitlint/travis-cli": "^9.1.2",
"@nextcloud/axios": "^1.3.2", "@nextcloud/axios": "^1.3.2",
"@nextcloud/router": "^2.0.0", "@nextcloud/router": "^1.0.2",
"@octokit/rest": "^18.0.4", "@octokit/rest": "^18.0.4",
"archiver": "^5.0.0", "archiver": "^5.0.0",
"colors": "^1.4.0", "colors": "^1.4.0",
"dotenv": "^16.0.0", "dotenv": "^8.2.0",
"execa": "^6.1.0", "execa": "^4.0.0",
"libxmljs": "^0.19.7", "libxmljs": "^0.19.7",
"qrcode.react": "^2.0.0", "qrcode.react": "^1.0.1",
"react-copy-to-clipboard": "^5.0.2", "react-copy-to-clipboard": "^5.0.2",
"simple-git": "^3.3.0" "simple-git": "^2.20.1"
}, },
"husky": { "husky": {
"hooks": { "hooks": {
@ -60,59 +60,58 @@
"extends @nextcloud/browserslist-config" "extends @nextcloud/browserslist-config"
], ],
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=10.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.0", "@babel/core": "^7.9.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.9.0", "@babel/preset-env": "^7.9.0",
"@nextcloud/browserslist-config": "^2.2.0", "@nextcloud/browserslist-config": "^1.0.0",
"@nextcloud/eslint-plugin": "^2.0.0", "@nextcloud/eslint-plugin": "^2.0.0",
"@nextcloud/files": "^2.1.0", "@nextcloud/files": "^1.0.1",
"@types/bootstrap": "^5.1.9", "@types/bootstrap": "^4.3.2",
"@types/inquirer": "^8.2.0", "@types/inquirer": "^7.3.1",
"@types/jquery": "^3.3.35", "@types/jquery": "^3.3.35",
"@types/node": "^17.0.21", "@types/node": "^14.6.2",
"@types/react": "^17.0.40", "@types/react": "^16.9.34",
"@types/webpack": "^5.28.0", "@types/webpack": "^4.41.12",
"@types/webpack-env": "^1.15.2", "@types/webpack-env": "^1.15.2",
"@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^5.15.0", "@typescript-eslint/parser": "^4.0.1",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"css-loader": "^6.7.1", "css-loader": "^4.2.2",
"dotenv-cli": "^5.0.0", "dotenv-cli": "^3.1.0",
"eslint": "^8.11.0", "eslint": "^7.8.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^14.1.1",
"eslint-import-resolver-webpack": "^0.13.2", "eslint-import-resolver-webpack": "^0.12.1",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.19.0", "eslint-plugin-react": "^7.19.0",
"eslint-plugin-standard": "^5.0.0", "eslint-plugin-standard": "^4.0.1",
"eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"husky": "^4.2.5", "husky": "^4.2.5",
"inquirer": "^8.2.1", "inquirer": "^7.1.0",
"install": "^0.13.0", "node-sass": "^4.13.1",
"node-sass": "^7.0.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"react": "^17.0.2", "react": "^16.13.1",
"react-dom": "^17.0.2", "react-dom": "^16.13.1",
"react-flip-move": "^3.0.4", "react-flip-move": "^3.0.4",
"react-hot-loader": "^4.12.20", "react-hot-loader": "^4.12.20",
"react-select": "^5.2.2", "react-select": "^3.1.0",
"sass-loader": "^12.6.0", "sass-loader": "^10.0.1",
"style-loader": "^3.3.1", "style-loader": "^1.2.0",
"stylelint": "^14.5.3", "stylelint": "^13.7.0",
"stylelint-config-recommended-scss": "^5.0.2", "stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^4.2.0", "stylelint-scss": "^3.16.0",
"ts-loader": "^9.2.8", "ts-loader": "^8.0.3",
"typescript": "^4.0.2", "typescript": "^4.0.2",
"url-loader": "^4.0.0", "url-loader": "^4.0.0",
"webpack": "^5.70.0", "webpack": "^4.42.1",
"webpack-cli": "^4.9.2", "webpack-cli": "^3.3.11",
"webpack-merge": "^5.1.3", "webpack-merge": "^5.1.3",
"webpack-node-externals": "^3.0.0" "webpack-node-externals": "^2.5.2"
} }
} }

View File

@ -12,7 +12,6 @@ export enum Access {
Public = 'public', Public = 'public',
Password = 'password', Password = 'password',
WaitingRoom = 'waiting_room', WaitingRoom = 'waiting_room',
WaitingRoomAll = 'waiting_room_all',
Internal = 'internal', Internal = 'internal',
InternalRestricted = 'internal_restricted', InternalRestricted = 'internal_restricted',
} }

View File

@ -4,7 +4,6 @@ export const AccessOptions = {
[Access.Public]: t('bbb', 'Public'), [Access.Public]: t('bbb', 'Public'),
[Access.Password]: t('bbb', 'Internal + Password protection for guests'), [Access.Password]: t('bbb', 'Internal + Password protection for guests'),
[Access.WaitingRoom]: t('bbb', 'Internal + Waiting room for guests'), [Access.WaitingRoom]: t('bbb', 'Internal + Waiting room for guests'),
[Access.WaitingRoomAll]: t('bbb', 'Waiting room for all users'),
[Access.Internal]: t('bbb', 'Internal'), [Access.Internal]: t('bbb', 'Internal'),
[Access.InternalRestricted]: t('bbb', 'Internal restricted'), [Access.InternalRestricted]: t('bbb', 'Internal restricted'),
}; };

View File

@ -2,9 +2,6 @@ import axios from '@nextcloud/axios';
import { generateOcsUrl, generateUrl } from '@nextcloud/router'; import { generateOcsUrl, generateUrl } from '@nextcloud/router';
import { api } from './Common/Api'; import { api } from './Common/Api';
type OC_Dialogs_Message = (content: string, title: string, dialogType: 'notice' | 'alert' | 'warn' | 'none', buttons?: number, callback?: () => void, modal?: boolean, allowHtml?: boolean) => Promise<void>;
type ExtendedDialogs = typeof OC.dialogs & { message: OC_Dialogs_Message };
const mimeTypes = [ const mimeTypes = [
'application/pdf', 'application/pdf',
'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.presentation',
@ -21,16 +18,10 @@ const mimeTypes = [
'image/png', 'image/png',
'text/plain', 'text/plain',
'text/rtf', 'text/rtf',
] as const; ];
type MimeTypes = typeof mimeTypes[number];
async function createDirectShare(fileId: number): Promise<string> { async function createDirectShare(fileId: number): Promise<string> {
const url = generateOcsUrl('apps/dav/api/v1/', undefined, { const url = generateOcsUrl('apps/dav/api/v1', 1) + 'direct';
ocsVersion: 1,
escape: true,
noRewrite: true,
}) + 'direct';
const createResponse = await axios.post(url, { const createResponse = await axios.post(url, {
fileId, fileId,
}); });
@ -38,7 +29,7 @@ async function createDirectShare(fileId: number): Promise<string> {
return createResponse.data?.ocs?.data?.url; return createResponse.data?.ocs?.data?.url;
} }
async function share(fileId: number, filename: string, roomUid: string) { async function share(fileId: number, filename: string, roomUid) {
const shareUrl = await createDirectShare(fileId); const shareUrl = await createDirectShare(fileId);
const joinUrl = generateUrl('/apps/bbb/b/{uid}?u={url}&filename={filename}', { const joinUrl = generateUrl('/apps/bbb/b/{uid}?u={url}&filename={filename}', {
uid: roomUid, uid: roomUid,
@ -49,59 +40,15 @@ async function share(fileId: number, filename: string, roomUid: string) {
window.open(joinUrl, '_blank', 'noopener,noreferrer'); window.open(joinUrl, '_blank', 'noopener,noreferrer');
} }
async function openDialog(fileId: number, filename: string) { function registerFileAction(fileActions, mime, id, uid, name) {
const initContent = '<div id="bbb-file-action"><span className="icon icon-loading-small icon-visible"></span></div>';
const title = t('bbb', 'Send file to BBB');
await (OC.dialogs as ExtendedDialogs).message(initContent, title, 'none', -1, undefined, true, true);
const rooms = await api.getRooms();
const container = $('#bbb-file-action').empty();
const table = $('<table>').appendTo(container);
table.attr('style', 'margin-top: 1em; width: 100%;');
for (const room of rooms) {
const row = $('<tr>');
const button = $('<button>');
button.text(t('bbb', 'Start'));
button.addClass('primary');
button.attr('type', 'button');
button.on('click', (ev) => {
ev.preventDefault();
share(fileId, filename, room.uid);
container.parents('.oc-dialog').find('.oc-dialog-close').trigger('click');
});
row.append($('<td>').append(button));
row.append($('<td>').attr('style', 'width: 100%;').text(room.name));
row.appendTo(table);
}
if (rooms.length > 0) {
const description = t('bbb', 'Please select the room in which you like to use the file "{filename}".', { filename });
container.append(description);
container.append(table);
} else {
container.append($('p').text(t('bbb', 'No rooms available!')));
}
}
function registerFileAction(fileActions: any, mime: MimeTypes) {
fileActions.registerAction({ fileActions.registerAction({
name: 'bbb', name: 'bbb-' + id,
displayName: t('bbb', 'Send to BBB'), displayName: name,
mime, mime,
permissions: OC.PERMISSION_SHARE, permissions: OC.PERMISSION_SHARE,
icon: OC.imagePath('bbb', 'app-dark.svg'), icon: OC.imagePath('bbb', 'app-dark.svg'),
actionHandler: (fileName, context) => { actionHandler: (fileName, context) => {
console.log('Action handler'); share(context.fileInfoModel.id, fileName, uid);
openDialog(context.fileInfoModel.id, fileName);
}, },
}); });
} }
@ -116,7 +63,11 @@ const BBBFileListPlugin = {
return; return;
} }
mimeTypes.forEach(mime => registerFileAction(fileList.fileActions, mime)); api.getRooms().then(rooms => {
rooms.forEach(room => {
mimeTypes.forEach(mime => registerFileAction(fileList.fileActions, mime, room.id, room.uid, room.name));
});
});
}, },
}; };

View File

@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */ const path = require('path')
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = { module.exports = {
entry: { entry: {
@ -21,7 +19,7 @@ module.exports = {
], ],
waiting: [ waiting: [
path.join(__dirname, 'ts', 'waiting.ts'), path.join(__dirname, 'ts', 'waiting.ts'),
], ]
}, },
output: { output: {
path: path.resolve(__dirname, './js'), path: path.resolve(__dirname, './js'),
@ -52,6 +50,11 @@ module.exports = {
test: /\.scss$/, test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'], use: ['style-loader', 'css-loader', 'sass-loader'],
}, },
{
test: /\.(js)$/,
use: 'eslint-loader',
enforce: 'pre',
},
{ {
test: /\.js$/, test: /\.js$/,
loader: 'babel-loader', loader: 'babel-loader',
@ -68,10 +71,9 @@ module.exports = {
], ],
}, },
plugins: [ plugins: [
new ESLintPlugin(),
], ],
resolve: { resolve: {
extensions: ['*', '.tsx', '.ts', '.js', '.scss'], extensions: ['*', '.tsx', '.ts', '.js', '.scss'],
symlinks: false, symlinks: false,
}, },
}; }

View File

@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { merge } = require('webpack-merge'); const { merge } = require('webpack-merge');
const common = require('./webpack.common.js'); const common = require('./webpack.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: 'development', mode: 'development',
devtool: 'cheap-source-map', devtool: '#cheap-source-map',
}); })

View File

@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */ const { merge } = require('webpack-merge')
const { merge } = require('webpack-merge'); const common = require('./webpack.common.js')
const common = require('./webpack.common.js');
module.exports = merge(common, { module.exports = merge(common, {
mode: 'production', mode: 'production',
devtool: 'source-map', devtool: '#source-map'
}); })

6669
yarn.lock

File diff suppressed because it is too large Load Diff