@@ -28,144 +28,137 @@ import { DurableObject } from 'cloudflare:workers';
2828
2929// Worker
3030export default {
31- async fetch(request : Request , env : Env , ctx : ExecutionContext ): Promise <Response > {
32- if (request .url .endsWith (' /websocket' )) {
33- // Expect to receive a WebSocket Upgrade request.
34- // If there is one, accept the request and return a WebSocket Response.
35- const upgradeHeader = request .headers .get (' Upgrade' );
36- if (! upgradeHeader || upgradeHeader !== ' websocket' ) {
37- return new Response (' Worker expected Upgrade: websocket' , {
38- status: 426 ,
39- });
40- }
41-
42- if (request .method !== ' GET' ) {
43- return new Response (' Worker expected GET method' , {
44- status: 400 ,
45- });
46- }
47-
48- // Since we are hard coding the Durable Object ID by providing the constant name 'foo',
49- // all requests to this Worker will be sent to the same Durable Object instance.
50- let id = env .WEBSOCKET_HIBERNATION_SERVER .idFromName (' foo' );
51- let stub = env .WEBSOCKET_HIBERNATION_SERVER .get (id );
52-
53- return stub .fetch (request );
54- }
55- return stub .fetch (request );
56- }
57-
58- return new Response(
59- ` Supported endpoints:
31+ async fetch(request : Request , env : Env , ctx : ExecutionContext ): Promise <Response > {
32+ if (request .url .endsWith (' /websocket' )) {
33+ // Expect to receive a WebSocket Upgrade request.
34+ // If there is one, accept the request and return a WebSocket Response.
35+ const upgradeHeader = request .headers .get (' Upgrade' );
36+ if (! upgradeHeader || upgradeHeader !== ' websocket' ) {
37+ return new Response (' Worker expected Upgrade: websocket' , {
38+ status: 426 ,
39+ });
40+ }
41+
42+ if (request .method !== ' GET' ) {
43+ return new Response (' Worker expected GET method' , {
44+ status: 400 ,
45+ });
46+ }
47+
48+ // Since we are hard coding the Durable Object ID by providing the constant name 'foo',
49+ // all requests to this Worker will be sent to the same Durable Object instance.
50+ let id = env .WEBSOCKET_HIBERNATION_SERVER .idFromName (' foo' );
51+ let stub = env .WEBSOCKET_HIBERNATION_SERVER .get (id );
52+
53+ return stub .fetch (request );
54+ }
55+
56+ return new Response (
57+ ` Supported endpoints:
6058/websocket: Expects a WebSocket upgrade request ` ,
61- {
62- status : 200 ,
63- headers : {
64- ' Content-Type' : ' text/plain' ,
65- },
66- }
67- );
68- },
59+ {
60+ status: 200 ,
61+ headers: {
62+ ' Content-Type' : ' text/plain' ,
63+ },
64+ }
65+ );
66+ }
6967};
7068
7169// Durable Object
7270export class WebSocketHibernationServer extends DurableObject {
73- // Keeps track of all WebSocket connections
74- // When the DO hibernates, gets reconstructed in the constructor
75- sessions: Map <WebSocket , { [key : string ]: string }>;
76-
77- constructor (ctx : DurableObjectState , env : Env ) {
78- super (ctx , env );
79- this .sessions = new Map ();
80-
81- // As part of constructing the Durable Object,
82- // we wake up any hibernating WebSockets and
83- // place them back in the `sessions` map.
84-
85- // Get all WebSocket connections from the DO
86- this .ctx .getWebSockets ().forEach ((ws ) => {
87- let attachment = ws .deserializeAttachment ();
88- if (ws .deserializeAttachment ()) {
89- // If we previously attached state to our WebSocket,
90- // let's add it to `sessions` map to restore the state of the connection.
91- const { ... session } = attachment ;
92- this .sessions .set (ws , { ... session });
93- }
94- });
95-
96- // Sets an application level auto response that does not wake hibernated WebSockets.
97- this .ctx .setWebSocketAutoResponse (new WebSocketRequestResponsePair (' ping' , ' pong' ));
98- }
99-
100- async fetch(request : Request ): Promise <Response > {
101- // Creates two ends of a WebSocket connection.
102- const webSocketPair = new WebSocketPair ();
103- const [client, server] = Object .values (webSocketPair );
104-
105- // Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
106- // request within the Durable Object. It has the effect of "accepting" the connection,
107- // and allowing the WebSocket to send and receive messages.
108- // Unlike `ws.accept()`, `this.ctx.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
109- // is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
110- // the connection is open. During periods of inactivity, the Durable Object can be evicted
111- // from memory, but the WebSocket connection will remain open. If at some later point the
112- // WebSocket receives a message, the runtime will recreate the Durable Object
113- // (run the `constructor`) and deliver the message to the appropriate handler.
114- this .ctx .acceptWebSocket (server );
115-
116- // Generate a random UUID for the session.
117- const id = crypto .randomUUID ();
118-
119- // Attach the session ID to the WebSocket connection and serialize it.
120- // This is necessary to restore the state of the connection when the Durable Object wakes up.
121- server .serializeAttachment ({ id });
122-
123- // Add the WebSocket connection to the map of active sessions.
124- this .sessions .set (server , { id });
125-
126- return new Response (null , {
127- status: 101 ,
128- webSocket: client ,
129- });
130- }
131- return new Response (null , {
132- status: 101 ,
133- webSocket: client ,
134- });
135- }
136-
137- async webSocketMessage (ws : WebSocket , message : ArrayBuffer | string ) {
138- // Get the session associated with the WebSocket connection.
139- const session = this .sessions .get (ws )! ;
140-
141- // Upon receiving a message from the client, the server replies with the same message, the session ID of the connection,
142- // and the total number of connections with the "[Durable Object]: " prefix
143- ws .send (` [Durable Object] message: ${message }, from: ${session .id }. Total connections: ${this .sessions .size } ` );
144-
145- // Send a message to all WebSocket connections, loop over all the connected WebSockets.
146- this .sessions .forEach ((attachment , session ) => {
147- session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
148- });
149-
150- // Send a message to all WebSocket connections except the connection (ws),
151- // loop over all the connected WebSockets and filter out the connection (ws).
152- this .sessions .forEach ((attachment , session ) => {
153- if (session !== ws ) {
154- session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
155- }
156- });
157- }
158-
159- async webSocketClose (ws : WebSocket , code : number , reason : string , wasClean : boolean ) {
160- // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
161- ws .close (code , ' Durable Object is closing WebSocket' );
162- }
71+ // Keeps track of all WebSocket connections
72+ // When the DO hibernates, gets reconstructed in the constructor
73+ sessions: Map <WebSocket , { [key : string ]: string }>;
74+
75+ constructor (ctx : DurableObjectState , env : Env ) {
76+ super (ctx , env );
77+ this .sessions = new Map ();
78+
79+ // As part of constructing the Durable Object,
80+ // we wake up any hibernating WebSockets and
81+ // place them back in the `sessions` map.
82+
83+ // Get all WebSocket connections from the DO
84+ this .ctx .getWebSockets ().forEach ((ws ) => {
85+ let attachment = ws .deserializeAttachment ();
86+ if (ws .deserializeAttachment ()) {
87+ // If we previously attached state to our WebSocket,
88+ // let's add it to `sessions` map to restore the state of the connection.
89+ const { ... session } = attachment ;
90+ this .sessions .set (ws , { ... session });
91+ }
92+ });
93+
94+ // Sets an application level auto response that does not wake hibernated WebSockets.
95+ this .ctx .setWebSocketAutoResponse (new WebSocketRequestResponsePair (' ping' , ' pong' ));
96+ }
97+
98+ async fetch(request : Request ): Promise <Response > {
99+ // Creates two ends of a WebSocket connection.
100+ const webSocketPair = new WebSocketPair ();
101+ const [client, server] = Object .values (webSocketPair );
102+
103+ // Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
104+ // request within the Durable Object. It has the effect of "accepting" the connection,
105+ // and allowing the WebSocket to send and receive messages.
106+ // Unlike `ws.accept()`, `this.ctx.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
107+ // is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
108+ // the connection is open. During periods of inactivity, the Durable Object can be evicted
109+ // from memory, but the WebSocket connection will remain open. If at some later point the
110+ // WebSocket receives a message, the runtime will recreate the Durable Object
111+ // (run the `constructor`) and deliver the message to the appropriate handler.
112+ this .ctx .acceptWebSocket (server );
113+
114+ // Generate a random UUID for the session.
115+ const id = crypto .randomUUID ();
116+
117+ // Attach the session ID to the WebSocket connection and serialize it.
118+ // This is necessary to restore the state of the connection when the Durable Object wakes up.
119+ server .serializeAttachment ({ id });
120+
121+ // Add the WebSocket connection to the map of active sessions.
122+ this .sessions .set (server , { id });
123+
124+ return new Response (null , {
125+ status: 101 ,
126+ webSocket: client ,
127+ });
128+ }
129+
130+ async webSocketMessage(ws : WebSocket , message : ArrayBuffer | string ) {
131+ // Get the session associated with the WebSocket connection.
132+ const session = this .sessions .get (ws )! ;
133+
134+ // Upon receiving a message from the client, the server replies with the same message, the session ID of the connection,
135+ // and the total number of connections with the "[Durable Object]: " prefix
136+ ws .send (` [Durable Object] message: ${message }, from: ${session .id }. Total connections: ${this .sessions .size } ` );
137+
138+ // Send a message to all WebSocket connections, loop over all the connected WebSockets.
139+ this .sessions .forEach ((attachment , session ) => {
140+ session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
141+ });
142+
143+ // Send a message to all WebSocket connections except the connection (ws),
144+ // loop over all the connected WebSockets and filter out the connection (ws).
145+ this .sessions .forEach ((attachment , session ) => {
146+ if (session !== ws ) {
147+ session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
148+ }
149+ });
150+ }
151+
152+ async webSocketClose(ws : WebSocket , code : number , reason : string , wasClean : boolean ) {
153+ // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
154+ ws .close (code , ' Durable Object is closing WebSocket' );
155+ }
163156}
164157
165158```
166159</TypeScriptExample >
167160
168- Finally, configure your ` wrangler.toml ` file to include a Durable Object [ binding] ( /durable-objects/get-started/#4-configure-durable-object-bindings ) and [ migration] ( /durable-objects/reference/durable-objects-migrations/ ) based on the namespace and class name chosen previously.
161+ Finally, configure your Wrangler file to include a Durable Object [ binding] ( /durable-objects/get-started/#4-configure-durable-object-bindings ) and [ migration] ( /durable-objects/reference/durable-objects-migrations/ ) based on the namespace and class name chosen previously.
169162
170163<WranglerConfig >
171164
0 commit comments