From e015320d54a8a9679bc20da0dc0308009ef8dcf1 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Sun, 3 Oct 2021 00:05:47 -0700 Subject: [PATCH] Introduce `` Closes https://github.com/hotwired/turbo/issues/413 The `` element accepts a `[src]` attribute, and uses that to connect Turbo to poll for streams published on the server side. When the element is connected to the document, the stream source is connected. When the element is disconnected, the stream is disconnected. When declared with an `ws://` or `wss://` URL, the underlying Stream Source will be a `WebSocket` instance. Otherwise, the connection is through an `EventSource`. Since the document's `` is persistent across navigations, the `` is meant to be mounted within the `` element. Typical full page navigations driven by Turbo will result in the `` being discarded and replaced with the resulting document. It's the server's responsibility to ensure that the element is present on each page that requires streaming. --- src/elements/index.ts | 2 ++ src/elements/stream_source_element.ts | 24 ++++++++++++++++++++++++ src/tests/fixtures/stream.html | 3 ++- src/tests/functional/stream_tests.ts | 12 ++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/elements/stream_source_element.ts diff --git a/src/elements/index.ts b/src/elements/index.ts index 34ae5410a..71c20239b 100644 --- a/src/elements/index.ts +++ b/src/elements/index.ts @@ -1,6 +1,7 @@ import { FrameController } from "../core/frames/frame_controller" import { FrameElement } from "./frame_element" import { StreamElement } from "./stream_element" +import { StreamSourceElement } from "./stream_source_element" FrameElement.delegateConstructor = FrameController @@ -9,3 +10,4 @@ export * from "./stream_element" customElements.define("turbo-frame", FrameElement) customElements.define("turbo-stream", StreamElement) +customElements.define("turbo-stream-source", StreamSourceElement) diff --git a/src/elements/stream_source_element.ts b/src/elements/stream_source_element.ts new file mode 100644 index 000000000..2c4adf127 --- /dev/null +++ b/src/elements/stream_source_element.ts @@ -0,0 +1,24 @@ +import { StreamSource } from "../core/types" +import { connectStreamSource, disconnectStreamSource } from "../index" + +export class StreamSourceElement extends HTMLElement { + streamSource: StreamSource | null = null + + connectedCallback() { + this.streamSource = this.src.match(/^ws{1,2}:/) ? + new WebSocket(this.src) : + new EventSource(this.src) + + connectStreamSource(this.streamSource) + } + + disconnectedCallback() { + if (this.streamSource) { + disconnectStreamSource(this.streamSource) + } + } + + get src(): string { + return this.getAttribute("src") || "" + } +} diff --git a/src/tests/fixtures/stream.html b/src/tests/fixtures/stream.html index 8fa09e708..9b591a7b5 100644 --- a/src/tests/fixtures/stream.html +++ b/src/tests/fixtures/stream.html @@ -4,8 +4,9 @@ Turbo Streams - + +
diff --git a/src/tests/functional/stream_tests.ts b/src/tests/functional/stream_tests.ts index 67d0cd4d1..74acc3350 100644 --- a/src/tests/functional/stream_tests.ts +++ b/src/tests/functional/stream_tests.ts @@ -50,6 +50,18 @@ export class StreamTests extends FunctionalTestCase { this.assert.ok(messages[0]) this.assert.ok(messages[1], "receives streams when connected") this.assert.notOk(messages[2], "receives streams when connected") + + await this.evaluate(() => document.getElementById("stream-source")?.remove()) + await this.nextBeat + + await this.clickSelector("#async button") + await this.nextBeat + + messages = await this.querySelectorAll("#messages > *") + + this.assert.ok(messages[0]) + this.assert.ok(messages[1], "receives streams when connected") + this.assert.notOk(messages[2], "does not receive streams when disconnected") } }