Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117757741
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
102 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php
index 8273720b..f3e374c0 100644
--- a/src/app/Http/Controllers/API/V4/OpenViduController.php
+++ b/src/app/Http/Controllers/API/V4/OpenViduController.php
@@ -1,388 +1,390 @@
<?php
namespace App\Http\Controllers\API\V4;
use App\Http\Controllers\Controller;
use App\OpenVidu\Room;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class OpenViduController extends Controller
{
/**
* Accepting the room join request.
*
* @param string $id Room identifier (name)
* @param string $reqid Request identifier
*
* @return \Illuminate\Http\JsonResponse
*/
public function acceptJoinRequest($id, $reqid)
{
$room = Room::where('name', $id)->first();
// This isn't a room, bye bye
if (!$room) {
return $this->errorResponse(404, \trans('meet.room-not-found'));
}
$user = Auth::guard()->user();
// Only the room owner can do it
if (!$user || $user->id != $room->user_id) {
return $this->errorResponse(403);
}
if (!$room->requestAccept($reqid)) {
return $this->errorResponse(500, \trans('meet.session-request-accept-error'));
}
return response()->json(['status' => 'success']);
}
/**
* Denying the room join request.
*
* @param string $id Room identifier (name)
* @param string $reqid Request identifier
*
* @return \Illuminate\Http\JsonResponse
*/
public function denyJoinRequest($id, $reqid)
{
$room = Room::where('name', $id)->first();
// This isn't a room, bye bye
if (!$room) {
return $this->errorResponse(404, \trans('meet.room-not-found'));
}
$user = Auth::guard()->user();
// Only the room owner can do it
if (!$user || $user->id != $room->user_id) {
return $this->errorResponse(403);
}
if (!$room->requestDeny($reqid)) {
return $this->errorResponse(500, \trans('meet.session-request-deny-error'));
}
return response()->json(['status' => 'success']);
}
/**
* Close the room session.
*
* @param string $id Room identifier (name)
*
* @return \Illuminate\Http\JsonResponse
*/
public function closeRoom($id)
{
$room = Room::where('name', $id)->first();
// This isn't a room, bye bye
if (!$room) {
return $this->errorResponse(404, \trans('meet.room-not-found'));
}
$user = Auth::guard()->user();
// Only the room owner can do it
if (!$user || $user->id != $room->user_id) {
return $this->errorResponse(403);
}
if (!$room->deleteSession()) {
return $this->errorResponse(500, \trans('meet.session-close-error'));
}
return response()->json([
'status' => 'success',
'message' => __('meet.session-close-success'),
]);
}
/**
* Accepting the room join request.
*
* @param string $id Room identifier (name)
* @param string $conn Connection identifier
*
* @return \Illuminate\Http\JsonResponse
*/
public function dismissConnection($id, $conn)
{
$room = Room::where('name', $id)->first();
// This isn't a room, bye bye
if (!$room) {
return $this->errorResponse(404, \trans('meet.room-not-found'));
}
$user = Auth::guard()->user();
// Only the room owner can do it
if (!$user || $user->id != $room->user_id) {
return $this->errorResponse(403);
}
if (!$room->closeOVConnection($conn)) {
return $this->errorResponse(500, \trans('meet.session-dismiss-connection-error'));
}
return response()->json(['status' => 'success']);
}
/**
* Listing of rooms that belong to the current user.
*
* @return \Illuminate\Http\JsonResponse
*/
public function index()
{
$user = Auth::guard()->user();
$rooms = Room::where('user_id', $user->id)->orderBy('name')->get();
if (count($rooms) == 0) {
// Create a room for the user (with a random and unique name)
while (true) {
$name = strtolower(\App\Utils::randStr(3, 3, '-'));
if (!Room::where('name', $name)->count()) {
break;
}
}
$room = Room::create([
'name' => $name,
'user_id' => $user->id
]);
$rooms = collect([$room]);
}
$result = [
'list' => $rooms,
'count' => count($rooms),
];
return response()->json($result);
}
/**
* Join the room session. Each room has one owner, and the room isn't open until the owner
* joins (and effectively creates the session).
*
* @param string $id Room identifier (name)
*
* @return \Illuminate\Http\JsonResponse
*/
public function joinRoom($id)
{
$room = Room::where('name', $id)->first();
// Room does not exist, or the owner is deleted
if (!$room || !$room->owner) {
return $this->errorResponse(404, \trans('meet.room-not-found'));
}
// Check if there's still a valid beta entitlement for the room owner
$sku = \App\Sku::where('title', 'meet')->first();
if ($sku && !$room->owner->entitlements()->where('sku_id', $sku->id)->first()) {
return $this->errorResponse(404, \trans('meet.room-not-found'));
}
$user = Auth::guard()->user();
$isOwner = $user && $user->id == $room->user_id;
// There's no existing session
if (!$room->hasSession()) {
// Participants can't join the room until the session is created by the owner
if (!$isOwner) {
- return $this->errorResponse(423, \trans('meet.session-not-found'));
+ return $this->errorResponse(422, \trans('meet.session-not-found'), ['code' => 323]);
}
// The room owner can create the session on request
if (empty(request()->input('init'))) {
- return $this->errorResponse(424, \trans('meet.session-not-found'));
+ return $this->errorResponse(422, \trans('meet.session-not-found'), ['code' => 324]);
}
$session = $room->createSession();
if (empty($session)) {
return $this->errorResponse(500, \trans('meet.session-create-error'));
}
}
$password = (string) $room->getSetting('password');
$config = [
'locked' => $room->getSetting('locked') === 'true',
'password' => $isOwner ? $password : '',
'requires_password' => !$isOwner && strlen($password),
];
+ $response = ['config' => $config];
+
// Validate room password
if (!$isOwner && strlen($password)) {
$request_password = request()->input('password');
if ($request_password !== $password) {
- return $this->errorResponse(425, \trans('meet.session-password-error'), ['config' => $config]);
+ return $this->errorResponse(422, \trans('meet.session-password-error'), $response + ['code' => 325]);
}
}
// Handle locked room
if (!$isOwner && $config['locked']) {
$nickname = request()->input('nickname');
$picture = request()->input('picture');
$requestId = request()->input('requestId');
$request = $requestId ? $room->requestGet($requestId) : null;
$error = \trans('meet.session-room-locked-error');
// Request already has been processed (not accepted yet, but it could be denied)
if (empty($request['status']) || $request['status'] != Room::REQUEST_ACCEPTED) {
if (!$request) {
if (empty($nickname) || empty($requestId) || !preg_match('/^[a-z0-9]{8,32}$/i', $requestId)) {
- return $this->errorResponse(426, $error, ['config' => $config]);
+ return $this->errorResponse(422, $error, $response + ['code' => 326]);
}
if (empty($picture)) {
$svg = file_get_contents(resource_path('images/user.svg'));
$picture = 'data:image/svg+xml;base64,' . base64_encode($svg);
} elseif (!preg_match('|^data:image/png;base64,[a-zA-Z0-9=+/]+$|', $picture)) {
- return $this->errorResponse(426, $error, ['config' => $config]);
+ return $this->errorResponse(422, $error, $response + ['code' => 326]);
}
// TODO: Resize when big/make safe the user picture?
$request = ['nickname' => $nickname, 'requestId' => $requestId, 'picture' => $picture];
if (!$room->requestSave($requestId, $request)) {
// FIXME: should we use error code 500?
- return $this->errorResponse(426, $error, ['config' => $config]);
+ return $this->errorResponse(422, $error, $response + ['code' => 326]);
}
// Send the request (signal) to the owner
$result = $room->signal('joinRequest', $request, Room::ROLE_MODERATOR);
}
- return $this->errorResponse(427, $error, ['config' => $config]);
+ return $this->errorResponse(422, $error, $response + ['code' => 327]);
}
}
// Create session token for the current user/connection
$response = $room->getSessionToken($isOwner ? Room::ROLE_MODERATOR : Room::ROLE_PUBLISHER);
if (empty($response)) {
return $this->errorResponse(500, \trans('meet.session-join-error'));
}
// Create session token for screen sharing connection
if (!empty(request()->input('screenShare'))) {
$add_token = $room->getSessionToken(Room::ROLE_PUBLISHER);
$response['shareToken'] = $add_token['token'];
}
// Tell the UI who's the room owner
$response['owner'] = $isOwner;
// Append the room configuration
$response['config'] = $config;
return response()->json($response);
}
/**
* Set the domain configuration.
*
* @param string $id Room identifier (name)
*
* @return \Illuminate\Http\JsonResponse|void
*/
public function setRoomConfig($id)
{
$room = Room::where('name', $id)->first();
// Room does not exist, or the owner is deleted
if (!$room || !$room->owner) {
return $this->errorResponse(404);
}
$user = Auth::guard()->user();
// Only room owner can configure the room
if ($user->id != $room->user_id) {
return $this->errorResponse(403);
}
$input = request()->input();
$errors = [];
foreach ($input as $key => $value) {
switch ($key) {
case 'password':
if ($value === null || $value === '') {
$input[$key] = null;
} else {
// TODO: Do we have to validate the password in any way?
}
break;
case 'locked':
$input[$key] = $value ? 'true' : null;
break;
default:
$errors[$key] = \trans('meet.room-unsupported-option-error');
}
}
if (!empty($errors)) {
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
if (!empty($input)) {
$room->setSettings($input);
}
return response()->json([
'status' => 'success',
'message' => \trans('meet.room-setconfig-success'),
]);
}
/**
* Webhook as triggered from OpenVidu server
*
* @param \Illuminate\Http\Request $request The API request.
*
* @return \Illuminate\Http\Response The response
*/
public function webhook(Request $request)
{
\Log::debug($request->getContent());
switch ((string) $request->input('event')) {
case 'sessionDestroyed':
// When all participants left the room OpenVidu dispatches sessionDestroyed
// event. We'll remove the session reference from the database.
$sessionId = $request->input('sessionId');
$room = Room::where('session_id', $sessionId)->first();
if ($room) {
$room->session_id = null;
$room->save();
}
break;
}
return response('Success', 200);
}
}
diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue
index a7daa277..77ba8621 100644
--- a/src/resources/vue/Meet/Room.vue
+++ b/src/resources/vue/Meet/Room.vue
@@ -1,515 +1,521 @@
<template>
<div id="meet-component">
<div id="meet-session-toolbar" class="hidden">
<div id="meet-session-menu">
<button class="btn btn-link link-audio" @click="switchSound" title="Mute audio">
<svg-icon icon="microphone"></svg-icon>
</button>
<button class="btn btn-link link-video" @click="switchVideo" title="Mute video">
<svg-icon icon="video"></svg-icon>
</button>
<button class="btn btn-link link-screen text-danger" @click="switchScreen" :disabled="!canShareScreen" title="Share screen">
<svg-icon icon="desktop"></svg-icon>
</button>
<button class="btn btn-link link-chat text-danger" @click="switchChat" title="Chat">
<svg-icon icon="align-left"></svg-icon>
</button>
<button class="btn btn-link link-fullscreen closed hidden" @click="switchFullscreen" title="Full screen">
<svg-icon icon="expand"></svg-icon>
</button>
<button class="btn btn-link link-fullscreen open hidden" @click="switchFullscreen" title="Full screen">
<svg-icon icon="compress"></svg-icon>
</button>
<button class="btn btn-link link-security" v-if="session && session.owner" @click="securityOptions" title="Security options">
<svg-icon icon="shield-alt"></svg-icon>
</button>
<button class="btn btn-link link-logout" @click="logout" title="Leave session">
<svg-icon icon="power-off"></svg-icon>
</button>
</div>
</div>
<div id="meet-setup" class="card container mt-2 mt-md-5 mb-5">
<div class="card-body">
<div class="card-title">Set up your session</div>
<div class="card-text">
<form class="setup-form row">
<div id="setup-preview" class="col-sm-6 mb-3 mb-sm-0">
<video class="rounded"></video>
<div class="volume"><div class="bar"></div></div>
</div>
<div class="col-sm-6 align-self-center">
<div class="input-group">
<label for="setup-microphone" class="input-group-prepend mb-0">
<span class="input-group-text" title="Microphone"><svg-icon icon="microphone"></svg-icon></span>
</label>
<select class="custom-select" id="setup-microphone" v-model="microphone" @change="setupMicrophoneChange">
<option value="">None</option>
<option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option>
</select>
</div>
<div class="input-group mt-2">
<label for="setup-camera" class="input-group-prepend mb-0">
<span class="input-group-text" title="Camera"><svg-icon icon="video"></svg-icon></span>
</label>
<select class="custom-select" id="setup-camera" v-model="camera" @change="setupCameraChange">
<option value="">None</option>
<option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option>
</select>
</div>
<div class="input-group mt-2">
<label for="setup-nickname" class="input-group-prepend mb-0">
<span class="input-group-text" title="Nickname"><svg-icon icon="user"></svg-icon></span>
</label>
<input class="form-control" type="text" id="setup-nickname" v-model="nickname" placeholder="Your name">
</div>
<div class="input-group mt-2" v-if="session.config && session.config.requires_password">
<label for="setup-password" class="input-group-prepend mb-0">
<span class="input-group-text" title="Password"><svg-icon icon="key"></svg-icon></span>
</label>
<input type="password" class="form-control" id="setup-password" v-model="password" placeholder="Password">
</div>
<div class="mt-3">
<button type="button"
@click="joinSession"
- :disabled="roomState == 'init' || roomState == 427 || roomState == 404"
+ :disabled="roomState == 'init' || roomState == 327 || roomState == 404"
:class="'btn w-100 btn-' + (isRoomReady() ? 'success' : 'primary')"
>
<span v-if="isRoomReady()">JOIN NOW</span>
- <span v-else-if="roomState == 423">I'm the owner</span>
+ <span v-else-if="roomState == 323">I'm the owner</span>
<span v-else>JOIN</span>
</button>
</div>
</div>
<div class="mt-4 col-sm-12">
<status-message :status="roomState" :status-labels="roomStateLabels"></status-message>
</div>
</form>
</div>
</div>
</div>
<div id="meet-session-layout" class="d-flex hidden">
<div id="meet-session"></div>
<div id="meet-chat">
<div class="chat"></div>
<div class="chat-input m-2">
<textarea class="form-control" rows="1"></textarea>
</div>
</div>
</div>
<logon-form id="meet-auth" class="hidden" :dashboard="false" @success="authSuccess"></logon-form>
<div id="leave-dialog" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Room closed</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>The session has been closed by the room owner.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger modal-action" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<session-security-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate"></session-security-options>
</div>
</template>
<script>
import Meet from '../../js/meet/app.js'
import StatusMessage from '../Widgets/StatusMessage'
import LogonForm from '../Login'
import SessionSecurityOptions from './SessionSecurityOptions'
export default {
components: {
LogonForm,
SessionSecurityOptions,
StatusMessage
},
data() {
return {
setup: {
cameras: [],
microphones: [],
},
canShareScreen: false,
camera: '',
meet: null,
microphone: '',
nickname: '',
password: '',
room: null,
roomState: 'init',
roomStateLabels: {
init: 'Checking the room...',
404: 'The room does not exist.',
- 423: 'The room is closed. Please, wait for the owner to start the session.',
- 424: 'The room is closed. It will be open for others after you join.',
- 425: 'The room is ready. Please, provide a valid password.',
- 426: 'The room is locked. Please, enter your name and try again.',
- 427: 'Waiting for permission to join the room.',
+ 323: 'The room is closed. Please, wait for the owner to start the session.',
+ 324: 'The room is closed. It will be open for others after you join.',
+ 325: 'The room is ready. Please, provide a valid password.',
+ 326: 'The room is locked. Please, enter your name and try again.',
+ 327: 'Waiting for permission to join the room.',
500: 'Failed to create a session. Server error.'
},
session: {}
}
},
mounted() {
this.room = this.$route.params.room
// Initialize OpenVidu and do some basic checks
this.meet = new Meet($('#meet-session')[0]);
this.canShareScreen = this.meet.isScreenSharingSupported()
// Check the room and init the session
this.initSession()
// Setup the room UI
this.setupSession()
},
beforeDestroy() {
clearTimeout(window.roomRequest)
if (this.meet) {
this.meet.leaveRoom()
}
},
methods: {
authSuccess() {
// The user authentication succeeded, we still don't know it's really the room owner
this.initSession()
$('#meet-setup').removeClass('hidden')
$('#meet-auth').addClass('hidden')
},
configUpdate(config) {
this.session.config = Object.assign({}, this.session.config, config)
},
dismissParticipant(id) {
axios.post('/api/v4/openvidu/rooms/' + this.room + '/connections/' + id + '/dismiss')
},
initSession(init) {
this.post = {
password: this.password,
nickname: this.nickname,
screenShare: this.canShareScreen ? 1 : 0,
init: init ? 1 : 0,
picture: init ? this.makePicture() : '',
requestId: this.requestId()
}
$('#setup-password,#setup-nickname').removeClass('is-invalid')
axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true })
.then(response => {
// Response data contains: session, token and shareToken
this.roomState = 'ready'
this.session = response.data
if (init) {
this.joinSession()
}
})
.catch(error => {
- this.roomState = String(error.response.status)
+ const data = error.response.data || {}
- if (error.response.data && error.response.data.config) {
- this.session.config = error.response.data.config
+ if (data.code) {
+ this.roomState = data.code
+ } else {
+ this.roomState = error.response.status
+ }
+
+ if (data.config) {
+ this.session.config = data.config
}
switch (this.roomState) {
- case '423':
+ case 323:
// Waiting for the owner to open the room...
// Update room state every 10 seconds
window.roomRequest = setTimeout(() => { this.initSession() }, 10000)
break;
- case '425':
+ case 325:
// Missing/invalid password
if (init) {
$('#setup-password').addClass('is-invalid').focus()
}
break;
- case '426':
+ case 326:
// Locked room prerequisites error
if (init && !$('#setup-nickname').val()) {
$('#setup-nickname').addClass('is-invalid').focus()
}
break;
- case '427':
+ case 327:
// Waiting for the owner's approval to join
// Update room state every 10 seconds
window.roomRequest = setTimeout(() => { this.initSession(true) }, 10000)
break;
}
})
if (document.fullscreenEnabled) {
$('#meet-session-menu').find('.link-fullscreen.closed').removeClass('hidden')
}
},
isRoomReady() {
- return ['ready', '424', '425', '426', '427'].includes(this.roomState)
+ return ['ready', 324, 325, 326, 327].includes(this.roomState)
},
// An event received by the room owner when a participant is asking for a permission to join the room
joinRequest(data) {
// The toast for this user request already exists, ignore
// It's not really needed as we do this on server-side already
if ($('#i' + data.requestId).length) {
return
}
// FIXME: Should the message close button act as the Deny button? Do we need the Deny button?
let body = $(
`<div>`
+ `<div class="picture"><img src="${data.picture}"></div>`
+ `<div class="content">`
+ `<p class="mb-2"></p>`
+ `<div class="text-right">`
+ `<button type="button" class="btn btn-sm btn-success accept">Accept</button>`
+ `<button type="button" class="btn btn-sm btn-danger deny ml-2">Deny</button>`
)
this.$toast.message({
className: 'join-request',
icon: 'user',
timeout: 0,
title: 'Join request',
// titleClassName: '',
body: body.html(),
onShow: element => {
const id = data.requestId
$(element).find('p').text((data.nickname || '') + ' requested to join.')
// add id attribute, so we can identify it
$(element).attr('id', 'i' + id)
// add action to the buttons
.find('button.accept,button.deny').on('click', e => {
const action = $(e.target).is('.accept') ? 'accept' : 'deny'
axios.post('/api/v4/openvidu/rooms/' + this.room + '/request/' + id + '/' + action)
.then(response => {
$('#i' + id).remove()
})
})
}
})
},
// Entering the room
joinSession() {
- if (this.roomState == 423) {
+ if (this.roomState == 323) {
$('#meet-setup').addClass('hidden')
$('#meet-auth').removeClass('hidden')
return
}
if (this.roomState != 'ready') {
this.initSession(true)
return
}
clearTimeout(window.roomRequest)
$('#app').addClass('meet')
$('#meet-setup').addClass('hidden')
$('#meet-session-toolbar,#meet-session-layout').removeClass('hidden')
if (!this.canShareScreen) {
this.setMenuItem('screen', false, true)
}
this.session.nickname = this.nickname
this.session.menuElement = $('#meet-session-menu')[0]
this.session.chatElement = $('#meet-chat')[0]
this.session.onDestroy = event => {
// TODO: Display different message for each reason: forceDisconnectByUser,
// forceDisconnectByServer, sessionClosedByServer?
if (event.reason != 'disconnect' && event.reason != 'networkDisconnect' && !this.session.owner) {
$('#leave-dialog').on('hide.bs.modal', () => {
// FIXME: Where exactly the user should land? Currently he'll land
// on dashboard (if he's logged in) or login form (if he's not).
window.location = window.config['app.url']
}).modal()
}
}
this.session.onDismiss = connId => { this.dismissParticipant(connId) }
if (this.session.owner) {
this.session.onJoinRequest = data => { this.joinRequest(data) }
}
this.meet.joinRoom(this.session)
},
logout() {
const logout = () => {
this.meet.leaveRoom()
this.meet = null
window.location = window.config['app.url']
}
if (this.session.owner) {
axios.post('/api/v4/openvidu/rooms/' + this.room + '/close').then(logout)
} else {
logout()
}
},
makePicture() {
const video = $("#setup-preview video")[0];
// Skip if video is not "playing"
if (!video.videoWidth || !this.camera) {
return ''
}
// we're going to crop a square from the video and resize it
const maxSize = 64
// Calculate sizing
let sh = Math.floor(video.videoHeight / 1.5)
let sw = sh
let sx = (video.videoWidth - sw) / 2
let sy = (video.videoHeight - sh) / 2
let dh = Math.min(sh, maxSize)
let dw = sh < maxSize ? sw : Math.floor(sw * dh/sh)
const canvas = $("<canvas>")[0];
canvas.width = dw;
canvas.height = dh;
// draw the image on the canvas (square cropped and resized)
canvas.getContext('2d').drawImage(video, sx, sy, sw, sh, 0, 0, dw, dh);
// convert it to a usable data URL (png format)
return canvas.toDataURL();
},
requestId() {
if (!this.reqId) {
// FIXME: Shall we use some UUID generator? Or better something that identifies the
// user/browser so we could deny the join request for a longer time.
// I'm thinking about e.g. a bad actor knocking again and again and again,
// we don't want the room owner to be bothered every few seconds.
// Maybe a solution would be to store the identifier in the browser storage
// This would not prevent hackers from sending the new identifier on every request,
// but could make sure that it is kept after page refresh for the avg user.
// This will create max. 24-char numeric string
this.reqId = (String(Date.now()) + String(Math.random()).substring(2)).substring(0, 24)
}
return this.reqId
},
securityOptions() {
$('#security-options-dialog').modal()
},
setMenuItem(type, state, disabled) {
let button = $('#meet-session-menu').find('.link-' + type)
button[state ? 'removeClass' : 'addClass']('text-danger')
if (disabled !== undefined) {
button.prop('disabled', disabled)
}
},
setupSession() {
this.meet.setup({
videoElement: $('#setup-preview video')[0],
volumeElement: $('#setup-preview .volume')[0],
onSuccess: setup => {
this.setup = setup
this.microphone = setup.audioSource
this.camera = setup.videoSource
this.setMenuItem('audio', setup.audioActive)
this.setMenuItem('video', setup.videoActive)
},
onError: error => {
this.setMenuItem('audio', false, true)
this.setMenuItem('video', false, true)
}
})
},
setupCameraChange() {
this.meet.setupSetVideoDevice(this.camera).then(enabled => {
this.setMenuItem('video', enabled)
})
},
setupMicrophoneChange() {
this.meet.setupSetAudioDevice(this.microphone).then(enabled => {
this.setMenuItem('audio', enabled)
})
},
switchChat() {
let chat = $('#meet-chat')
let enabled = chat.is('.open')
this.setMenuItem('chat', !enabled)
chat.toggleClass('open')
if (!enabled) {
chat.find('textarea').focus()
}
// Trigger resize, so participant matrix can update its layout
window.dispatchEvent(new Event('resize'));
},
switchFullscreen() {
const element = this.$el
$(element).off('fullscreenchange').on('fullscreenchange', (e) => {
let enabled = document.fullscreenElement == element
let buttons = $('#meet-session-menu').find('.link-fullscreen')
buttons.first()[enabled ? 'addClass' : 'removeClass']('hidden')
buttons.last()[!enabled ? 'addClass' : 'removeClass']('hidden')
})
if (document.fullscreenElement) {
document.exitFullscreen()
} else {
element.requestFullscreen()
}
},
switchSound() {
const enabled = this.meet.switchAudio()
this.setMenuItem('audio', enabled)
},
switchVideo() {
const enabled = this.meet.switchVideo()
this.setMenuItem('video', enabled)
},
switchScreen() {
this.meet.switchScreen(enabled => {
this.setMenuItem('screen', enabled)
// After one screen sharing session ended request a new token
// for the next screen sharing session
if (!enabled) {
// TODO: This might need to be a different route. E.g. the room password might have
// changed since user joined the session
axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true })
.then(response => {
// Response data contains: session, token and shareToken
this.session.shareToken = response.data.token
this.meet.updateSession(this.session)
})
}
})
}
}
}
</script>
diff --git a/src/resources/vue/Widgets/StatusMessage.vue b/src/resources/vue/Widgets/StatusMessage.vue
index 71935346..80fd3120 100644
--- a/src/resources/vue/Widgets/StatusMessage.vue
+++ b/src/resources/vue/Widgets/StatusMessage.vue
@@ -1,49 +1,49 @@
<template>
<div v-if="status != 'ready'" :class="statusClass()">
<div v-if="status == 'init'" class="app-loader small">
<div class="spinner-border" role="status"></div>
</div>
<span v-if="status == 'init'">{{ statusLabel() }}</span>
- <svg-icon v-if="Number(status) >= 400 && status in statusLabels" icon="exclamation-circle"></svg-icon>
- <span v-if="Number(status) >= 400 && status in statusLabels">{{ statusLabel() }}</span>
+ <svg-icon v-if="status != 'init' && statusLabel()" :icon="Number(status) >= 400 ? 'exclamation-circle' : 'info-circle'"></svg-icon>
+ <span v-if="status != 'init' && statusLabel()">{{ statusLabel() }}</span>
</div>
</template>
<script>
const defaultLabels = {
init: 'Loading...',
404: 'Resource not found.'
}
export default {
props: {
status: { type: String, default: () => 'init' },
statusLabels: { type: Object, default: () => defaultLabels }
},
methods: {
statusClass() {
let className = 'status-message'
if (this.status === 'init') {
className += ' loading'
} else if (Number(this.status) >= 400) {
className += ' text-danger'
}
return className
},
statusLabel() {
if (this.status in this.statusLabels) {
return this.statusLabels[this.status]
}
if (this.status in defaultLabels) {
return defaultLabels[this.status]
}
return ''
}
}
}
</script>
diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php
index 67f1f0c0..ab47aed6 100644
--- a/src/tests/Browser/Meet/RoomControlsTest.php
+++ b/src/tests/Browser/Meet/RoomControlsTest.php
@@ -1,342 +1,338 @@
<?php
namespace Tests\Browser\Meet;
use App\OpenVidu\Room;
use Tests\Browser;
use Tests\Browser\Pages\Meet\Room as RoomPage;
use Tests\TestCaseDusk;
class RoomControlsTest extends TestCaseDusk
{
/**
* {@inheritDoc}
*/
public function setUp(): void
{
parent::setUp();
$this->clearBetaEntitlements();
}
public function tearDown(): void
{
$this->clearBetaEntitlements();
parent::tearDown();
}
/**
* Test fullscreen buttons
*
* @group openvidu
*/
public function testFullscreen(): void
{
// TODO: This test does not work in headless mode
$this->markTestIncomplete();
// Make sure there's no session yet
$room = Room::where('name', 'john')->first();
if ($room->session_id) {
$room->session_id = null;
$room->save();
}
$this->assignBetaEntitlement('john@kolab.org', 'meet');
$this->browse(function (Browser $browser) {
// Join the room as an owner (authenticate)
$browser->visit(new RoomPage('john'))
->click('@setup-button')
->assertMissing('@toolbar')
->assertMissing('@menu')
->assertMissing('@session')
->assertMissing('@chat')
->assertMissing('@setup-form')
->assertVisible('@login-form')
->submitLogon('john@kolab.org', 'simple123')
->waitFor('@setup-form')
->assertMissing('@login-form')
->waitUntilMissing('@setup-status-message.loading')
->click('@setup-button')
->waitFor('@session')
// Test fullscreen for the whole room
->click('@menu button.link-fullscreen.closed')
->assertVisible('@toolbar')
->assertVisible('@session')
->assertMissing('nav')
->assertMissing('@menu button.link-fullscreen.closed')
->click('@menu button.link-fullscreen.open')
->assertVisible('nav')
// Test fullscreen for the participant video
->click('@session button.link-fullscreen.closed')
->assertVisible('@session')
->assertMissing('@toolbar')
->assertMissing('nav')
->assertMissing('@session button.link-fullscreen.closed')
->click('@session button.link-fullscreen.open')
->assertVisible('nav')
->assertVisible('@toolbar');
});
}
/**
* Test nickname and muting audio/video
*
* @group openvidu
*/
public function testNicknameAndMuting(): void
{
// Make sure there's no session yet
$room = Room::where('name', 'john')->first();
if ($room->session_id) {
$room->session_id = null;
$room->save();
}
$this->assignBetaEntitlement('john@kolab.org', 'meet');
$this->browse(function (Browser $owner, Browser $guest) {
// Join the room as an owner (authenticate)
$owner->visit(new RoomPage('john'))
->click('@setup-button')
->submitLogon('john@kolab.org', 'simple123')
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->type('@setup-nickname-input', 'john')
->click('@setup-button')
->waitFor('@session');
// In another browser act as a guest
$guest->visit(new RoomPage('john'))
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->assertMissing('@setup-status-message')
->assertSeeIn('@setup-button', "JOIN")
// Join the room, disable cam/mic
->select('@setup-mic-select', '')
->select('@setup-cam-select', '')
->click('@setup-button')
->waitFor('@session');
// Assert current UI state
$owner->assertToolbar([
'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
'security' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
])
->whenAvailable('div.meet-video.publisher', function (Browser $browser) {
$browser->assertVisible('video')
->assertAudioMuted('video', true)
->assertSeeIn('.nickname', 'john')
- ->assertMissing('.nickname button')
->assertVisible('.controls button.link-fullscreen')
->assertMissing('.controls button.link-audio')
->assertMissing('.status .status-audio')
->assertMissing('.status .status-video');
})
->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) {
$browser->assertMissing('video')
- ->assertMissing('.nickname')
+ ->assertVisible('.nickname')
->assertVisible('.controls button.link-fullscreen')
->assertVisible('.controls button.link-audio')
->assertVisible('.status .status-audio')
->assertVisible('.status .status-video');
})
->assertElementsCount('@session div.meet-video', 2);
// Assert current UI state
$guest->assertToolbar([
'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED,
'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED,
'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
])
->whenAvailable('div.meet-video.publisher', function (Browser $browser) {
$browser->assertVisible('video')
//->assertAudioMuted('video', true)
- ->assertVisible('.nickname button')
- ->assertMissing('.nickname span')
->assertVisible('.controls button.link-fullscreen')
->assertMissing('.controls button.link-audio')
->assertVisible('.status .status-audio')
->assertVisible('.status .status-video');
})
->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) {
$browser->assertVisible('video')
->assertSeeIn('.nickname', 'john')
- ->assertMissing('.nickname button')
->assertVisible('.controls button.link-fullscreen')
->assertVisible('.controls button.link-audio')
->assertMissing('.status .status-audio')
->assertMissing('.status .status-video');
})
->assertElementsCount('@session div.meet-video', 2);
// Test nickname change propagation
// Use script() because type() does not work with this contenteditable widget
$guest->setNickname('div.meet-video.publisher', 'guest');
$owner->waitFor('div.meet-video:not(.publisher) .nickname')
->assertSeeIn('div.meet-video:not(.publisher) .nickname', 'guest');
// Test muting audio
$owner->click('@menu button.link-audio')
->assertToolbarButtonState('audio', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED)
->assertVisible('div.meet-video.publisher .status .status-audio');
// FIXME: It looks that we can't just check the <video> element state
// We might consider using OpenVidu API to make sure
$guest->waitFor('div.meet-video:not(.publisher) .status .status-audio');
// Test unmuting audio
$owner->click('@menu button.link-audio')
->assertToolbarButtonState('audio', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED)
->assertMissing('div.meet-video.publisher .status .status-audio');
$guest->waitUntilMissing('div.meet-video:not(.publisher) .status .status-audio');
// Test muting video
$owner->click('@menu button.link-video')
->assertToolbarButtonState('video', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED)
->assertVisible('div.meet-video.publisher .status .status-video');
// FIXME: It looks that we can't just check the <video> element state
// We might consider using OpenVidu API to make sure
$guest->waitFor('div.meet-video:not(.publisher) .status .status-video');
// Test unmuting video
$owner->click('@menu button.link-video')
->assertToolbarButtonState('video', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED)
->assertMissing('div.meet-video.publisher .status .status-video');
$guest->waitUntilMissing('div.meet-video:not(.publisher) .status .status-video');
// Test muting other user
$guest->with('div.meet-video:not(.publisher)', function (Browser $browser) {
$browser->click('.controls button.link-audio')
->assertAudioMuted('video', true)
->assertVisible('.controls button.link-audio.text-danger')
->click('.controls button.link-audio')
->assertAudioMuted('video', false)
->assertVisible('.controls button.link-audio:not(.text-danger)');
});
});
}
/**
* Test text chat
*
* @group openvidu
* @depends testNicknameAndMuting
*/
public function testChat(): void
{
$this->assignBetaEntitlement('john@kolab.org', 'meet');
$this->browse(function (Browser $owner, Browser $guest) {
// Join the room as an owner
$owner->visit(new RoomPage('john'))
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->type('@setup-nickname-input', 'john')
->click('@setup-button')
->waitFor('@session');
// In another browser act as a guest
$guest->visit(new RoomPage('john'))
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->assertMissing('@setup-status-message')
->assertSeeIn('@setup-button', "JOIN")
// Join the room, disable cam/mic
->select('@setup-mic-select', '')
->select('@setup-cam-select', '')
->click('@setup-button')
->waitFor('@session');
// Test chat elements
$owner->click('@menu button.link-chat')
->assertToolbarButtonState('chat', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED)
->assertVisible('@chat')
->assertVisible('@session')
->assertFocused('@chat-input')
->assertElementsCount('@chat-list .message', 0)
->keys('@chat-input', 'test1', '{enter}')
->assertValue('@chat-input', '')
->assertElementsCount('@chat-list .message', 1)
->assertSeeIn('@chat-list .message .nickname', 'john')
->assertSeeIn('@chat-list .message div:last-child', 'test1');
$guest->waitFor('@menu button.link-chat .badge')
->assertSeeIn('@menu button.link-chat .badge', '1')
->click('@menu button.link-chat')
->assertToolbarButtonState('chat', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED)
->assertMissing('@menu button.link-chat .badge')
->assertVisible('@chat')
->assertVisible('@session')
->assertElementsCount('@chat-list .message', 1)
->assertSeeIn('@chat-list .message .nickname', 'john')
->assertSeeIn('@chat-list .message div:last-child', 'test1');
// Test the number of (hidden) incoming messages
$guest->click('@menu button.link-chat')
->assertMissing('@chat');
$owner->keys('@chat-input', 'test2', '{enter}', 'test3', '{enter}')
->assertElementsCount('@chat-list .message', 1)
->assertSeeIn('@chat-list .message .nickname', 'john')
->assertElementsCount('@chat-list .message div', 4)
->assertSeeIn('@chat-list .message div:last-child', 'test3');
$guest->waitFor('@menu button.link-chat .badge')
->assertSeeIn('@menu button.link-chat .badge', '2')
->click('@menu button.link-chat')
->assertElementsCount('@chat-list .message', 1)
->assertSeeIn('@chat-list .message .nickname', 'john')
->assertSeeIn('@chat-list .message div:last-child', 'test3')
->keys('@chat-input', 'guest1', '{enter}')
->assertElementsCount('@chat-list .message', 2)
->assertMissing('@chat-list .message:last-child .nickname')
->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest1');
$owner->assertElementsCount('@chat-list .message', 2)
->assertMissing('@chat-list .message:last-child .nickname')
->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest1');
// Test nickname change is propagated to chat messages
$guest->setNickname('div.meet-video.publisher', 'guest')
->keys('@chat-input', 'guest2', '{enter}')
->assertElementsCount('@chat-list .message', 2)
->assertSeeIn('@chat-list .message:last-child .nickname', 'guest')
->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest2');
$owner->assertElementsCount('@chat-list .message', 2)
->assertSeeIn('@chat-list .message:last-child .nickname', 'guest')
->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest2');
// TODO: Test text chat features, e.g. link handling
});
}
/**
* Test screen sharing
*
* @group openvidu
*/
public function testShareScreen(): void
{
// It looks that screen sharing API is not available in headless chrome
// Note that other tests already assert that the button is disabled
$this->markTestIncomplete();
}
}
diff --git a/src/tests/Browser/Meet/RoomSecurityTest.php b/src/tests/Browser/Meet/RoomSecurityTest.php
index bb7c3392..675596ed 100644
--- a/src/tests/Browser/Meet/RoomSecurityTest.php
+++ b/src/tests/Browser/Meet/RoomSecurityTest.php
@@ -1,248 +1,248 @@
<?php
namespace Tests\Browser\Meet;
use App\OpenVidu\Room;
use Tests\Browser;
use Tests\Browser\Components\Dialog;
use Tests\Browser\Components\Toast;
use Tests\Browser\Pages\Meet\Room as RoomPage;
use Tests\TestCaseDusk;
class RoomSecurityTest extends TestCaseDusk
{
/**
* {@inheritDoc}
*/
public function setUp(): void
{
parent::setUp();
$this->clearBetaEntitlements();
$this->assignBetaEntitlement('john@kolab.org', 'meet');
$room = Room::where('name', 'john')->first();
$room->setSettings(['password' => null, 'locked' => null]);
}
public function tearDown(): void
{
$this->clearBetaEntitlements();
$room = Room::where('name', 'john')->first();
$room->setSettings(['password' => null, 'locked' => null]);
parent::tearDown();
}
/**
* Test password protected room
*
* @group openvidu
*/
public function testRoomPassword(): void
{
$this->browse(function (Browser $owner, Browser $guest) {
// Make sure there's no session yet
$room = Room::where('name', 'john')->first();
if ($room->session_id) {
$room->session_id = null;
$room->save();
}
// Join the room as an owner (authenticate)
$owner->visit(new RoomPage('john'))
->click('@setup-button')
->submitLogon('john@kolab.org', 'simple123')
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->assertMissing('@setup-password-input')
->click('@setup-button')
->waitFor('@session')
// Enter Security option dialog
->click('@menu button.link-security')
->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) {
$browser->assertSeeIn('@title', 'Security options')
->assertSeeIn('@button-action', 'Close')
->assertElementsCount('.modal-footer button', 1)
->assertSeeIn('#password-input .label', 'Password:')
->assertSeeIn('#password-input-text.text-muted', 'none')
->assertVisible('#password-input + small')
->assertSeeIn('#password-set-btn', 'Set password')
->assertElementsCount('#password-input button', 1)
->assertMissing('#password-input input')
// Test setting a password
->click('#password-set-btn')
->assertMissing('#password-input-text')
->assertVisible('#password-input input')
->assertValue('#password-input input', '')
->assertSeeIn('#password-input #password-save-btn', 'Save')
->assertElementsCount('#password-input button', 1)
->type('#password-input input', 'pass')
->click('#password-input #password-save-btn')
->assertToast(Toast::TYPE_SUCCESS, 'Room configuration updated successfully.')
->assertMissing('#password-input input')
->assertSeeIn('#password-input-text:not(.text-muted)', 'pass')
->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password')
->assertElementsCount('#password-input button', 1)
->click('@button-action');
$this->assertSame('pass', $room->fresh()->getSetting('password'));
});
// In another browser act as a guest, expect password required
$guest->visit(new RoomPage('john'))
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
- ->assertSeeIn('@setup-status-message.text-danger', "Please, provide a valid password.")
+ ->assertSeeIn('@setup-status-message', "Please, provide a valid password.")
->assertVisible('@setup-form .input-group:nth-child(4) svg')
->assertAttribute('@setup-form .input-group:nth-child(4) .input-group-text', 'title', 'Password')
->assertAttribute('@setup-password-input', 'placeholder', 'Password')
->assertValue('@setup-password-input', '')
->assertSeeIn('@setup-button', "JOIN")
// Try to join w/o password
->click('@setup-button')
->waitFor('#setup-password.is-invalid')
// Try to join with a valid password
->type('#setup-password', 'pass')
->click('@setup-button')
->waitFor('@session');
// Test removing the password
$owner->click('@menu button.link-security')
->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) {
$browser->assertSeeIn('@title', 'Security options')
->assertSeeIn('#password-input-text:not(.text-muted)', 'pass')
->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password')
->assertElementsCount('#password-input button', 1)
->click('#password-clear-btn')
->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.")
->assertMissing('#password-input input')
->assertSeeIn('#password-input-text.text-muted', 'none')
->assertSeeIn('#password-set-btn', 'Set password')
->assertElementsCount('#password-input button', 1)
->click('@button-action');
$this->assertSame(null, $room->fresh()->getSetting('password'));
});
});
}
/**
* Test locked room
*
* @group openvidu
*/
public function testLockedRoom(): void
{
$this->browse(function (Browser $owner, Browser $guest) {
// Make sure there's no session yet
$room = Room::where('name', 'john')->first();
if ($room->session_id) {
$room->session_id = null;
$room->save();
}
// Join the room as an owner (authenticate)
$owner->visit(new RoomPage('john'))
// ->click('@setup-button')
// ->submitLogon('john@kolab.org', 'simple123')
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->type('@setup-nickname-input', 'John')
->click('@setup-button')
->waitFor('@session')
// Enter Security option dialog
->click('@menu button.link-security')
->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) {
$browser->assertSeeIn('@title', 'Security options')
->assertSeeIn('#room-lock label', 'Locked room:')
->assertVisible('#room-lock input[type=checkbox]:not(:checked)')
->assertVisible('#room-lock + small')
// Test setting the lock
->click('#room-lock input')
->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.")
->click('@button-action');
$this->assertSame('true', $room->fresh()->getSetting('locked'));
});
// In another browser act as a guest
$guest->visit(new RoomPage('john'))
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->assertSeeIn('@setup-button:not([disabled]).btn-success', 'JOIN NOW')
// try without the nickname
->click('@setup-button')
->waitFor('@setup-nickname-input.is-invalid')
->assertSeeIn(
- '@setup-status-message.text-danger',
+ '@setup-status-message',
"The room is locked. Please, enter your name and try again."
)
->assertMissing('@setup-password-input')
->assertSeeIn('@setup-button:not([disabled]).btn-success', 'JOIN NOW')
->type('@setup-nickname-input', 'Guest<p>')
->click('@setup-button')
->assertMissing('@setup-nickname-input.is-invalid')
->waitFor('@setup-button[disabled]')
- ->assertSeeIn('@setup-status-message.text-danger', "Waiting for permission to join the room.");
+ ->assertSeeIn('@setup-status-message', "Waiting for permission to join the room.");
// Test denying the request (this will also test custom toasts)
$owner
->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) {
$browser->assertToastTitle('Join request')
->assertVisible('.toast-header svg.fa-user')
->assertSeeIn('@message', 'Guest<p> requested to join.')
->assertAttributeRegExp('@message img', 'src', '|^data:image|')
->assertSeeIn('@message button.accept.btn-success', 'Accept')
->assertSeeIn('@message button.deny.btn-danger', 'Deny')
->click('@message button.deny');
})
->waitUntilMissing('.toast')
// wait 10 seconds to make sure the request message does not show up again
->pause(10 * 1000)
->assertMissing('.toast');
// Test accepting the request
$guest->refresh()
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->type('@setup-nickname-input', 'guest')
->click('@setup-button')
->waitFor('@setup-button[disabled]')
- ->assertSeeIn('@setup-status-message.text-danger', "Waiting for permission to join the room.");
+ ->assertSeeIn('@setup-status-message', "Waiting for permission to join the room.");
$owner
->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) {
$browser->assertToastTitle('Join request')
->assertSeeIn('@message', 'guest requested to join.')
->click('@message button.accept');
});
// Guest automatically anters the room
$guest->waitFor('@session', 12)
// make sure he has no access to the Options menu
->waitFor('@session .meet-video:not(.publisher)')
->assertSeeIn('@session .meet-video:not(.publisher) a.nickname', 'John')
// TODO: Assert title and icon
->click('@session .meet-video:not(.publisher) a.nickname')
->pause(100)
->assertMissing('.dropdown-menu');
// Test dismissing the participant
$owner->click('@session .meet-video:not(.publisher) a.nickname')
->waitFor('@session .meet-video:not(.publisher) .dropdown-menu')
->assertSeeIn('@session .meet-video:not(.publisher) .dropdown-menu > .action-dismiss', 'Dismiss')
->click('@session .meet-video:not(.publisher) .dropdown-menu > .action-dismiss')
->waitUntilMissing('.dropdown-menu')
->waitUntilMissing('@session .meet-video:not(.publisher)');
// Expect a "end of session" dialog on the participant side
$guest->with(new Dialog('#leave-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Room closed')
->assertSeeIn('@body', "The session has been closed by the room owner.")
->assertMissing('@button-cancel')
->assertSeeIn('@button-action', 'Close');
});
});
}
}
diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php
index 1fbc42e8..05fe91ba 100644
--- a/src/tests/Browser/Meet/RoomSetupTest.php
+++ b/src/tests/Browser/Meet/RoomSetupTest.php
@@ -1,283 +1,282 @@
<?php
namespace Tests\Browser\Meet;
use App\OpenVidu\Room;
use Tests\Browser;
use Tests\Browser\Components\Dialog;
use Tests\Browser\Components\Menu;
use Tests\Browser\Pages\Meet\Room as RoomPage;
use Tests\TestCaseDusk;
class RoomSetupTest extends TestCaseDusk
{
/**
* {@inheritDoc}
*/
public function setUp(): void
{
parent::setUp();
$this->clearBetaEntitlements();
}
public function tearDown(): void
{
$this->clearBetaEntitlements();
parent::tearDown();
}
/**
* Test non-existing room
*
* @group openvidu
*/
public function testRoomNonExistingRoom(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(new RoomPage('unknown'))
->within(new Menu(), function ($browser) {
$browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']);
});
if ($browser->isDesktop()) {
$browser->within(new Menu('footer'), function ($browser) {
$browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']);
});
} else {
$browser->assertMissing('#footer-menu .navbar-nav');
}
// FIXME: Maybe it would be better to just display the usual 404 Not Found error page?
$browser->assertMissing('@toolbar')
->assertMissing('@menu')
->assertMissing('@session')
->assertMissing('@chat')
->assertMissing('@login-form')
->assertVisible('@setup-form')
->assertSeeIn('@setup-status-message', "The room does not exist.")
->assertVisible('@setup-button[disabled]');
});
}
/**
* Test the room setup page
*
* @group openvidu
*/
public function testRoomSetup(): void
{
// Make sure there's no session yet
$room = Room::where('name', 'john')->first();
if ($room->session_id) {
$room->session_id = null;
$room->save();
}
$this->assignBetaEntitlement('john@kolab.org', 'meet');
$this->browse(function (Browser $browser) {
$browser->visit(new RoomPage('john'))
->within(new Menu(), function ($browser) {
$browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']);
});
if ($browser->isDesktop()) {
$browser->within(new Menu('footer'), function ($browser) {
$browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']);
});
} else {
$browser->assertMissing('#footer-menu .navbar-nav');
}
// Note: I've found out that if I have another Chrome instance running
// that uses media, here the media devices will not be available
// TODO: Test enabling/disabling cam/mic in the setup widget
$browser->assertMissing('@toolbar')
->assertMissing('@menu')
->assertMissing('@session')
->assertMissing('@chat')
->assertMissing('@login-form')
->assertVisible('@setup-form')
->assertSeeIn('@setup-title', 'Set up your session')
->assertVisible('@setup-video')
->assertVisible('@setup-form .input-group:nth-child(1) svg')
->assertAttribute('@setup-form .input-group:nth-child(1) .input-group-text', 'title', 'Microphone')
->assertVisible('@setup-mic-select')
->assertVisible('@setup-form .input-group:nth-child(2) svg')
->assertAttribute('@setup-form .input-group:nth-child(2) .input-group-text', 'title', 'Camera')
->assertVisible('@setup-cam-select')
->assertVisible('@setup-form .input-group:nth-child(3) svg')
->assertAttribute('@setup-form .input-group:nth-child(3) .input-group-text', 'title', 'Nickname')
->assertValue('@setup-nickname-input', '')
->assertAttribute('@setup-nickname-input', 'placeholder', 'Your name')
->assertMissing('@setup-password-input')
->assertSeeIn(
'@setup-status-message',
"The room is closed. Please, wait for the owner to start the session."
)
->assertSeeIn('@setup-button', "I'm the owner");
});
}
/**
* Test two users in a room (joining/leaving and some basic functionality)
*
* @group openvidu
* @depends testRoomSetup
*/
public function testTwoUsersInARoom(): void
{
$this->assignBetaEntitlement('john@kolab.org', 'meet');
$this->browse(function (Browser $browser, Browser $guest) {
// In one browser window act as a guest
$guest->visit(new RoomPage('john'))
->assertMissing('@toolbar')
->assertMissing('@menu')
->assertMissing('@session')
->assertMissing('@chat')
->assertMissing('@login-form')
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->assertSeeIn(
'@setup-status-message',
"The room is closed. Please, wait for the owner to start the session."
)
->assertSeeIn('@setup-button', "I'm the owner");
// In another window join the room as the owner (authenticate)
$browser->on(new RoomPage('john'))
->assertSeeIn('@setup-button', "I'm the owner")
->click('@setup-button')
->assertMissing('@toolbar')
->assertMissing('@menu')
->assertMissing('@session')
->assertMissing('@chat')
->assertMissing('@setup-form')
->assertVisible('@login-form')
->submitLogon('john@kolab.org', 'simple123')
->waitFor('@setup-form')
->assertMissing('@login-form')
->waitUntilMissing('@setup-status-message.loading')
->assertSeeIn('@setup-status-message', "The room is closed. It will be open for others after you join.")
->assertSeeIn('@setup-button', "JOIN")
->type('@setup-nickname-input', 'john')
// Join the room
->click('@setup-button')
->waitFor('@session')
->assertMissing('@setup-form')
->whenAvailable('div.meet-video.publisher', function (Browser $browser) {
$browser->assertVisible('video')
->assertSeeIn('.nickname', 'john')
->assertVisible('.controls button.link-fullscreen')
->assertMissing('.controls button.link-audio')
->assertMissing('.status .status-audio')
->assertMissing('.status .status-video');
})
->within(new Menu(), function ($browser) {
$browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']);
});
if ($browser->isDesktop()) {
$browser->within(new Menu('footer'), function ($browser) {
$browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']);
});
}
// After the owner "opened the room" guest should be able to join
$guest->waitUntilMissing('@setup-status-message', 10)
->assertSeeIn('@setup-button', "JOIN")
// Join the room, disable cam/mic
->select('@setup-mic-select', '')
->select('@setup-cam-select', '')
->click('@setup-button')
->waitFor('@session')
->assertMissing('@setup-form')
->whenAvailable('div.meet-video.publisher', function (Browser $browser) {
$browser->assertVisible('video')
- ->assertVisible('.nickname button')
- ->assertMissing('.nickname span')
+ ->assertVisible('.nickname')
->assertVisible('.controls button.link-fullscreen')
->assertMissing('.controls button.link-audio')
->assertVisible('.status .status-audio')
->assertVisible('.status .status-video');
})
->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) {
$browser->assertVisible('video')
->assertSeeIn('.nickname', 'john')
->assertVisible('.controls button.link-fullscreen')
->assertVisible('.controls button.link-audio')
->assertMissing('.status .status-audio')
->assertMissing('.status .status-video');
})
->assertElementsCount('@session div.meet-video', 2)
->within(new Menu(), function ($browser) {
$browser->assertMenuItems(['explore', 'blog', 'support', 'signup', 'login']);
});
if ($guest->isDesktop()) {
$guest->within(new Menu('footer'), function ($browser) {
$browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'signup', 'login']);
});
}
// Check guest's elements in the owner's window
$browser->waitFor('@session div.meet-video:nth-child(2)')
->assertElementsCount('@session div.meet-video', 2)
->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) {
$browser->assertMissing('video')
- ->assertMissing('.nickname')
+ ->assertVisible('.nickname')
->assertVisible('.controls button.link-fullscreen')
->assertVisible('.controls button.link-audio')
->assertVisible('.status .status-audio')
->assertVisible('.status .status-video');
});
// Test leaving the room
// Guest is leaving
$guest->click('@menu button.link-logout')
->waitForLocation('/login');
// Expect the participant removed from other users windows
$browser->waitUntilMissing('@session div.meet-video:nth-child(2)');
// Join the room as guest again
$guest->visit(new RoomPage('john'))
->assertMissing('@toolbar')
->assertMissing('@menu')
->assertMissing('@session')
->assertMissing('@chat')
->assertMissing('@login-form')
->waitFor('@setup-form')
->waitUntilMissing('@setup-status-message.loading')
->assertMissing('@setup-status-message')
->assertSeeIn('@setup-button', "JOIN")
// Join the room, disable cam/mic
->select('@setup-mic-select', '')
->select('@setup-cam-select', '')
->click('@setup-button')
->waitFor('@session');
// Leave the room as the room owner
// TODO: Test leaving the room by closing the browser window,
// it should not destroy the session
$browser->click('@menu button.link-logout')
->waitForLocation('/dashboard');
// Expect other participants be informed about the end of the session
$guest->with(new Dialog('#leave-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Room closed')
->assertSeeIn('@body', "The session has been closed by the room owner.")
->assertMissing('@button-cancel')
->assertSeeIn('@button-action', 'Close')
->click('@button-action');
})
->assertMissing('#leave-dialog')
->waitForLocation('/login');
});
}
}
diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/OpenViduTest.php
index da7864d3..dd726e97 100644
--- a/src/tests/Feature/Controller/OpenViduTest.php
+++ b/src/tests/Feature/Controller/OpenViduTest.php
@@ -1,503 +1,527 @@
<?php
namespace Tests\Feature\Controller;
use App\Http\Controllers\API\V4\OpenViduController;
use App\OpenVidu\Room;
use Tests\TestCase;
class OpenViduTest extends TestCase
{
/**
* {@inheritDoc}
*/
public function setUp(): void
{
parent::setUp();
$this->clearBetaEntitlements();
$room = Room::where('name', 'john')->first();
$room->setSettings(['password' => null, 'locked' => null]);
}
public function tearDown(): void
{
$this->clearBetaEntitlements();
$room = Room::where('name', 'john')->first();
$room->setSettings(['password' => null, 'locked' => null]);
parent::tearDown();
}
/**
* Test listing user rooms
*
* @group openvidu
*/
public function testIndex(): void
{
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
Room::where('user_id', $jack->id)->delete();
// Unauth access not allowed
$response = $this->get("api/v4/openvidu/rooms");
$response->assertStatus(401);
// John has one room
$response = $this->actingAs($john)->get("api/v4/openvidu/rooms");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertSame('john', $json['list'][0]['name']);
// Jack has no room, but it will be auto-created
$response = $this->actingAs($jack)->get("api/v4/openvidu/rooms");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(1, $json['count']);
$this->assertCount(1, $json['list']);
$this->assertRegExp('/^[0-9a-z-]{11}$/', $json['list'][0]['name']);
}
/**
* Test joining the room
*
* @group openvidu
*/
public function testJoinRoom(): void
{
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$room = Room::where('name', 'john')->first();
$room->session_id = null;
$room->save();
$this->assignBetaEntitlement($john, 'meet');
// Unauth access, no session yet
$response = $this->post("api/v4/openvidu/rooms/{$room->name}");
- $response->assertStatus(423);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+ $this->assertSame(323, $json['code']);
// Non-existing room name
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing");
$response->assertStatus(404);
// TODO: Test accessing an existing room of deleted owner
// Non-owner, no session yet
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}");
- $response->assertStatus(423);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+ $this->assertSame(323, $json['code']);
// Room owner, no session yet
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}");
- $response->assertStatus(424);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+ $this->assertSame(324, $json['code']);
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]);
$response->assertStatus(200);
$json = $response->json();
$session_id = $room->fresh()->session_id;
$this->assertSame(Room::ROLE_MODERATOR, $json['role']);
$this->assertSame($session_id, $json['session']);
$this->assertTrue(is_string($session_id) && !empty($session_id));
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
$this->assertTrue(!array_key_exists('shareToken', $json));
$john_token = $json['token'];
// Non-owner, now the session exists
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(Room::ROLE_PUBLISHER, $json['role']);
$this->assertSame($session_id, $json['session']);
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
$this->assertTrue($json['token'] != $john_token);
$this->assertTrue(!array_key_exists('shareToken', $json));
$this->assertEmpty($json['config']['password']);
$this->assertEmpty($json['config']['requires_password']);
// Non-owner, password protected room, password not provided
$room->setSettings(['password' => 'pass']);
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}");
- $response->assertStatus(425);
+ $response->assertStatus(422);
$json = $response->json();
- $this->assertCount(3, $json);
+ $this->assertCount(4, $json);
+ $this->assertSame(325, $json['code']);
$this->assertSame('error', $json['status']);
$this->assertSame('Failed to join the session. Invalid password.', $json['message']);
$this->assertEmpty($json['config']['password']);
$this->assertTrue($json['config']['requires_password']);
// Non-owner, password protected room, invalid provided
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'aa']);
- $response->assertStatus(425);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+ $this->assertSame(325, $json['code']);
// Non-owner, password protected room, valid password provided
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'pass']);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame($session_id, $json['session']);
// Make sure the room owner can access the password protected room w/o password
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}");
$response->assertStatus(200);
}
/**
* Test locked room and join requests
*
* @group openvidu
*/
public function testJoinRequests(): void
{
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$room = Room::where('name', 'john')->first();
$room->session_id = null;
$room->save();
$room->setSettings(['password' => null, 'locked' => 'true']);
$this->assignBetaEntitlement($john, 'meet');
// Create the session (also makes sure the owner can access a locked room)
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]);
$response->assertStatus(200);
// Non-owner, locked room, invalid/missing input
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}");
- $response->assertStatus(426);
+ $response->assertStatus(422);
$json = $response->json();
- $this->assertCount(3, $json);
+ $this->assertCount(4, $json);
+ $this->assertSame(326, $json['code']);
$this->assertSame('error', $json['status']);
$this->assertSame('Failed to join the session. Room locked.', $json['message']);
$this->assertTrue($json['config']['locked']);
// Non-owner, locked room, invalid requestId
$post = ['nickname' => 'name', 'requestId' => '-----'];
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post);
- $response->assertStatus(426);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+ $this->assertSame(326, $json['code']);
// Non-owner, locked room, invalid requestId
$post = ['nickname' => 'name', 'picture' => '-----'];
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post);
- $response->assertStatus(426);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+ $this->assertSame(326, $json['code']);
// Non-owner, locked room, valid input
$reqId = '12345678';
$post = ['nickname' => 'name', 'requestId' => $reqId, 'picture' => 'data:image/png;base64,01234'];
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post);
- $response->assertStatus(427);
+ $response->assertStatus(422);
$json = $response->json();
- $this->assertCount(3, $json);
+ $this->assertCount(4, $json);
+ $this->assertSame(327, $json['code']);
$this->assertSame('error', $json['status']);
$this->assertSame('Failed to join the session. Room locked.', $json['message']);
$this->assertTrue($json['config']['locked']);
// TODO: How do we assert that a signal has been sent to the owner?
// Test denying a request
// Unknown room
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/deny");
$response->assertStatus(404);
// Unknown request Id
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/deny");
$response->assertStatus(500);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('error', $json['status']);
$this->assertSame('Failed to deny the join request.', $json['message']);
// Non-owner access forbidden
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny");
$response->assertStatus(403);
// Valid request
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
// Non-owner, locked room, join request denied
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post);
- $response->assertStatus(427);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+ $this->assertSame(327, $json['code']);
// Test accepting a request
// Unknown room
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/accept");
$response->assertStatus(404);
// Unknown request Id
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/accept");
$response->assertStatus(500);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('error', $json['status']);
$this->assertSame('Failed to accept the join request.', $json['message']);
// Non-owner access forbidden
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept");
$response->assertStatus(403);
// Valid request
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
// Non-owner, locked room, join request accepted
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(Room::ROLE_PUBLISHER, $json['role']);
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
// TODO: Test a scenario where both password and lock are enabled
}
/**
* Test joining the room
*
* @group openvidu
* @depends testJoinRoom
*/
public function testJoinRoomGuest(): void
{
$this->assignBetaEntitlement('john@kolab.org', 'meet');
// There's no asy way to logout the user in the same test after
// using actingAs(). That's why this is moved to a separate test
$room = Room::where('name', 'john')->first();
// Guest, request with screenShare token
$response = $this->post("api/v4/openvidu/rooms/{$room->name}", ['screenShare' => 1]);
$response->assertStatus(200);
$json = $response->json();
$this->assertSame(Room::ROLE_PUBLISHER, $json['role']);
$this->assertSame($room->session_id, $json['session']);
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
$this->assertTrue(strpos($json['shareToken'], 'wss://') === 0);
$this->assertTrue($json['shareToken'] != $json['token']);
}
/**
* Test closing the room (session)
*
* @group openvidu
* @depends testJoinRoom
*/
public function testCloseRoom(): void
{
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$room = Room::where('name', 'john')->first();
// Unauth access not allowed
$response = $this->post("api/v4/openvidu/rooms/{$room->name}/close", []);
$response->assertStatus(401);
// Non-existing room name
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/close", []);
$response->assertStatus(404);
// Non-owner
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/close", []);
$response->assertStatus(403);
// Room owner
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []);
$response->assertStatus(200);
$json = $response->json();
$this->assertNull($room->fresh()->session_id);
$this->assertSame('success', $json['status']);
$this->assertSame("The session has been closed successfully.", $json['message']);
$this->assertCount(2, $json);
// TODO: Test if the session is removed from the OpenVidu server too
// Test error handling when it's not possible to delete the session on
// the OpenVidu server (use fake session_id)
$room->session_id = 'aaa';
$room->save();
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []);
$response->assertStatus(500);
$json = $response->json();
$this->assertSame('aaa', $room->fresh()->session_id);
$this->assertSame('error', $json['status']);
$this->assertSame("Failed to close the session.", $json['message']);
$this->assertCount(2, $json);
}
/**
* Test dismissing a participant (closing a connection)
*
* @group openvidu
*/
public function testDismissConnection(): void
{
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$room = Room::where('name', 'john')->first();
$room->session_id = null;
$room->save();
$this->assignBetaEntitlement($john, 'meet');
// First we create the session
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]);
$response->assertStatus(200);
$json = $response->json();
// And the other user connection
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]);
$response->assertStatus(200);
$json = $response->json();
$conn_id = $json['connectionId'];
$room->refresh();
$conn_data = $room->getOVConnection($conn_id);
$this->assertSame($conn_id, $conn_data['connectionId']);
// Non-existing room name
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/connections/{$conn_id}/dismiss");
$response->assertStatus(404);
// TODO: Test accessing an existing room of deleted owner
// Non-existing connection
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/123/dismiss");
$response->assertStatus(500);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('error', $json['status']);
$this->assertSame('Failed to dismiss the connection.', $json['message']);
// Non-owner access
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss");
$response->assertStatus(403);
// Expected success
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss");
$response->assertStatus(200);
$json = $response->json();
$this->assertSame('success', $json['status']);
$this->assertNull($room->getOVConnection($conn_id));
}
/**
* Test configuring the room (session)
*
* @group openvidu
*/
public function testSetRoomConfig(): void
{
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$room = Room::where('name', 'john')->first();
// Unauth access not allowed
$response = $this->post("api/v4/openvidu/rooms/{$room->name}/config", []);
$response->assertStatus(401);
// Non-existing room name
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/config", []);
$response->assertStatus(404);
// TODO: Test a room with a deleted owner
// Non-owner
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/config", []);
$response->assertStatus(403);
// Room owner
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", []);
$response->assertStatus(200);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('success', $json['status']);
$this->assertSame("Room configuration updated successfully.", $json['message']);
// Set password and room lock
$post = ['password' => 'aaa', 'locked' => 1];
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post);
$response->assertStatus(200);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('success', $json['status']);
$this->assertSame("Room configuration updated successfully.", $json['message']);
$room->refresh();
$this->assertSame('aaa', $room->getSetting('password'));
$this->assertSame('true', $room->getSetting('locked'));
// Unset password and room lock
$post = ['password' => '', 'locked' => 0];
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post);
$response->assertStatus(200);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('success', $json['status']);
$this->assertSame("Room configuration updated successfully.", $json['message']);
$room->refresh();
$this->assertSame(null, $room->getSetting('password'));
$this->assertSame(null, $room->getSetting('locked'));
// Test invalid option error
$post = ['password' => 'eee', 'unknown' => 0];
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post);
$response->assertStatus(422);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('error', $json['status']);
$this->assertSame("Invalid room configuration option.", $json['errors']['unknown']);
$room->refresh();
$this->assertSame(null, $room->getSetting('password'));
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Apr 4, 9:24 AM (3 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18823449
Default Alt Text
(102 KB)
Attached To
Mode
rK kolab
Attached
Detach File
Event Timeline