Skip to content
This repository has been archived by the owner on Nov 5, 2023. It is now read-only.

Commit

Permalink
Add start of share captions work
Browse files Browse the repository at this point in the history
  • Loading branch information
curtgrimes committed Oct 1, 2018
1 parent 54ed16e commit 00b5b2e
Show file tree
Hide file tree
Showing 23 changed files with 8,298 additions and 204 deletions.
19 changes: 19 additions & 0 deletions app/api/index.js
@@ -0,0 +1,19 @@
const express = require('express');
const app = express();
const routes = require('./routes');

app.use(function(req,res,next){setTimeout(next,1000)}); // Simulate latency

if (process.env.DEBUG_API_ONLY === 'true') {
require('dotenv').config();
app.use('/api', routes);
app.listen(8080);
}
else {
app.use('/', routes);
}

module.exports = {
path: '/api',
handler: app
};
23 changes: 23 additions & 0 deletions app/api/redis.js
@@ -0,0 +1,23 @@
const {promisify} = require('util');
let sharedClient;

function getNewClient() {
let redisClient = require('redis').createClient(process.env.REDIS_URL);
redisClient.getAsync = promisify(redisClient.get).bind(redisClient);
redisClient.existsAsync = promisify(redisClient.exists).bind(redisClient);
redisClient.hgetAsync = promisify(redisClient.hget).bind(redisClient);
redisClient.delAsync = promisify(redisClient.del).bind(redisClient);
return redisClient;
}

function getSharedClient() {
if (!sharedClient) {
sharedClient = getNewClient();
}
return sharedClient;
}

module.exports = {
getNewClient,
getSharedClient,
}
5 changes: 5 additions & 0 deletions app/api/routes/index.js
@@ -0,0 +1,5 @@
const routes = require('express').Router();

routes.use('/rooms', require('./rooms'));

module.exports = routes;
58 changes: 58 additions & 0 deletions app/api/routes/rooms/index.js
@@ -0,0 +1,58 @@
const rooms = require('express').Router();
const redis = require('./../../redis').getSharedClient();
const nanoid = require('nanoid');

const expireHours = 6;

rooms.post('/', async (req, res, next) => {
let roomKey, roomId, roomKeyAlreadyExists;
do {
roomId = nanoid(8);
roomKey = 'rooms:' + roomId;
roomKeyAlreadyExists = await redis.existsAsync(roomKey) === 1;
}
while (roomKeyAlreadyExists); // repeat roomkey generation on collision

const ownerKey = nanoid(50);

redis.hmset(roomKey, 'ownerKey', ownerKey);
redis.expire(roomKey, 60 * 60 * expireHours);

res.send(JSON.stringify(
{
roomId,
ownerKey,
url: process.env.HOSTNAME + '/s/' + roomId,
expireDate: new Date((new Date()).getTime() + (1000 * 60 * 60 * expireHours)),
}
));
return;
});

rooms.delete('/:roomId', async (req, res) => {
const {roomId} = req.params;
const {ownerKey} = req.query;

if (!roomId || !ownerKey) {
res.sendStatus(403);
return;
}
const roomKey = 'rooms:' + roomId;
const ownerKeyForRoom = await redis.hgetAsync(roomKey, 'ownerKey');

if (!ownerKeyForRoom) {
// That room ID doesn't exist (or for some reason it doesn't have an owner key)
res.sendStatus(404);
}
else if (ownerKeyForRoom === ownerKey) {
// Delete this room
await redis.delAsync(roomKey);
res.sendStatus(200);
}
else {
// Room exists, but correct ownerKey wasn't given
res.sendStatus(403);
}
});

module.exports = rooms;
5 changes: 3 additions & 2 deletions app/assets/scss/app.scss
Expand Up @@ -166,11 +166,12 @@ input[type="color"] {
padding:.575rem 1rem .425rem 1rem;
}

