Skip to content

Commit

Permalink
Introduce <turbo-stream-source>
Browse files Browse the repository at this point in the history
Closes #413

The `<turbo-stream-source>` 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 `<head>` is persistent across navigations, the
`<turbo-stream-source>` is meant to be mounted within the `<body>`
element.

Typical full page navigations driven by Turbo will result in the
`<body>` 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.
  • Loading branch information
seanpdoyle committed Oct 3, 2021
1 parent 8f94b5c commit e015320
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/elements/index.ts
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -9,3 +10,4 @@ export * from "./stream_element"

customElements.define("turbo-frame", FrameElement)
customElements.define("turbo-stream", StreamElement)
customElements.define("turbo-stream-source", StreamSourceElement)
24 changes: 24 additions & 0 deletions src/elements/stream_source_element.ts
Original file line number Diff line number Diff line change
@@ -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") || ""
}
}
3 changes: 2 additions & 1 deletion src/tests/fixtures/stream.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
<meta charset="utf-8">
<title>Turbo Streams</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
<script>Turbo.connectStreamSource(new EventSource("/__turbo/messages"))</script></head>
</head>
<body>
<turbo-stream-source id="stream-source" src="/__turbo/messages"></turbo-stream-source>
<form id="create" method="post" action="/__turbo/messages">
<input type="hidden" name="content" value="Hello world!">
<input type="hidden" name="type" value="stream">
Expand Down
12 changes: 12 additions & 0 deletions src/tests/functional/stream_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down

0 comments on commit e015320

Please sign in to comment.