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

Commit

Permalink
Add ability to have vanity links
Browse files Browse the repository at this point in the history
  • Loading branch information
curtgrimes committed Apr 30, 2019
1 parent 5effeb4 commit b6728eb
Show file tree
Hide file tree
Showing 14 changed files with 1,258 additions and 251 deletions.
1 change: 1 addition & 0 deletions app/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ FIREBASE_DATABASE_URL=
FIREBASE_PROJECT_ID=
FIREBASE_STORAGE_BUCKET=
FIREBASE_MESSAGING_SENDER_ID=
FIREBASE_NODE_SERVICE_ACCOUNT_KEY=

ADMIN_TOKEN=
60 changes: 52 additions & 8 deletions app/api/routes/rooms/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const openGraphScraper = require('open-graph-scraper');
const vibrant = require('node-vibrant')
const url = require('url');
const twitch = require('./twitch');
const admin = require('firebase-admin');

admin.initializeApp({
credential: admin.credential.cert(JSON.parse(process.env.FIREBASE_NODE_SERVICE_ACCOUNT_KEY))
});

const expireHours = 48;

Expand Down Expand Up @@ -71,13 +76,43 @@ rooms.post('/', async (req, res, next) => {
}

let roomKey, roomId, roomKeyAlreadyExists;
do {
roomId = nanoidGenerate('23456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghjkmnpqrstuvwxyz-', 8);
roomKey = 'rooms:' + roomId;
roomKeyAlreadyExists = await redisClient.existsAsync(roomKey) === 1;
}
while (roomKeyAlreadyExists); // repeat roomkey generation on collision
if (req.body.urlType === 'vanity') {
if (!req.body.uid) {
// Need a user ID to continue
res.sendStatus(403);
return;
}

// Check that they're allowed to request this vanity URL
let db = admin.firestore();
let vanity = await db.collection('users')
.doc(req.body.uid)
.collection('privileges')
.doc('share')
.get()
.then((document) => {
if (document.exists) {
return document.data().vanity;
}
});

if (vanity) {
// They have a vanity URL
roomId = vanity;
roomKey = 'rooms:' + roomId;
} else {
// They don't have a vanity URL
res.sendStatus(403);
return;
}
} else {
do {
roomId = nanoidGenerate('23456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghjkmnpqrstuvwxyz-', 8);
roomKey = 'rooms:' + roomId;
roomKeyAlreadyExists = await redisClient.existsAsync(roomKey) === 1;
}
while (roomKeyAlreadyExists); // repeat roomkey generation on collision
}
const ownerKey = nanoid(50);

const backlink = req.body.backlink ? ['backlink', req.body.backlink] : [];
Expand All @@ -86,13 +121,22 @@ rooms.post('/', async (req, res, next) => {
const appearance = req.body.appearance && req.body.appearance.length <= 1000 ? ['appearance', req.body.appearance] : [];

redisClient.hmset(roomKey, 'ownerKey', ownerKey, ...backlink, ...appearance);
redisClient.expire(roomKey, 60 * 60 * expireHours);

if (req.body.urlType === 'random') {
// Random URLs expire
redisClient.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)),

// Vanity URLs never expire
expires: req.body.urlType === 'random',
expireDate: req.body.urlType === 'random' ?
(new Date((new Date()).getTime() + (1000 * 60 * 60 * expireHours))) :
null,
}));
return;
});
Expand Down
52 changes: 46 additions & 6 deletions app/components/ShareButton.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<template>
<div>
<b-btn-group class="mr-2">
<b-btn
v-b-tooltip.hover
:title="tooltip"
:variant="hasValidShareLink ? 'secondary' : 'info'"
class="mr-2"
:variant="on ? 'secondary' : 'outline-light'"
@click="showShareSettings()"
>
<fa icon="broadcast-tower"/>
Expand All @@ -16,18 +15,43 @@
<fa icon="exclamation-triangle"></fa>
</b-badge>
</b-btn>
</div>
<b-btn
v-b-tooltip.hover
:title="on ? 'Turn off sharing' : 'Turn on sharing'"
:variant="on ? 'light' : 'light'"
id="share-on-off-toggle"
v-if="hasValidShareLink && !expired"
class="px-3"
@click="on = !on; $event.target.blur(); $event.target.parentElement.blur()"
>
<fa :icon="on ? 'toggle-on' : 'toggle-off'" style="font-size:1.3rem;margin-top:0.2rem"/>
</b-btn>
</b-btn-group>
</template>


