Browse Source
			
			
			Implement backend APIs to be used by standalone signaling server.
			
				
		Implement backend APIs to be used by standalone signaling server.
	
		
	
			
				A standalone signaling server can be configured in the admin UI and is notified through the BackendController on changes that should be sent to connected clients. See #339 for a description of the backend API. Signed-off-by: Joachim Bauch <bauch@struktur.de>pull/366/head
				
				  
				  Failed to extract signature
				  
				
			
		
		
		
	
				 10 changed files with 595 additions and 3 deletions
			
			
		- 
					1appinfo/info.xml
- 
					8appinfo/routes.php
- 
					43js/admin/signaling-server.js
- 
					82lib/Config.php
- 
					183lib/Controller/BackendController.php
- 
					12lib/Controller/PageController.php
- 
					23lib/Controller/RoomController.php
- 
					154lib/Controller/SignalingController.php
- 
					68lib/Settings/Admin/SignalingServer.php
- 
					24templates/settings/admin/signaling-server.php
| @ -0,0 +1,43 @@ | |||
| /* global OC, OCP, OCA, $, _, Handlebars */ | |||
| 
 | |||
| (function(OC, OCP, OCA, $) { | |||
| 	'use strict'; | |||
| 
 | |||
| 	OCA.VideoCalls = OCA.VideoCalls || {}; | |||
| 	OCA.VideoCalls.Admin = OCA.VideoCalls.Admin || {}; | |||
| 	OCA.VideoCalls.Admin.SignalingServer = { | |||
| 
 | |||
| 		$signaling: undefined, | |||
| 
 | |||
| 		init: function() { | |||
| 			this.$signaling = $('div.signaling-server'); | |||
| 			this.$signaling.find('input').on('change', this.saveServer); | |||
| 		}, | |||
| 
 | |||
| 		saveServer: function() { | |||
| 			// this.$signaling.find('input').removeClass('error');
 | |||
| 			// this.$signaling.find('.icon-checkmark-color').addClass('hidden');
 | |||
| 
 | |||
| 			// OCP.AppConfig.setValue('spreed', $(this).attr('name'), $(this).value, {
 | |||
| 			// 	success: function() {
 | |||
| 			// 		self.temporaryShowSuccess($server);
 | |||
| 			// 	}
 | |||
| 			// });
 | |||
| 		}, | |||
| 
 | |||
| 		temporaryShowSuccess: function($server) { | |||
| 			var $icon = $server.find('.icon-checkmark-color'); | |||
| 			$icon.removeClass('hidden'); | |||
| 			setTimeout(function() { | |||
| 				$icon.addClass('hidden'); | |||
| 			}, 2000); | |||
| 		} | |||
| 
 | |||
| 	}; | |||
| 
 | |||
| 
 | |||
| })(OC, OCP, OCA, $); | |||
| 
 | |||
| $(document).ready(function(){ | |||
| 	OCA.VideoCalls.Admin.SignalingServer.init(); | |||
| }); | |||
| @ -0,0 +1,183 @@ | |||
| <?php | |||
| /** | |||
|  * @copyright Copyright (c) 2017 Joachim Bauch <bauch@struktur.de> | |||
|  * | |||
|  * @author Joachim Bauch <bauch@struktur.de> | |||
|  * | |||
|  * @license GNU AGPL version 3 or any later version | |||
|  * | |||
|  * This program is free software: you can redistribute it and/or modify | |||
|  * it under the terms of the GNU Affero General Public License as | |||
|  * published by the Free Software Foundation, either version 3 of the | |||
|  * License, or (at your option) any later version. | |||
|  * | |||
|  * This program is distributed in the hope that it will be useful, | |||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | |||
|  * GNU Affero General Public License for more details. | |||
|  * | |||
|  * You should have received a copy of the GNU Affero General Public License | |||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | |||
|  * | |||
|  */ | |||
| 
 | |||
| namespace OCA\Spreed\Controller; | |||
| 
 | |||
| use OCA\Spreed\Config; | |||
| use OCA\Spreed\Room; | |||
| use OCP\AppFramework\Controller; | |||
| use OCP\Http\Client\IClientService; | |||
| use OCP\ILogger; | |||
| use OCP\IRequest; | |||
| use OCP\Security\ISecureRandom; | |||
| 
 | |||
