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

@ -69,16 +69,15 @@ used configuration keys in the list below. Please beware that there will be no
check if those values are correct. Therefore this is not the recommended way. 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.
### Avatar cache (v2.2+) ### Avatar cache (v2.2+)
The generation of avatars puts a high load on your Nextcloud instance, since the The generation of avatars puts a high load on your Nextcloud instance, since the

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