.btn-group > * {
.btn-group.captioning-split-button > * {
border-left:1px solid rgba(0,0,0,.2);
}

.btn-group .btn:first-child, .btn-group .btn:first-child:hover {
.btn-group.captioning-split-button .btn:first-child,
.btn-group.captioning-split-button .btn:first-child:hover {
border-left:none;
}

Expand Down
5 changes: 4 additions & 1 deletion app/components/Navbar.vue
Expand Up @@ -26,6 +26,7 @@
<span v-else>{{$t('navbar.captioner.listening')}}</span>
</div>
<cast-button></cast-button>
<share-button></share-button>
<div v-if="showVmixNotFullySetUpMessage && !vmixNotFullySetUpMessageDismissed" class="mr-4">
<span class="navbar-text text-white pr-3 text-primary">
<fa icon="exclamation-triangle" /> {{$t('navbar.vmixNotConnected')}}
Expand Down Expand Up @@ -68,7 +69,7 @@
</b-dropdown-item>
</b-dropdown>
</transition>
<b-button-group :size="largerLayout ? 'lg' : ''">
<b-button-group :size="largerLayout ? 'lg' : ''" class="captioning-split-button">
<b-button id="startCaptioningDropdown" :class="incompatibleBrowser ? 'button-only-disabled' : ''" :variant="captioningToggleButtonVariant" @click="captioningToggleButtonClick">
<div :class="{'px-4 py-2' : largerLayout}">
<span v-if="!this.captioningOn">
Expand Down Expand Up @@ -117,6 +118,7 @@
<script>
import VolumeMeter from './VolumeMeter.vue'
import CastButton from '../components/CastButton.vue'
import ShareButton from '../components/ShareButton.vue'
import saveToFile from '~/mixins/saveToFile'
import dateFormat from '~/mixins/dateFormat'
Expand All @@ -129,6 +131,7 @@ export default {
components: {
VolumeMeter,
CastButton,
ShareButton,
},
data: function() {
return {
Expand Down
8 changes: 4 additions & 4 deletions app/components/ReceiverSplash.vue
Expand Up @@ -90,10 +90,10 @@ export default {
}
},
initConnectId: function() {
this.$socket.sendObj({
action: 'getMyConnectId',
deviceInfo: this.getDeviceInfo(),
});
// this.$socket.sendObj({
// action: 'getMyConnectId',
// deviceInfo: this.getDeviceInfo(),
// });
},
getDeviceInfo: function() {
let userAgent = navigator.userAgent || navigator.vendor || window.opera;
Expand Down
188 changes: 188 additions & 0 deletions app/components/ShareButton.vue
@@ -0,0 +1,188 @@
<template>
<div>
<b-button
v-b-tooltip.hover
title="Share"
:disabled="showPopover"
:variant="hasValidShareLink ? 'secondary' : 'info'"
class="mr-2"
ref="shareButton"
>
<fa icon="share-square"/>
</b-button>
<!-- Our popover title and content render container -->
<!-- We use placement 'auto' so popover fits in the best spot on viewport -->
<!-- We specify the same container as the trigger button, so that popover is close to button -->
<b-popover v-if="mounted"
:target="getShareButtonRef()"
triggers="click"
:show.sync="showPopover"
placement="auto"
container="myContainer"
ref="popover"
@show="onShow">
<template slot="title">
<b-btn @click="onClose" class="close" aria-label="Close" variant="link">
<span class="d-inline-block" aria-hidden="true">&times;</span>
</b-btn>
Share
</template>
<div v-if="!hasValidShareLink">
<p class="mb-2">Share live captions with others.</p>
<p class="mb-1">
<b-btn @click="getLink()" size="sm" class="px-2 py-1" :disabled="gettingLink">Get Link <fa v-if="gettingLink" icon="spinner" spin /></b-btn>
</p>
</div>
<div v-else style="width:500px; min-width:200px; max-width:100%">
<p class="mb-2">Use this link to share live captions with others.</p>
<input @focus="shareLinkSelect()" @click="shareLinkSelect()" ref="shareLinkInput" type="text" class="form-control small mb-2" style="font-size:.7rem" readonly :value="shareLink" :disabled="expiringLink"/>
<p class="small text-muted mb-2">Link expires <timeago :datetime="expireDate"></timeago></p>
<b-dropdown text="Options" variant="outline-secondary" size="sm" toggle-class="px-2 py-1" :disabled="expiringLink">
<template slot="button-content">
<fa icon="cog"/>
</template>
<b-dropdown-item :to="localePath('captioner-share')">Customize Link</b-dropdown-item>
<b-dropdown-divider/>
<b-dropdown-item @click="expireLink()">Expire Now</b-dropdown-item>
</b-dropdown> <span class="pl-2 text-secondary"><fa v-if="expiringLink" icon="spinner" spin /></span>
<b-btn size="sm" variant="light" class="text-white px-2 py-1 float-right" style="background:#1b95e0;border-color:#1b95e0;font-family:'Roboto', sans-serif;text-transform:none" :href="twitterShareLink"><fa :icon="['fab', 'twitter']" /> Tweet</b-btn>
</div>
<p v-if="somethingWentWrong" class="text-danger small mt-2 mb-1 font-weight-bold">Something went wrong.</p>
</b-popover>




<b-modal :title="$t('googleCast.castingFailed')" :hide-header="true" ref="castFailedModal" :ok-only="true" ok-variant="secondary" :hide-header-close="true">
<div class="py-2">
<div class="pb-2 h4"><fa icon="exclamation-triangle" size="3x" /></div>
<h2>{{$t('googleCast.unableToCast')}}</h2>
<p class="lead">{{$t('googleCast.pleaseTryAgain')}}</p>
</div>
</b-modal>
</div>
</template>

<style scoped>
</style>


<script>
export default {
name: 'shareButton',
data: () => {
return {
somethingWentWrong: false,
gettingLink: false,
expiringLink: false,
showPopover: false,
mounted: false,
};
},
mounted: function() {
this.mounted = true;
},
computed: {
shareLink: function() {
return this.$store.state.settings.share.url;
},
roomId: function() {
return this.$store.state.settings.share.roomId;
},
expireDate: function() {
return this.$store.state.settings.share.expireDate;
},
hasValidShareLink() {
return this.shareLink && this.roomId && this.expireDate;
},
twitterShareLink() {
return 'https://twitter.com/intent/tweet?' + 'text=' + encodeURIComponent('I\'m now captioning live with @WebCaptioner') + '&url='+ this.shareLink;
},
},
methods: {
getShareButtonRef() {
return this.$refs.shareButton;
},
async getLink() {
this.somethingWentWrong = false;
this.gettingLink = true;
try {
const {roomId, ownerKey, url, expireDate} = await this.$axios.$post('/api/rooms');
this.$store.commit('SET_SHARE_ROOM_ID', { roomId });
this.$store.commit('SET_SHARE_OWNER_KEY', { ownerKey });
this.$store.commit('SET_SHARE_URL', { url });
this.$store.commit('SET_SHARE_EXPIRE_DATE', { expireDate });
this.$socket.sendObj({
action: 'authenticateRoomOwner',
roomId: this.$store.state.settings.share.roomId,
ownerKey: this.$store.state.settings.share.ownerKey,
});
}
catch (e) {
this.somethingWentWrong = true;
}
finally {
this.gettingLink = false;
}
},
async expireLink() {
this.expiringLink = true;
const ownerKey = this.$store.state.settings.share.ownerKey;
try {
await this.$axios.$delete('/api/rooms/' + this.roomId + '?ownerKey=' + ownerKey) === 'OK';
this.nullLinkProperties();
}
catch(e) {
// We might get a 403 if the wrong ownerKey is provided or 404 if the given roomKey doesn't
// exist for some reason. We won't invalidate the link server-side, but as far as this client
// is concerned, we'll remove our references to that link so they
// can generate a new one.
this.nullLinkProperties();
}
finally {
this.expiringLink = false;
}
},
nullLinkProperties() {
this.$store.commit('SET_SHARE_ROOM_ID', { roomId: null });
this.$store.commit('SET_SHARE_OWNER_KEY', { ownerKey: null });
this.$store.commit('SET_SHARE_URL', { url: null });
this.$store.commit('SET_SHARE_EXPIRE_DATE', { expireDate: null });
},
shareLinkSelect() {
this.$nextTick(function () {
if (this.$refs.shareLinkInput) {
this.$refs.shareLinkInput.focus();
this.$refs.shareLinkInput.select();
}
});
},
onClose () {
this.showPopover = false;
},
onOk () {
},
onShow () {
// Called just before popover is shown
// Hide any tooltips that may be open on the cast button
this.$root.$emit('bv::hide::tooltip');
if (this.$refs.shareLinkInput) {
this.$refs.shareLinkInput.focus();
this.$refs.shareLinkInput.select();
}
},
},
watch: {
'hasValidShareLink': function () {
this.shareLinkSelect();
},
},
}
</script>

0 comments on commit 00b5b2e

Please sign in to comment.