UNPKG

mediasfu-reactnative

Version:
640 lines (636 loc) 26.1 kB
/* eslint-disable no-catch-shadow */ import React, { useState, useEffect, useRef } from 'react'; import { View, Text, TextInput, Pressable, Image, StyleSheet, ScrollView, KeyboardAvoidingView, Platform, } from 'react-native'; import Orientation from 'react-native-orientation-locker'; import RNPickerSelect from 'react-native-picker-select'; import { checkLimitsAndMakeRequest } from '../../methods/utils/checkLimitsAndMakeRequest'; import { createRoomOnMediaSFU } from '../../methods/utils/createRoomOnMediaSFU'; import { joinRoomOnMediaSFU } from '../../methods/utils/joinRoomOnMediaSFU'; /** * PreJoinPage component allows users to either create a new room or join an existing one. * * @component * @param {PreJoinPageOptions} props - The properties for the PreJoinPage component. * @param {PreJoinPageParameters} props.parameters - Various parameters required for the component. * @param {ShowAlert} [props.parameters.showAlert] - Function to show alert messages. * @param {() => void} props.parameters.updateIsLoadingModalVisible - Function to update the loading modal visibility. * @param {ConnectSocketType} props.parameters.connectSocket - Function to connect to the socket. * @param {ConnectSocketType} props.parameters.connectLocalSocket - Function to connect to the local socket. * @param {Socket} props.parameters.updateSocket - Function to update the socket. * @param {Socket} props.parameters.updateLocalSocket - Function to update the local socket. * @param {() => void} props.parameters.updateValidated - Function to update the validation status. * @param {string} [props.parameters.imgSrc] - The source URL for the logo image. * @param {Credentials} [props.credentials=user_credentials] - The user credentials containing the API username and API key. * @param {boolean} [props.returnUI=false] - Flag to determine if the component should return the UI. * @param {CreateMediaSFURoomOptions | JoinMediaSFURoomOptions} [props.noUIPreJoinOptions] - The options for creating/joining a room without UI. * @param {string} [props.localLink=''] - The link to the local server. * @param {boolean} [props.connectMediaSFU=true] - Flag to determine if the component should connect to MediaSFU. * @param {CreateRoomOnMediaSFUType} [props.createMediaSFURoom] - Function to create a room on MediaSFU. * @param {JoinRoomOnMediaSFUType} [props.joinMediaSFURoom] - Function to join a room on MediaSFU. * * @returns {JSX.Element} The rendered PreJoinPage component. * * @example * ```tsx * import React from 'react'; * import { PreJoinPage } from 'mediasfu-reactnative'; * import { JoinLocalRoomOptions } from 'mediasfu-reactnative'; * * function App() { * * const showAlertFunction = (message: string) => console.log(message); * const updateLoadingFunction = (visible: boolean) => console.log(`Loading: ${visible}`); * const connectSocketFunction = () => {}; // Connect socket function * const updateSocketFunction = (socket: Socket) => {}; // Update socket function * const updateValidatedFunction = (validated: boolean) => {}; // Update validated function * const updateApiUserNameFunction = (userName: string) => {}; // Update API username function * const updateApiTokenFunction = (token: string) => {}; // Update API token function * const updateLinkFunction = (link: string) => {}; // Update link function * const updateRoomNameFunction = (roomName: string) => {}; // Update room name function * const updateMemberFunction = (member: string) => {}; // Update member function * * return ( * <PreJoinPage * parameters={{ * showAlert: showAlertFunction, * updateIsLoadingModalVisible: updateLoadingFunction, * connectSocket: connectSocketFunction, * updateSocket: updateSocketFunction, * updateValidated: updateValidatedFunction, * updateApiUserName: updateApiUserNameFunction, * updateApiToken: updateApiTokenFunction, * updateLink: updateLinkFunction, * updateRoomName: updateRoomNameFunction, * updateMember: updateMemberFunction, * imgSrc: 'https://example.com/logo.png' * }} * credentials={{ * apiUserName: 'user123', * apiKey: 'apikey123' * }} * returnUI={true} * noUIPreJoinOptions={{ * action: 'create', * capacity: 10, * duration: 15, * eventType: 'broadcast', * userName: 'Prince', * }} * connectMediaSFU={true} * localLink='http://localhost:3000' * /> * ); * }; * * * export default App; * ``` */ const PreJoinPage = ({ localLink = '', connectMediaSFU = true, parameters, credentials, returnUI = false, noUIPreJoinOptions, createMediaSFURoom = createRoomOnMediaSFU, joinMediaSFURoom = joinRoomOnMediaSFU, }) => { // State variables const [isCreateMode, setIsCreateMode] = useState(false); const [name, setName] = useState(''); const [duration, setDuration] = useState(''); const [eventType, setEventType] = useState(''); const [capacity, setCapacity] = useState(''); const [eventID, setEventID] = useState(''); const [error, setError] = useState(''); const pending = useRef(false); const localConnected = useRef(false); const localData = useRef(undefined); const initSocket = useRef(undefined); const { showAlert, updateIsLoadingModalVisible, connectLocalSocket, updateSocket, updateValidated, updateApiUserName, updateApiToken, updateLink, updateRoomName, updateMember, } = parameters; const handleCreateRoom = async () => { var _a, _b; if (pending.current) { return; } pending.current = true; let payload = {}; if (returnUI) { if (!name || !duration || !eventType || !capacity) { setError('Please fill all the fields.'); return; } payload = { action: 'create', duration: parseInt(duration, 10), capacity: parseInt(capacity, 10), eventType: eventType, userName: name, recordOnly: false, }; } else { if (noUIPreJoinOptions && 'action' in noUIPreJoinOptions && noUIPreJoinOptions.action === 'create') { payload = noUIPreJoinOptions; } else { pending.current = false; throw new Error('Invalid options provided for creating a room without UI.'); } } updateIsLoadingModalVisible(true); if (localLink.length > 0) { const secureCode = Math.random().toString(30).substring(2, 14) + Math.random().toString(30).substring(2, 14); let eventID = new Date().getTime().toString(30) + new Date().getUTCMilliseconds() + Math.floor(10 + Math.random() * 99).toString(); eventID = 'm' + eventID; const eventRoomParams = (_a = localData.current) === null || _a === void 0 ? void 0 : _a.meetingRoomParams_; eventRoomParams.type = eventType; const createData = { eventID: eventID, duration: payload.duration, capacity: payload.capacity, userName: payload.userName, scheduledDate: new Date(), secureCode: secureCode, waitRoom: false, recordingParams: (_b = localData.current) === null || _b === void 0 ? void 0 : _b.recordingParams_, eventRoomParams: eventRoomParams, videoPreference: null, audioPreference: null, audioOutputPreference: null, mediasfuURL: '', }; // socket in main window is required and for no local room, no use of initSocket // for local room, initSocket becomes the local socket, and localSocket is the connection to MediaSFU (if connectMediaSFU is true) // else localSocket is the same as initSocket if (connectMediaSFU && initSocket.current && localData.current && localData.current.apiUserName && localData.current.apiKey) { payload.recordOnly = true; // allow production to mediasfu only; no consumption const response = await roomCreator({ payload, apiUserName: localData.current.apiUserName, apiKey: localData.current.apiKey, validate: false, }); if (response && response.success && response.data && 'roomName' in response.data) { createData.eventID = response.data.roomName; createData.secureCode = response.data.secureCode || ''; createData.mediasfuURL = response.data.publicURL; await createLocalRoom({ createData: createData, link: response.data.link, }); } else { pending.current = false; updateIsLoadingModalVisible(false); setError('Unable to create room on MediaSFU.'); try { updateSocket(initSocket.current); await createLocalRoom({ createData: createData }); pending.current = false; } catch (error) { pending.current = false; updateIsLoadingModalVisible(false); setError(`Unable to create room. ${error}`); } } } else { try { updateSocket(initSocket.current); await createLocalRoom({ createData: createData }); pending.current = false; } catch (error) { pending.current = false; updateIsLoadingModalVisible(false); setError(`Unable to create room. ${error}`); } } } else { await roomCreator({ payload, apiUserName: credentials.apiUserName, apiKey: credentials.apiKey, validate: true, }); pending.current = false; } }; const handleJoinRoom = async () => { if (pending.current) { return; } pending.current = true; let payload = {}; if (returnUI) { if (!name || !eventID) { setError('Please fill all the fields.'); return; } payload = { action: 'join', meetingID: eventID, userName: name, }; } else { if (noUIPreJoinOptions && 'action' in noUIPreJoinOptions && noUIPreJoinOptions.action === 'join') { payload = noUIPreJoinOptions; } else { throw new Error('Invalid options provided for joining a room without UI.'); } } if (localLink.length > 0 && !localLink.includes('mediasfu.com')) { const joinData = { eventID: payload.meetingID, userName: payload.userName, secureCode: '', videoPreference: null, audioPreference: null, audioOutputPreference: null, }; await joinLocalRoom({ joinData: joinData }); pending.current = false; return; } updateIsLoadingModalVisible(true); const response = await joinMediaSFURoom({ payload, apiUserName: credentials.apiUserName, apiKey: credentials.apiKey, localLink: localLink, }); if (response.success && response.data && 'roomName' in response.data) { await checkLimitsAndMakeRequest({ apiUserName: response.data.roomName, apiToken: response.data.secret, link: response.data.link, userName: payload.userName, parameters: parameters, }); setError(''); pending.current = false; } else { pending.current = false; updateIsLoadingModalVisible(false); setError(`Unable to join room. ${response.data ? 'error' in response.data ? response.data.error : '' : ''}`); } }; const joinLocalRoom = async ({ joinData, link = localLink, }) => { var _a; (_a = initSocket.current) === null || _a === void 0 ? void 0 : _a.emit('joinEventRoom', joinData, (response) => { var _a; if (response.success) { updateSocket(initSocket.current); updateApiUserName(((_a = localData.current) === null || _a === void 0 ? void 0 : _a.apiUserName) || ''); updateApiToken(response.secret); updateLink(link); updateRoomName(joinData.eventID); updateMember(joinData.userName); updateIsLoadingModalVisible(false); updateValidated(true); } else { updateIsLoadingModalVisible(false); setError(`Unable to join room. ${response.reason}`); } }); }; const createLocalRoom = async ({ createData, link = localLink, }) => { var _a; (_a = initSocket.current) === null || _a === void 0 ? void 0 : _a.emit('createRoom', createData, (response) => { var _a; if (response.success) { updateSocket(initSocket.current); updateApiUserName(((_a = localData.current) === null || _a === void 0 ? void 0 : _a.apiUserName) || ''); updateApiToken(response.secret); updateLink(link); updateRoomName(createData.eventID); // local needs islevel updated from here // we update member as `userName` + '_2' and split it in the room updateMember(createData.userName + '_2'); updateIsLoadingModalVisible(false); updateValidated(true); } else { updateIsLoadingModalVisible(false); setError(`Unable to create room. ${response.reason}`); } }); }; const roomCreator = async ({ payload, apiUserName, apiKey, validate = true, }) => { const response = await createMediaSFURoom({ payload, apiUserName: apiUserName, apiKey: apiKey, localLink: localLink, }); if (response.success && response.data && 'roomName' in response.data) { await checkLimitsAndMakeRequest({ apiUserName: response.data.roomName, apiToken: response.data.secret, link: response.data.link, userName: payload.userName, parameters: parameters, validate: validate, }); return response; } else { updateIsLoadingModalVisible(false); setError(`Unable to create room. ${response.data ? 'error' in response.data ? response.data.error : '' : ''}`); } }; const checkProceed = async ({ returnUI, noUIPreJoinOptions, }) => { if (!returnUI && noUIPreJoinOptions) { if ('action' in noUIPreJoinOptions && noUIPreJoinOptions.action === 'create') { // update all the required parameters and call const createOptions = noUIPreJoinOptions; if (!createOptions.userName || !createOptions.duration || !createOptions.eventType || !createOptions.capacity) { throw new Error('Please provide all the required parameters: userName, duration, eventType, capacity'); } await handleCreateRoom(); } else if ('action' in noUIPreJoinOptions && noUIPreJoinOptions.action === 'join') { // update all the required parameters and call const joinOptions = noUIPreJoinOptions; if (!joinOptions.userName || !joinOptions.meetingID) { throw new Error('Please provide all the required parameters: userName, meetingID'); } await handleJoinRoom(); } else { throw new Error('Invalid options provided for creating/joining a room without UI.'); } } }; useEffect(() => { if (localLink.length > 0 && !localConnected.current && !initSocket.current) { try { connectLocalSocket === null || connectLocalSocket === void 0 ? void 0 : connectLocalSocket({ link: localLink }).then((response) => { localData.current = response.data; initSocket.current = response.socket; localConnected.current = true; if (!returnUI && noUIPreJoinOptions) { checkProceed({ returnUI, noUIPreJoinOptions }); } }).catch((error) => { showAlert === null || showAlert === void 0 ? void 0 : showAlert({ message: `Unable to connect to ${localLink}. ${error}`, type: 'danger', duration: 3000, }); }); } catch (_a) { showAlert === null || showAlert === void 0 ? void 0 : showAlert({ message: `Unable to connect to ${localLink}. Something went wrong.`, type: 'danger', duration: 3000, }); } } else if (localLink.length === 0 && !initSocket.current) { if (!returnUI && noUIPreJoinOptions) { checkProceed({ returnUI, noUIPreJoinOptions }); } } }, []); const handleToggleMode = () => { setIsCreateMode(!isCreateMode); setError(''); }; /** * Locks the orientation to portrait mode when the component mounts and unlocks on unmount. */ useEffect(() => { Orientation.lockToPortrait(); return () => { Orientation.unlockAllOrientations(); }; }, []); if (!returnUI) { return <></>; } return (<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} style={styles.keyboardAvoidingContainer}> <ScrollView contentContainerStyle={styles.scrollContainer}> <View style={[ styles.container, Platform.OS === 'web' && { maxWidth: 600, alignSelf: 'center' }, ]}> {/* Logo */} <View style={styles.logoContainer}> <Image source={{ uri: parameters.imgSrc || 'https://mediasfu.com/images/logo192.png', }} style={styles.logoImage}/> </View> {/* Input Fields */} <View style={styles.inputContainer}> {isCreateMode ? (<> <TextInput style={styles.inputField} placeholder="Display Name" value={name} onChangeText={setName} autoCapitalize="none" autoCorrect={false} accessibilityLabel="Display Name" placeholderTextColor="gray"/> <TextInput style={styles.inputField} placeholder="Duration (minutes)" value={duration} onChangeText={setDuration} keyboardType="numeric" autoCapitalize="none" autoCorrect={false} accessibilityLabel="Duration (minutes)" placeholderTextColor="gray"/> <RNPickerSelect onValueChange={(value) => { setEventType(value); }} items={[ { label: 'Chat', value: 'chat' }, { label: 'Broadcast', value: 'broadcast' }, { label: 'Webinar', value: 'webinar' }, { label: 'Conference', value: 'conference' }, ]} value={eventType} style={pickerSelectStyles} placeholder={{ label: 'Select Event Type', value: '', color: 'gray', }} useNativeAndroidPickerStyle={false}/> <View style={styles.gap}/> <TextInput style={styles.inputField} placeholder="Room Capacity" value={capacity} onChangeText={setCapacity} keyboardType="numeric" autoCapitalize="none" autoCorrect={false} accessibilityLabel="Room Capacity" placeholderTextColor="gray"/> <Pressable style={styles.actionButton} onPress={handleCreateRoom} accessibilityRole="button" accessibilityLabel="Create Room"> <Text style={styles.actionButtonText}>Create Room</Text> </Pressable> </>) : (<> <TextInput style={styles.inputField} placeholder="Display Name" value={name} onChangeText={setName} autoCapitalize="none" autoCorrect={false} accessibilityLabel="Display Name" placeholderTextColor="gray"/> <TextInput style={styles.inputField} placeholder="Event ID" value={eventID} onChangeText={setEventID} autoCapitalize="none" autoCorrect={false} accessibilityLabel="Event ID" placeholderTextColor="gray"/> <Pressable style={styles.actionButton} onPress={handleJoinRoom} accessibilityRole="button" accessibilityLabel="Join Room"> <Text style={styles.actionButtonText}>Join Room</Text> </Pressable> </>)} {error !== '' && <Text style={styles.errorText}>{error}</Text>} </View> {/* OR Separator */} <View style={styles.orContainer}> <Text style={styles.orText}>OR</Text> </View> {/* Toggle Mode Button */} <View style={styles.toggleContainer}> <Pressable style={styles.toggleButton} onPress={handleToggleMode} accessibilityRole="button" accessibilityLabel={isCreateMode ? 'Switch to Join Mode' : 'Switch to Create Mode'}> <Text style={styles.toggleButtonText}> {isCreateMode ? 'Switch to Join Mode' : 'Switch to Create Mode'} </Text> </Pressable> </View> </View> </ScrollView> </KeyboardAvoidingView>); }; export default PreJoinPage; /** * Stylesheet for the PreJoinPage component. */ const styles = StyleSheet.create({ keyboardAvoidingContainer: { flex: 1, }, scrollContainer: { flexGrow: 1, justifyContent: 'center', backgroundColor: '#53C6E0', paddingVertical: 10, maxHeight: '100%', }, container: { flex: 1, paddingHorizontal: '10%', justifyContent: 'center', alignItems: 'center', }, logoContainer: { marginBottom: 30, alignItems: 'center', }, logoImage: { width: 100, height: 100, borderRadius: 50, }, inputContainer: { width: '100%', marginBottom: 10, }, inputField: { height: 40, width: '100%', borderColor: 'black', borderWidth: 1, marginBottom: 15, paddingHorizontal: 15, borderRadius: 8, backgroundColor: '#ffffff', fontSize: 16, }, actionButton: { backgroundColor: 'black', paddingVertical: 8, borderRadius: 8, alignItems: 'center', marginBottom: 10, }, actionButtonText: { color: 'white', fontWeight: 'bold', fontSize: 14, }, toggleContainer: { alignItems: 'center', justifyContent: 'center', }, toggleButton: { backgroundColor: 'black', paddingVertical: 5, paddingHorizontal: 25, borderRadius: 8, alignItems: 'center', justifyContent: 'center', }, toggleButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, }, errorText: { color: 'red', textAlign: 'center', marginTop: 10, fontSize: 14, }, orContainer: { flexDirection: 'row', alignItems: 'center', marginVertical: 20, }, orText: { color: 'black', fontSize: 16, fontWeight: 'bold', marginHorizontal: 10, }, gap: { marginBottom: 10, }, }); const pickerSelectStyles = StyleSheet.create({ inputIOS: { height: 40, borderColor: 'gray', borderWidth: 1, marginBottom: 20, paddingHorizontal: 5, borderRadius: 5, backgroundColor: '#ffffff', fontSize: 16, color: 'black', paddingRight: 20, }, inputAndroid: { height: 40, borderColor: 'gray', borderWidth: 1, marginBottom: 10, paddingHorizontal: 10, borderRadius: 5, backgroundColor: '#ffffff', fontSize: 16, color: 'black', paddingRight: 20, }, inputWeb: { height: 30, borderColor: 'gray', borderWidth: 1, marginBottom: 10, paddingHorizontal: 10, borderRadius: 5, backgroundColor: '#ffffff', fontSize: 16, color: 'black', paddingRight: 20, }, placeholder: { color: 'gray', }, }); //# sourceMappingURL=PreJoinPage.js.map