<style>
.pointerSwitch,
.pointerSwitch:hover,
.pointerSwitch.custom-switch .custom-control-label::before,
.pointerSwitch.custom-switch .custom-control-label::after {
cursor: pointer;
}
</style>

<script>
import bBtn from 'bootstrap-vue/es/components/button/button';
import bBtnGroup from 'bootstrap-vue/es/components/button-group/button-group';
import bBadge from 'bootstrap-vue/es/components/badge/badge';
import bTooltip from 'bootstrap-vue/es/directives/tooltip/tooltip';
import bFormCheckbox from 'bootstrap-vue/es/components/form-checkbox/form-checkbox';
export default {
components: {
bBtn,
bBtnGroup,
bBadge,
bFormCheckbox,
},
directives: {
bTooltip,
Expand All @@ -43,14 +67,16 @@ export default {
this.$store.commit('share/SET_SHOW_SETTINGS', { on: true });
}
},
hideAllTooltips: function() {
this.$root.$emit('bv::hide::tooltip');
},
},
computed: {
tooltip: function() {
if (this.expired) {
return 'Share Captions (Link Expired)';
} else if (this.hasValidShareLink && this.subscriberCount > 0) {
return (
'Sharing captions with ' +
this.subscriberCount +
' viewer' +
(this.subscriberCount != 1 ? 's' : '')
Expand All @@ -59,6 +85,18 @@ export default {
return 'Share Captions';
}
},
on: {
get() {
return this.$store.state.settings.share.on;
},
set(on) {
this.$store.commit('SET_SHARE_ON', { on });
this.hideAllTooltips();
this.$nextTick(function() {
this.$root.$emit('bv::show::tooltip', 'share-on-off-toggle');
});
},
},
shareLink: function() {
return this.$store.state.settings.share.url;
},
Expand All @@ -75,7 +113,9 @@ export default {
return this.$store.state.share.expired;
},
hasValidShareLink() {
return this.shareLink && this.roomId && this.expireDate;
return (
this.shareLink && this.roomId && (this.expireDate || !this.expires)
);
},
},
};
Expand Down
98 changes: 73 additions & 25 deletions app/components/toasts/Share.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,30 @@
Random link
<span class="small text-muted">(Expires in 48 hours)</span>
</b-form-radio>
<b-form-radio value="vanity" v-if="false">
My custom vanity link
<b-form-radio value="vanity">
Custom vanity link
<span class="small text-muted">(Never expires)</span>
</b-form-radio>
</b-form-radio-group>
</b-form-group>
<p
v-if="urlType === 'vanity'"
class="small text-danger mb-0 mt-2"
>Sorry, vanity links aren't available to everyone yet.</p>
<transition name="fade-in">
<div v-if="urlType === 'vanity'">
<b-badge
v-if="vanity"
variant="success"
class="ml-4 mt-1 px-2 py-1"
style="font-size:.9rem"
>
<fa icon="star"/>
{{vanity}}
</b-badge>
<p
v-else-if="vanity === false"
class="small text-danger mb-0 mt-2"
>Hang tight! Vanity links aren't available to everyone yet.</p>
<b-spinner v-else small class="mt-2 ml-4" variant="muted"></b-spinner>
</div>
</transition>
</form>
</div>
<div v-else style="width:500px; min-width:200px; max-width:100%">
Expand Down Expand Up @@ -108,15 +122,13 @@
</div>
</b-collapse>
<hr class="my-3">
<p class="small text-muted mb-2">
Link expires
<timeago :datetime="expireDate"></timeago>
</p>
<!-- <div class="card p-2 bg-primary text-info mb-3">
<p class="text-monospace text-uppercase font-weight-bold mb-1"><fa icon="info-circle"/> Enjoy this Preview!</p>
<span class="small">I hope you enjoy the preview of this new feature! Please send me feedback on <a href="https://facebook.com/webcaptioner" target="_blank">Facebook</a> or <a href="https://twitter.com/webcaptioner" target="_blank">Twitter</a> about how well it works for you and your viewers.</span>
</div>-->
<hr class="my-3">
<div v-if="expireDate !== null">
<p class="small text-muted mb-2">
Link expires
<timeago :datetime="expireDate"></timeago>
</p>
<hr class="my-3">
</div>
<b-dropdown
text="Options"
variant="outline-secondary"
Expand Down Expand Up @@ -155,8 +167,8 @@
<template v-if="!hasValidShareLink" slot="footer">
<b-btn
@click="getLink()"
:disabled="gettingLink || urlType === 'vanity'"
:variant="urlType === 'vanity' ? 'light' : 'secondary'"
:disabled="gettingLink || (urlType === 'vanity' && !vanity)"
:variant="(urlType === 'vanity' && !vanity) ? 'light' : 'secondary'"
>
Get Link
<fa v-if="gettingLink" icon="spinner" spin/>
Expand All @@ -176,6 +188,8 @@ import bFormCheckbox from 'bootstrap-vue/es/components/form-checkbox/form-checkb
import bFormGroup from 'bootstrap-vue/es/components/form-group/form-group';
import bFormRadio from 'bootstrap-vue/es/components/form-radio/form-radio';
import bFormRadioGroup from 'bootstrap-vue/es/components/form-radio/form-radio-group';
import bSpinner from 'bootstrap-vue/es/components/spinner/spinner';
import bBadge from 'bootstrap-vue/es/components/badge/badge';
export default {
components: {
Expand All @@ -188,6 +202,8 @@ export default {
bFormGroup,
bFormRadio,
bFormRadioGroup,
bSpinner,
bBadge,
},
directives: {
bTooltip,
Expand Down Expand Up @@ -236,17 +252,24 @@ export default {
}
try {
const { roomId, ownerKey, url, expireDate } = await this.$axios.$post(
'/api/rooms',
{
backlink: this.backlink,
appearance: JSON.stringify(this.$store.state.settings.appearance),
}
);
const {
roomId,
ownerKey,
url,
expires,
expireDate,
} = await this.$axios.$post('/api/rooms', {
backlink: this.backlink,
appearance: JSON.stringify(this.$store.state.settings.appearance),
urlType: this.urlType,
uid: this.$store.state.user.uid,
});
this.$store.commit('SET_SHARE_ON', { on: true });
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_EXPIRES', { expires });
this.$store.commit('SET_SHARE_EXPIRE_DATE', { expireDate });
this.$store.commit('SET_SHARE_SUBSCRIBER_COUNT', {
subscriberCount: 0,
Expand Down Expand Up @@ -284,10 +307,12 @@ export default {
}
},
nullLinkProperties() {
this.$store.commit('SET_SHARE_ON', { on: false });
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 });
this.$store.commit('SET_SHARE_URL_TYPE', { urlType: 'random' });
},
shareLinkSelect() {
this.$nextTick(function() {
Expand Down Expand Up @@ -332,6 +357,24 @@ export default {
}
});
},
urlType: function(urlType) {
if (urlType === 'vanity') {
let db = this.$firebase.firestore();
db.collection('users')
.doc(this.$store.state.user.uid)
.collection('privileges')
.doc('share')
.get()
.then((document) => {
if (document.exists) {
const { vanity } = document.data();
this.$store.commit('SET_SHARE_VANITY', {
vanity: vanity || false,
});
}
});
}
},
},
computed: {
show: function() {
Expand Down Expand Up @@ -360,8 +403,13 @@ export default {
this.$store.commit('SET_SHARE_URL_TYPE', { urlType });
},
},
vanity: function() {
return this.$store.state.settings.share.vanity;
},
hasValidShareLink() {
return this.shareLink && this.roomId && this.expireDate;
return (
this.shareLink && this.roomId && (this.expireDate || !this.expires)
);
},
// facebookShareLink() {
// return 'https://www.facebook.com/dialog/share?app_id=1339681726086659&amp;display=popup&amp;href=' + encodeURIComponent(this.shareLink);
Expand Down
4 changes: 4 additions & 0 deletions app/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ module.exports = {
'FIREBASE_STORAGE_BUCKET',
'FIREBASE_MESSAGING_SENDER_ID',
'GOOGLE_CAST_APP_ID',
'HOSTNAME',
'STRIPE_API_KEY_PUBLIC',
]
}],
Expand Down Expand Up @@ -164,6 +165,9 @@ module.exports = {
'faWindowRestore',
'faBars',
'faUserCircle',
'faStar',
'faToggleOn',
'faToggleOff',
],
},
{
Expand Down
Loading

0 comments on commit b6728eb

Please sign in to comment.