Skip to content

Commit

Permalink
feat: enhanced turbo-train-stream-source JS API
Browse files Browse the repository at this point in the history
  • Loading branch information
timaramazanov committed Nov 24, 2024
1 parent d8da603 commit 8508324
Showing 1 changed file with 80 additions and 6 deletions.
86 changes: 80 additions & 6 deletions app/assets/javascripts/turbo-train.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,99 @@
import { Turbo } from "@hotwired/turbo-rails"


export default class TurboTrain extends HTMLElement {
static get observedAttributes() {
return [ 'href' ];
return [ 'href' ]
}

error = null

#eventSource = null

constructor() {
super();
super()
}

connectedCallback() {
this.eventSource = new EventSource(this.href);
Turbo.connectStreamSource(this.eventSource);
this.init()
}

// Public API - client can trigger initialization manually
init = () => {
this.#eventSource = new EventSource(this.href)
this.#eventSource.addEventListener('message', this.#onMessage)
this.#eventSource.addEventListener('error', this.#onError)
this.#eventSource.addEventListener('open', this.#onOpen)

if (this.shouldConnectTurboStream) {
Turbo.connectStreamSource(this.#eventSource)
}
}

disconnectedCallback() {
Turbo.disconnectStreamSource(this.eventSource);
this.destroy()
}

// Public API - client can trigger destruction manually
destroy = () => {
this.#eventSource?.close()
if (this.shouldConnectTurboStream) {
Turbo.disconnectStreamSource(this.#eventSource)
}
this.#eventSource?.removeEventListener('message', this.#onMessage)
this.#eventSource?.removeEventListener('error', this.#onError)
this.#eventSource?.removeEventListener('open', this.#onOpen)
}

get href() {
return this.getAttribute('href');
return this.getAttribute('href')
}

// By default component should always connect to turbo stream
// Only in case when we want to listen for event with JS we don't need to connect to turbo stream
get shouldConnectTurboStream() {
return !this.hasAttribute('no-turbo-stream')
}

// Humanize event source ready state
get state() {
if (!this.#eventSource) return null

switch (this.#eventSource.readyState) {
case EventSource.CONNECTING:
return 'connecting'
case EventSource.OPEN:
return 'open'
case EventSource.CLOSED:
return 'closed'
}
}

#onMessage = (event) => {
this.error = null
const data = JSON.parse(event.data)
this.#emit('message', { detail: data })
}

#onError = (error) => {
this.error = error
this.#emit('error', { detail: error })
}

#onOpen = (event) => {
this.#emit('open', { detail: event })
}

#emit = (name, options = {}) => {
const event = new CustomEvent(name, {
bubbles: true,
cancelable: false,
composed: true,
detail: {},
...options
})
this.dispatchEvent(event)
return event
}
}

Expand Down

0 comments on commit 8508324

Please sign in to comment.