diff --git a/app/package-lock.json b/app/package-lock.json index 2b57873..3507536 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "watch-party-crx", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1914,6 +1914,64 @@ } } }, + "@voxeet/voxeet-web-sdk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@voxeet/voxeet-web-sdk/-/voxeet-web-sdk-2.3.1.tgz", + "integrity": "sha512-y3/rewt+Rtkk7KVUY9DuYffAY9s+m2nrvumfFuwMnOxAeJw2N4fB1KuyGx80MQqk70xpFfZCLTt3JO6l19uJIg==", + "requires": { + "bowser": "^2.8.1", + "browserslist": "^4.5.2", + "btoa": "^1.1.2", + "events": "^1.1.0", + "get-float-time-domain-data": "^0.1.0", + "global": "^4.4.0", + "lodash": "^4.17.4", + "moment": "^2.24.0", + "sdp": "^2.3.0", + "webrtc-adapter": "^6.3.2" + }, + "dependencies": { + "browserslist": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", + "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "requires": { + "caniuse-lite": "^1.0.30001043", + "electron-to-chromium": "^1.3.413", + "node-releases": "^1.1.53", + "pkg-up": "^2.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001066", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz", + "integrity": "sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==" + }, + "electron-to-chromium": { + "version": "1.3.455", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.455.tgz", + "integrity": "sha512-4lwnxp+ArqOX9hiLwLpwhfqvwzUHFuDgLz4NTiU3lhygUzWtocIJ/5Vix+mWVNE2HQ9aI1k2ncGe5H/0OktMvA==" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "node-releases": { + "version": "1.1.57", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.57.tgz", + "integrity": "sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw==" + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "^2.1.0" + } + } + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -3054,6 +3112,11 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "bowser": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", + "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -3248,6 +3311,11 @@ "node-int64": "^0.4.0" } }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -4951,6 +5019,11 @@ "urijs": "^1.16.1" } }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -7628,6 +7701,11 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, + "get-float-time-domain-data": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-float-time-domain-data/-/get-float-time-domain-data-0.1.0.tgz", + "integrity": "sha1-XYVZJKQwOITJY4qrEnzOTQBlljs=" + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -7686,6 +7764,15 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", @@ -10084,6 +10171,14 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, "mini-css-extract-plugin": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz", @@ -10178,6 +10273,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz", + "integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -12375,8 +12475,7 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.1", @@ -13662,6 +13761,14 @@ "resolved": "https://registry.npmjs.org/route-lite/-/route-lite-0.3.0.tgz", "integrity": "sha512-5A5Mh7gbQfk1P7u0jh0O87vu+/zAyS1xx16YO6ds+4oeLR6cuVtncEA8/d4qevSBv4hijvLloI+IKybtZMUB9g==" }, + "rtcpeerconnection-shim": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz", + "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==", + "requires": { + "sdp": "^2.6.0" + } + }, "run-async": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", @@ -13760,6 +13867,11 @@ "ajv-keywords": "^3.4.1" } }, + "sdp": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", + "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -16514,6 +16626,15 @@ "source-map": "~0.6.1" } }, + "webrtc-adapter": { + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-6.4.8.tgz", + "integrity": "sha512-YM8yl545c/JhYcjGHgaCoA7jRK/KZuMwEDFeP2AcP0Auv5awEd+gZE0hXy9z7Ed3p9HvAXp8jdbe+4ESb1zxAw==", + "requires": { + "rtcpeerconnection-shim": "^1.2.14", + "sdp": "^2.9.0" + } + }, "websocket-driver": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", diff --git a/app/package.json b/app/package.json index 6ad1fb9..35f9778 100644 --- a/app/package.json +++ b/app/package.json @@ -7,6 +7,7 @@ "@giphy/react-components": "^1.0.4", "@material-ui/core": "^4.9.10", "@material-ui/icons": "^4.9.1", + "@voxeet/voxeet-web-sdk": "^2.3.1", "autoprefixer": "7.1.6", "babel-core": "6.26.3", "babel-eslint": "10.x", diff --git a/app/public/background.js b/app/public/background.js index 402cdc8..0614d97 100644 --- a/app/public/background.js +++ b/app/public/background.js @@ -1,5 +1,5 @@ /* global chrome */ -/*global firebase*/ +/* global firebase */ const firebaseConfig = { apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU', diff --git a/app/public/manifest.json b/app/public/manifest.json index 4e61f34..a00c95b 100644 --- a/app/public/manifest.json +++ b/app/public/manifest.json @@ -12,7 +12,8 @@ "https://*.amazon.com/*", "https://*.disneyplus.com/*", "https://*.youtube.com/*", - "https://play.hbonow.com/*" + "https://play.hbonow.com/*", + "http://localhost/*" ], "content_security_policy": "script-src 'self' https://www.gstatic.com/ https://*.firebaseio.com https://www.googleapis.com; object-src 'self'", "content_scripts": [ diff --git a/app/src/Api.js b/app/src/Api.js index 1c68191..3d427f8 100644 --- a/app/src/Api.js +++ b/app/src/Api.js @@ -1,9 +1,9 @@ /*global chrome*/ const API_HOST = window.location.href.startsWith('http://localhost') - ? 'http://localhost:5001/watch-party-crx/us-central1/widgets' - : // : 'http://localhost:5001/watch-party-crx/us-central1/widgets'; - 'https://us-central1-watch-party-crx.cloudfunctions.net/widgets'; + ? 'http://localhost:5000/watch-party-crx/us-central1/widgets' + : // : 'https://us-central1-watch-party-crx.cloudfunctions.net/widgets'; + 'http://localhost:5000/watch-party-crx/us-central1/widgets'; export const PLAYER_STATUS = { PLAYING: 'PLAYING', @@ -32,6 +32,16 @@ export const getPlayerSource = () => { if (HREF.startsWith(PLAYER_SOURCE.LOCAL)) return PLAYER_SOURCE.LOCAL; }; +export const LocalData = () => { + return JSON.parse(window.localStorage.getItem('WatchParty')) || {}; +}; + +export const updateLocalData = (data) => { + window.localStorage.setItem('WatchParty', JSON.stringify(Object.assign({}, LocalData(), data))); +}; + +export const emptyLocalData = () => window.localStorage.setItem('WatchParty', undefined); + export const getUserUid = async () => new Promise((resolve, reject) => { chrome.runtime.sendMessage( @@ -40,7 +50,7 @@ export const getUserUid = async () => }, (response) => { const { uid } = response; - window.localStorage.setItem('watchPartyUserUid', uid); + updateLocalData({ userUid: uid }); resolve(uid); } ); diff --git a/app/src/App.js b/app/src/App.js index 793af17..4fc0f84 100644 --- a/app/src/App.js +++ b/app/src/App.js @@ -12,7 +12,10 @@ const darkTheme = createMuiTheme({ palette: { type: 'dark', primary: { - main: '#a6d4fa', + main: '#FF0000', + }, + secondary: { + main: '#c0f300', }, }, }); diff --git a/app/src/CallManager.js b/app/src/CallManager.js index 5455848..55ec8f5 100644 --- a/app/src/CallManager.js +++ b/app/src/CallManager.js @@ -1,7 +1,7 @@ import { VoxeetSDK } from '@voxeet/voxeet-web-sdk'; import { GetVoxeetTokenApi, GetRefreshedVoxeetTokenApi } from './Api'; -class CallManager { +export default class CallManager { constructor(listener) { this.listener = listener; this.__refreshToken = null; diff --git a/app/src/Chat.js b/app/src/Chat.js index 10643de..15f0395 100644 --- a/app/src/Chat.js +++ b/app/src/Chat.js @@ -7,6 +7,11 @@ import { IconButton, ButtonGroup, Button, + ExpansionPanel, + ExpansionPanelSummary, + ExpansionPanelDetails, + Card, + CardContent, } from '@material-ui/core'; import Thread from './Thread'; import Send from '@material-ui/icons/Send'; @@ -27,11 +32,20 @@ import { UpdatePlayerApi, getPlayerSource, PLAYER_SOURCE, + LocalData, + PLAYER_STATUS, } from './Api'; import { goTo } from 'route-lite'; import Create from './Create'; import VideoListener from './VideoListener'; import CallManager from './CallManager'; +import { + MoreVert, + FileCopyTwoTone, + PauseCircleFilled, + LinkOutlined, + PlayCircleFilled, +} from '@material-ui/icons'; const giphyFetch = new GiphyFetch('ojJbIMUN6XRMn4mHiFFav3rMCpXUg8sq'); const alertMessage = { @@ -51,6 +65,7 @@ export default class Chat extends Component { gif: false, onCall: false, muted: false, + menuPopover: true, }; this.port = null; this.gifClick = this.gifClick.bind(this); @@ -86,11 +101,11 @@ export default class Chat extends Component { this.mVideoListener.start(); // CHROME FIREBASE FIRESTORE LISTENER - this.port = chrome.runtime.connect({ name: window.localStorage.getItem('watchPartyRoomId') }); + this.port = chrome.runtime.connect({ name: LocalData().partyName }); // Initialize listener this.port.postMessage({ type: 'INIT', - roomId: window.localStorage.getItem('watchPartyRoomId'), + roomId: LocalData().partyName, }); // Listener this.port.onMessage.addListener((response) => { @@ -119,15 +134,16 @@ export default class Chat extends Component { const { status, showName, time, source, element, showChanged } = video; if (element) { UpdatePlayerApi({ - roomId: window.localStorage.getItem('watchPartyRoomId'), + roomId: LocalData().partyName, showChanged, - userName: window.localStorage.getItem('watchPartyUserName'), + userName: LocalData().userName, player: { status, showName, time, source, - updatedBy: window.localStorage.getItem('watchPartyUserUid'), + updatedBy: LocalData().userUid, + url: window.location.href, }, }).catch((err) => { this.props.updateLoading(false); @@ -142,12 +158,12 @@ export default class Chat extends Component { this.props.notify('Ending party...'); this.props.updateLoading(true); const data = { - roomId: window.localStorage.getItem('watchPartyRoomId'), + roomId: LocalData().partyName, }; DeleteRoomApi(data) .then((res) => { this.props.notify('Ended party'); - window.localStorage.removeItem('watchPartyRoomId'); + // window.localStorage.removeItem('watchPartyRoomId'); goTo(Create, this.props); this.props.updateLoading(false); }) @@ -161,13 +177,13 @@ export default class Chat extends Component { this.props.notify('Leaving party...'); this.props.updateLoading(true); const data = { - roomId: window.localStorage.getItem('watchPartyRoomId'), - uid: window.localStorage.getItem('watchPartyUserUid'), + roomId: LocalData().partyName, + uid: LocalData().userUid, }; LeaveRoomApi(data) .then((res) => { this.props.notify('Left party'); - window.localStorage.removeItem('watchPartyRoomId'); + // window.localStorage.removeItem('watchPartyRoomId'); goTo(Create, this.props); this.props.updateLoading(false); }) @@ -179,12 +195,14 @@ export default class Chat extends Component { } shareParty() { const el = document.createElement('textarea'); - el.value = window.localStorage.getItem('watchPartyRoomId'); + 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 room code'); + this.props.notify('Copied party invitation'); } sendMessage(message) { const data = { @@ -225,54 +243,136 @@ export default class Chat extends Component { this.mCallManager.toggleMute(); this.setState({ muted: !this.state.muted }); } + getPrettyTime(t) { + return t > 3600 + ? new Date(t * 1000).toISOString().substr(11, 8) + : new Date(t * 1000).toISOString().substr(14, 5); + } render() { - const { admin, name, messages, newMessage, gif, onCall, muted } = this.state; + const { admin, name, messages, newMessage, gif, onCall, muted, player } = this.state; if (getPlayerSource() === PLAYER_SOURCE.HBONOW && messages[0] !== alertMessage) messages.unshift(alertMessage); return (
-
- - WATCH PARTY - - - {name} - -
- - - - {admin && ( - - )} - + + + } + > +
+ + WATCH PARTY + + + {name} + +
+
+ + + + + + + ), + }} + /> + + + {admin && ( + + )} + + +
+ {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}
{gif || newMessage ? null : ( - + {onCall ? : } )} {!gif && !newMessage && onCall && !muted ? ( - + ) : !gif && !newMessage && onCall && muted ? ( - + ) : null} { - const { name, notify } = data; - window.localStorage.setItem('watchPartyUserName', name); - notify('Saved name'); +const CONSTANTS = { + USER_NAME_LENGTH: 12, + NAME_LENGTH: 24, + PIN_LENGTH: 9, }; -const getName = () => window.localStorage.getItem('watchPartyUserName'); - -const saveRoomId = (data) => { - const { rid, notify } = data; - window.localStorage.setItem('watchPartyRoomId', rid); - notify('Saved room'); -}; -const getRoomId = () => window.localStorage.getItem('watchPartyRoomId'); - -const joinRoom = async (data) => { - data.props.updateLoading(true); - data.props.notify('Joining party...'); - const apidata = { - roomId: data.joinName.trim(), - userName: data.name.trim(), - source: getPlayerSource(), - }; - const confirmedRoomId = await JoinRoomApi(apidata) - .then((res) => res.json()) - .then((res) => res.roomId) - .catch((err) => { - data.props.updateLoading(false); - data.props.notify(err.message || err.statusText); - console.error('Join Room API error', err); - }); - if (confirmedRoomId && confirmedRoomId === apidata.roomId) { - saveName({ - name: apidata.userName, - notify: data.props.notify, - }); - saveRoomId({ - rid: confirmedRoomId, - notify: data.props.notify, - }); - data.props.notify('Joined party'); - goTo(Chat, data.props); +export default class Create extends Component { + constructor(props) { + super(props); + const defaultMode = LocalData().partyName ? 'join' : 'create'; + this.state = { + userName: LocalData().userName || '', + name: LocalData().partyName || '', + pin: LocalData().partyPin || '', + mode: defaultMode, + }; + this.createCall = this.createCall.bind(this); + this.joinCall = this.joinCall.bind(this); + this.success = this.success.bind(this); } -}; - -const createRoom = async (data) => { - data.props.updateLoading(true); - data.props.notify('Starting party...'); - - const apidata = { - roomPin: data.createPin, - roomName: data.createName, - userName: data.name, - player: { - source: getPlayerSource(), - status: PLAYER_STATUS.PAUSED, - time: 0, - url: '', - }, - }; - - CreateRoomApi(apidata) - .then((res) => res.json()) - .then((res) => { - if (res.status <= 299) { - saveName({ - name: data.name, - notify: data.props.notify, - }); - saveRoomId({ - rid: res.roomId, - notify: data.props.notify, - }); - data.props.notify('Started party'); - goTo(Chat, data.props); - } else { - data.props.updateLoading(false); - data.props.notify(res.message || res.statusText); + partyNameChange(value) { + if (value.length <= CONSTANTS.NAME_LENGTH) this.setState({ name: value }); + } + partyPinChange(value) { + if (value.length <= CONSTANTS.PIN_LENGTH) this.setState({ pin: value }); + } + userNameChange(value) { + if (value.length <= CONSTANTS.USER_NAME_LENGTH) this.setState({ userName: value }); + } + createOrJoin() { + const { userName, name, pin, mode } = this.state; + if (!userName.replace(/\s+/g, '')) { + this.props.notify('Missing user name'); + return; + } + if (!name.replace(/\s+/g, '')) { + this.props.notify('Missing party name'); + return; + } + if (!pin.replace(/\s+/g, '')) { + this.props.notify('Missing party pin'); + return; + } + if (userName && name && pin) { + switch (mode) { + case 'create': + this.createCall(userName, name, pin); + break; + case 'join': + this.joinCall(userName, name, pin); + break; + default: + break; } - }) - .catch((err) => { - data.props.updateLoading(false); - data.props.notify(err.message || err.statusText); - console.error('Create Room API error', err); - }); -}; - -const Create = (props) => { - const [name, setName] = useState(getName() || ''); - const [createName, setCreateName] = useState(''); - const [createPin, setCreatePin] = useState(''); - const [joinName, setJoinName] = useState(getRoomId() || ''); - const [joinPin, setJoinPin] = useState(''); - const { notify } = props; - - return ( -
- - WATCH PARTY - -
- (e.target.value.length <= 24 ? setName(e.target.value) : null)} - className="name-field" - label="Name" - variant="filled" - helperText={name.length > 0 ? `${name.length}/24` : null} - InputProps={{ - endAdornment: ( - - saveName({ name, notify })}> - - - - ), - }} - /> - - - CREATE PARTY - - (e.target.value.length <= 24 ? setCreateName(e.target.value) : null)} - helperText={createName.length > 0 ? `${createName.length}/24` : null} - label="Party Name" - /> - (e.target.value.length <= 10 ? setCreatePin(e.target.value) : null)} - helperText={createPin.length > 0 ? `${createPin.length}/10` : null} - label="Pin Code" - /> - - - - - JOIN PARTY - + } else { + this.props.notify('Double check your inputs'); + } + } + createCall(userName, partyName, partyPin) { + this.props.updateLoading(true); + const apidata = { + userName, + partyName, + partyPin, + player: { + source: getPlayerSource(), + status: PLAYER_STATUS.PAUSED, + time: 0, + url: window.location.href, + }, + }; + CreateRoomApi(apidata) + .then((res) => res.json()) + .then((createResult) => { + if (createResult.status <= 299) { + this.success(userName, partyName, partyPin); + } else { + this.props.updateLoading(false); + this.props.notify(createResult.message || createResult.statusText); + } + }) + .catch((err) => { + console.error('Create Party API Error', err); + this.props.notify(err.message || 'Something went wrong'); + this.props.updateLoading(false); + }); + } + joinCall(userName, partyName, partyPin) { + const apidata = { + userName, + partyName, + partyPin, + source: getPlayerSource(), + }; + JoinRoomApi(apidata) + .then((res) => res.json()) + .then((joinResult) => { + if (joinResult.status <= 299) { + this.success(userName, partyName, partyPin); + } else { + this.props.updateLoading(false); + this.props.notify(joinResult.message || joinResult.statusText); + } + }) + .catch((err) => { + console.error('Join Party API Error', err); + this.props.notify(err.message || 'Something went wrong'); + this.props.updateLoading(false); + }); + } + success(userName, partyName, partyPin) { + updateLocalData({ userName, partyName, partyPin }); + this.props.updateLoading(false); + goTo(Chat, this.props); + } + render() { + const { mode, name, pin, userName } = this.state; + return ( +
+ + WATCH PARTY + + + + + + setJoinName(e.target.value)} - label="Party Name" + size="small" + className="create-name" + label="Name" + variant="outlined" + value={userName} + onChange={(e) => this.userNameChange(e.target.value)} + helperText={ + userName ? `${userName.length}/${CONSTANTS.USER_NAME_LENGTH} Characters` : null + } /> this.partyNameChange(e.target.value)} + helperText={name ? `${name.length}/${CONSTANTS.NAME_LENGTH} Characters` : null} + /> + setJoinPin(e.target.value)} - helperText="Ask your party leader for this code" - label="Pin Code" + value={pin} + onChange={(e) => this.partyPinChange(e.target.value)} + helperText={pin ? `${pin.length}/${CONSTANTS.PIN_LENGTH} Digits` : null} />
-
- ); -}; - -export default Create; + ); + } +} diff --git a/app/src/index.css b/app/src/index.css index e0204a6..064c257 100644 --- a/app/src/index.css +++ b/app/src/index.css @@ -129,11 +129,10 @@ body.amazon-prime textarea:focus { position: fixed; background-color: rgb(27, 27, 27); bottom: 0; - width: calc(var(--party-width) - 2rem); right: var(--neg-party-width); transition: all ease-in-out 0.3s; - height: calc(100% - 2rem); - padding: 1rem; + width: var(--party-width); + height: 100%; color: white; display: flex; flex-direction: column; @@ -150,7 +149,6 @@ body.amazon-prime textarea:focus { width: calc(var(--party-width) - 2rem) !important; } .party-notification-container .MuiPaper-root { - border-radius: 26px !important; color: white; background-color: black; } @@ -177,14 +175,15 @@ body.amazon-prime textarea:focus { background-image: url('chrome-extension://__MSG_@@extension_id__/android-icon-192x192.png'); background-position: center; background-repeat: no-repeat; - background-size: 2rem; + background-size: 75%; animation-name: showbutton; animation-iteration-count: 1; animation-duration: 2s; transition: all ease-in-out 0.3s !important; } .watch-party-fab:hover { - right: 1rem !important; + right: 0rem !important; + background-size: 90%; } .watch-party-fab .fab-logo { max-width: 75%; @@ -192,37 +191,24 @@ body.amazon-prime textarea:focus { } .party-create-container { + padding: 3.5rem; 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; +.party-create-container .create-input-card { display: flex; flex-direction: column; - justify-content: center; padding: 2rem; + margin: auto 0; } - -.party-chat-container { - display: flex; - max-height: 100%; - flex-direction: column; - flex: 1; +.party-create-container .create-input-card .create-mode-select, +.party-create-container .create-input-card .create-name, +.party-create-container .create-input-card .create-pin { + margin-bottom: 1rem; +} +.party-create-container .create-input-card { + background-color: #2d2d2d; } .thread-container { @@ -258,11 +244,32 @@ textarea::-webkit-scrollbar { .chat-message-box > div { border-radius: 26px; } + +.party-chat-container { + display: flex; + max-height: 100%; + flex-direction: column; + flex: 1; + padding: 2rem; + padding-bottom: 1rem; +} + .party-chat-header { display: flex; align-items: flex-start; - padding-left: 2.25rem; + padding-left: 1rem; + flex-direction: column; } + +.party-chat-header .expander { + flex: 1; + width: 100%; + margin-bottom: 0 !important; +} +.party-chat-header .expander .MuiExpansionPanelDetails-root { + flex-direction: column; +} + .party-chat-header-text { flex: 1; } @@ -277,10 +284,47 @@ textarea::-webkit-scrollbar { } .party-controls button:hover .party-control-button-text { display: initial; + font-size: 1rem !important; } .party-controls button:hover .MuiButton-startIcon { margin-right: 8px !important; } + +.media-info-card { + width: 100%; + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; + padding: 0 2rem !important; + padding-bottom: 1rem !important; +} +.media-info-card .MuiCardContent-root { + padding: 0 !important; +} +.media-info-at { + font-size: 1rem !important; + padding-left: 3px !important; +} +.media-info-container { + display: flex; + flex-direction: row; +} +.media-info-icon { + width: 3.5rem !important; + height: 3.5rem !important; +} +.media-info-right { + flex: 1; + padding-left: 1rem; +} +.media-info-right .show-name { + font-size: 1.2rem; + line-height: 1.75rem; + white-space: break-spaces !important; +} +.media-info-link { + color: inherit !important; +} + .giphy-carousel::before { position: relative; bottom: 40%; diff --git a/firebase.json b/firebase.json index 21551a1..1494a46 100644 --- a/firebase.json +++ b/firebase.json @@ -6,7 +6,7 @@ "source": "functions" }, "hosting": { - "public": "./website", + "public": "website", "ignore": [ "firebase.json", "**/.*", @@ -26,5 +26,9 @@ "ui": { "enabled": true } + }, + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" } } diff --git a/firestore.indexes.json b/firestore.indexes.json new file mode 100644 index 0000000..415027e --- /dev/null +++ b/firestore.indexes.json @@ -0,0 +1,4 @@ +{ + "indexes": [], + "fieldOverrides": [] +} diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..1fbdf44 --- /dev/null +++ b/firestore.rules @@ -0,0 +1,19 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + + function userInRoom(rid) { + return exists(/databases/$(database)/documents/rooms/$(rid)/users/$(request.auth.uid)); + } + + match /rooms/{rid}/{detail=**} { + allow read: if userInRoom(rid); + allow write: if false; + } + + match /server/status { + allow read: if true; + allow write: if false; + } + } +} \ No newline at end of file diff --git a/functions/.gitignore b/functions/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/functions/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/functions/index.js b/functions/index.js index c45356a..c68da7b 100644 --- a/functions/index.js +++ b/functions/index.js @@ -8,10 +8,6 @@ const FIREBASE_OPTIONS = { credential: admin.credential.cert(secret), databaseURL: 'https://watch-party-crx.firebaseio.com', }; -if (process.env.NODE_ENV === 'DEV') { - FIREBASE_OPTIONS.databaseURL = 'http://localhost:4000/firestore'; - console.info('RUNNING IN DEV ENV'); -} admin.initializeApp(FIREBASE_OPTIONS); diff --git a/functions/modules/authenticator.js b/functions/modules/authenticator.js index 303cc8c..878697c 100644 --- a/functions/modules/authenticator.js +++ b/functions/modules/authenticator.js @@ -1,3 +1,5 @@ +const admin = require('firebase-admin'); + module.exports = (req, res, next) => { res.set('Access-Control-Allow-Origin', ['*']); if (req.method === 'OPTIONS') { @@ -30,11 +32,11 @@ module.exports = (req, res, next) => { }) .catch((err) => { console.error('Failed Auth - ', req.headers.authorization, '\n', err); - return res.status(500).send({ message: 'Unable to verify user token', ...err }); + return res.status(500).send({ message: 'Unable to verify user token' }); }); } catch (err) { - return res.status(401).send({ message: 'Got no user token', ...err }); + console.log(err); + return res.status(401).send({ message: 'Something went wrong when authorizing' }); } - } - return res.status(401).send({ message: 'Got no user token' }); + } else return res.status(401).send({ message: 'Got no user token' }); }; diff --git a/functions/modules/createRoom.js b/functions/modules/createRoom.js index b7e6232..5927774 100644 --- a/functions/modules/createRoom.js +++ b/functions/modules/createRoom.js @@ -4,20 +4,22 @@ module.exports = async (req, res) => { const bodydata = JSON.parse(req.body); console.log('Requested Create - UID - ', bodydata.uid); if (!bodydata.userName) res.status(400).send({ message: 'Missing user name' }); - if (!bodydata.roomName) res.status(400).send({ message: 'Missing party name' }); + if (!bodydata.partyName) res.status(400).send({ message: 'Missing party name' }); + if (!bodydata.partyPin) res.status(400).send({ message: 'Missing party pin' }); const roomNameTaken = await admin .firestore() .collection('rooms') - .doc(bodydata.roomName) + .doc(bodydata.partyName) .get() .then((docSnap) => docSnap.exists); if (roomNameTaken) return res.status(409).send({ message: 'That party name is already taken' }); await admin .firestore() .collection('rooms') - .doc(bodydata.roomName) + .doc(bodydata.partyName) .set({ - name: bodydata.roomName, + name: bodydata.partyName, + pin: bodydata.partyPin, player: bodydata.player, createdAt: new Date(), updatedAt: new Date(), @@ -29,7 +31,7 @@ module.exports = async (req, res) => { await admin .firestore() .collection('rooms') - .doc(bodydata.roomName) + .doc(bodydata.partyName) .collection('messages') .add({ uid: '', @@ -39,14 +41,14 @@ module.exports = async (req, res) => { }) .catch((err) => { console.error( - `Failed to add system create party message : \n\t Room: ${bodydata.roomName}`, + `Failed to add system create party message : \n\t Room: ${bodydata.partyName}`, err ); }); await admin .firestore() .collection('rooms') - .doc(bodydata.roomName) + .doc(bodydata.partyName) .collection('users') .doc(bodydata.uid) .set({ @@ -55,9 +57,9 @@ module.exports = async (req, res) => { }) .catch((err) => { console.error( - `Failed to set admin when creating party : \n\t Room: ${bodydata.roomName} \n\t User: ${bodydata.uid}`, + `Failed to set admin when creating party : \n\t Room: ${bodydata.partyName} \n\t User: ${bodydata.uid}`, err ); }); - res.status(201).send({ roomId: bodydata.roomName, message: 'Started a party!' }); + res.status(201).send({ roomId: bodydata.partyName, message: 'Started a party!' }); }; diff --git a/functions/modules/joinRoom.js b/functions/modules/joinRoom.js index d68f97d..b882c70 100644 --- a/functions/modules/joinRoom.js +++ b/functions/modules/joinRoom.js @@ -2,46 +2,72 @@ const admin = require('firebase-admin'); module.exports = async (req, res) => { const bodydata = JSON.parse(req.body); - console.log('Requested Join - UID - ', bodydata.uid, '\n\tRoom - ', bodydata.roomId); - const [exists, roomSource] = await admin + console.log('Requested Join - UID - ', bodydata.uid, '\n\tRoom - ', bodydata.partyName); + // GET ROOM EXISTS, ROOM SOURCE, & ROOM PIN + const [exists, roomSource, roomPin] = await admin .firestore() .collection('rooms') - .doc(bodydata.roomId) + .doc(bodydata.partyName) .get() - .then((roomSnap) => [roomSnap.exists, roomSnap.exists ? roomSnap.data().player.source : '']) + .then((roomSnap) => [ + roomSnap.exists, + roomSnap.exists ? roomSnap.data().player.source : '', + roomSnap.exists ? roomSnap.data().pin : '', + ]) .catch((err) => { console.error( - `Failed to get room when joining room : \n\t Room: ${bodydata.roomId} \n\t User: ${bodydata.uid}`, + `Failed to get room when joining room : \n\t Room: ${bodydata.partyName} \n\t User: ${bodydata.uid}`, err ); }); - const [userExists, userAdmin] = await admin + if (exists && roomPin !== bodydata.partyPin) + return res.status(401).send({ message: 'Incorrect party pin' }); + // GET IF USER EXISTS AND IS ADMIN + const [userExists, userAdmin, userName] = await admin .firestore() .collection('rooms') - .doc(bodydata.roomId) + .doc(bodydata.partyName) .collection('users') .doc(bodydata.uid) .get() - .then((userSnap) => [userSnap.exists, userSnap.exists ? userSnap.data().admin : false]) + .then((userSnap) => [ + userSnap.exists, + userSnap.exists ? userSnap.data().admin : false, + userSnap.exists ? userSnap.data().name : '', + ]) .catch((err) => { console.error( - `Failed to get user when joining room : \n\t Room: ${bodydata.roomId} \n\t User: ${bodydata.uid}`, + `Failed to get user when joining room : \n\t Room: ${bodydata.partyName} \n\t User: ${bodydata.uid}`, err ); }); - if (userExists && exists && roomSource === bodydata.source) - res.status(200).send({ roomId: bodydata.roomId, message: 'Joined a party' }); + // IF USER PRE-EXISTS AND SAME SOURCE - JOIN + if (userExists && exists && roomSource === bodydata.source) { + // UPDATE NAME IF NEEDED + if (userName !== bodydata.userName) { + await admin + .firestore() + .collection('rooms') + .doc(bodydata.partyName) + .collection('users') + .doc(body.uid) + .update({ name: bodydata.userName }); + } + res.status(200).send({ partyName: bodydata.partyName, message: 'Joined a party' }); + } + // USER DOES NOT PRE-EXIST BUT SAME SOURCE AND RIGHT PIN else if ( !userExists && exists && - bodydata.roomId && + bodydata.partyName && bodydata.userName && - roomSource === bodydata.source + roomSource === bodydata.source && + roomPin === bodydata.partyPin ) { await admin .firestore() .collection('rooms') - .doc(bodydata.roomId) + .doc(bodydata.partyName) .collection('messages') .add({ uid: '', @@ -51,14 +77,14 @@ module.exports = async (req, res) => { }) .catch((err) => { console.error( - `Failed to add system join party message : \n\t Room: ${roomId} \n\t ${bodydata.uid}`, + `Failed to add system join party message : \n\t Room: ${partyName} \n\t ${bodydata.uid}`, err ); }); await admin .firestore() .collection('rooms') - .doc(bodydata.roomId) + .doc(bodydata.partyName) .collection('users') .doc(bodydata.uid) .set({ @@ -67,36 +93,37 @@ module.exports = async (req, res) => { }) .catch((err) => { console.error( - `Failed to set admin when joining party : \n\t Room: ${roomId} \n\t User: ${bodydata.uid}`, + `Failed to set admin when joining party : \n\t Room: ${partyName} \n\t User: ${bodydata.uid}`, err ); }); await admin .firestore() .collection('rooms') - .doc(bodydata.roomId) + .doc(bodydata.partyName) .update({ updatedAt: new Date(), }) .catch((err) => { console.error( - `Failed to set updated time when joining party : \n\t Room: ${roomId} \n\t User: ${bodydata.uid}`, + `Failed to set updated time when joining party : \n\t Room: ${partyName} \n\t User: ${bodydata.uid}`, err ); }); - res.status(200).send({ roomId: bodydata.roomId, message: 'Joined a party' }); - } else if (!userExists && !bodydata.userName) - res.status(400).send({ message: 'Missing user name' }); + res.status(200).send({ partyName: bodydata.partyName, message: 'Joined a party' }); + } + // ONLY WRONG SOURCE else if (exists && roomSource !== bodydata.source) { + // MOVE SOURCE IF ADMIN if (userAdmin === true) { await admin .firestore() .collection('rooms') - .doc(bodydata.roomId) + .doc(bodydata.partyName) .update({ player: { showName: null, - source: roomSource, + source: null, status: 'NOT_PLAYING', time: 0, updatedBy: bodydata.uid, @@ -104,7 +131,7 @@ module.exports = async (req, res) => { }) .catch(() => { console.error( - `Failed to update player source when admin joining party \n\t Room: ${roomId} \n\t User: ${bodydata.uid}` + `Failed to update player source when admin joining party \n\t Room: ${partyName} \n\t User: ${bodydata.uid}` ); res.status(500).send({ message: 'Unable to move party, please try again' }); }); diff --git a/functions/modules/updatePlayer.js b/functions/modules/updatePlayer.js index a38087c..777a155 100644 --- a/functions/modules/updatePlayer.js +++ b/functions/modules/updatePlayer.js @@ -2,18 +2,18 @@ const admin = require('firebase-admin'); module.exports = async (req, res) => { const { player, roomId, uid, showChanged, userName } = JSON.parse(req.body); - const statusString = - player.status === 'PLAYING' - ? 'started playing from' - : player.status === 'PAUSED' - ? 'paused at' - : player.status === 'NOT_PLAYING' - ? 'stopped at' - : 'is at'; - const timeString = - player.time > 3600 - ? new Date(player.time * 1000).toISOString().substr(11, 8) - : new Date(player.time * 1000).toISOString().substr(14, 5); + // const statusString = + // player.status === 'PLAYING' + // ? 'started playing from' + // : player.status === 'PAUSED' + // ? 'paused at' + // : player.status === 'NOT_PLAYING' + // ? 'stopped at' + // : 'is at'; + // const timeString = + // player.time > 3600 + // ? new Date(player.time * 1000).toISOString().substr(11, 8) + // : new Date(player.time * 1000).toISOString().substr(14, 5); const [roomExists, roomData] = await admin .firestore() .collection('rooms') @@ -55,23 +55,23 @@ module.exports = async (req, res) => { console.error(`Failed to update player : \n\t Room: ${roomId} \n\t ${uid}`, err); res.status(500).send({ message: "Uh oh party could't be synced", err }); }); - await admin - .firestore() - .collection('rooms') - .doc(roomId) - .collection('messages') - .add({ - uid: '', - name: '', - text: `${userName} ${statusString} ${timeString}`, - time: new Date(), - }) - .catch((err) => { - console.error( - `Failed to add system update player message : \n\t Room: ${roomId} \n\t ${uid}`, - err - ); - }); + // await admin + // .firestore() + // .collection('rooms') + // .doc(roomId) + // .collection('messages') + // .add({ + // uid: '', + // name: '', + // text: `${userName} ${statusString} ${timeString}`, + // time: new Date(), + // }) + // .catch((err) => { + // console.error( + // `Failed to add system update player message : \n\t Room: ${roomId} \n\t ${uid}`, + // err + // ); + // }); return res.status(200).send({ message: 'Synced party player' }); } else { return res.status(208).send({ message: 'Player already up to date' }); diff --git a/functions/package-lock.json b/functions/package-lock.json index d69ef49..f58cb2e 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -1,6 +1,6 @@ { - "name": "watchparty", - "version": "1.0.0-beta", + "name": "watch-party-api", + "version": "1.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b12ad18..bbd0b04 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.2.1", "description": "Watch Party", "scripts": { - "start": "env NODE_ENV=DEV firebase emulators:start", + "start": "firebase emulators:start", "build": "cd ./app && node ./scripts/build.js" }, "private": true,