{"id":15010760,"url":"https://github.com/guest271314/direct-sockets-http-ws-server","last_synced_at":"2025-04-09T18:35:23.380Z","repository":{"id":235367473,"uuid":"790542759","full_name":"guest271314/direct-sockets-http-ws-server","owner":"guest271314","description":"HTTP and WebSocket server run from Chrome browser using Direct Sockets TCPServerSocket","archived":false,"fork":false,"pushed_at":"2025-03-09T23:07:33.000Z","size":108,"stargazers_count":22,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-23T20:37:19.037Z","etag":null,"topics":["chrome","chromium","direct-sockets","http-server","isolated-web-apps","tcp","websocket-server"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/guest271314.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-23T04:25:44.000Z","updated_at":"2025-03-20T13:19:08.000Z","dependencies_parsed_at":"2024-04-23T07:12:53.776Z","dependency_job_id":"8a320f88-4d36-46b2-be10-6b13ce5db9bc","html_url":"https://github.com/guest271314/direct-sockets-http-ws-server","commit_stats":null,"previous_names":["guest271314/direct-sockets-http-ws-server"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guest271314%2Fdirect-sockets-http-ws-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guest271314%2Fdirect-sockets-http-ws-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guest271314%2Fdirect-sockets-http-ws-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guest271314%2Fdirect-sockets-http-ws-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/guest271314","download_url":"https://codeload.github.com/guest271314/direct-sockets-http-ws-server/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248088435,"owners_count":21045713,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chrome","chromium","direct-sockets","http-server","isolated-web-apps","tcp","websocket-server"],"created_at":"2024-09-24T19:35:42.685Z","updated_at":"2025-04-09T18:35:23.344Z","avatar_url":"https://github.com/guest271314.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## direct-sockets-http-ws-server\nHTTP and WebSocket server run from Chromium and Chrome browsers using Direct Sockets `TCPServerSocket`.\n\n## Synopsis\n\n[WICG Direct Sockets](https://wicg.github.io/direct-sockets) specifies an API \nthat provides `TCPSocket`, `UDPSocket`, and `TCPServerSocket`. Prior art: [chrome.socket](https://developer.chrome.com/docs/apps/reference/socket).\n\nIn Chromium based browsers, for example Chrome, this capability is exposed in [Isolated Web Apps](https://github.com/WICG/isolated-web-apps) (IWA).\n\nPreviously we have created an IWA that we launch from arbitrary Web sites\nwith [`open()`](https://html.spec.whatwg.org/multipage/window-object.html#dom-open-dev),\nincluding [SDP](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols#sdp)\nfrom a [`RTCDataChannel`](https://w3c.github.io/webrtc-pc/#rtcdatachannel) in query string of the URL,\ncreated in the Web page, and exchanged signals with the `RTCDataChannel`\ncreated in the IWA `window` using [WICG File System Access](https://wicg.github.io/file-system-access/) for the\nability to send data to the IWA which is then passed to a `TCPSocket` instance for \nthat sends the data to a Node.js, Deno, Bun, or txiki.js TCP socket server for processing, \nthen sends the processed data back to the Web page using `RTCDataChannel`\nin each `window`, see [telnet-client (user-defined-tcpsocket-controller-web-api branch)](https://github.com/guest271314/telnet-client), which is a\nfork of [telnet-client](https://github.com/GoogleChromeLabs/telnet-client).\n\nNow we will use the browser itself as a HTTP and WebSocket server over the `TCPServerSocket` interface.\n\n### HTTP server\n\n\u003e [Basic aspects of HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview#basic_aspects_of_http)\n\u003e\n\u003e HTTP is simple\n\u003e\n\u003e HTTP is generally designed to be simple and human-readable, even with the added \ncomplexity introduced in HTTP/2 by encapsulating HTTP messages into frames. HTTP \nmessages can be read and understood by humans, providing easier testing for developers, \nand reduced complexity for newcomers.\n\nWe'll also note this claim on the MDN Web Docs page from [Client: the user-agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview#client_the_user-agent)\n\n\u003e The browser is **always** the entity initiating the request. It is never the server (though some mechanisms have been added over the years to simulate server-initiated messages).\n\nis not technically accurate, as we'll demonstrate below, in code.\n\nSome further reading about HTTP can be found here [HTTP - Hypertext Transfer Protocol](https://www.w3.org/Protocols/).\n\nThe reason for and use of the `Access-Control-Request-Private-Network` and `Access-Control-Allow-Private-Network`\nheaders can be found here [Private Network Access: introducing preflights](https://developer.chrome.com/blog/private-network-access-preflight).\n\nAn article and example of a basic HTTP server with comments explaining what is going on, including comments in the code, written in C,\ncan be found here [Making a simple HTTP webserver in C](https://bruinsslot.jp/post/simple-http-webserver-in-c/). We have\npreviously used that example to create a simple HTTP Web server for QuickJS, which does\nnot include a built-in Web server in the compiled `qjs` executable, see [webserver-c (quickjs-webserver branch)](https://github.com/guest271314/webserver-c/tree/quickjs-webserver).\n\n### WebSocket server\n\nFor the WebSocket implementation [WebSocket - binary broadcast example (pure NodeJs implementation without any dependency)](https://gist.github.com/robertrypula/b813ffe23a9489bae1b677f1608676c8) is used.\n\n### Chromium command-line switches\n\nLaunch Chromium or Chrome with \n\n```\n--unsafely-treat-insecure-origin-as-secure=http://0.0.0.0:44818,ws://0.0.0.0:44818\n```\n\nto avoid Chromium rendering insecure connection notification in the address bar.\n\n### Isolated Web App and Signed Web Bundle\n\n- Substitute Web Cryptography API ([wbn-sign-webcrypto](https://github.com/guest271314/wbn-sign-webcrypto)) for `node:crypto` implementation of Ed25519 algorithm \n- Install and run same JavaScript source code in different JavaScript runtimes, e.g., `node`, `deno`, `bun`\n- Integrity Block V2 supported\n\n### TODO\n\n- Create valid close frame (server to client) for WebSocket server; currently we abort the request in the server with `AbortController` when the WebSocket client closes the connection. Completed.\n- Substitute `ArrayBuffer`, `DataView`, `TypedArray` for Node.js Buffer polyfill\n- TLS and HTTP/2 support\n- Create Signed Web Bundle and Isolated Web App in the browser\n- Improve HTTP and WebSocket header parsing\n\n## Building\n\n### Fetch dependencies\n\n```\nbun install\n```\n\nor \n\n```\nnpm install\n```\n\nor \n\n```\ndeno add npm:wbn\n```\n\n### Signed Web Bundle/Isolated Web App source files\n\nEntry point is `assets` directory which contains `index.html`, `script.js`, `.well-known` directory with `manifest.webmanifest`, and any other scripts or resources to be bundled. \n\n### Generate private and public keys, write to file system \n\nThis only has to be done once. `generateWebCryptoKeys.js` can be run with `node`, `deno`, or `bun`.\n\n```\ndeno -A generateWebCryptoKeys.js\n```\n\n\n### Build the Signed Web Bundle and Isolated Web App\n\nWrite `signed.swbn` to current directory\n\nNode.js \n```\nnode index.js\n```\n\nBun\n```\nbun run index.js\n```\n\nDeno (Can be run without `node_modules` folder in current directory; fetches dependencies from https://esm.sh)\n```\ndeno -A --import-map import-map.json index.js\n```\n\n### Build/rebuild `wbn-bundle.js` from `webbundle-plugins/packages/rollup-plugin-webbundle/src/index.ts` with `bun`\n\n\n1. `git clone https://github.com/GoogleChromeLabs/webbundle-plugins`\n2. `cd webbundle-plugins/packages/rollup-plugin-webbundle`\n3. `bun install -p`\n4. In `src/index.ts` comment line 18, `: EnforcedPlugin`, line 32 `const opts = await getValidatedOptionsWithDefaults(rawOpts);` and lines 65-121, because I will not be using Rollup\n5. Bundle with Bun `bun build --target=node --format=esm --sourcemap=none --outfile=webpackage-bundle.js ./webbundle-plugins/packages/rollup-plugin-webbundle/src/index.ts`\n6. Create reference to Web Cryptography API that will be used in the code in the bundled script instead of `node:crypto` directly `import { webcrypto } from \"node:crypto\";`\n7. In `/node_modules/wbn-sign/lib/utils/utils.js` use `switch (key.algorithm.name) {`\n8. `getRawPublicKey` becomes an `async` function for substituting `const exportedKey = await webcrypto.subtle.exportKey(\"spki\", publicKey);` for `publicKey.export({ type: \"spki\", format: \"der\" });`\n9. In `/node_modules/wbn-sign/lib/signers/integrity-block-signer.js` use `const publicKey = await signingStrategy.getPublicKey();` and `[getPublicKeyAttributeName(publicKey)]: await getRawPublicKey(publicKey)`; `verifySignature()` also becomes an `async` function where `const algorithm = { name: \"Ed25519\" }; const isVerified = await webcrypto.subtle.verify(algorithm, publicKey, signature, data);` is substituted for `const isVerified = crypto2.verify(undefined, data, publicKey, signature);`\n10. In `/node_modules/wbn-sign/lib/web-bundle-id.js` `serialize()` function becomes `async` for `return base32Encode(new Uint8Array([...await getRawPublicKey(this.key), ...this.typeSuffix]), \"RFC4648\", { padding: false }).toLowerCase();`; and `serializeWithIsolatedWebAppOrigin()` becomes an `async` function for `return ${this.scheme}${await this.serialize()}/;`; `toString()` becomes an `async` function for `return Web Bundle ID: ${await this.serialize()} Isolated Web App Origin: ${await this.serializeWithIsolatedWebAppOrigin()};`\n11. In `src/index.ts` `export {WebBundleId, bundleIsolatedWebApp};`\n12. In `index.js`, the entry point for how I am creating the SWBN and IWA I get the public and private keys created with Web Cryptography API, and use Web Cryptography API to sign and verify\n\n## Install Isolated Web App using Signed Web Bundle\n\nNavigate to `chrome://web-app-internals/`, on the line beginning with `Install IWA from Signed Web Bundle:` click `Select file...` and select `signed.swbn`.\n\n## Usage\n\nSee `https.js` and `ws.js` in `examples` directory.\n\nWe could recently open the IWA `window` from arbitrary Web sites in DevTools `console` or Snippets with \n\n```\nvar iwa = open(\"isolated-app://\u003cIWA_ID\u003e\");\n```\n\n[iwa: Mark isolated-app: as being handled by Chrome](https://chromium-review.googlesource.com/c/chromium/src/+/5466063) evidently had the side effect of blocking that capability, see [window.open(\"isolated-app://\u003cID\u003e\") is blocked](https://issues.chromium.org/issues/339994757#comment6). [isolated-web-app-utilities](https://github.com/guest271314/isolated-web-app-utilities) provides approaches to open the IWA window from arbitrary Web sites, `chrome:`, `chrome-extension:` URL's.\n\n### HTTP and WebSocket server\n\n```\nconst socket = new TCPServerSocket(\"0.0.0.0\", {\n    localPort: 44818,\n  });\n\n  const {\n    readable: server,\n    localAddress,\n    localPort,\n  } = await socket.opened;\n\n  console.log({ server });\n  // TODO: Handle multiple connections\n  await server.pipeTo(\n    new WritableStream({\n      async write(connection) {\n        const {\n          readable: client,\n          writable,\n          remoteAddress,\n          remotePort,\n        } = await connection.opened;\n        console.log({ connection });\n        const writer = writable.getWriter();\n        console.log({\n          remoteAddress,\n          remotePort,\n        });\n\n        const abortable = new AbortController();\n        const { signal } = abortable;\n        // Text streaming\n        // .pipeThrough(new TextDecoderStream())\n        await client.pipeTo(\n          new WritableStream({\n            start(controller) {             \n              console.log(controller);\n            },\n            async write(r, controller) {\n              // Do stuff with encoded request\n              const request = decoder.decode(r);\n              console.log(request);\n              // HTTP and WebSocket request and response logic              \n              // Create and send valid WebSocket close frame to client\n              await writer.write(new Uint8Array([0x88, 0x00])); // 136, 0\n              await writer.close();\n              return await writer.closed;\n            },\n            close: () =\u003e {\n              console.log(\"Client closed\");\n            },\n            abort(reason) {\n              console.log(reason);\n            },\n          })\n        , {signal}).catch(console.warn);\n      },\n      close() {\n        console.log(\"Host closed\");\n      },\n      abort(reason) {\n        console.log(\"Host aborted\", reason);\n      },\n    }),\n  ).then(() =\u003e console.log(\"Server closed\")).catch(console.warn);\n};\n```\n\n### HTTP client\n\nUsing WHATWG Fetch\n\n```\nfetch(\"http://0.0.0.0:44818\", {\n  method: \"post\",\n  body: \"test\",\n  headers: {\n    \"Access-Control-Request-Private-Network\": true,\n  },\n})\n  .then((r) =\u003e r.text()).then((text) =\u003e\n    console.log({\n      text,\n    })\n  ).catch(console.error);\n```\n\n### WebSocket client\n\n```\nvar wss = new WebSocketStream(\"ws://0.0.0.0:44818\");\nconsole.log(wss);\nwss.closed.catch((e) =\u003e {});\nwss.opened.catch((e) =\u003e {});\nvar {\n  readable,\n  writable,\n} = await wss.opened.catch(console.error);\nvar writer = writable.getWriter();\nvar abortable = new AbortController();\nvar {\n  signal,\n} = abortable;\n// .pipeThrough(new TextDecoderStream())\nvar pipe = readable.pipeTo(\n  new WritableStream({\n    start(c) {\n      console.log(\"Start\", c);\n    },\n    async write(v) {\n      console.log(v, decoder.decode(v));\n    },\n    close() {\n      console.log(\"Socket closed\");\n    },\n    abort(reason) {\n      // console.log({ reason });\n    },\n  }),\n  {\n    signal,\n  },\n).then(() =\u003e ({ done: true, e: null })).catch((e) =\u003e ({ done: true, e }));\n\nvar encoder = new TextEncoder();\nvar decoder = new TextDecoder();\nvar encode = (text) =\u003e encoder.encode(text);\nawait writer.write(encode(\"X\"));\n// Later on close the WebSocketStream connection\nawait writer.close().catch(() =\u003e pipe).then(console.log);\n```\n\n## License\n\nDo What the Fuck You Want to Public License [WTFPLv2](http://www.wtfpl.net/about/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguest271314%2Fdirect-sockets-http-ws-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguest271314%2Fdirect-sockets-http-ws-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguest271314%2Fdirect-sockets-http-ws-server/lists"}