From 85083242933e803c48373fde82ca63fe1ecab00e Mon Sep 17 00:00:00 2001 From: timaramazanov Date: Sun, 24 Nov 2024 19:15:23 +0000 Subject: [PATCH] feat: enhanced turbo-train-stream-source JS API --- app/assets/javascripts/turbo-train.js | 86 +++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/turbo-train.js b/app/assets/javascripts/turbo-train.js index ab31aa0..14a7496 100644 --- a/app/assets/javascripts/turbo-train.js +++ b/app/assets/javascripts/turbo-train.js @@ -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 } }