diff --git a/lib/BigBlueButton/API.php b/lib/BigBlueButton/API.php index efd27cc..f197845 100644 --- a/lib/BigBlueButton/API.php +++ b/lib/BigBlueButton/API.php @@ -14,6 +14,7 @@ use OCA\BigBlueButton\Event\MeetingStartedEvent; use OCA\BigBlueButton\Db\Room; use OCA\BigBlueButton\Permission; use OCA\BigBlueButton\Crypto; +use OCA\BigBlueButton\UrlHelper; use OCP\IConfig; use OCP\IURLGenerator; use OCP\IL10N; @@ -40,13 +41,17 @@ class API { /** @var IL10N */ private $l10n; + /** @var UrlHelper */ + private $urlHelper; + public function __construct( IConfig $config, IURLGenerator $urlGenerator, Permission $permission, Crypto $crypto, IEventDispatcher $eventDispatcher, - IL10N $l10n + IL10N $l10n, + UrlHelper $urlHelper ) { $this->config = $config; $this->urlGenerator = $urlGenerator; @@ -54,6 +59,7 @@ class API { $this->crypto = $crypto; $this->eventDispatcher = $eventDispatcher; $this->l10n = $l10n; + $this->urlHelper = $urlHelper; } private function getServer() { @@ -132,7 +138,7 @@ class API { $recordingReadyUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.recordingReady', ['token' => $room->uid, 'mac' => $mac]); $createMeetingParams->setRecordingReadyCallbackUrl($recordingReadyUrl); - $invitationUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.join.index', ['token' => $room->uid]); + $invitationUrl = $this->urlHelper->linkToInvitationAbsolute($room); $createMeetingParams->setModeratorOnlyMessage($this->l10n->t('To invite someone to the meeting, send them this link: %s', [$invitationUrl])); if (!empty($room->welcome)) { diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index c42440e..49e95f1 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -5,10 +5,16 @@ namespace OCA\BigBlueButton\Controller; use OCP\IRequest; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Controller; +use OCP\IConfig; class PageController extends Controller { - public function __construct(string $appName, IRequest $request) { + /** @var IConfig */ + private $config; + + public function __construct(string $appName, IRequest $request, IConfig $config) { parent::__construct($appName, $request); + + $this->config = $config; } /** @@ -16,6 +22,8 @@ class PageController extends Controller { * @NoCSRFRequired */ public function index() { - return new TemplateResponse($this->appName, 'manager'); + return new TemplateResponse($this->appName, 'manager', [ + 'shortener' => $this->config->getAppValue('bbb', 'app.shortener', ''), + ]); } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 2ffb63f..cf090ab 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -28,6 +28,7 @@ class Admin implements ISettings { 'api.url' => $this->config->getAppValue('bbb', 'api.url'), 'api.secret' => $this->config->getAppValue('bbb', 'api.secret'), 'app.navigation' => $this->config->getAppValue('bbb', 'app.navigation') === 'true' ? 'checked' : '', + 'app.shortener' => $this->config->getAppValue('bbb', 'app.shortener'), ]; return new TemplateResponse('bbb', 'admin', $parameters); diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php index bfdc4ab..b0f416d 100644 --- a/lib/Settings/Personal.php +++ b/lib/Settings/Personal.php @@ -34,7 +34,10 @@ class Personal implements ISettings { $warning = $this->l->t('API URL or secret not configured. Please contact your administrator.'); } - return new TemplateResponse('bbb', 'manager', ['warning' => $warning]); + return new TemplateResponse('bbb', 'manager', [ + 'warning' => $warning, + 'shortener' => $this->config->getAppValue('bbb', 'app.shortener', ''), + ]); } /** diff --git a/lib/UrlHelper.php b/lib/UrlHelper.php new file mode 100644 index 0000000..3f0b7cb --- /dev/null +++ b/lib/UrlHelper.php @@ -0,0 +1,44 @@ +config = $config; + $this->urlGenerator = $urlGenerator; + } + + public function linkToInvitationAbsolute(Room $room): string { + $url = $this->config->getAppValue('bbb', 'app.shortener', ''); + + if (empty($url) || strpos($url, 'https://') !== 0 || strpos($url, '{token}') === false) { + return $this->urlGenerator->linkToRouteAbsolute('bbb.join.index', ['token' => $room->getUid()]); + } + + $placeholders = []; + $replacements = [ + 'token' => $room->getUid(), + 'user' => $room->getUserId(), + ]; + + + foreach ($replacements as $placeholder => $parameter) { + $placeholders[] = '{' . $placeholder . '}'; + } + + return str_replace($placeholders, $replacements, $url); + } +} diff --git a/templates/admin.php b/templates/admin.php index 1e77b70..74e8589 100644 --- a/templates/admin.php +++ b/templates/admin.php @@ -11,12 +11,12 @@ script('bbb', 'restrictions');

t('Get your API URL and secret by executing "sudo bbb-conf --secret" on your BigBlueButton server.')); ?>

-
+ -
+

@@ -24,6 +24,18 @@ script('bbb', 'restrictions');

+

URL Shortener

+

t('If you like to use shorter urls, you can enter a forwarding proxy below.')); ?>

+ +
+ + + +
+ +
+
+

Restrictions

diff --git a/templates/manager.php b/templates/manager.php index 994c65d..71cf8cf 100644 --- a/templates/manager.php +++ b/templates/manager.php @@ -2,7 +2,7 @@ script('bbb', 'manager'); ?> -
+
diff --git a/ts/Common/Api.ts b/ts/Common/Api.ts index f37ee18..4f02914 100644 --- a/ts/Common/Api.ts +++ b/ts/Common/Api.ts @@ -122,6 +122,12 @@ class Api { } public getRoomUrl(room: Room) { + const shortener = document.getElementById('bbb-root')?.getAttribute('data-shortener') || ''; + + if (shortener) { + return shortener.replace(/\{user\}/g, room.userId).replace(/\{token\}/g, room.uid); + } + return window.location.origin + api.getUrl(`b/${room.uid}`); } diff --git a/ts/Manager/App.scss b/ts/Manager/App.scss index e2a3039..0f277f5 100644 --- a/ts/Manager/App.scss +++ b/ts/Manager/App.scss @@ -27,6 +27,14 @@ width: 100%; } +pre { + font-weight: normal; + background-color: #f1f1f1; + padding: 1em 2em; + display: inline-block; + font-family: monospace; +} + #bbb-warning { padding: 1em; background-color: rgb(255, 255, 123); diff --git a/ts/Manager/RoomRow.tsx b/ts/Manager/RoomRow.tsx index 2ae4c0e..426213e 100644 --- a/ts/Manager/RoomRow.tsx +++ b/ts/Manager/RoomRow.tsx @@ -187,10 +187,10 @@ const RoomRow: React.FC = (props) => { <> - + - + diff --git a/ts/admin.ts b/ts/admin.ts index 43180ac..ad1de5b 100644 --- a/ts/admin.ts +++ b/ts/admin.ts @@ -34,7 +34,7 @@ $(() => { }); } - async function saveSettings(url: string, secret: string) { + async function saveApiSettings(url: string, secret: string) { url += url.endsWith('/') ? '' : '/'; await checkServer(url, secret); @@ -44,19 +44,19 @@ $(() => { OCP.AppConfig.setValue('bbb', 'api.secret', secret); } - $('#bbb-settings form').submit(function (ev) { + $('#bbb-api').on('submit', function (ev) { ev.preventDefault(); - $('#bbb-result').empty(); + const resultElement = $(this).find('.bbb-result').empty(); - saveSettings(this['api.url'].value, this['api.secret'].value).then(() => { + saveApiSettings(this['api.url'].value, this['api.secret'].value).then(() => { const successElement = generateSuccessElement(t('bbb', 'Settings saved')); setTimeout(() => { - $('#bbb-result').empty(); + resultElement.empty(); }, 3000); - $('#bbb-result').append(successElement); + resultElement.append(successElement); }).catch(err => { let message = t('bbb', 'Unexpected error occurred'); @@ -68,11 +68,90 @@ $(() => { const warningElement = generateWarningElement(message); - $('#bbb-result').append(warningElement); + resultElement.append(warningElement); }); }); - $('#bbb-settings [name="app.navigation"]').change((ev) => { + function generateExampleShortener(shortener: string) { + return shortener.replace(/${OC.currentUser}`).replace(/\{token\}/g, 'your_room_id'); + } + + async function saveAppSettings(shortener: string) { + await checkPasswordConfirmation(); + + if (shortener.indexOf('https://') !== 0) { + throw 'https'; + } + + if (shortener.indexOf('{token}') < 0) { + throw 'token'; + } + + OCP.AppConfig.setValue('bbb', 'app.shortener', shortener); + } + + $('#bbb-shortener').on('submit', function (ev) { + ev.preventDefault(); + + const resultElement = $(this).find('.bbb-result').empty(); + + saveAppSettings(this['app.shortener'].value).then(() => { + const successElement = generateSuccessElement(t('bbb', 'Settings saved')); + + setTimeout(() => { + resultElement.empty(); + }, 3000); + + resultElement.append(successElement); + }).catch(err => { + let message = t('bbb', 'Unexpected error occurred'); + + if (err === 'https') { + message = t('bbb', 'URL has to start with https'); + } else if (err === 'token') { + message = t('bbb', 'URL has to contain the {token} placeholder'); + } + + const warningElement = generateWarningElement(message); + + console.warn('Could not save app settings', err); + + resultElement.append(warningElement); + }); + }); + + $('#bbb-shortener [name="app.shortener"]').on('keyup', (ev) => { + ev.preventDefault(); + + const {value} = ev.target; + + if (!value || value.indexOf('https://') !== 0 || value.indexOf('{token}') < 0) { + $('#bbb-shortener-example').text(t('bbb', 'URL has to start with https:// and contain {token}. Additionally the {user} placeholder can be used.')); + + return; + } + + const target = window.location.origin + OC.generateUrl('apps/bbb/b/$1'); + const url = (new URL(value)); + const rewritePath = '^' + url.pathname.replace(/^\//, '').replace(/%7Buser%7D/g, '.+').replace(/%7Btoken%7D/g, '(.+)'); + + $('#bbb-shortener-example').html(`

${generateExampleShortener(value)}

+
+ ${t('bbb', 'Example configuration for Apache and Nginx')} +
#Apache with mod_rewrite
+ServerName    ${url.hostname}
+RewriteEngine on
+RewriteRule   "${rewritePath}"  "${target}"  [R=307,L]
+
+#Nginx config
+server_name ${url.hostname};
+rewrite ${rewritePath} ${target} last;
+return 307;
+ `); + }); + $('#bbb-shortener [name="app.shortener"]').trigger('keyup'); + + $('#bbb-settings [name="app.navigation"]').on('change', (ev) => { ev.preventDefault(); console.log('checkbox changed to', ev.target.checked);