Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117750439
D1912.1775182942.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
29 KB
Referenced Files
None
Subscribers
None
D1912.1775182942.diff
View Options
diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php
--- a/src/app/Http/Controllers/API/V4/OpenViduController.php
+++ b/src/app/Http/Controllers/API/V4/OpenViduController.php
@@ -10,6 +10,68 @@
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.
*
@@ -136,19 +198,52 @@
if (!$isOwner && strlen($password)) {
$request_password = request()->input('password');
if ($request_password !== $password) {
- // Note: We send the config to the client so it knows to display the password field
- $response = [
- 'config' => $config,
- 'message' => \trans('meet.session-password-error'),
- 'status' => 'error',
- ];
-
- return response()->json($response, 425);
+ return $this->errorResponse(425, \trans('meet.session-password-error'), ['config' => $config]);
+ }
+ }
+
+ // Handle locked room
+ if (!$isOwner && $config['locked']) {
+ $nickname = request()->input('nickname');
+ $picture = request()->input('picture');
+ $requestId = request()->input('requestId');
+
+ $request = $room->requestGet($requestId);
+
+ // Request already has been processed (not accepted yet, but it could be denied)
+ if ($request && $request['status'] != Room::REQUEST_ACCEPTED) {
+ return $this->errorResponse(427, \trans('meet.session-room-locked'), ['config' => $config]);
+ }
+
+ if (!$request) {
+ if (empty($nickname) || empty($requestId)) {
+ return $this->errorResponse(426, \trans('meet.session-room-locked'), ['config' => $config]);
+ }
+
+ // TODO: Validate requestId and nickname
+ // TODO: Validate/resize/make safe the user picture
+
+ if (empty($picture)) {
+ $svg = file_get_contents(resource_path('images/user.svg'));
+ $picture = 'data:image/svg+xml;base64,' . base64_encode($svg);
+ }
+
+ $request = ['nickname' => $nickname, 'requestId' => $requestId, 'picture' => $picture];
+
+ if (!$room->requestSave($requestId, $request)) {
+ // FIXME: should we use error code 500?
+ return $this->errorResponse(426, \trans('meet.session-room-locked'), ['config' => $config]);
+ }
+
+ // Send the request (signal) to the owner
+ $result = $room->signal('joinRequest', $request, 'MODERATOR');
+
+ return $this->errorResponse(427, \trans('meet.session-room-locked'), ['config' => $config]);
}
}
// Create session token for the current user/connection
- $response = $room->getSessionToken('PUBLISHER');
+ $response = $room->getSessionToken($isOwner ? 'MODERATOR' : 'PUBLISHER');
if (empty($response)) {
return $this->errorResponse(500, \trans('meet.session-join-error'));
diff --git a/src/app/Http/Controllers/Controller.php b/src/app/Http/Controllers/Controller.php
--- a/src/app/Http/Controllers/Controller.php
+++ b/src/app/Http/Controllers/Controller.php
@@ -19,10 +19,11 @@
*
* @param int $code Error code
* @param string $message Error message
+ * @param array $data Additional response data
*
* @return \Illuminate\Http\JsonResponse
*/
- protected function errorResponse(int $code, string $message = null)
+ protected function errorResponse(int $code, string $message = null, array $data = [])
{
$errors = [
400 => "Bad request",
@@ -39,6 +40,10 @@
'message' => $message ?: (isset($errors[$code]) ? $errors[$code] : "Server error"),
];
+ if (!empty($data)) {
+ $response = $response + $data;
+ }
+
return response()->json($response, $code);
}
}
diff --git a/src/app/OpenVidu/Room.php b/src/app/OpenVidu/Room.php
--- a/src/app/OpenVidu/Room.php
+++ b/src/app/OpenVidu/Room.php
@@ -4,6 +4,7 @@
use App\Traits\SettingsTrait;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
/**
* The eloquent definition of a Room.
@@ -17,6 +18,9 @@
{
use SettingsTrait;
+ public const REQUEST_ACCEPTED = 'accepted';
+ public const REQUEST_DENIED = 'denied';
+
protected $fillable = [
'user_id',
'name'
@@ -73,6 +77,7 @@
if ($response->getStatusCode() !== 200) {
$this->session_id = null;
$this->save();
+ return null;
}
$session = json_decode($response->getBody(), true);
@@ -162,6 +167,75 @@
return $this->belongsTo('\App\User', 'user_id', 'id');
}
+ /**
+ * Accept the join request
+ *
+ * @param string $id Request identifier
+ *
+ * @return bool True on success, False on failure
+ */
+ public function requestAccept(string $id): bool
+ {
+ $request = Cache::get($this->session_id . '-' . $id);
+
+ if ($request) {
+ $request['status'] = self::REQUEST_ACCEPTED;
+
+ return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1));
+ }
+
+ return false;
+ }
+
+ /**
+ * Deny the join request
+ *
+ * @param string $id Request identifier
+ *
+ * @return bool True on success, False on failure
+ */
+ public function requestDeny(string $id): bool
+ {
+ $request = Cache::get($this->session_id . '-' . $id);
+
+ if ($request) {
+ $request['status'] = self::REQUEST_DENIED;
+
+ return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1));
+ }
+
+ return false;
+ }
+
+ /**
+ * Accept the join request
+ *
+ * @param string $id Request identifier
+ *
+ * @return array|null Request data (e.g. nickname, status, picture?)
+ */
+ public function requestGet(string $id)
+ {
+ return Cache::get($this->session_id . '-' . $id);
+ }
+
+ /**
+ * Save the join request
+ *
+ * @param string $id Request identifier
+ * @param array $request Request data
+ *
+ * @return bool True on success, False on failure
+ */
+ public function requestSave(string $id, array $request): bool
+ {
+ // We don't really need the picture in the cache
+ // As we use this cache for the request status only
+ unset($request['picture']);
+
+ return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1));
+ }
+
/**
* Any (additional) properties of this room.
*
@@ -171,4 +245,65 @@
{
return $this->hasMany('App\OpenVidu\RoomSetting', 'room_id');
}
+
+ /**
+ * Send a OpenVidu signal to the session participants (connections)
+ *
+ * @param string $name Signal name (type)
+ * @param array $data Signal data array
+ * @param array|string $target List of target connections, Null for all connections.
+ * It can be also a participant role.
+ *
+ * @return bool True on success, False on failure
+ * @throws \Exception if session does not exist
+ */
+ public function signal(string $name, array $data = [], $target = null): bool
+ {
+ if (!$this->session_id) {
+ throw new \Exception("The room session does not exist");
+ }
+
+ $post = [
+ 'session' => $this->session_id,
+ 'type' => $name,
+ 'data' => $data ? json_encode($data) : '',
+ ];
+
+ // Get connection IDs by participant role
+ if (is_string($target)) {
+ // TODO: We should probably store this in our database/redis. I foresee a use-case
+ // for such a connections store on our side, e.g. keeping participant
+ // metadata, e.g. selected language, extra roles like a "language interpreter", etc.
+
+ $response = $this->client()->request('GET', 'sessions/' . $this->session_id);
+
+ if ($response->getStatusCode() !== 200) {
+ return false;
+ }
+
+ $json = json_decode($response->getBody(), true);
+ $connections = [];
+
+ foreach ($json['connections']['content'] as $connection) {
+ if ($connection['role'] === $target) {
+ $connections[] = $connection['id'];
+ break;
+ }
+ }
+
+ if (empty($connections)) {
+ return false;
+ }
+
+ $target = $connections;
+ }
+
+ if (!empty($target)) {
+ $post['to'] = $target;
+ }
+
+ $response = $this->client()->request('POST', 'signal', ['json' => $post]);
+
+ return $response->getStatusCode() == 200;
+ }
}
diff --git a/src/resources/images/user.svg b/src/resources/images/user.svg
new file mode 100644
--- /dev/null
+++ b/src/resources/images/user.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 96c48.6 0 88 39.4 88 88s-39.4 88-88 88-88-39.4-88-88 39.4-88 88-88zm0 344c-58.7 0-111.3-26.6-146.5-68.2 18.8-35.4 55.6-59.8 98.5-59.8 2.4 0 4.8.4 7.1 1.1 13 4.2 26.6 6.9 40.9 6.9 14.3 0 28-2.7 40.9-6.9 2.3-.7 4.7-1.1 7.1-1.1 42.9 0 79.7 24.4 98.5 59.8C359.3 421.4 306.7 448 248 448z"/></svg>
\ No newline at end of file
diff --git a/src/resources/js/meet/app.js b/src/resources/js/meet/app.js
--- a/src/resources/js/meet/app.js
+++ b/src/resources/js/meet/app.js
@@ -70,7 +70,7 @@
* Join the room session
*
* @param data Session metadata and event handlers (session, token, shareToken, nickname,
- * chatElement, menuElement, onDestroy)
+ * chatElement, menuElement, onDestroy, onJoinRequest)
*/
function joinRoom(data) {
resize();
@@ -414,7 +414,7 @@
*/
function signalEventHandler(signal) {
let conn, data
- let connId = signal.from.connectionId
+ let connId = signal.from ? signal.from.connectionId : null
switch (signal.type) {
case 'signal:userChanged':
@@ -431,6 +431,12 @@
data.id = connId
pushChatMessage(data)
break
+
+ case 'signal:joinRequest':
+ if (sessionData.onJoinRequest) {
+ sessionData.onJoinRequest(JSON.parse(signal.data))
+ }
+ break;
}
}
diff --git a/src/resources/themes/meet.scss b/src/resources/themes/meet.scss
--- a/src/resources/themes/meet.scss
+++ b/src/resources/themes/meet.scss
@@ -253,3 +253,29 @@
}
}
}
+
+.toast.join-request {
+ .toast-header {
+ color: #eee;
+ }
+
+ .toast-body {
+ display: flex;
+ }
+
+ .picture {
+ margin-right: 1em;
+
+ img {
+ width: 64px;
+ height: 64px;
+ border: 1px solid #555;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+ }
+
+ .content {
+ flex: 1;
+ }
+}
diff --git a/src/resources/themes/toast.scss b/src/resources/themes/toast.scss
--- a/src/resources/themes/toast.scss
+++ b/src/resources/themes/toast.scss
@@ -4,6 +4,10 @@
right: 0;
margin: 0.5rem;
width: 320px;
+ max-height: calc(100% - 1rem);
+ overflow-y: auto;
+ scrollbar-width: thin;
+ scrollbar-color: rgba(52, 58, 64, 0.95) transparent;
z-index: 1055; // above Bootstrap's modal backdrop and dialogs
@media (max-width: 375px) {
diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue
--- a/src/resources/vue/Meet/Room.vue
+++ b/src/resources/vue/Meet/Room.vue
@@ -70,16 +70,15 @@
<input type="password" class="form-control" id="setup-password" v-model="password" placeholder="Password">
</div>
<div class="mt-3">
- <button v-if="roomState == 'ready' || roomState == 424 || roomState == 425"
- type="button"
+ <button type="button"
@click="joinSession"
- :class="'btn w-100 btn-' + (roomState == 'ready' ? 'success' : 'primary')"
- >JOIN</button>
- <button v-if="roomState == 423"
- type="button"
- @click="joinSession"
- class="btn btn-primary w-100"
- >I'm the owner</button>
+ :disabled="roomState == 'init' || roomState == 427"
+ :class="'btn w-100 btn-' + (isRoomReady() ? 'success' : 'primary')"
+ >
+ <span v-if="roomState == 423">I'm the owner</span>
+ <span v-else-if="isRoomReady()">JOIN NOW</span>
+ <span v-else>JOIN</span>
+ </button>
</div>
</div>
<div class="mt-4 col-sm-12">
@@ -157,6 +156,8 @@
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.',
500: 'Failed to create a session. Server error.'
},
session: {}
@@ -184,8 +185,7 @@
},
methods: {
authSuccess() {
- // The user (owner) authentication succeeded
- this.roomState = 'init'
+ // The user authentication succeeded, we still don't know it's really the room owner
this.initSession()
$('#meet-setup').removeClass('hidden')
@@ -199,10 +199,12 @@
password: this.password,
nickname: this.nickname,
screenShare: this.canShareScreen ? 1 : 0,
- init: init ? 1 : 0
+ init: init ? 1 : 0,
+ picture: init ? this.makePicture() : '',
+ requestId: this.requestId()
}
- $('#setup-password').removeClass('is-invalid')
+ $('#setup-password,#setup-nickname').removeClass('is-invalid')
axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true })
.then(response => {
@@ -234,6 +236,19 @@
$('#setup-password').addClass('is-invalid').focus()
}
break;
+
+ case '426':
+ // Locked room prerequisites error
+ if (init && !$('#setup-nickname').val()) {
+ $('#setup-nickname').addClass('is-invalid').focus()
+ }
+ break;
+
+ case '427':
+ // Waiting for the owner's approval to join
+ // Update room state every 10 seconds
+ window.roomRequest = setTimeout(() => { this.initSession(true) }, 10000)
+ break;
}
})
@@ -241,6 +256,53 @@
$('#meet-session-menu').find('.link-fullscreen.closed').removeClass('hidden')
}
},
+ isRoomReady() {
+ return ['ready', '424', '425'].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">${data.nickname || ''} requested to join.</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
+
+ // 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) {
$('#meet-setup').addClass('hidden')
@@ -248,7 +310,7 @@
return
}
- if (this.roomState == 424 || this.roomState == 425) {
+ if (this.roomState != 'ready') {
this.initSession(true)
return
}
@@ -279,21 +341,70 @@
}
}
+ if (this.session.owner) {
+ this.session.onJoinRequest = data => { this.joinRequest(data) }
+ }
+
this.meet.joinRoom(this.session)
},
logout() {
- if (this.session.owner) {
- axios.post('/api/v4/openvidu/rooms/' + this.room + '/close')
- .then(response => {
- this.meet.leaveRoom()
- this.meet = null
- window.location = window.config['app.url']
- })
- } else {
+ 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()
diff --git a/src/resources/vue/Meet/SessionSecurityOptions.vue b/src/resources/vue/Meet/SessionSecurityOptions.vue
--- a/src/resources/vue/Meet/SessionSecurityOptions.vue
+++ b/src/resources/vue/Meet/SessionSecurityOptions.vue
@@ -27,8 +27,8 @@
the password before they are allowed to join the meeting.
</small>
</form>
- <hr v-if="false">
- <form v-if="false" id="security-options-lock">
+ <hr>
+ <form id="security-options-lock">
<div id="room-lock" class="">
<span class="">Locked room:</span>
<input type="checkbox" name="lock" value="1" :checked="config.locked" @click="lockSave">
diff --git a/src/resources/vue/Widgets/Toast.vue b/src/resources/vue/Widgets/Toast.vue
--- a/src/resources/vue/Widgets/Toast.vue
+++ b/src/resources/vue/Widgets/Toast.vue
@@ -81,6 +81,16 @@
data.title = title
}
+ return this.addToast(data)
+ },
+ message(data) {
+ if (data.type === undefined) {
+ data.type = 'custom'
+ }
+ if (data.timeout === undefined) {
+ data.timeout = this.defaultTimeout
+ }
+
return this.addToast(data)
}
},
diff --git a/src/resources/vue/Widgets/ToastMessage.vue b/src/resources/vue/Widgets/ToastMessage.vue
--- a/src/resources/vue/Widgets/ToastMessage.vue
+++ b/src/resources/vue/Widgets/ToastMessage.vue
@@ -1,18 +1,18 @@
<template>
- <div :class="'toast hide toast-' + data.type" role="alert" aria-live="assertive" aria-atomic="true">
- <div class="toast-header">
- <svg-icon icon="info-circle" :class="className()" v-if="data.type == 'info'"></svg-icon>
- <svg-icon icon="check-circle" :class="className()" v-else-if="data.type == 'success'"></svg-icon>
- <svg-icon icon="exclamation-circle" :class="className()" v-else-if="data.type == 'error'"></svg-icon>
- <svg-icon icon="exclamation-circle" :class="className()" v-else-if="data.type == 'warning'"></svg-icon>
- <strong :class="className()">{{ data.title || title() }}</strong>
+ <div :class="toastClassName()" role="alert" aria-live="assertive" aria-atomic="true">
+ <div class="toast-header" :class="className()">
+ <svg-icon icon="info-circle" v-if="data.type == 'info'"></svg-icon>
+ <svg-icon icon="check-circle" v-else-if="data.type == 'success'"></svg-icon>
+ <svg-icon icon="exclamation-circle" v-else-if="data.type == 'error'"></svg-icon>
+ <svg-icon icon="exclamation-circle" v-else-if="data.type == 'warning'"></svg-icon>
+ <svg-icon :icon="data.icon" v-else-if="data.type == 'custom' && data.icon"></svg-icon>
+ <strong>{{ data.title || title() }}</strong>
<button type="button" class="close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
- <div class="toast-body">
- {{ data.msg }}
- </div>
+ <div v-if="data.body" v-html="data.body" class="toast-body"></div>
+ <div v-else class="toast-body">{{ data.msg }}</div>
</div>
</template>
@@ -22,13 +22,19 @@
data: { type: Object, default: () => {} }
},
mounted() {
- $(this.$el).on('hidden.bs.toast', () => {
+ $(this.$el)
+ .on('hidden.bs.toast', () => {
(this.$el).remove()
this.$destroy()
})
+ .on('shown.bs.toast', () => {
+ if (this.data.onShow) {
+ this.data.onShow(this.$el)
+ }
+ })
.toast({
animation: true,
- autohide: true,
+ autohide: this.data.timeout > 0,
delay: this.data.timeout
})
.toast('show')
@@ -42,6 +48,8 @@
case 'info':
case 'success':
return 'text-' + this.data.type
+ case 'custom':
+ return this.data.titleClassName || ''
}
},
title() {
@@ -54,6 +62,12 @@
case 'success':
return type.charAt(0).toUpperCase() + type.slice(1)
}
+
+ return ''
+ },
+ toastClassName() {
+ return 'toast hide toast-' + this.data.type
+ + (this.data.className ? ' ' + this.data.className : '')
}
}
}
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -84,6 +84,8 @@
Route::get('openvidu/rooms', 'API\V4\OpenViduController@index');
Route::post('openvidu/rooms/{id}/close', 'API\V4\OpenViduController@closeRoom');
Route::post('openvidu/rooms/{id}/config', 'API\V4\OpenViduController@setRoomConfig');
+ Route::post('openvidu/rooms/{id}/request/{reqid}/accept', 'API\V4\OpenViduController@acceptJoinRequest');
+ Route::post('openvidu/rooms/{id}/request/{reqid}/deny', 'API\V4\OpenViduController@denyJoinRequest');
}
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 3, 2:22 AM (1 d, 5 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822127
Default Alt Text
D1912.1775182942.diff (29 KB)
Attached To
Mode
D1912: OpenVidu: Room lock
Attached
Detach File
Event Timeline