Redone UI, add leave and delete logic

This commit is contained in:
Ayushya Amitabh
2020-04-16 01:28:29 -04:00
parent 24b2c6a4e0
commit 1acbca3bf0
15 changed files with 1197 additions and 1968 deletions
+182 -56
View File
@@ -4,7 +4,7 @@ const secret = require('./FirebaseSecret.json');
const express = require('express');
const cors = require('cors');
const authenticator = (req,res,next) => {
const authenticator = (req, res, next) => {
if (req.method === 'OPTIONS') {
res.status(200).send();
return next();
@@ -13,86 +13,212 @@ const authenticator = (req,res,next) => {
const token = req.headers.authorization.split(' ')[1];
const reqToken = token.split(':')[0];
const reqUid = token.split(':')[1];
admin.auth().verifyIdToken(reqToken).then( user => {
if (user.uid === reqUid) {
console.log('Verified Token - UID - ', reqUid);
if (req.method === 'POST') {
const bodydata = JSON.parse(req.body);
bodydata.uid = user.uid;
req.body = JSON.stringify(bodydata);
admin
.auth()
.verifyIdToken(reqToken)
.then((user) => {
if (user.uid === reqUid) {
console.log('Verified Token - UID - ', reqUid);
if (req.method === 'POST') {
const bodydata = JSON.parse(req.body);
bodydata.uid = user.uid;
req.body = JSON.stringify(bodydata);
}
if (req.method === 'GET' || req.method === 'DELETE') {
req.body = JSON.stringify({ uid: user.uid });
}
return next();
} else {
return res.status(401).send('Unauthorized');
}
if (req.method === 'GET') {
req.body = JSON.stringify({uid: user.uid});
}
return next();
} else {
return res.status(401).send('Unauthorized');
}
}).catch(err => {
console.log('Failed Auth - ', req.headers.authorization, '\n', err);
return res.status(500).send(err);
});
})
.catch((err) => {
console.log('Failed Auth - ', req.headers.authorization, '\n', err);
return res.status(500).send(err);
});
return null;
};
const createRoom = async (req,res) => {
const createRoom = async (req, res) => {
const bodydata = JSON.parse(req.body);
console.log('Requested Create - UID - ', bodydata.uid);
if (!bodydata.userName) res.status(400).send('Missing user name');
const roomId = await admin.firestore().collection('rooms').add({
name: bodydata.roomName,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}).then(ref => ref.id)
.catch(err => {
console.error(err);
});
await admin.firestore().collection('rooms').doc(roomId).collection('messages').add({
uid: '<system>',
name: '',
text: `${bodydata.userName} started the party.`,
time: new Date().toISOString(),
});
await admin.firestore().collection('rooms').doc(roomId).collection('users').doc(bodydata.uid).set({
admin: true,
name: bodydata.userName,
});
const roomId = await admin
.firestore()
.collection('rooms')
.add({
name: bodydata.roomName,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
.then((ref) => ref.id)
.catch((err) => {
console.error(err);
});
await admin
.firestore()
.collection('rooms')
.doc(roomId)
.collection('messages')
.add({
uid: '<system>',
name: '',
text: `${bodydata.userName} started the party.`,
time: new Date().toISOString(),
});
await admin
.firestore()
.collection('rooms')
.doc(roomId)
.collection('users')
.doc(bodydata.uid)
.set({
admin: true,
name: bodydata.userName,
});
res.status(201).send({ roomId });
}
};
const joinRoom = async (req, res) => {
const bodydata = JSON.parse(req.body);
console.log('Requested Create - UID - ', bodydata.uid, '\n\tRoom - ', bodydata.roomId);
const exists = await admin.firestore().collection('rooms').doc(bodydata.roomId).get().then(roomSnap => roomSnap.exists);
const userExists = await admin.firestore().collection('rooms').doc(bodydata.roomId).collection('users').doc(bodydata.uid).get().then(userSnap => userSnap.exists);
if (userExists) return res.status(200).send({roomId: bodydata.roomId});
console.log('Requested Join - UID - ', bodydata.uid, '\n\tRoom - ', bodydata.roomId);
const exists = await admin
.firestore()
.collection('rooms')
.doc(bodydata.roomId)
.get()
.then((roomSnap) => roomSnap.exists);
const userExists = await admin
.firestore()
.collection('rooms')
.doc(bodydata.roomId)
.collection('users')
.doc(bodydata.uid)
.get()
.then((userSnap) => userSnap.exists);
if (userExists) return res.status(200).send({ roomId: bodydata.roomId });
if (exists && bodydata.roomId && bodydata.userName) {
await admin.firestore().collection('rooms').doc(bodydata.roomId).collection('messages').add({
uid: '<system>',
name: '',
text: `${bodydata.userName} joined the party.`,
time: new Date().toISOString(),
await admin
.firestore()
.collection('rooms')
.doc(bodydata.roomId)
.collection('messages')
.add({
uid: '<system>',
name: '',
text: `${bodydata.userName} joined the party.`,
time: new Date().toISOString(),
});
await admin
.firestore()
.collection('rooms')
.doc(bodydata.roomId)
.collection('users')
.doc(bodydata.uid)
.set({
admin: false,
name: bodydata.userName,
});
await admin.firestore().collection('rooms').doc(bodydata.roomId).update({
updatedAt: new Date().toISOString(),
});
await admin.firestore().collection('rooms').doc(bodydata.roomId).collection('users').doc(bodydata.uid).set({
admin: false,
name: bodydata.userName,
});
res.status(200).send({roomId: bodydata.roomId});
res.status(200).send({ roomId: bodydata.roomId });
}
if (!bodydata.userName) res.status(400).send('Missing user name');
else res.status(404).send('Party Not Found');
}
};
const sendMessage = async (req, res) => {
const { name, text, time, uid, roomId } = JSON.parse(req.body);
await admin
.firestore()
.collection('rooms')
.doc(roomId)
.collection('messages')
.add({
name,
text,
time,
uid,
})
.catch((messageErr) => res.status(500).send(messageErr));
await admin.firestore().collection('rooms').doc(bodydata.roomId).update({
updatedAt: new Date().toISOString(),
});
};
const leaveRoom = async (req, res) => {
console.log('Requested Leave - UID - ', req.params.uid, '\n\tRoom - ', req.params.rid);
await admin
.firestore()
.collection('rooms')
.doc(req.params.rid)
.collection('users')
.doc(req.params.uid)
.delete()
.catch((err) => res.status(500).send(err));
res.status(200).send();
};
const deleteRoom = async (req, res) => {
console.log('Requested Delete - Room - ', req.params.rid);
console.log(req.body);
const isAdmin = await admin
.firestore()
.collection('rooms')
.doc(req.params.rid)
.collection('users')
.doc(JSON.parse(req.body).uid)
.get()
.then((v) => {
if (v.exists) {
return v.data().admin;
}
return false;
});
if (!isAdmin) {
console.log('[UNAUTHORIZED] Requested Delete - Room - ', req.params.rid);
res.status(401).send();
return;
}
await admin
.firestore()
.collection('rooms')
.doc(req.params.rid)
.collection('messages')
.listDocuments()
.then((messages) => messages.forEach((message) => message.delete()))
.catch((err) => res.status(500).send(err));
await admin
.firestore()
.collection('rooms')
.doc(req.params.rid)
.collection('users')
.listDocuments()
.then((users) => users.forEach((user) => user.delete()))
.catch((err) => res.status(500).send(err));
await admin
.firestore()
.collection('rooms')
.doc(req.params.rid)
.delete()
.catch((err) => res.status(500).send(err));
res.status(200).send();
};
const app = express();
app.use(cors({origin: true}));
app.use(cors({ origin: true }));
app.use(authenticator);
admin.initializeApp({
credential: admin.credential.cert(secret),
databaseURL: "https://watch-party-crx.firebaseio.com"
databaseURL: 'https://watch-party-crx.firebaseio.com',
});
app.post('/create', createRoom);
app.post('/join', joinRoom);
app.post('/message', sendMessage);
app.delete('/leave/:rid/users/:uid', leaveRoom);
app.delete('/delete/:rid', deleteRoom);
exports.widgets = functions.https.onRequest(app);
+353 -1601
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -3,10 +3,8 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.0.4",
"@giphy/js-fetch-api": "^1.5.2",
"@giphy/react-components": "^0.7.1",
"antd": "^4.0.4",
"@material-ui/core": "^4.9.10",
"@material-ui/icons": "^4.9.1",
"autoprefixer": "7.1.6",
"babel-core": "6.26.3",
"babel-eslint": "10.x",
@@ -26,7 +24,7 @@
"eslint-plugin-jsx-a11y": "6.x",
"eslint-plugin-react": "7.x",
"file-loader": "4.2.0",
"firebase": "^7.12.0",
"firebase": "^7.14.0",
"fs-extra": "3.0.1",
"html-webpack-plugin": "^4.0.0-beta.8",
"jest": "20.0.4",
@@ -42,6 +40,7 @@
"react-dom": "^16.4.1",
"react-frame-component": "^4.0.0",
"resolve": "1.6.0",
"route-lite": "^0.3.0",
"style-loader": "0.19.0",
"sw-precache-webpack-plugin": "^0.11.5",
"url-loader": "0.6.2",
+8 -1
View File
@@ -9,7 +9,7 @@
"browser_action": {
"default_popup": "popup.html"
},
"permissions": ["https://*.hulu.com/*", "https://*.netflix.com/*"],
"permissions": ["https://*.hulu.com/*", "https://*.netflix.com/*", "https://*.amazon.com/*"],
"content_scripts": [
{
"matches": ["https://*.hulu.com/*"],
@@ -22,8 +22,15 @@
"run_at": "document_idle",
"js": ["/static/js/content.js"],
"css": ["/static/css/app.css"]
},
{
"matches": ["https://*.amazon.com/*"],
"run_at": "document_idle",
"js": ["/static/js/content.js"],
"css": ["/static/css/app.css"]
}
],
"web_accessible_resources": ["logo192.png"],
"icons": {
"16": "favicon.ico",
"32": "favicon.ico",
+9 -14
View File
@@ -7,7 +7,7 @@ process.env.NODE_ENV = 'development';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
process.on('unhandledRejection', (err) => {
throw err;
});
@@ -51,9 +51,7 @@ if (process.env.HOST) {
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(`If this was unintentional, check that you haven't mistakenly set it in your shell.`);
console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
console.log();
}
@@ -61,7 +59,7 @@ if (process.env.HOST) {
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
choosePort(HOST, DEFAULT_PORT)
.then(port => {
.then((port) => {
if (port == null) {
// We have not found a port.
return;
@@ -70,18 +68,15 @@ choosePort(HOST, DEFAULT_PORT)
const appName = require(paths.appPackageJson).name;
const urls = prepareUrls(protocol, HOST, port);
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler({webpack, config, appName, urls, useYarn});
const compiler = createCompiler({ webpack, config, appName, urls, useYarn });
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web sever.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const serverConfig = createDevServerConfig(proxyConfig, urls.lanUrlForConfig);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
devServer.listen(port, HOST, (err) => {
if (err) {
return console.log(err);
}
@@ -92,14 +87,14 @@ choosePort(HOST, DEFAULT_PORT)
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
['SIGINT', 'SIGTERM'].forEach(function (sig) {
process.on(sig, function () {
devServer.close();
process.exit();
});
});
})
.catch(err => {
.catch((err) => {
if (err && err.message) {
console.log(err.message);
}
+64
View File
@@ -0,0 +1,64 @@
import firebase from 'firebase/app';
import 'firebase/auth';
const API_HOST = window.location.href.startsWith('http://localhost')
? 'http://localhost:5001/watch-party-crx/us-central1/widgets'
: 'https://us-central1-watch-party-crx.cloudfunctions.net/widgets';
export const isAuthed = () => true && firebase.auth().currentUser;
export const doAuth = () => firebase.auth().signInAnonymously();
export const fetcher = async ({ ENDPOINT, METHOD, ...args }) => {
const idToken = await firebase.auth().currentUser.getIdToken();
return fetch(`${API_HOST}/${ENDPOINT}`, {
method: METHOD,
headers: {
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
},
...args,
});
};
export const CreateRoomApi = async (data) => {
if (!isAuthed()) await doAuth();
return fetcher({
ENDPOINT: 'create',
METHOD: 'POST',
body: JSON.stringify(data),
});
};
export const JoinRoomApi = async (data) => {
if (!isAuthed()) await doAuth();
return fetcher({
ENDPOINT: 'join',
METHOD: 'POST',
body: JSON.stringify(data),
});
};
export const SendMessageApi = async (data) => {
if (!isAuthed()) await doAuth();
return fetcher({
ENDPOINT: 'message',
METHOD: 'post',
body: JSON.stringify(data),
});
};
export const LeaveRoomApi = async (data) => {
if (!isAuthed()) await doAuth();
return fetcher({
ENDPOINT: `leave/${data.roomId}/users/${data.uid}`,
METHOD: 'DELETE',
});
};
export const DeleteRoomApi = async (data) => {
if (!isAuthed()) await doAuth();
return fetcher({
ENDPOINT: `delete/${data.roomId}`,
METHOD: 'DELETE',
});
};
+40 -60
View File
@@ -1,65 +1,45 @@
import React, { Component } from 'react';
import firebase from 'firebase/app';
import Chat from './Chat';
import React, { useState } from 'react';
import Router from 'route-lite';
import { Fab, createMuiTheme, ThemeProvider } from '@material-ui/core';
import Create from './Create';
import Loading from './Loading';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
loggedIn: false,
page: 'create',
};
this.updateLoading = this.updateLoading.bind(this);
this.changePage = this.changePage.bind(this);
const darkTheme = createMuiTheme({
palette: {
type: 'dark',
primary: {
main: '#a6d4fa',
},
},
});
const Routes = (props) => (
<Router>
<Create />
</Router>
);
const App = () => {
const [visible, setVisible] = useState(false);
const PartyRoutesWrapper = document.getElementById('party-routes-wrapper');
if (visible) {
document.body.classList.add('visible');
PartyRoutesWrapper && PartyRoutesWrapper.classList.add('visible');
} else {
document.body.classList.remove('visible');
PartyRoutesWrapper && PartyRoutesWrapper.classList.remove('visible');
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
const roomId = window.localStorage.getItem('roomId');
if (user) {
this.setState({
loggedIn: true,
});
if (roomId)
this.setState({
page: 'chat',
});
} else {
if (roomId) {
window.localStorage.removeItem('roomId');
window.localStorage.removeItem('watchPartyUserName');
window.localStorage.removeItem('watchPartyUser');
}
}
});
}
changePage(page) {
this.setState({
page,
});
}
updateLoading(loading) {
this.setState({
loading,
});
}
render() {
const { loading, loggedIn, page } = this.state;
return (
return (
<ThemeProvider theme={darkTheme}>
<div className="party-content-wrapper">
{loading && <Loading />}
{page === 'chat' ? (
<Chat changePage={this.changePage} updateLoading={this.updateLoading} />
) : page === 'create' ? (
<Create
changePage={this.changePage}
loggedIn={loggedIn}
updateLoading={this.updateLoading}
/>
) : null}
<div id="party-routes-wrapper">
<Routes />
</div>
<Fab onClick={() => setVisible(!visible)} className="watch-party-fab">
<span />
</Fab>
</div>
);
}
}
</ThemeProvider>
);
};
export default App;
+149 -40
View File
@@ -1,61 +1,170 @@
import React, { Component } from 'react';
import { Typography, Input, message } from 'antd';
import {
Typography,
TextField,
InputAdornment,
IconButton,
ButtonGroup,
Button,
} from '@material-ui/core';
import firebase from 'firebase/app';
import 'firebase/firestore';
import { RightCircleFilled } from '@ant-design/icons';
import 'firebase/auth';
import Thread from './Thread';
import Send from '@material-ui/icons/SendRounded';
import Sync from '@material-ui/icons/SyncOutlined';
import Exit from '@material-ui/icons/ExitToAppRounded';
import Share from '@material-ui/icons/ShareRounded';
import Delete from '@material-ui/icons/Delete';
import { SendMessageApi, LeaveRoomApi, DeleteRoomApi } from './Api';
import { goTo } from 'route-lite';
import Create from './Create';
export default class Chat extends Component {
constructor (props) {
constructor(props) {
super(props);
this.state = {
messages: []
name: '',
messages: [],
newMessage: '',
admin: false,
};
this.sendMessage = this.sendMessage.bind(this);
}
componentDidMount() {
firebase.firestore()
.collection('rooms')
.doc(window.localStorage.getItem('roomId'))
.onSnapshot(snap => {
this.setState(snap.data());
}, err => {
this.props.updateLoading(true);
if (err.code === 'permission-denied') {
message.error('Couldn\'t join old party');
this.props.changePage('create');
this.props.updateLoading(false);
window.addEventListener('keyup', (ev) => {
if (ev.code === 'Enter') {
if (ev.target === document.getElementById('chat-message-box-input')) {
if (ev.shiftKey === false) {
const trim = this.state.newMessage.replace(/^\s+|\s+$/g, '');
if (trim !== '') this.sendMessage(trim);
this.setState({ newMessage: '' });
}
}
}
});
firebase
.firestore()
.collection('rooms')
.doc(window.localStorage.getItem('watchPartyRoomId'))
.onSnapshot(
(snap) => {
this.setState(snap.data());
},
(err) => console.error(err)
);
firebase
.firestore()
.collection('rooms')
.doc(window.localStorage.getItem('watchPartyRoomId'))
.collection('users')
.onSnapshot((snap) => {
const isAdmin = snap.docs
.filter((user) => user.id === firebase.auth().currentUser.uid)[0]
.data().admin;
this.setState({ admin: isAdmin });
});
firebase
.firestore()
.collection('rooms')
.doc(window.localStorage.getItem('watchPartyRoomId'))
.collection('messages')
.orderBy('time')
.onSnapshot((snap) => {
this.setState({ messages: snap.docs.map((v) => ({ ...v.data(), id: v.id })) });
const pcc = document.getElementsByClassName('thread-container')[0];
if (pcc) pcc.scrollTop = pcc.scrollHeight;
});
}
endParty() {
const data = {
roomId: window.localStorage.getItem('watchPartyRoomId'),
};
DeleteRoomApi(data).then((res) => {
if (res.status === 200) {
window.localStorage.removeItem('watchPartyRoomId');
goTo(Create);
}
})
firebase.firestore()
.collection('rooms')
.doc(window.localStorage.getItem('roomId'))
.collection('messages')
.onSnapshot(snap => {
this.setState({messages: snap.docs.map(v => ({ ...v.data(), id: v.id}) )});
});
}
leaveParty() {
const data = {
roomId: window.localStorage.getItem('watchPartyRoomId'),
uid: firebase.auth().currentUser.uid,
};
LeaveRoomApi(data).then((res) => {
if (res.status === 200) {
window.localStorage.removeItem('watchPartyRoomId');
goTo(Create);
}
});
}
shareParty() {
const el = document.createElement('textarea');
el.value = window.localStorage.getItem('watchPartyRoomId');
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
sendMessage(message) {
const data = {
roomId: window.localStorage.getItem('watchPartyRoomId'),
name: window.localStorage.getItem('watchPartyUserName'),
uid: firebase.auth().currentUser.uid,
text: message,
time: new Date().toISOString(),
};
SendMessageApi(data);
}
render() {
const {messages, name} = this.state;
const { admin, name, messages, newMessage } = this.state;
return (
<div className="content__wrapper">
<div>
<Typography.Title level={3}>{name}</Typography.Title>
<Typography.Text underline>Watch Party</Typography.Text>
</div>
<div className="party_chat_container">
<div className="party-chat-container">
<Typography className="brand-title" variant="overline" display="block">
WATCH PARTY
</Typography>
<Typography className="room-title" variant="h4" gutterBottom>
{name}
</Typography>
<ButtonGroup className="party-controls" size="small" color="secondary" fullWidth>
<Button onClick={this.shareParty} startIcon={<Share />}>
Share
</Button>
<Button startIcon={<Sync />}>Sync</Button>
<Button onClick={this.leaveParty} startIcon={<Exit />}>
Leave
</Button>
{admin && (
<Button onClick={this.endParty} startIcon={<Delete />}>
End Party
</Button>
)}
</ButtonGroup>
<div className="thread-container">
<Thread messages={messages} />
</div>
<div className="party_chat_input">
<Input.TextArea
autoSize={{
maxRows:3,
minRows:1,
}}
id="chat_input"
placeholder="Begin typing..."
/>
<RightCircleFilled id="chat_send" />
</div>
<TextField
onSubmit={(e) => console.log(e)}
multiline
rowsMax="3"
size="small"
placeholder="New message... (<Enter> to send)"
variant="outlined"
className="chat-message-box"
value={newMessage}
onChange={(e) => this.setState({ newMessage: e.target.value })}
InputProps={{
id: 'chat-message-box-input',
endAdornment: (
<InputAdornment position="end">
<IconButton edge="end">
<Send />
</IconButton>
</InputAdornment>
),
}}
/>
</div>
);
}
+117 -131
View File
@@ -1,143 +1,129 @@
import React, { useState } from 'react';
import firebase from 'firebase/app';
import 'firebase/auth';
import { Button, Input, Divider, Typography, message } from 'antd';
import { PlusCircleFilled, PlayCircleFilled } from '@ant-design/icons';
import {
Button,
InputAdornment,
TextField,
Typography,
IconButton,
Paper,
} from '@material-ui/core';
import Save from '@material-ui/icons/Save';
import { JoinRoomApi, CreateRoomApi } from './Api';
import { goTo } from 'route-lite';
import Chat from './Chat';
const login = async () => firebase.auth().signInAnonymously();
const saveName = (name) => window.localStorage.setItem('watchPartyUserName', name);
const getName = () => window.localStorage.getItem('watchPartyUserName');
const Create = props => {
const [userName, setUserName] = useState(window.localStorage.getItem('watchPartyUserName') || '');
const [roomName, setRoomName] = useState('');
const [roomCode, setRoomCode] = useState(window.localStorage.getItem('roomId') || '');
const saveRoomId = (rid) => window.localStorage.setItem('watchPartyRoomId', rid);
const getRoomId = () => window.localStorage.getItem('watchPartyRoomId');
const requestCreateRoom = async () => {
props.updateLoading(true);
if (userName === '') {
message.error('You forgot your name');
props.updateLoading(false);
return;
}
if (roomName === '') {
message.error('You forgot to give a party name');
props.updateLoading(false);
return;
}
if (!props.loggedIn) await login();
firebase
.auth()
.currentUser.getIdToken()
.then(idToken => {
fetch('http://localhost:5001/watch-party-crx/us-central1/widgets/create', {
method: 'POST',
headers: {
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
},
body: JSON.stringify({
roomName,
userName,
}),
})
.then(res => res.json())
.then(res => {
res.roomId && window.localStorage.setItem('roomId', res.roomId);
window.localStorage.setItem('watchPartyUserName', userName);
window.localStorage.setItem('watchPartyUser', firebase.auth().currentUser.uid);
props.changePage('chat');
props.updateLoading(false);
});
});
const joinRoom = async (data) => {
const apidata = {
roomId: data.joinName.trim(),
userName: data.name.trim(),
};
const confirmedRoomId = await JoinRoomApi(apidata)
.then((res) => res.json())
.then((res) => res.roomId)
.catch((err) => console.error('Join Room API error', err));
if (confirmedRoomId === apidata.roomId) {
saveRoomId(confirmedRoomId);
saveName(apidata.userName);
goTo(Chat);
}
};
const requestJoinRoom = async () => {
props.updateLoading(true);
if (userName === '') {
message.error('You forgot your name');
props.updateLoading(false);
return;
}
if (roomCode === '') {
message.error('Please enter a party code');
props.updateLoading(false);
return;
}
if (!props.loggedIn) await login();
firebase
.auth()
.currentUser.getIdToken()
.then(idToken => {
fetch(`http://localhost:5001/watch-party-crx/us-central1/widgets/join`, {
method: 'POST',
headers: {
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
},
body: JSON.stringify({
roomId: roomCode.trim(),
userName,
}),
})
.then(res => {
if (res.status === 200) return res.json();
if (res.status === 404) message.error("That party doesn't exist");
return {};
})
.then(res => {
res.roomId && window.localStorage.setItem('roomId', res.roomId);
window.localStorage.setItem('watchPartyUserName', userName);
window.localStorage.setItem('watchPartyUser', firebase.auth().currentUser.uid);
props.changePage('chat');
props.updateLoading(false);
});
});
const createRoom = async (data) => {
const apidata = {
roomName: data.createName,
userName: data.name,
};
CreateRoomApi(apidata)
.then((res) => res.json())
.then((res) => {
saveRoomId(res.roomId);
saveName(data.name);
goTo(Chat);
});
};
const Create = (props) => {
const [name, setName] = useState(getName() || '');
const [createName, setCreateName] = useState('');
const [joinName, setJoinName] = useState(getRoomId() || '');
if (getName() && getRoomId())
joinRoom({
joinName: getRoomId(),
name: getName(),
});
return (
<div
style={{
padding: 20,
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
position: 'relative',
}}
>
<Typography.Title style={{ position: 'absolute', top: 20, left: 20 }} level={3}>
Watch Party
</Typography.Title>
<Input
style={{ borderRadius: 23, marginBottom: 15 }}
value={userName}
onChange={e => setUserName(e.target.value)}
placeholder="Your Name"
size="large"
/>
<Divider />
<Input
style={{ borderRadius: 23, marginBottom: 15 }}
value={roomName}
onChange={e => setRoomName(e.target.value)}
placeholder="Room Name"
size="large"
/>
<Button onClick={requestCreateRoom} block size="large" type="primary" shape="round">
<PlusCircleFilled style={{ color: 'white' }} />
Start Watch Party
</Button>
<Divider />
<Input
style={{ borderRadius: 23, marginBottom: 15 }}
value={roomCode}
onChange={e => setRoomCode(e.target.value)}
placeholder="Room Code"
size="large"
/>
<Button onClick={requestJoinRoom} block size="large" shape="round">
<PlayCircleFilled />
Join Watch Party
</Button>
<div className="party-create-container">
<Typography className="brand-title" variant="h6" display="block" gutterBottom>
WATCH PARTY
</Typography>
<div className="party-create-inputs">
<TextField
fullWidth
value={name}
onChange={(e) => setName(e.target.value)}
className="name-field"
label="Name"
variant="filled"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => saveName(name)}>
<Save />
</IconButton>
</InputAdornment>
),
}}
/>
<Paper className="join-card" elevation={3}>
<Typography variant="button" display="block" gutterBottom>
CREATE PARTY
</Typography>
<TextField
fullWidth
value={createName}
className="card-field"
onChange={(e) => setCreateName(e.target.value)}
label="Party Name"
/>
<Button
onClick={() => createRoom({ name, createName })}
disabled={createName === ''}
color="primary"
fullWidth
variant="contained"
>
CREATE
</Button>
</Paper>
<Paper className="join-card" elevation={3}>
<Typography variant="button" display="block" gutterBottom>
JOIN PARTY
</Typography>
<TextField
color="secondary"
fullWidth
value={joinName}
className="card-field"
onChange={(e) => setJoinName(e.target.value)}
label="Party Name"
/>
<Button
onClick={() => joinRoom({ joinName, name })}
disabled={joinName === ''}
variant="contained"
color="secondary"
fullWidth
>
JOIN
</Button>
</Paper>
</div>
</div>
);
};
+55 -1
View File
@@ -1,10 +1,64 @@
.thread-item {
width: 100%;
white-space: pre-wrap;
margin-top: 25px !important;
font-size: 12px !important;
position: relative;
}
.thread-item.no-space {
margin-top: 0 !important;
}
.system-message {
display: block;
text-align: center;
width: 100%;
text-align: center;
color: rgba(100,100,100,0.8) !important;
font-size: 12px;
margin-top: 7.5px !important;
color: rgba(100, 100, 100, 0.8) !important;
}
.my-message {
display: block;
width: fit-content;
text-align: right;
margin-left: auto !important;
margin-right: 5px !important;
margin-top: 5px !important;
padding: 2.5px 10px !important;
border-radius: 13px;
max-width: 60%;
word-wrap: break-word;
background-color: rgb(49, 49, 49);
}
.party-message {
display: block;
width: fit-content;
max-width: 60%;
word-wrap: break-word;
margin-top: 5px !important;
padding: 2.5px 10px !important;
border-radius: 13px;
background-color: rgb(88, 88, 88);
}
.thread-item .name {
color: rgb(149, 149, 149);
font-size: 10px !important;
}
.time {
position: absolute;
bottom: 50%;
opacity: 0;
transition: all ease-in-out 0.3s;
}
.party-message ~ .time {
right: 0;
}
.party-message:hover ~ .time {
opacity: 1;
}
.my-message ~ .time {
left: 0;
}
.my-message:hover ~ .time {
opacity: 1;
}
+42 -6
View File
@@ -1,13 +1,49 @@
import React from 'react';
import { Typography } from 'antd';
import { Typography } from '@material-ui/core';
import './Thread.css';
import firebase from 'firebase/app';
import 'firebase/auth';
const Thread = props =>
props.messages.map(m => {
console.log(m);
const Thread = (props) =>
props.messages.map((m, midx) => {
if (m.uid === '<system>') {
return <Typography.Text className="thread-item system-message" key={m.id} disabled>{m.text}</Typography.Text>
return (
<Typography variant="body2" className="thread-item system-message" key={m.id} disabled>
{m.text}
</Typography>
);
} else if (m.uid === firebase.auth().currentUser.uid) {
return (
<div
className={`thread-item ${
midx > 0 ? (props.messages[midx - 1].uid !== m.uid ? '' : 'no-space') : ''
}`}
key={m.id}
>
<Typography variant="body1" className="my-message">
{m.text}
</Typography>
<span className="time">{new Date(m.time).toLocaleTimeString()}</span>
</div>
);
} else {
return (
<div
className={`thread-item ${
midx > 0 ? (props.messages[midx - 1].uid !== m.uid ? '' : 'no-space') : ''
}`}
key={m.id}
>
<Typography variant="caption" className="name">
{midx > 0 ? (props.messages[midx - 1].uid !== m.uid ? m.name : '') : m.name}
</Typography>
<Typography variant="body1" className="party-message">
{m.text}
</Typography>
<span className="time">{new Date(m.time).toLocaleTimeString()}</span>
</div>
);
}
});
export default Thread;
export default Thread;
-23
View File
@@ -1,23 +0,0 @@
/* #party_close {
display: inline;
margin-right: 0.625rem;
}
#__player__ .Player__container {
width: 75%;
resize: horizontal;
}
#__next {
width: 75%;
resize: horizontal;
}
#party_create {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.hidden {
display: none;
} */
+135 -4
View File
@@ -1,7 +1,138 @@
@import '~antd/dist/antd.css';
.party-content-wrapper {
position: relative;
body {
margin: 0 !important;
background-color: black;
transition: max-width ease-in-out 0.35s;
}
body.visible {
width: 65%;
}
body.visible .watch-party-fab {
right: calc(35% - 4rem) !important;
top: 1.5rem;
}
body.netflix.visible .AkiraPlayer, /* NETFLIX */
body.hulu.visible .Player__container.addFocus,/* HULU */
body.amazon-prime.visible #dv-web-player.dv-player-fullscreen /* AMAZON PRIME */ {
max-width: 65% !important;
}
/* HULU MINIMIZED VIDEO */
body.hulu.visible #__player__.Player__container.Player__container--modal {
right: calc(35% + 2rem) !important;
}
/* HULU & AMAZON PRIME FIX */
body.hulu #party-routes-wrapper,
body.amazon-prime #party-routes-wrapper {
width: 35% !important;
height: 100% !important;
}
/* NETFLIX HEADER FIX */
body.netflix .pinning-header-container {
right: 35% !important;
}
#party-routes-wrapper {
margin: 0;
position: fixed;
background-color: rgb(27, 27, 27);
bottom: 0;
width: calc(35% - 2rem);
right: -35%;
transition: all ease-in-out 0.3s;
height: calc(100% - 2rem);
padding: 1rem;
color: white;
display: flex;
flex-direction: column;
}
#party-routes-wrapper.visible {
right: 0;
}
.watch-party-fab {
z-index: 9999;
position: fixed !important;
bottom: 1rem !important;
right: -2rem !important;
background-color: black !important;
/* background-image: url('/logo192.png'); */
background-image: url('chrome-extension://__MSG_@@extension_id__/logo192.png');
background-position: center;
background-repeat: no-repeat;
background-size: 2rem;
transition: all ease-in-out 0.3s !important;
}
.watch-party-fab:hover {
right: 1rem !important;
}
.watch-party-fab .fab-logo {
max-width: 65%;
max-height: 65%;
}
.party-create-container {
display: flex;
flex-direction: column;
height: 100%;
}
.party-create-container .brand-title {
padding-top: 1.4rem !important;
padding-left: 3.5rem !important;
}
.party-create-container .name-field {
margin-bottom: 2rem !important;
}
.party-create-container .join-card {
padding: 1rem !important;
margin-bottom: 2rem;
}
.party-create-container .join-card .card-field {
margin-bottom: 1rem !important;
}
.party-create-container .party-create-inputs {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding: 2rem;
}
.party-chat-container {
display: flex;
width: 100%;
max-height: 100%;
flex-direction: column;
flex: 1;
}
.thread-container {
max-height: 100%;
flex: 1;
overflow-y: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.room-title,
.brand-title {
padding-left: 3.5rem !important;
}
.thread-container::-webkit-scrollbar {
width: 5px;
}
.thread-container::-webkit-scrollbar-track {
background-color: rgb(90, 90, 90);
border-radius: 1rem;
}
.thread-container::-webkit-scrollbar-thumb {
background-color: rgb(118, 182, 255);
border-radius: 13px;
}
textarea::-webkit-scrollbar {
display: none;
}
.chat-message-box > div {
border-radius: 26px;
}
.party-controls {
margin-top: 1rem;
margin-bottom: 1rem;
}
+10 -18
View File
@@ -1,11 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import firebase from 'firebase/app';
import 'antd/dist/antd.css';
import './netflix.css';
import './hulu.css';
import './index.css';
import App from './App';
import './index.css';
const firebaseConfig = {
apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU',
@@ -24,20 +21,15 @@ const PartyWrapper = document.createElement('div');
PartyWrapper.id = 'party_wrapper';
document.body.appendChild(PartyWrapper);
// STYLINGS
if (window.location.href.startsWith('https://www.hulu.com')) {
// HULU
} else if (window.location.href.startsWith('https://www.netflix.com')) {
// NETFLIX
PartyWrapper.classList.add('netflix');
document.body.classList.add('netflix');
} else if (window.location.href.includes('amazon.com')) {
// PRIME VIDEO
} else {
// LOCAL HOST
PartyWrapper.classList.add('netflix');
document.body.classList.add('netflix');
}
const HREF = window.location.href;
// https://www.netflix. -- netflix
// https://www.hulu. -- hulu
// https://www.amazon. -- amazon prime
if (HREF.startsWith('https://www.amazon')) document.body.classList.add('amazon-prime');
if (HREF.startsWith('https://www.hulu')) document.body.classList.add('hulu');
if (HREF.startsWith('https://www.netflix')) document.body.classList.add('netflix');
ReactDOM.render(
<React.StrictMode>
+23 -2
View File
@@ -2,6 +2,7 @@ body.netflix {
display: flex;
height: 100vh;
overflow: hidden;
background-color: black;
}
body.netflix #appMountPoint {
z-index: 999;
@@ -41,11 +42,28 @@ body.netflix #appMountPoint {
flex-direction: column;
}
#party_wrapper.netflix .party_chat_container {
max-height: max-content;
overflow-y: auto;
padding-bottom: 1rem;
flex: 1;
padding-right: 1rem;
}
#party_wrapper.netflix h3{
#party_wrapper.netflix h3 {
color: white;
}
#party_wrapper.netflix h4 {
display: flex;
align-items: center;
font-weight: 200;
color: white;
}
#party_wrapper.netflix h4 button {
margin-right: 15px;
}
#party_wrapper.netflix h4 span > svg {
width: 15px;
height: 15px;
}
.party_chat_input {
position: relative;
display: flex;
@@ -80,8 +98,11 @@ body.netflix #appMountPoint {
justify-content: center;
align-items: center;
}
#loading_container svg{
#loading_container svg {
color: wheat;
width: 40px;
height: 40px;
}
.ant-popover-inner-content {
padding: 0;
}