Skip to content

Instantly share code, notes, and snippets.

@matt-e-king
Created April 17, 2024 19:06
Show Gist options
  • Save matt-e-king/3ac2127025cf5dbc03001d9b21c1984a to your computer and use it in GitHub Desktop.
Save matt-e-king/3ac2127025cf5dbc03001d9b21c1984a to your computer and use it in GitHub Desktop.
Hypothesis Embed and sock in Vue app
// HypothesisEmbed.vue
// This is the component that mounts on the page that has the annotations
// It's important to destroy the client when componenent is unmounted
// because if the component remounts, it creates duplicate annotations in the DOM
<template>
<div
id="HypothesisEmbed"
class="HypothesisEmbed"
/>
</template>
<script>
const scriptId = 'HypothesisEmbed__script'
const scriptSrc = 'https://hypothes.is/embed.js'
export default {
name: 'HypothesisEmbed',
mounted () {
if (window) {
if (!window.document.getElementById(scriptId)) {
const script = document.createElement('script')
script.id = scriptId
script.async = true
script.src = scriptSrc
window.document.getElementById('HypothesisEmbed').appendChild(script)
}
}
},
beforeDestroy () {
if (window && window.document) {
const annotatorLink = window.document.querySelector(
'link[type="application/annotator+html"]'
)
if (annotatorLink) {
// Dispatch a 'destroy' event which is handled by the code in
// annotator/main.js to remove the client.
const destroyEvent = new Event('destroy')
annotatorLink.dispatchEvent(destroyEvent)
}
}
}
}
</script>
// HypothesisSocket.js
// This component is responsible for openining the socket to detect udpates
// This socket currently fails on loop, and then eventually resolves, I haven't reviewed this code in a while
// replace process.env.HYPOTHESIS_KEY with your developer API key
import debounce from 'lodash.debounce'
export default {
name: 'hypothesis-socket',
watch: {
'$route.params.page'() {
this.sendDataDebounce.cancel()
this.sendDataDebounce()
}
},
mounted () {
if (
window
&&
(
!this.hypSocket
|| (this.hypSocket && (this.hypSocket.readyState === 2 || this.hypSocket.readyState === 3))
)
) {
this.connectDebounce.cancel()
this.connectDebounce()
}
},
data () {
return {
connectDebounce: debounce(this.connect, 750),
sendDataDebounce: debounce(this.sendData, 750),
retries: 0
}
},
methods: {
connect () {
this.hypSocket = new WebSocket(`wss://hypothes.is/ws?access_token=${process.env.HYPOTHESIS_KEY}`)
this.hypSocket.onopen = () => {
console.log('Grazing Hypothesis Socket open')
this.hypSocket.send(JSON.stringify({"type":"whoami","id":1}))
this.sendData()
}
this.hypSocket.onmessage = (e) => {
this.$emit('message', JSON.parse(e.data))
}
this.hypSocket.onerror = (e) => {
console.error('Grazing Hypothesis Socket Error: ', e)
this.$emit('error', e)
this.$sentry.captureException(e)
// make sure the connection is not already CLOSING or CLOSED
if (this.hypSocket.readyState !== 2 || this.hypSocket.readyState !== 3) {
this.hypSocket.close()
}
}
this.hypSocket.onclose = (e) => {
const { wasClean, code } = e
if (this.retries <= 12) {
console.log('Grazing Hypothesis Socket is closed: ', e.reason)
// cancel any previous attempts to open connection and then reopen
this.connectDebounce.cancel()
// only reopen it if it wasn't a clean closure
if (!wasClean && code !== 1000) {
this.connectDebounce()
} else {
this.$emit('closed')
}
this.retries++
} else {
const error = 'Grazing Hypothesis Socket reopen retry maximum'
this.$emit('closed')
this.$sentry.captureException(new Error(error))
}
}
},
sendData () {
if (this.hypSocket) {
this.hypSocket.send(JSON.stringify({
"filter": {
"match_policy": "include_any",
"clauses": [
{
"field": "/uri",
"operator": "one_of",
"value": [window.location.href],
}
],
"actions": {
"create": true,
"update": true,
"delete": true
}
}
}))
}
}
},
beforeDestroy () {
if (this.hypSocket) {
this.hypSocket.close(1000)
}
},
render() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment