Skip to content

Commit

Permalink
draft fixes for html-plugin, (#30)
Browse files Browse the repository at this point in the history
fixes #26
fixes #27
fixes #28
  • Loading branch information
ferishili authored Aug 8, 2024
1 parent 5c3872a commit b221f0f
Show file tree
Hide file tree
Showing 13 changed files with 4,367 additions and 8,766 deletions.
12,001 changes: 3,480 additions & 8,521 deletions html-plugin/package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions html-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
"@types/react": "^18.2.13",
"@types/react-dom": "^18.2.6",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"bigbluebutton-html-plugin-sdk": "^0.0.46",
"bigbluebutton-html-plugin-sdk": "^0.0.54",
"graphql-ws": "^5.16.0",
"path": "^0.12.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-modal": "^3.16.1",
"react-pin-input": "^1.3.1",
"react-select": "^5.7.3",
"subscriptions-transport-ws": "^0.11.0"
"react-spinners": "^0.14.1"
},
"scripts": {
"build-bundle": "webpack --mode production",
Expand Down
2 changes: 1 addition & 1 deletion html-plugin/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { RoomMediaPlugin } from './room-media-plugin/component';
import { RoomMediaPlugin } from './room-media-plugin/RoomMediaPlugin';

const uuid = document.currentScript?.getAttribute('uuid') || 'root';

Expand Down
354 changes: 354 additions & 0 deletions html-plugin/src/room-media-plugin/RoomMediaPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
import * as React from 'react';
import {useState, useEffect} from 'react';
import * as ReactModal from 'react-modal';
import {pluginApolloClient} from './libs/apolloClient'
import './style.css';

import {
BbbPluginSdk,
PluginApi,
ActionButtonDropdownSeparator,
ActionButtonDropdownOption
} from 'bigbluebutton-html-plugin-sdk';
import {Config, Layout, ResponseData, RoomMediaPluginProps} from './types';

import PinComponent from './Shared/PinComponent';
import LoaderComponent from './Shared/LoaderComponent';
import LayoutComponent from './Shared/LayoutComponent';
import ConfirmationComponent from './Shared/ConfirmationComponent';

import { USER_SET_MUTED, SET_MUTED } from './libs/mutations';


export function RoomMediaPlugin({pluginUuid: uuid}: RoomMediaPluginProps) {
BbbPluginSdk.initialize(uuid);
const [showModal, setShowModal] = useState<boolean>(false);
const pluginApi: PluginApi = BbbPluginSdk.getPluginApi(uuid);
const {data: currentUser} = pluginApi.useCurrentUser();
const {data: pluginSettings} = pluginApi.usePluginSettings();
const [webSocket, setWebSocket] = useState<WebSocket | null>(null);
const [filteredLayout, setFilteredLayout] = useState<Layout | null>(null);
const [tempFilteredLayout, setTempFilteredLayout] = useState<Layout | null>(null);
const [filteredLayouts, setFilteredLayouts] = useState<Layout[] | null>(null);
const [roomConfig, setRoomConfig] = useState<Config | null>(null);
const [offerResponse, setOfferResponse] = useState<string | null>(null);
const [pairingPin, setPairingPin] = useState<string | null>(null);
const [roomJoinUrls, setRoomJoinUrls] = useState(null);

const [pinValue, setPinValue] = useState<string | null>(null);
const [pinError, setPinError] = useState<boolean>(false);
const [isPairing, setIsPairing] = useState<boolean>(false);
const [status, setStatus] = useState<string | null>(null);
const [confirmationTitle, setConfirmationTitle] = useState<string | null>(null);
const [apolloClient, setApolloCleint] = useState<any>(null);
const {data: talkingIndicator} = pluginApi.useTalkingIndicator();
const [isUserMuted, setIsUserMuted] = useState<boolean>(true);

const extractFilterLayouts = (jsonData: ResponseData, index: number) => {
const layoutsArray = Object.values(jsonData.config.layouts);
setFilteredLayouts(layoutsArray);
const filteredLayout = layoutsArray.find((layout) => layout.index === index);
setFilteredLayout(filteredLayout);
}

// console.log('Plugin settings:', pluginSettings);

const createWebSocket = () => {

if (!pluginSettings || typeof pluginSettings.pairingWebsocketUrl !== 'string') {
console.error('Plugin settings or pairingWebsocketUrl URL not yet available');
return;
}

const ws = new WebSocket(pluginSettings.pairingWebsocketUrl);
ws.onopen = () => {
// Send user input to WebSocket
if (ws.readyState === WebSocket.OPEN) {
try {
const data = {
pin: Number(pinValue),
};
ws.send(JSON.stringify(data));
} catch (error) {
console.error('Room Integration Plugin: Error sending data via WebSocket:', error);
}
}
};

ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('websocket onmessage: ', data);
if (data.status === 200 && data.msg === 'ok') {
console.log('data', data);
setRoomConfig(data.config);
extractFilterLayouts(data, 0);
resetPinValues();
}

if (data.status === 200 && data.msg === 'pairing') {
setIsPairing(false);
setPairingPin(data.pairing_pin);
}

if (data.type == 'offer_response') {
setOfferResponse(data.response);
}
};

ws.onclose = (e) => {
console.log('Room Integration Plugin: WebSocket connection closed', e);
setPinError(true);
setIsPairing(false);
};

setWebSocket(ws);

return ws;
};

const handlePinCompletion = (value: string, index: number): void => {
if (index === 5) { // Make sure it happens at the end of the pin (6th)
setPinValue(value);
}
};

const resetPinValues = () => {
setPinError(false);
setPinValue(null);
}

useEffect(() => {
if (pinValue) {
setIsPairing(true);
setPinError(false);
createWebSocket();
}
}, [pinValue]);

useEffect(() => {
if (offerResponse === 'accept') {
setStatus('accepted');
}
}, [offerResponse]);

useEffect(() => {
// Since it is a real time value, we set it once!
if (talkingIndicator && talkingIndicator.length > 0) {
const userTalkingIndicator = talkingIndicator.find((ti) => ti.userId === currentUser?.userId);
if (userTalkingIndicator) {
setIsUserMuted(userTalkingIndicator.muted);
}
}
}, [talkingIndicator]);

const muteCurrentUser = async () => {
if (apolloClient && !isUserMuted) {
const result = await apolloClient.mutate({
mutation: USER_SET_MUTED,
variables: {
userId: currentUser.userId,
muted: true,
},
});
}
}

const performLayoutSelection = async () => {
setStatus('selectingLayout');
await muteCurrentUser();
setFilteredLayout(tempFilteredLayout);
setStatus('layoutSelected');
resetPinValues();
setShowModal(false);
}

const cancelLayoutSelection = (): void => {
setTempFilteredLayout(null);
setConfirmationTitle(null);
setStatus('layoutSelection');
}

const prepareLayoutSelection = (index: number): void => {
const filteredLayout = filteredLayouts.find((layout) => layout.index === index);
setTempFilteredLayout(filteredLayout);
setConfirmationTitle(filteredLayout.label);
setStatus('confirmLayoutSelection');
}

useEffect(() => {
const prepareApolloClient = async () => {
const joinUrl = await pluginApi.getJoinUrl({
"redirect": "true",
"fullName": currentUser.name,
"userID": currentUser.userId
});
const apolloClient = await pluginApolloClient(joinUrl, pluginApi.getSessionToken());
setApolloCleint(apolloClient);
}

if (currentUser?.role == "MODERATOR") {
pluginApi.setActionButtonDropdownItems([
new ActionButtonDropdownSeparator(),
new ActionButtonDropdownOption({
label: 'Room media connection',
icon: 'more',
tooltip: 'Control the room media connection',
allowed: true,
onClick: () => {
setShowModal(true);
},
}),
]);
prepareApolloClient();
}
}, [currentUser]);

useEffect(() => {
const fetchJoinUrls = async () => {
if (!filteredLayout) return;

const baseJoinParameters = {
"redirect": "true",
"fullName": roomConfig.bbb_user_name,
"userID": roomConfig.bbb_user_id
};
const controlJoinUrl: string = await pluginApi.getJoinUrl(
{
...baseJoinParameters,
userID: baseJoinParameters.userID,
role: "MODERATOR"
}
);

const screenJoinUrls: { [key: string]: string } = {};

try {
await Promise.all(
Object.entries(filteredLayout.screens).map(async ([key, value]) => {
const joinParametersMap = {
...baseJoinParameters,
...value['bbb-join-parameters'],
userID: baseJoinParameters.userID + "-" + key,
role: 'MODERATOR'
};
screenJoinUrls[key] = await pluginApi.getJoinUrl(joinParametersMap);
})
);
const roomJoinUrls = {
"urls": {"control": controlJoinUrl, "screens": screenJoinUrls}
};
setRoomJoinUrls(roomJoinUrls);
console.log("Room Join URLs: ", roomJoinUrls)
} catch (error) {
console.error("Room Integration Plugin: Error fetching join URLs:", error);
}
};

fetchJoinUrls();
}, [filteredLayout]);

useEffect(() => {
try {
if (roomJoinUrls) {
webSocket.send(JSON.stringify(roomJoinUrls));
}
} catch (error) {
console.error('Room Integration Plugin: Error sending urls via WebSocket:', error);
}
}, [webSocket, roomJoinUrls]);

return (
<ReactModal
className="plugin-modal"
overlayClassName="modal-overlay"
isOpen={showModal}
onRequestClose={() => setShowModal(false)}
ariaHideApp={false}
>
<div
style={{
width: '100%', height: '100%', alignItems: 'center', display: 'flex', flexDirection: 'column',
}}
>
{!pairingPin ?
<>
{!isPairing ?
<>
<PinComponent
performCompletion={handlePinCompletion}
hasError={pinError}
/>
</>
:
<>
<LoaderComponent title="Pairing..." />
</>
}
</>
:
!offerResponse ?
<>
<h3>Pairing with '{roomConfig.name}'</h3>
<h4>Please verify the pairing PIN and confirm on the appliance</h4>
<h2>{pairingPin}</h2>
</>
:
<>
{offerResponse == "accept" ?
<>
{status == "accepted" &&
<>
<LoaderComponent title="Accepted, loading layouts..." />
</>
}

{status == "layoutSelection" &&
<>
<LayoutComponent
layouts={filteredLayouts}
prepareSelection={prepareLayoutSelection}
/>
</>
}

{status == "confirmLayoutSelection" &&
<>
<ConfirmationComponent
title={confirmationTitle}
text="Are you sure to select this layout?"
confirm={performLayoutSelection}
cancel={cancelLayoutSelection}
/>
</>
}

{status == "selectingLayout" &&
<>
<LoaderComponent title="Applying layout..." />
</>
}

{status == "layoutSelected" &&
<>
<h2>Room already connected!</h2>
</>
}
</>
:
<>
<h3>Connection declined</h3>
<button
className="button-style"
type="button"
onClick={() => setShowModal(false)}
>
Close
</button>
</>
}
</>
}
</div>
</ReactModal>
);
}
Loading

0 comments on commit b221f0f

Please sign in to comment.