/************************************************************************ Copyright (C) Ayushya Amitabh - All Rights Reserved Unauthorized copying of this file, via any medium is strictly prohibited Proprietary and confidential Written by Ayushya Amitabh , 2020 https://www.chromewatchparty.com/copyright ========================================================================= ALL CONTENT ON IN WATCH PARTY'S CHROME EXTENSION APP, UNLESS OTHERWISE CITED ARE THE SOLE INTELECTUAL PROPERTY OF AYUSHYA AMITABH, AND MAY NOT BE COPIED, EDITED, OR REPRODUCED WITHOUT PERMISSION. Netflix logo is a trademark and intellectual property of Netflix, Inc. Disney+ logo is a trademark and intellectual property of The Walt Disney Company Prime logo is a trademark and intellectual property of Amazon.com, Inc. YouTube logo is a trademark and intellectual property of Google, Inc. Hulu logo is a trademark and intellectual property of Hulu, LLC. HBO Now and HBO Max logos are trademark and intellectual property of WarnerMedia Direct,LLC. ************************************************************************/ /*global chrome */ import React, { Component } from 'react'; import { Typography, TextField, InputAdornment, IconButton, ButtonGroup, Button, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails, Card, CardContent, Tooltip, Switch, FormControlLabel, Chip, } from '@material-ui/core'; import Thread from './Thread'; import { Carousel } from '@giphy/react-components'; import { GiphyFetch } from '@giphy/js-fetch-api'; import { SendMessageApi, LeaveRoomApi, DeleteRoomApi, UpdatePlayerApi, getPlayerSource, PLAYER_SOURCE, LocalData, PLAYER_STATUS, emptyLocalData, updateLocalData, } from './Api'; import { Send, ExitToApp as Exit, Share, Delete, CallRounded as PhoneEnable, CallEndRounded as PhoneDisable, MicOff, Mic as MicOn, Gif, KeyboardArrowDown, PauseCircleFilled, LinkOutlined, PlayCircleFilled, Cancel, Error, FullscreenExit, Fullscreen, } from '@material-ui/icons'; import Create from './Create'; import { goTo } from 'route-lite'; import CallManager from './CallManager'; import VideoListener from './VideoListener'; const giphyFetch = new GiphyFetch('ojJbIMUN6XRMn4mHiFFav3rMCpXUg8sq'); const alerts = [ { condition: () => getPlayerSource() === PLAYER_SOURCE.HBOMAX || getPlayerSource() === PLAYER_SOURCE.HBONOW, message: 'Functionality may be buggy, HBO has a horrible design.', }, { condition: () => getPlayerSource() === PLAYER_SOURCE.PEACOCK, message: 'Functionality may be buggy, support on this site is in testing.', }, ]; export default class Chat extends Component { constructor(props) { super(props); this.state = { name: '', messages: [], newMessage: '', admin: null, users: [], usersOnCall: {}, gif: false, onCall: false, muted: false, callActionPending: false, menuPopover: true, allowChatNotification: LocalData().allowChatNotification !== undefined ? LocalData().allowChatNotification : true, isFullscreen: document.fullscreen, }; this.port = null; this.gifClick = this.gifClick.bind(this); this.endParty = this.endParty.bind(this); this.handleMute = this.handleMute.bind(this); this.leaveParty = this.leaveParty.bind(this); this.shareParty = this.shareParty.bind(this); this.syncPlayer = this.syncPlayer.bind(this); this.sendMessage = this.sendMessage.bind(this); this.getCarousel = this.getCarousel.bind(this); this.handlePhoneCall = this.handlePhoneCall.bind(this); this.callEventListener = this.callEventListener.bind(this); this.sendMessageInvoker = this.sendMessageInvoker.bind(this); this.videoEventListener = this.videoEventListener.bind(this); this.mVideoListener = new VideoListener(this.videoEventListener); this.updateAllowChatNotification = this.updateAllowChatNotification.bind(this); this.mCallManager = new CallManager(this.callEventListener, this.props.notify); } componentDidMount() { // EVENT CAPTURE FOR ENTER TO SEND window.onkeypress = (ev) => { if (ev.code === 'Enter') { if (ev.target === document.getElementById('chat-message-box-input')) { if (ev.shiftKey === false && this.state.gif === true) ev.preventDefault(); if (ev.shiftKey === false && this.state.gif === false) { ev.preventDefault(); this.sendMessageInvoker(); } } } }; // FULLSCREEN LISTENER document.addEventListener('fullscreenchange', (ev) => { this.setState({ isFullscreen: document.fullscreen }); }); // ACTIVE TRACKER updateLocalData({ lastActive: new Date().getTime() }); setInterval(() => { updateLocalData({ lastActive: new Date().getTime() }); }, 60000); // VIDEO LISTENER START this.mVideoListener.start(); // CHROME FIREBASE FIRESTORE LISTENER this.port = chrome.runtime.connect({ name: LocalData().partyName }); // Initialize listener this.port.postMessage({ type: 'INIT', roomId: LocalData().partyName, }); // Listener this.port.onMessage.addListener((response) => { if (response.isError === true) { this.props.notify('Uh oh. Got lost from the party.'); goTo(Create, this.props); return; } // IF CHAT IS HIDDEN AND ALLOWS NOTIFICATION if (!document.querySelector('body.visible') && this.state.allowChatNotification === true) { if (response.messages && response.messages.length !== this.state.messages.length) { const lastMessage = response.messages[response.messages.length - 1]; var notificationMessage = ''; if (lastMessage.uid !== '') notificationMessage += lastMessage.name + ': '; notificationMessage += lastMessage.text; this.props.notify(notificationMessage); } } this.setState(response); if (response.player) this.mVideoListener.doUpdate(response.player); const pcc = document.querySelector('.thread-container'); if (pcc) pcc.scrollTop = pcc.scrollHeight; }); // DONE WITH MOUNTING TASKS this.props.updateLoading(false); } componentWillUnmount() { this.mVideoListener.end(); this.leaveParty(); } videoEventListener(video) { this.syncPlayer(video); } callEventListener(event) { this.setState(event); } syncPlayer(video) { const { status, showName, time, source, element, showChanged } = video; if (element) { UpdatePlayerApi({ roomId: LocalData().partyName, showChanged, userName: LocalData().userName, player: { status, showName, time, source, updatedBy: LocalData().userUid, url: window.location.href, }, }).catch((err) => { this.props.updateLoading(false); this.props.notify(err.message || err.statusText); console.error('Sync Party API error', err); }); } else { this.mVideoListener.start(); } } endParty() { this.props.notify('Ending party...'); this.props.updateLoading(true); const data = { partyName: LocalData().partyName, }; DeleteRoomApi(data) .then((res) => { this.props.notify('Ended party'); emptyLocalData(); goTo(Create, this.props); this.props.updateLoading(false); }) .catch((err) => { this.props.updateLoading(false); this.props.notify(err.message || err.statusText); console.error('End Party API error', err); }); } leaveParty() { this.props.notify('Leaving party...'); this.props.updateLoading(true); const data = { partyName: LocalData().partyName, userUid: LocalData().userUid, }; LeaveRoomApi(data) .then((res) => { this.props.notify('Left party'); emptyLocalData(); goTo(Create, this.props); this.props.updateLoading(false); }) .catch((err) => { this.props.updateLoading(false); this.props.notify(err.message || err.statusText); console.error('End Party API error', err); }); } shareParty() { const el = document.createElement('textarea'); el.value = `Join my party on Watch Party!\nParty Name: ${LocalData().partyName}\nParty Pin: ${ LocalData().partyPin }\n${window.location.href}\n---\nGet Watch Party at https:\\\\get.chromewatchparty.com`; document.body.appendChild(el); el.select(); document.execCommand('copy'); document.body.removeChild(el); this.props.notify('Copied party invitation'); } sendMessage(message) { const data = { roomId: LocalData().partyName, name: LocalData().userName, uid: LocalData().userUid, text: message, time: new Date(), type: '', }; if (this.state.gif === true) data.type = 'giphy'; SendMessageApi(data).catch((err) => { this.props.notify(err.message || err.statusText); console.error('Send Message API error', err); }); } sendMessageInvoker(overrideMessage) { if (overrideMessage) this.sendMessage(overrideMessage); else { const trim = this.state.newMessage.replace(/^\s+|\s+$/g, ''); if (trim !== '') this.sendMessage(trim); } this.setState({ newMessage: '' }); } gifClick(iGif, ev) { ev.preventDefault(); this.sendMessageInvoker(iGif.images.original.url); } getCarousel(offset) { return this.state.newMessage === '' ? giphyFetch.trending({ offset, limit: 1 }) : giphyFetch.search(this.state.newMessage, { offset, limit: 1 }); } handlePhoneCall() { const { onCall, conference, admin } = this.state; this.mCallManager.toggleCall(onCall, conference, admin); } handleMute() { this.mCallManager.toggleMute(this.state.muted); } getPrettyTime(t) { return t > 3600 ? new Date(t * 1000).toISOString().substr(11, 8) : new Date(t * 1000).toISOString().substr(14, 5); } updateAllowChatNotification() { updateLocalData({ allowChatNotification: !this.state.allowChatNotification }); this.setState({ allowChatNotification: !this.state.allowChatNotification }); } toggleFullscreen() { if (document.fullscreen) { document.exitFullscreen(); } else { document.documentElement.requestFullscreen(); } } render() { const { admin, name, messages, newMessage, gif, onCall, muted, player, allowChatNotification, users, callActionPending, usersOnCall, isFullscreen, } = this.state; return (
} >
WATCH PARTY {name}
), }} /> {admin && ( )} } label={ Allow chat notification when side panel is closed
{allowChatNotification ? 'Currently Enabled' : 'Currently Disabled'}
} labelPlacement="end" /> PARTY MEMBERS
{users.map((user) => ( ))}
{Object.keys(usersOnCall).length > 0 ? ( MEMBERS IN CALL ) : null} {Object.keys(usersOnCall).length > 0 ? (
{Object.keys(usersOnCall).map((id) => { return usersOnCall[id].status === 'Connected' || usersOnCall[id].status === 'Connecting' ? ( ) : null; })}
) : null}
{player ? ( {player.showName && player.status !== PLAYER_STATUS.NOT_PLAYING ? ( {player.status === PLAYER_STATUS.PAUSED ? `PAUSED AT ${this.getPrettyTime(player.time)}` : player.status === PLAYER_STATUS.PLAYING ? `STARTED PLAYING FROM ${this.getPrettyTime(player.time)}` : `SHOW IS AT ${this.getPrettyTime(player.time)}`}
{player.status === PLAYER_STATUS.PAUSED ? ( ) : ( )}
{player.showName}
) : ( Nothing playing right now )} {player && player.showName && player.status ? ( ) : null}
) : null}
{alerts.map((a) => { if (a.condition() === true) { return ( {a.message} ); } else return null; })}
{gif || newMessage ? null : ( {isFullscreen ? : } )} {gif || newMessage ? null : ( {onCall ? ( ) : ( )} )} {!gif && !newMessage && onCall && !muted ? ( ) : !gif && !newMessage && onCall && muted ? ( ) : null} { this.setState({ newMessage: e.target.value }); if (this.state.gif) { const carousel = document.querySelector('.giphy-carousel'); setTimeout(() => carousel.scroll(carousel.scrollWidth, carousel.scrollHeight), 250); } }} InputProps={{ id: 'chat-message-box-input', endAdornment: ( this.sendMessageInvoker(newMessage)} edge="end" > ), startAdornment: ( this.setState({ gif: !gif })} edge="start"> {gif ? : } ), }} />
{gif ? ( ) : null}
); } }