Page MenuHomePhorge

D1912.1775182942.diff
No OneTemporary

Authored By
Unknown
Size
29 KB
Referenced Files
None
Subscribers
None

D1912.1775182942.diff

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">&times;</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

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)

Event Timeline