Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
102 KB
Referenced Files
None
Subscribers
None
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">&times;</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

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)

Event Timeline