| class BackendController extends Controller { | |||
| 	/** @var Config */ | |||
| 	private $config; | |||
| 	/** @var ILogger */ | |||
| 	private $logger; | |||
| 	/** @var IClientService */ | |||
| 	private $clientService; | |||
| 	/** @var ISecureRandom */ | |||
| 	private $secureRandom; | |||
| 
 | |||
| 	/** | |||
| 	 * @param string $appName | |||
| 	 * @param IRequest $request | |||
| 	 * @param IConfig $config | |||
| 	 * @param ILogger $logger | |||
| 	 * @param IClientService $clientService | |||
| 	 */ | |||
| 	public function __construct($appName, | |||
| 								IRequest $request, | |||
| 								Config $config, | |||
| 								ILogger $logger, | |||
| 								IClientService $clientService, | |||
| 								ISecureRandom $secureRandom) { | |||
| 		parent::__construct($appName, $request); | |||
| 		$this->config = $config; | |||
| 		$this->logger = $logger; | |||
| 		$this->clientService = $clientService; | |||
| 		$this->secureRandom = $secureRandom; | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * Perform a request to the signaling backend. | |||
| 	 * | |||
| 	 * @param string $url | |||
| 	 * @param array $data | |||
| 	 */ | |||
| 	private function backendRequest($url, $data) { | |||
| 		$signaling = $this->config->getSignalingServer(); | |||
| 		if (empty($signaling)) { | |||
| 			return; | |||
| 		} | |||
| 
 | |||
| 		if (substr($signaling, -1) === '/') { | |||
| 			$signaling = substr($signaling, 0, strlen($signaling) - 1); | |||
| 		} | |||
| 		$url = $signaling . $url; | |||
| 		if (substr($url, 0, 6) === 'wss://') { | |||
| 			$url = 'https://' . substr($url, 6); | |||
| 		} else if (substr($url, 0, 5) === 'ws://') { | |||
| 			$url = 'http://' . substr($url, 5); | |||
| 		} | |||
| 		$client = $this->clientService->newClient(); | |||
| 		$body = json_encode($data); | |||
| 		$headers = [ | |||
| 			'Content-Type' => 'application/json', | |||
| 		]; | |||
| 
 | |||
| 		$random = $this->secureRandom->generate(64); | |||
| 		$hash = hash_hmac('sha256', $random . $body, $this->config->getSignalingSecret()); | |||
| 		$headers['Spreed-Signaling-Random'] = $random; | |||
| 		$headers['Spreed-Signaling-Checksum'] = $hash; | |||
| 
 | |||
| 		$response = $client->post($url, [ | |||
| 			'headers' => $headers, | |||
| 			'body' => $body, | |||
| 			'verify' => false, | |||
| 		]); | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * The given users are now invited to a room. | |||
| 	 * | |||
| 	 * @param Room $room | |||
| 	 * @param array $userIds | |||
| 	 */ | |||
| 	public function roomInvited($room, $userIds) { | |||
| 		$this->logger->info("Now invited to " . $room->getToken() . ": " + print_r($userIds, true)); | |||
| 		$this->backendRequest('/api/v1/room/' . $room->getToken(), [ | |||
| 			'type' => 'invite', | |||
| 			'invite' => [ | |||
| 				'userids' => $userIds, | |||
| 				'properties' => [ | |||
| 					'name' => $room->getName(), | |||
| 					'type' => $room->getType(), | |||
| 				], | |||
| 			], | |||
| 		]); | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * The given users are no longer invited to a room. | |||
| 	 * | |||
| 	 * @param Room $room | |||
| 	 * @param array $userIds | |||
| 	 */ | |||
| 	public function roomsDisinvited($room, $userIds) { | |||
| 		$this->logger->info("No longer invited to " . $room->getToken() . ": " + print_r($userIds, true)); | |||
| 		$this->backendRequest('/api/v1/room/' . $room->getToken(), [ | |||
| 			'type' => 'disinvite', | |||
| 			'disinvite' => [ | |||
| 				'userids' => $userIds, | |||
| 			], | |||
| 		]); | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * The given room has been modified. | |||
| 	 * | |||
| 	 * @param Room $room | |||
| 	 */ | |||
| 	public function roomModified($room) { | |||
| 		$this->logger->info("Room modified: " . $room->getToken()); | |||
| 		$participants = $room->getParticipants(); | |||
| 		$userIds = []; | |||
| 		foreach ($participants['users'] as $participant => $data) { | |||
| 			array_push($userIds, $participant); | |||
| 		} | |||
| 		$this->backendRequest('/api/v1/room/' . $room->getToken(), [ | |||
| 			'type' => 'update', | |||
| 			'update' => [ | |||
| 				'userids' => $userIds, | |||
| 				'properties' => [ | |||
| 					'name' => $room->getName(), | |||
| 					'type' => $room->getType(), | |||
| 				], | |||
| 			], | |||
| 		]); | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * The given room has been deleted. | |||
| 	 * | |||
| 	 * @param Room $room | |||
| 	 */ | |||
| 	public function roomDeleted($room) { | |||
| 		$this->logger->info("Room deleted: " . $room->getToken()); | |||
| 		$participants = $room->getParticipants(); | |||
| 		$userIds = []; | |||
| 		foreach ($participants['users'] as $participant => $data) { | |||
| 			array_push($userIds, $participant); | |||
| 		} | |||
| 		$this->backendRequest('/api/v1/room/' . $room->getToken(), [ | |||
| 			'type' => 'delete', | |||
| 			'delete' => [ | |||
| 				'userids' => $userIds, | |||
| 			], | |||
| 		]); | |||
| 	} | |||
| 
 | |||
| } | |||
| @ -0,0 +1,68 @@ | |||
| <?php | |||
| /** | |||
|  * @author Joachim Bauch <mail@joachim-bauch.de> | |||
|  * | |||
|  * @license GNU AGPL version 3 or any later version | |||
|  * | |||
|  * This program is free software: you can redistribute it and/or modify | |||
|  * it under the terms of the GNU Affero General Public License as | |||
|  * published by the Free Software Foundation, either version 3 of the | |||
|  * License, or (at your option) any later version. | |||
|  * | |||
|  * This program is distributed in the hope that it will be useful, | |||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | |||
|  * GNU Affero General Public License for more details. | |||
|  * | |||
|  * You should have received a copy of the GNU Affero General Public License | |||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | |||
|  * | |||
|  */ | |||
| 
 | |||
| namespace OCA\Spreed\Settings\Admin; | |||
| 
 | |||
| 
 | |||
| use OCP\AppFramework\Http\TemplateResponse; | |||
| use OCP\IConfig; | |||
| use OCP\Settings\ISettings; | |||
| 
 | |||
| class SignalingServer implements ISettings { | |||
| 
 | |||
| 	/** @var IConfig */ | |||
| 	private $config; | |||
| 
 | |||
| 	public function __construct(IConfig $config) { | |||
| 		$this->config = $config; | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * @return TemplateResponse | |||
| 	 */ | |||
| 	public function getForm() { | |||
| 		$parameters = [ | |||
| 			'signalingServer' => $this->config->getAppValue('spreed', 'signaling_server'), | |||
| 			'signalingSecret' => $this->config->getAppValue('spreed', 'signaling_secret'), | |||
| 		]; | |||
| 
 | |||
| 		return new TemplateResponse('spreed', 'settings/admin/signaling-server', $parameters, ''); | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * @return string the section ID, e.g. 'sharing' | |||
| 	 */ | |||
| 	public function getSection() { | |||
| 		return 'videocalls'; | |||
| 	} | |||
| 
 | |||
| 	/** | |||
| 	 * @return int whether the form should be rather on the top or bottom of | |||
| 	 * the admin section. The forms are arranged in ascending order of the | |||
| 	 * priority values. It is required to return a value between 0 and 100. | |||
| 	 * | |||
| 	 * E.g.: 70 | |||
| 	 */ | |||
| 	public function getPriority() { | |||
| 		return 75; | |||
| 	} | |||
| 
 | |||
| } | |||
| @ -0,0 +1,24 @@ | |||
| <?php | |||
| /** @var array $_ */ | |||
| /** @var \OCP\IL10N $l */ | |||
| script('spreed', ['admin/signaling-server']); | |||
| style('spreed', ['settings-admin']); | |||
| ?>
 | |||
| 
 | |||
| <div class="videocalls section signaling-server"> | |||
| 	<h3><?php p($l->t('Signaling server')) ?></h3>
 | |||
| 	<p class="settings-hint"><?php p($l->t('An external signaling server can optionally be used for larger installations. Leave empty to use the internal signaling server.')) ?></p>
 | |||
| 
 | |||
| 	<p> | |||
| 		<label for="signaling_server"><?php p($l->t('External signaling server')) ?></label>
 | |||
| 		<input type="text" id="signaling_server" | |||
| 			   name="signaling_server" placeholder="wss://signaling.example.org" | |||
| 			   value="<?php p($_['signalingServer']) ?>" /> | |||
| 	</p> | |||
| 	<p> | |||
| 		<label for="signaling_secret"><?php p($l->t('Shared secret')) ?></label>
 | |||
| 		<input type="text" id="signaling_secret" | |||
| 			   name="signaling_secret" placeholder="shared secret" | |||
| 			   value="<?php p($_['signalingSecret']) ?>" /> | |||
| 	</p> | |||
| </div> | |||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue