v1.6.0/conference call upgrade

- anyone can start call
- colision check for calls
- better error handing for calls
This commit is contained in:
2020-08-02 01:11:48 -04:00
parent c80a99cea1
commit 86ee168045
9 changed files with 184 additions and 51 deletions
+2 -2
View File
@@ -149,9 +149,9 @@ export const UpdatePlayerApi = async (data) => {
}); });
}; };
export const GetVoxeetTokenApi = async () => { export const GetVoxeetTokenApi = async (data) => {
return fetcher({ return fetcher({
endpoint: 'validator', endpoint: `validator/${data.partyName}`,
method: 'GET', method: 'GET',
}); });
}; };
+31 -8
View File
@@ -33,10 +33,12 @@ export default class CallManager {
this.__accessToken = LocalData().vrT; this.__accessToken = LocalData().vrT;
this.__sessionInitialized = false; this.__sessionInitialized = false;
this.__admin = false; this.__admin = false;
this.__adminOnly = true;
} }
toggleCall(onCall, conference, admin) { toggleCall(onCall, conference, admin, adminOnly) {
this.listener({ callActionPending: true }); this.listener({ callActionPending: true });
this.__admin = admin; this.__admin = admin;
this.__adminOnly = adminOnly;
if (onCall === true) this.leaveCall(); if (onCall === true) this.leaveCall();
else this.boot(conference); else this.boot(conference);
} }
@@ -56,13 +58,17 @@ export default class CallManager {
console.error('Failed to fetch conference', err); console.error('Failed to fetch conference', err);
this.startCall(); this.startCall();
}); });
} else if (opened && !conference) this.startCall(); } else if (opened && !conference) {
else console.error('Unable to open Voxeet Session'); this.startCall();
} else {
this.listener({ onCall: false, muted: false, callActionPending: false });
console.error('Unable to open Voxeet Session');
}
} }
async startCall() { async startCall() {
if (this.__admin === true) { if (!this.__adminOnly || (this.__adminOnly && this.__admin)) {
VoxeetSDK.conference VoxeetSDK.conference
.create({ alias: LocalData().partyName, pin: LocalData().partyPin }) .create({ alias: `AUTODELETE_${LocalData().partyName}`, pin: LocalData().partyPin })
.then(async (conference) => { .then(async (conference) => {
const updatedVoxeet = await UpdateVoxeetApi({ const updatedVoxeet = await UpdateVoxeetApi({
conference, conference,
@@ -72,10 +78,21 @@ export default class CallManager {
}) })
.then((res) => res.json()) .then((res) => res.json())
.then((res) => res.status <= 299) .then((res) => res.status <= 299)
.catch((err) => console.error('Update Voxeet API Error', err)); .catch((err) => {
if (err.conference) {
this.joinCall(err.conference);
return true;
} else {
this.notify(err.message);
this.listener({ onCall: false, muted: false, callActionPending: false });
console.error('Update Voxeet API Error', err);
return false;
}
});
if (updatedVoxeet === true) { if (updatedVoxeet === true) {
this.joinCall(conference); this.joinCall(conference);
} else { } else {
this.listener({ onCall: false, muted: false, callActionPending: false });
console.error('Failed to update party Voxeet'); console.error('Failed to update party Voxeet');
} }
}); });
@@ -87,7 +104,13 @@ export default class CallManager {
async joinCall(conference) { async joinCall(conference) {
const joined = await VoxeetSDK.conference const joined = await VoxeetSDK.conference
.join(conference, { constraints: { audio: true, video: false } }) .join(conference, { constraints: { audio: true, video: false } })
.then(() => true); .then(() => true)
.catch(e => {
this.listener({ onCall: false, muted: false, callActionPending: false });
this.notify('Unable to join call. Try again in a moment.');
console.error('Error joining call', e);
return true;
});
if (!joined) { if (!joined) {
this.startCall(); this.startCall();
} }
@@ -158,7 +181,7 @@ export default class CallManager {
this.__accessToken !== null || this.__accessToken !== null ||
this.__refreshToken !== null this.__refreshToken !== null
) { ) {
await GetVoxeetTokenApi() await GetVoxeetTokenApi({ partyName: LocalData().partyName })
.then((res) => res.json()) .then((res) => res.json())
.then((tokenResult) => { .then((tokenResult) => {
if (tokenResult.status <= 299) { if (tokenResult.status <= 299) {
+5 -3
View File
@@ -235,6 +235,7 @@ export default class Chat extends Component {
const data = { const data = {
partyName: LocalData().partyName, partyName: LocalData().partyName,
}; };
if (this.state.onCall) this.mCallManager.toggleCall(this.state.onCall, this.state.conference, this.state.admin, this.state.adminOnly);
DeleteRoomApi(data) DeleteRoomApi(data)
.then((res) => { .then((res) => {
this.props.notify('Ended party'); this.props.notify('Ended party');
@@ -255,6 +256,7 @@ export default class Chat extends Component {
partyName: LocalData().partyName, partyName: LocalData().partyName,
userUid: LocalData().userUid, userUid: LocalData().userUid,
}; };
if (this.state.onCall) this.mCallManager.toggleCall(this.state.onCall, this.state.conference, this.state.admin, this.state.adminOnly);
LeaveRoomApi(data) LeaveRoomApi(data)
.then((res) => { .then((res) => {
this.props.notify('Left party'); this.props.notify('Left party');
@@ -312,7 +314,7 @@ export default class Chat extends Component {
: giphyFetch.search(this.state.newMessage, { offset, limit: 1 }); : giphyFetch.search(this.state.newMessage, { offset, limit: 1 });
} }
handlePhoneCall() { handlePhoneCall() {
const { onCall, conference, admin } = this.state; const { onCall, conference, admin, adminOnly } = this.state;
if (window.navigator && onCall === false) { if (window.navigator && onCall === false) {
try { try {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
@@ -328,7 +330,7 @@ export default class Chat extends Component {
}) })
.then(() => { .then(() => {
document.getElementById('mic-permission-overlay').remove(); document.getElementById('mic-permission-overlay').remove();
this.mCallManager.toggleCall(onCall, conference, admin); this.mCallManager.toggleCall(onCall, conference, admin, adminOnly);
}) })
.catch(() => { .catch(() => {
document.getElementById('mic-permission-overlay').remove(); document.getElementById('mic-permission-overlay').remove();
@@ -339,7 +341,7 @@ export default class Chat extends Component {
} catch (err) { } catch (err) {
console.error('Handle Phone Call Error', err); console.error('Handle Phone Call Error', err);
} }
} else this.mCallManager.toggleCall(onCall, conference, admin); } else this.mCallManager.toggleCall(onCall, conference, admin, adminOnly);
} }
handleMute() { handleMute() {
this.mCallManager.toggleMute(this.state.muted); this.mCallManager.toggleMute(this.state.muted);
+8 -1
View File
@@ -1,13 +1,19 @@
const FunctionModules = require('./modules/index'); const FunctionModules = require('./modules/index');
const FunctionModules2 = require('./modules2/index'); const FunctionModules2 = require('./modules2/index');
const Webhooks = require('./webhooks/index');
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
// WEBHOOKS
const webhooks = express();
webhooks.use(cors({ origin: true }));
webhooks.all('/conferenceEnded',Webhooks.conferenceEnd);
// VERSION 1 // VERSION 1
const app = express(); const app = express();
app.use(cors({ origin: true })); app.use(cors({ origin: true }));
app.use(FunctionModules.authenticator); app.use(FunctionModules.authenticator);
app.get('/validator', FunctionModules.tokenValidator); app.get('/validator/:partyName', FunctionModules.tokenValidator);
app.get('/refresher/:token', FunctionModules.tokenRefresher); app.get('/refresher/:token', FunctionModules.tokenRefresher);
app.post('/create', FunctionModules.createRoom); app.post('/create', FunctionModules.createRoom);
app.post('/join', FunctionModules.joinRoom); app.post('/join', FunctionModules.joinRoom);
@@ -35,4 +41,5 @@ app2.delete('/delete/:rid', FunctionModules2.deleteRoom);
module.exports = { module.exports = {
v1: app, v1: app,
v2: app2, v2: app2,
webhooks,
}; };
+2
View File
@@ -19,3 +19,5 @@ exports.widgets2 = functions.https.onRequest(REST.v2);
exports.scheduledCleanUp = functions.pubsub exports.scheduledCleanUp = functions.pubsub
.schedule('every 60 minutes') .schedule('every 60 minutes')
.onRun(FunctionModules.scheduledCleanUp); .onRun(FunctionModules.scheduledCleanUp);
exports.webhooks = functions.https.onRequest(REST.webhooks);
+47 -4
View File
@@ -1,6 +1,9 @@
const VoxeetSecret = require('../VoxeetSecret.json'); const VoxeetSecret = require('../VoxeetSecret.json');
const admin = require('firebase-admin');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
module.exports = async (req, res) => { module.exports = async (req, res) => {
const bodyData = JSON.parse(req.body);
const { key, secret } = VoxeetSecret; const { key, secret } = VoxeetSecret;
const credentials = Buffer.from(encodeURI(key) + ':' + encodeURI(secret)).toString('base64'); const credentials = Buffer.from(encodeURI(key) + ':' + encodeURI(secret)).toString('base64');
const headers = { const headers = {
@@ -14,8 +17,48 @@ module.exports = async (req, res) => {
body: JSON.stringify({ body: JSON.stringify({
grant_type: 'client_credentials', grant_type: 'client_credentials',
}), }),
}) }).then((oauthResponse) => oauthResponse.json())
.then((oauthResponse) => oauthResponse.json()) .catch((err) => res.status(500).send({err, message: 'Unable to generate token for call'}));
.catch((err) => res.status(500).send(err));
if (oauth) res.status(200).send(oauth); const [exists, conferencePending, conference, adminOnly, adminUid] = await admin
.firestore()
.collection('rooms')
.doc(req.params.partyName)
.get()
.then(snap => {
const data = snap.exists ? snap.data() : {};
return [snap.exists, data.conferencePending, data.conference, data.adminOnly, data.adminUid];
});
if (exists === true) {
if (conference === undefined || conference === null) {
if (conferencePending === undefined || conferencePending === null) {
if (!adminOnly || (adminOnly && (adminUid === bodyData.uid)) ) {
await admin
.firestore()
.collection('rooms')
.doc(req.params.partyName)
.update({
conferencePending: bodyData.uid
});
await admin
.firestore()
.collection('rooms')
.doc(req.params.partyName)
.collection('messages')
.add({
uid: '<system>',
name: '',
text: 'A call session is being created (will be available soon).',
time: new Date(),
});
}
}
}
if (oauth) res.status(200).send(oauth);
} else {
res.status(400).send({ message: 'Unable to get party for call.' });
}
return;
}; };
+54 -33
View File
@@ -1,47 +1,68 @@
const admin = require('firebase-admin'); const admin = require('firebase-admin');
module.exports = async (req, res) => { module.exports = async (req, res) => {
const { conference, partyName, partyPin, userName } = JSON.parse(req.body); const { conference, partyName, partyPin, userName, uid } = JSON.parse(req.body);
// GET ROOM PIN // GET ROOM PIN
const roomPin = await admin const [exists, roomPin, conferencePending, partyConference] = await admin
.firestore() .firestore()
.collection('rooms') .collection('rooms')
.doc(partyName) .doc(partyName)
.get() .get()
.then((roomSnap) => (roomSnap.exists ? roomSnap.data().pin : null)); .then((roomSnap) => {
const data = roomSnap.exists ? roomSnap.data() : {};
return [roomSnap.exists, data.pin, data.conferencePending, data.conference];
});
// CHECK IF ROOM EXISTS // CHECK IF ROOM EXISTS
if (roomPin) { if (exists === true) {
// CHECK FOR CORRECT PIN if (roomPin) {
if (roomPin === partyPin) { // CHECK FOR CORRECT PIN
// UPDATE CONFERENCE DETAILS if (roomPin === partyPin) {
await admin if (conferencePending) {
.firestore() if (conferencePending === uid) {
.collection('rooms') // UPDATE CONFERENCE DETAILS
.doc(partyName) await admin
.update({ conference }) .firestore()
.catch((err) => .collection('rooms')
res.status(500).send({ message: 'Unable to update the call details', error: err }) .doc(partyName)
); .update({ conference })
// ADD SYSTEM MESSAGE .catch((err) =>
await admin res.status(500).send({ message: 'Unable to update the call details', error: err })
.firestore() );
.collection('rooms') // REMOVE PENDING STATUS
.doc(partyName) await admin
.collection('messages') .firestore()
.add({ .collection('rooms')
uid: '<system>', .doc(partyName)
name: '', .update({ conferencePending : admin.firestore.FieldValue.delete() })
text: `${userName} started the call.`, .catch((err) => console.error('Failed to remove pending status', err));
time: new Date(), // ADD SYSTEM MESSAGE
}) await admin
.catch((err) => console.error('Failed to add update conference message', err)); .firestore()
res.status(200).send({ message: 'Started the conference' }); .collection('rooms')
} else { .doc(partyName)
// WRONG ROOM PIN .collection('messages')
return res.status(401).send({ message: 'Wrong party pin, try rejoining' }); .add({
uid: '<system>',
name: '',
text: `${userName} started the call, everyone can join now.`,
time: new Date(),
})
.catch((err) => console.error('Failed to add update conference message', err));
res.status(200).send({ message: 'Started the conference' });
} else {
res.status(409).send({ message: 'Looks like someone is already starting a call' });
}
} else if (partyConference) {
res.status(409).send({ message: 'Call already exists', conference: partyConference });
}
} else {
// WRONG ROOM PIN
res.status(401).send({ message: 'Wrong party pin, try rejoining' });
}
} }
} else { } else {
// ROOM DOESN'T EXIST // ROOM DOESN'T EXIST
return res.status(404).send({ message: 'Party Not Found' }); res.status(404).send({ message: 'Party Not Found' });
} }
return;
}; };
+30
View File
@@ -0,0 +1,30 @@
const admin = require('firebase-admin');
module.exports = async (req, res) => {
console.info(`[START]\tWEBHOOKS/Conference End\nID:${req.body.conference.confId}\nParty:${req.body.conference.confAlias}`);
if (req.body.eventType === 'Conference.Ended') {
if (req.body.participants.length > 0) {
if (req.body.confAlias.startsWith('AUTODELETE_')) {
const partyName = req.body.confAlias.replace(/AUTODELETE_/, "");
admin.firestore()
.collection('rooms')
.doc(partyName)
.update({ conference: admin.firestore.FieldValue.delete() })
.then(() => {
console.info(`[DONE]\tWEBHOOKS/Conference End\nID:${req.body.conference.confId}\nParty:${req.body.conference.confAlias}`);
return;
})
.catch(err => {
console.error(`[ERROR]\tWEBHOOKS/Conference End/Firebase Error\nID:${req.body.conference.confId}\nParty:${req.body.conference.confAlias}\nError:${JSON.stringify(err)}`)
});
res.status(200).send({ message: 'Processed conference end'});
}
} else {
console.info(`[DONE]\tWEBHOOKS/Conference End/End Of Duplicate Conference\nID:${req.body.conference.confId}\nParty:${req.body.conference.confAlias}`);
}
} else {
console.error(`[ERROR]\tWEBHOOKS/Conference End/Wrong Event Type\nID:${req.body.conference.confId}\nParty:${req.body.conference.confAlias}`)
res.status(400).send({ message: 'Wrong event type' });
}
return;
};
+5
View File
@@ -0,0 +1,5 @@
const conferenceEnd = require('./conferenceEnd');
module.exports = {
conferenceEnd,
};