Last active
December 13, 2024 21:02
-
-
Save SinZ163/9835a45aa8d06899c563d4e1ffa2ce24 to your computer and use it in GitHub Desktop.
Solid websocket poc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { defineConfig } from "@solidjs/start/config"; | |
| const app = defineConfig({ | |
| ssr: false, | |
| server: { | |
| experimental: { | |
| websocket: true | |
| } | |
| } | |
| }); | |
| app.addRouter({ | |
| name: "websocket", | |
| type: "http", | |
| handler: "./src/entry-websocket.ts", | |
| target: "server", | |
| "base": "/_ws" | |
| }) | |
| export default app; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Example() { | |
| const {createWSSignal, dispatch} = useWSContext(); | |
| const [test, setText] = createWSSignal("testSignal", 0); | |
| return ( | |
| <div> | |
| <h1>Hello {test()}</h1> | |
| <button onClick={() => setText(prev => prev + 1)}>Increment</button> | |
| <button onClick={() => setText(prev => prev - 1)}>Decrement</button> | |
| </div> | |
| ); | |
| } | |
| export default function App() { | |
| const {context, wsErrorReason, wsReady} = createWebSocket(); | |
| <WSContext.Provider value={context}> | |
| <Show when={wsReady()}> | |
| <Example /> | |
| </Show> | |
| </WSContext.Provider> | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const DataState: Record<string, any> = {}; | |
| export default eventHandler({ | |
| handler: () => {}, | |
| websocket: defineWebSocket({ | |
| async open(peer) { | |
| console.log("WebSocket opened"); | |
| }, | |
| async message(peer, event) { | |
| console.log("WebSocket message", event); | |
| let msg = JSON.parse(event.text()) as WSMessage; | |
| if (msg.type === "register") { | |
| peer.subscribe(msg.id); | |
| if (DataState[msg.id]) { | |
| console.log("Already registered, sending cached value" + DataState[msg.id]); | |
| peer.send({id: msg.id, value: DataState[msg.id]}); | |
| } | |
| } | |
| if (msg.type === "change") { | |
| // TODO: Server Validation | |
| DataState[msg.id] = msg.value; | |
| peer.publish(msg.id, msg); | |
| } | |
| }, | |
| async close(peer) { | |
| console.log("WebSocket closed"); | |
| }, | |
| }), | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| createContext, | |
| createEffect, | |
| createSignal, | |
| onCleanup, | |
| Signal, | |
| untrack, | |
| useContext, | |
| } from "solid-js"; | |
| import { getReasonForWebSocketClose } from "./server-actions"; | |
| import { EventMessage, StartEventMessage } from "./common/message"; | |
| interface WSContextType { | |
| ws: WebSocket; | |
| dispatch: <T extends Omit<EventMessage | StartEventMessage, "type">>( | |
| event: T | |
| ) => void; | |
| createWSSignal: <T,>( | |
| identifier: string, | |
| defaultValue: T | |
| ) => Signal<T>; | |
| } | |
| export const WSContext = createContext<WSContextType>(); | |
| export const useWSContext = () => { | |
| const context = useContext(WSContext); | |
| if (!context) { | |
| throw new Error("Missing WSContext"); | |
| } | |
| return context; | |
| } | |
| export const createWebSocket = () => { | |
| const [wsErrorReason, setWsErrorReason] = createSignal(""); | |
| const [wsReady, setWSReady] = createSignal(false); | |
| const protocol = window.location.protocol === "http:" ? "ws" : "wss"; | |
| const ws = new WebSocket(`${protocol}://${window.location.host}/_ws`); | |
| ws.addEventListener("open", () => setWSReady(true)); | |
| ws.addEventListener("close", async (e) => { | |
| setWSReady(false); | |
| }); | |
| const wsRegistrationCache: Record<string, any> = {}; | |
| ws.addEventListener("message", async (ev) => { | |
| let data = ev.data; | |
| if (ev.data instanceof Blob) { | |
| data = await ev.data.text(); | |
| } | |
| let msg = JSON.parse(data); | |
| // Cloudflare disconnects websockets if they are idle for ~60-100 seconds | |
| if ("type" in msg && msg["type"] === "ping") { | |
| ws.send(JSON.stringify({ type: "pong" })); | |
| } | |
| if ("id" in msg && "value" in msg) { | |
| wsRegistrationCache[msg.id] = msg.value; | |
| } | |
| }); | |
| const createWSSignal = <T,>( | |
| identifier: string, | |
| defaultValue: T | |
| ): Signal<T> => { | |
| const [value, setValue] = createSignal(defaultValue); | |
| const [bypass, setBypass] = createSignal(true); | |
| if (!wsRegistrationCache[identifier]) { | |
| ws.send( | |
| JSON.stringify({ | |
| type: "register", | |
| id: identifier, | |
| defaultValue, | |
| }) | |
| ); | |
| } else { | |
| setBypass(true); | |
| setValue(wsRegistrationCache[identifier]); | |
| } | |
| const handleMessage = async (ev: MessageEvent) => { | |
| let data = ev.data; | |
| if (ev.data instanceof Blob) { | |
| data = await ev.data.text(); | |
| } | |
| let msg = JSON.parse(data); | |
| if ("id" in msg && "value" in msg) { | |
| if (msg.id === identifier) { | |
| setBypass(true); | |
| setValue(msg.value); | |
| } | |
| } | |
| }; | |
| ws.addEventListener("message", handleMessage); | |
| onCleanup(() => { | |
| ws.removeEventListener("message", handleMessage); | |
| }); | |
| createEffect(() => { | |
| const val = value(); | |
| if (ws.readyState !== ws.OPEN) return; | |
| let bypassVal = false; | |
| untrack(() => { | |
| bypassVal = bypass(); | |
| }); | |
| if (bypassVal) { | |
| setBypass(false); | |
| return; | |
| } | |
| ws.send( | |
| JSON.stringify({ | |
| type: "change", | |
| id: identifier, | |
| value: value(), | |
| }) | |
| ); | |
| }); | |
| return [value, setValue]; | |
| }; | |
| return { | |
| context: { | |
| ws, | |
| createWSSignal, | |
| }, | |
| wsErrorReason, | |
| wsReady, | |
| }; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment