Skip to content

Commit 1f98658

Browse files
authored
Merge pull request #7 from jokester/fix-readme-manifest
v0.2.1: update readme & manifests
2 parents 823625b + 95c3bbb commit 1f98658

File tree

8 files changed

+75
-82
lines changed

8 files changed

+75
-82
lines changed

.github/workflows/build-publish-npm.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ jobs:
2727

2828
- run: make patch-upstream lib-build
2929

30+
- run: cp -rv README.md docs ./socket.io-serverless/
31+
3032
- uses: JS-DevTools/npm-publish@v3
3133
with:
3234
token: ${{ secrets.NPM_TOKEN }}
3335
dry-run: ${{ !(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) }}
34-
package: ./socket.io-serverless/package.json
36+
package: ./socket.io-serverless/package.json

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ dist-ssr
2828
.yarn
2929
*.tgz
3030
/socket.io-serverless/README.md
31+
/socket.io-serverless/docs

INTERNAL.md

-24
This file was deleted.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A custom [socket.io](https://socket.io/) build for serverless environments. Currently [Cloudflare Worker + Durable Objects](https://developers.cloudflare.com/durable-objects/).
44

5-
Demo client app: [sio-serverless-demo-client](https://sio-serverless-demo-client.ihate.work) running `demo-client/` `demo-server/` code in this repo.
5+
Demo client app: [sio-serverless-demo-client](https://sio-serverless-demo-client.ihate.work) running `demo-client/` `demo-server/` code in [source code repo](https://github.com/jokester/socket.io-serverless)
66

77
## Getting started
88

docs/development.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
### how the code is developed and built
2+
3+
Because socket.io does not publish original TS code in NPM, I included the `socket.io` repo ([now a monorepo too](https://github.com/socketio/socket.io/issues/3533)) as a git submodule. My monorepo therefore contains packages like `socket.io-serverless` `socket.io/packages/socket.io` ``socket.io/packages/engine.io` `
4+
5+
Some socket.io code need to be patched, including export map in `package.json`. The patches are contained in the monorepo and applied by Makefile.
6+
7+
`esbuild` bundle `socket.io-serverless` code , along with Socket.io and other deps, into a non minified bundle.
8+
9+
A `esbuild` [build script](https://github.com/jokester/socket.io-serverless/blob/main/socket.io-serverless/build.mjs) is used to customize the deps resolution process. Some npm packages are replaced with CF-compatible implementation (like `debug`), or simple stubbed (like `node:http` ).
10+

docs/how-it-works.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
### how socket.io works
2+
3+
Socket.io (the top level library) have 2 main components: npm packages `socket.io` and `engine.io`.
4+
5+
The `socket.io` packages deals with the high level concepts: namespace / room / clustering / etc. It depends on `engine.io` which holds a `http.Server` instance and deals with the transport-aware logic.
6+
7+
In Node.js the 2 components just run in the same process, communicate with a event emitter API.
8+
9+
### develop for CF worker / DO
10+
11+
In CF DO / worker, JS runs in a non-Node.js special serverless environment. I think [workerd](https://github.com/cloudflare/workerd/tree/main/src/workerd) .
12+
13+
The biggest difference compared to Node.js / web for a JS developer is perhaps the volatile state.
14+
15+
In a traditional environment like Node.js process or a browser tab, the code just run till server down or tab close. But in CF the serverless environment they will stop running and destroy the in-memory state of your JS code when it is inactive. Having a JS `setTimeout` or `setInterval` timer counts as active. A pending HTTP request counts. An active WebSocket connection may or may not count (depending on the API used to accept the connection).
16+
17+
Specificlly, for DO the destruction of in-memory state is actually called [hibernation](). Developers can manually persist/revive state using provided KV store-like API.
18+
19+
<!--
20+
For DO there is some guarantee like "no 2 instance of the same actor (identified by id) will exist at the same time". For worker I guess almost nothing is guaranteed. S
21+
-->
22+
Also the available standard libraries is different too.
23+
24+
The code using only JS language APIs should just work. Code requiring Node.js API can have Node.js polyfills behind Node.js compatibility flags.
25+
26+
Since sometime in 2024 the Node.js stdlib polyfill is based on [unenv]() , behind `nodejs_compat_v2` flag. This article has a quite complete explanation [Cloudflare Workersのnodejs\_compat\_v2で何が変わったのか](https://zenn.dev/laiso/articles/8280d026a08de0)
27+
28+
Prior to this, based on my non-authoritative investigation the `nodejs_compat` flag is eventually based on `ionic-team/rollup-plugin-node-polyfills` used by `@esbuild-plugins/node-modules-polyfill`, used by `esbuild`, used by `wrangler` CLI.
29+
30+
### how socket.io-serverless works
31+
32+
I used 2 DO to run heavily rewired `socket.io` `engine.io` code.
33+
34+
`class EngineActor extends DurableObject {...}` is the DO running `engine.io` code. It just accepts WebSocket connection, forwards bidirectional WS messages between `SocketActor` and real WS connection.
35+
36+
`class SocketActor extends DurableObject {...}` is the DO running `socket.io` code. It responds to RPC calls from `EngineActor`, emit messages into objects like `Namespace`. If application code above send message to a engine.io Socket (an abstraction of different transports), the message got forwarded to `EngineActor`, and flow to the other end of WS connection.
37+
38+
Therefore application logic code based on on `sio.Namespace` `sio.Client` `sio.Room` should work as with the original Socket.io, but with [limitations](https://github.com/jokester/socket.io-serverless?tab=readme-ov-file#limitations).
39+
40+
Besides the 2 DOs , there will need to be a worker entrypoint, a simple HTTP handler to forward request to `EngineActor`
41+
42+
While it is not impossible to prevent aforementioned hibernation (I did this in a first simpler version), I decided that a serverless version should instead exploit hibernation to save energy and protect our earth.
43+
44+
The states inside `EngineActor` `SocketActor`, including connection IDs, possibly dynamically created namespaces and conn IDs within, are now persisted/revived across different life cycles.
45+
46+
Most of `engine.io` `socket.io` code is already driven by message events. But there was a ping timer to drive [heartbeat check](https://socket.io/docs/v4/engine-io-protocol/#heartbeat). I had to stub the original code to use [alarm]() instead.
47+
48+
Currently socket.io-serverless only creates 1 instance for each DO class. In the future if performance becomes a problem it should be able to split the load with more DOs (similar to the adapter/cluster structure used by Socket.io)
49+
50+

refactor-me.mjs

-54
This file was deleted.

socket.io-serverless/package.json

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
{
22
"name": "socket.io-serverless",
33
"description": "A custom socket.io build to run in Cloudflare workers.",
4-
"version": "0.2.0",
4+
"version": "0.2.1",
55
"type": "module",
6+
"homepage": "https://github.com/jokester/socket.io-serverless",
7+
"repository": {
8+
"type": "git",
9+
"url": "git+https://github.com/jokester/socket.io-serverless.git"
10+
},
11+
"bugs": {
12+
"url": "https://github.com/jokester/socket.io-serverless/issues"
13+
},
614
"dependencies": {},
715
"files": [
816
"dist",
917
"README.md",
18+
"docs",
1019
"mocks",
1120
"src",
1221
"build.mjs",
1322
"tsconfig.json"
1423
],
1524
"scripts": {
16-
"prepack": "node build.mjs && cp -v ../README.md .",
1725
"build": "node build.mjs",
1826
"build:watch": "node build.mjs --watch",
1927
"lint": "eslint src",

0 commit comments

Comments
 (0)