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 set webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
curtgrimes committed Sep 2, 2018
1 parent 4fec727 commit fea294d
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 22 deletions.
2 changes: 1 addition & 1 deletion app/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module.exports = {
imports: [
{
set: '@fortawesome/free-solid-svg-icons',
icons: ['faFileAlt', 'faFileWord', 'faExclamationTriangle', 'faTimes', 'faMicrophone', 'faDesktop', 'faExternalLinkAlt', 'faSave', 'faTrashAlt', 'faCog', 'faCheckCircle', 'faSpinner', 'faChevronRight', 'faMinusCircle', 'faPlusCircle', 'faArrowLeft', 'faFlask'],
icons: ['faFileAlt', 'faFileWord', 'faExclamationTriangle', 'faTimes', 'faMicrophone', 'faDesktop', 'faExternalLinkAlt', 'faSave', 'faTrashAlt', 'faCog', 'faCheckCircle', 'faSpinner', 'faChevronRight', 'faMinusCircle', 'faPlusCircle', 'faArrowLeft', 'faFlask', 'faCaretRight', 'faCaretDown',],
},
{
set: '@fortawesome/free-regular-svg-icons',
Expand Down
73 changes: 73 additions & 0 deletions app/pages/captioner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,79 @@ export default {
this.refreshVmixStatus();
});
let lastWebhookInterimEventDate = 0;
RemoteEventBus.$on('sendMutationToReceivers', ({type, payload}) => {
if (
this.$store.state.settings.integrations.webhooks.on
&& [
'captioner/SET_TRANSCRIPT_INTERIM',
'captioner/APPEND_TRANSCRIPT_FINAL',
].includes(type)
) {
if (
type === 'captioner/SET_TRANSCRIPT_INTERIM'
&& Date.now() - lastWebhookInterimEventDate < this.$store.state.settings.integrations.webhooks.throttleMs
) {
// Skip this interim event due to throttling
return;
}
if (type === 'captioner/SET_TRANSCRIPT_INTERIM') {
lastWebhookInterimEventDate = Date.now();
}
let payloadToSend = payload;
delete payloadToSend.omitFromGoogleAnalytics; // if it exists
let typeToSend = type.replace('captioner/', '');
let bodyToSend = JSON.stringify({
type: typeToSend,
payload: payloadToSend
});
this.$store.commit('APPEND_WEBHOOK_LOG', {
event: {
type: 'send',
title: this.$store.state.settings.integrations.webhooks.method + ' ' + this.$store.state.settings.integrations.webhooks.url,
body: bodyToSend,
showBody: false,
}
});
fetch(this.$store.state.settings.integrations.webhooks.url, {
method: this.$store.state.settings.integrations.webhooks.method,
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: bodyToSend,
})
.then((response) => {
response.text()
.then(() => {
this.$store.commit('APPEND_WEBHOOK_LOG', {
event: {
type: 'receive',
title: response.status + ' ' + response.statusText,
error: response.status >= 300,
}
});
});
})
.catch((error) => {
this.$store.commit('APPEND_WEBHOOK_LOG', {
event: {
type: 'receive',
title: error.message,
error: true,
}
});
});
}
});
RemoteEventBus.$on('sendMutationToReceivers', throttle(({type, payload}) => {
if (self.remoteDisplays.length) {
this.$socket.sendObj({
Expand Down
1 change: 1 addition & 0 deletions app/pages/captioner/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<hr/>
<b-nav vertical pills>
<b-nav-item to="/captioner/settings/vmix">vMix</b-nav-item>
<b-nav-item to="/captioner/settings/webhooks">Webhooks</b-nav-item>
</b-nav>
</nav>
</div>
Expand Down
162 changes: 162 additions & 0 deletions app/pages/captioner/settings/webhooks/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<template>
<div>
<p>Webhooks allows you to receive real-time HTTP notifications of captioning events in your application. Events are sent client-side from this browser.</p>

<div class="form-group">
<div class="custom-control custom-checkbox mb-2">
<input v-model="on" class="custom-control-input" name="word-replacements-censor-profanity" type="checkbox" id="word-replacements-censor-profanity">
<label class="custom-control-label" for="word-replacements-censor-profanity">Use Webhook</label>
</div>
</div>

<div class="form-group row">
<label for="webhooksUrl" class="col-sm-4 col-md-3 col-form-label">URL</label>
<div class="col-sm-8 col-md-9">
<input id="webhooksUrl" :disabled="!on" name="webhooksUrl" v-model="url" class="form-control" type="url" placeholder="URL" />
</div>
</div>

<div class="form-group row">
<label for="webhooksMethod" class="col-sm-4 col-md-3 col-form-label">Method</label>
<div class="col-sm-8 col-md-9">
<select class="form-control" id="webhooksMethod" :disabled="!on" name="webhooksMethod" v-model="method">
<option value="POST" selected>POST</option>
<option value="PUT">PUT</option>
</select>
</div>
</div>

<div class="form-group row">
<label for="webhooksThrottleMs" class="col-sm-4 col-md-3 col-form-label">Throttle Interim Transcript Events</label>
<div class="col-sm-8 col-md-9">
<div class="input-group">
<input id="webhooksThrottleMs" :disabled="!on" name="webhooksThrottleMs" v-model="throttleMs" type="number" min="0" max="60000" step="5" class="form-control" maxlength="5" />
<span class="input-group-append">
<span class="input-group-text">ms</span>
</span>
</div>
<p class="small mb-0 mt-1">
Send an interim transcript event, at most, every
<span v-if="throttleMs >= 1000">{{(throttleMs / 1000).toFixed(1)}} second<span v-if="(throttleMs / 1000).toFixed(1) !== '1.0'">s</span></span>
<span v-else>{{throttleMs}} millisecond<span v-if="throttleMs != 1">s</span></span>.</p>
</div>
</div>

<div class="form-group row">
<label class="col-sm-4 col-md-3 col-form-label">Log</label>
<div class="col-sm-8 col-md-9">
<button v-if="!showLog" class="btn btn-outline-secondary btn-sm" @click="showLog = true">Show Log</button>
<transition name="fade">
<div v-show="showLog" ref="webhookLog" class="card bg-white small p-2 text-monospace" style="max-height:350px;overflow-y:auto">
<div v-for="(event, index) in log" :key="index" :class="event.type == 'receive' ? 'mb-2' : ''">
<span v-if="event.type === 'send'" class="font-weight-bold">{{event.title}}</span>
<span v-else :class="event.error ? 'text-danger' : 'text-success'">--> {{event.title}}</span>

<span v-if="event.body">
<a class="ml-2 btn btn-link btn-sm py-0 pl-0 pr-1" href="javascript:void(0)" @click="event.showBody = !event.showBody"><fa :icon="event.showBody ? 'caret-down' : 'caret-right'" fixed-width />Payload</a>
<span v-if="event.showBody">{{event.body}}</span>
</span>
</div>
</div>
</transition>
</div>
</div>

<h3 class="mt-4">Definitions</h3>
<p>The <strong>interim transcript</strong> is a phrase or partial phrase that represents what is currently being spoken. Each interim transcript you receive should invalidate and replace any past interim transcript you have.</p>
<p>The interim transcript becomes the <strong>final transcript</strong> after a pause in speech. Whenever you receive a final transcript, any interim transcript you currently have should be discarded, and you should append that final transcript to any past final transcript you have.</p>

<h3 class="mt-4">Events</h3>
<p>These are the JSON payloads your application should expect to receive.</p>

<h4>Interim Transcript Event</h4>
<p>Triggered when speech is recognized and an interim transcript is created or updated. When you receive this even, invalidate any past interim transcript you have.</p>
<div class="card bg-white p-3 mb-4">
<h5 class="card-title text-muted">Request Body</h5>
<pre class="m-0">
{
"type":"SET_TRANSCRIPT_INTERIM",
"payload": {
"transcriptInterim": "This is a transcript"
}
}</pre>
</div>

<h4>Final Transcript Event</h4>
<p>Triggered when there is a pause in speech and a final transcript is created. When you receive this even, invalidate any past interim transcript you have and append this final transcript to any past final transcript you have.</p>
<div class="card bg-white p-3">
<h5 class="card-title text-muted">Request Body</h5>
<pre class="m-0">
{
"type":"APPEND_TRANSCRIPT_FINAL",
"payload": {
"transcriptFinal": "This is a transcript"
}
}</pre>
</div>
</div>
</template>

<script>
import debounce from 'lodash.debounce'
export default {
name: 'settings-webhooks-view',
transition: 'fade',
middleware: [
'settings-meta',
],
meta: {
settingsPageTitle: 'Webhooks',
},
data: function() {
return {
showLog: false,
};
},
computed: {
on: {
get () {
return this.$store.state.settings.integrations.webhooks.on;
},
set: function (onOrOff) {
this.$store.commit('SET_WEBHOOKS_ON', { onOrOff });
},
},
url: {
get () {
return this.$store.state.settings.integrations.webhooks.url;
},
set: debounce(function(url) {
this.$store.commit('SET_WEBHOOKS_URL', { url });
}, 500, {leading: true})
},
method: {
get () {
return this.$store.state.settings.integrations.webhooks.method;
},
set: function(method) {
this.$store.commit('SET_WEBHOOKS_METHOD', { method });
},
},
throttleMs: {
get () {
return this.$store.state.settings.integrations.webhooks.throttleMs;
},
set (throttleMs) {
this.$store.commit('SET_WEBHOOKS_THROTTLE_MS', { throttleMs });
},
},
log: {
get () {
return this.$store.state.integrations.webhooks.log;
},
},
},
watch: {
log: function() {
this.$refs.webhookLog.scrollTop = this.$refs.webhookLog.scrollHeight;
}
},
}
</script>
5 changes: 5 additions & 0 deletions app/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ export default {

commitPropertySetting('SET_SEND_TO_VMIX', 'on', 'integrations.vmix.on');
commitPropertySetting('SET_VMIX_WEB_CONTROLLER_ADDRESS', 'webControllerAddress', 'integrations.vmix.webControllerAddress');

commitPropertySetting('SET_WEBHOOKS_ON', 'onOrOff', 'integrations.webhooks.on');
commitPropertySetting('SET_WEBHOOKS_URL', 'url', 'integrations.webhooks.url');
commitPropertySetting('SET_WEBHOOKS_METHOD', 'method', 'integrations.webhooks.method');
commitPropertySetting('SET_WEBHOOKS_THROTTLE_MS', 'throttleMs', 'integrations.webhooks.throttleMs');

commitPropertySetting('SET_LAST_WHATS_NEW_VERSION_SEEN', 'version', 'lastWhatsNewVersionSeen');

Expand Down
9 changes: 9 additions & 0 deletions app/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ const createStore = () => {
on: false,
webControllerAddress: '',
},
webhooks: {
on: false,
url: '',
method: 'POST',
throttleMs: 500,
},
},
lastWhatsNewVersionSeen: '',
exp: [],
Expand Down Expand Up @@ -145,6 +151,9 @@ const createStore = () => {
webControllerConnected: null,
cachedInputGUID: null,
},
webhooks: {
log: [],
},
},
eventLog: {
onUntilStopTime: null,
Expand Down
48 changes: 27 additions & 21 deletions app/store/mutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,32 @@ export default {
state.integrations.vmix.showNotFullySetUpMessage = on;
},

SET_WEBHOOKS_ON: (state, { onOrOff }) => {
state.settings.integrations.webhooks.on = onOrOff;
},
SET_WEBHOOKS_URL: (state, { url }) => {
state.settings.integrations.webhooks.url = url;
},
SET_WEBHOOKS_METHOD: (state, { method }) => {
// Should be one of this set of options; otherwise set default
let methodValidated = ['POST','PUT'].includes(method) ? method : 'POST';
state.settings.integrations.webhooks.method = methodValidated;
},
SET_WEBHOOKS_THROTTLE_MS: (state, { throttleMs }) => {
let throttleMsValidated = Number(throttleMs);
if (Number.isNaN(throttleMsValidated)) {
throttleMsValidated = 0;
}
if (throttleMsValidated > 60000) {
throttleMsValidated = 60000;
}

state.settings.integrations.webhooks.throttleMs = throttleMsValidated;
},
APPEND_WEBHOOK_LOG: (state, { event }) => {
state.integrations.webhooks.log.push(event);
},

SET_EVENT_LOG: (state, { eventLog }) => {
state.eventLog.log = eventLog;
},
Expand All @@ -229,25 +255,5 @@ export default {
SET_EVENT_LOG_STOP_TIME: (state, { stopTime }) => {
state.eventLog.onUntilStopTime = stopTime;
},


SET_ACTIVE_TYPE: (state, { type }) => {
state.activeType = type
},

SET_LIST: (state, { type, ids }) => {
state.lists[type] = ids
},

SET_ITEMS: (state, { items }) => {
items.forEach(item => {
if (item) {
Vue.set(state.items, item.id, item)
}
})
},

SET_USER: (state, { id, user }) => {
Vue.set(state.users, id, user || false) /* false means user not found */
}

}

0 comments on commit fea294d

Please sign in to comment.