{"id":13727118,"url":"https://github.com/sinclairzx81/smoke","last_synced_at":"2025-05-15T07:06:03.733Z","repository":{"id":40782004,"uuid":"180961050","full_name":"sinclairzx81/smoke","owner":"sinclairzx81","description":"Run Web Servers in Web Browsers over WebRTC","archived":false,"fork":false,"pushed_at":"2024-12-18T08:44:22.000Z","size":1395,"stargazers_count":589,"open_issues_count":4,"forks_count":45,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-05-11T06:43:10.875Z","etag":null,"topics":["filesystem","http","indexeddb","mediastream","net","sockets","webrtc","websocket"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sinclairzx81.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":"2019-04-12T08:08:13.000Z","updated_at":"2025-05-09T19:13:11.000Z","dependencies_parsed_at":"2024-06-03T06:23:53.928Z","dependency_job_id":"5a549f42-c755-415c-86f5-2d9909c5b30d","html_url":"https://github.com/sinclairzx81/smoke","commit_stats":{"total_commits":11,"total_committers":1,"mean_commits":11.0,"dds":0.0,"last_synced_commit":"dfd163da7e2b3e3331249e888a8d1abb54194b2b"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinclairzx81%2Fsmoke","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinclairzx81%2Fsmoke/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinclairzx81%2Fsmoke/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinclairzx81%2Fsmoke/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sinclairzx81","download_url":"https://codeload.github.com/sinclairzx81/smoke/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254292042,"owners_count":22046426,"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":["filesystem","http","indexeddb","mediastream","net","sockets","webrtc","websocket"],"created_at":"2024-08-03T01:03:40.190Z","updated_at":"2025-05-15T07:05:58.722Z","avatar_url":"https://github.com/sinclairzx81.png","language":"TypeScript","readme":"\u003cdiv align='center'\u003e\n\n\u003ch1\u003eSmoke\u003c/h1\u003e\n\n\u003cp\u003eRun Web Servers in Web Browsers over WebRTC\u003c/p\u003e\n\n\u003cimg src=\"https://github.com/sinclairzx81/smoke/blob/master/smoke.png?raw=true\" /\u003e\n\n\u003cbr /\u003e\n\u003cbr /\u003e\n\n[![Test](https://github.com/sinclairzx81/smoke/actions/workflows/test.yml/badge.svg)](https://github.com/sinclairzx81/smoke/actions/workflows/test.yml) [![npm version](https://badge.fury.io/js/%40sinclair%2Fsmoke.svg)](https://badge.fury.io/js/%40sinclair%2Fsmoke) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003c/div\u003e\n\n## Example\n\nSmoke enables Browsers run micro Web Servers over WebRTC\n\n```typescript\nimport { Network } from '@sinclair/smoke'\n\n// ------------------------------------------------------------------\n//\n// Create a Virtual Network\n//\n// ------------------------------------------------------------------\n\nconst { Http } = new Network()\n\n// ------------------------------------------------------------------\n//\n// Create a Http Listener on a Virtual Port\n//\n// ------------------------------------------------------------------\n\nHttp.listen({ port: 5000 }, request =\u003e new Response('hello webrtc'))\n\n// ------------------------------------------------------------------\n//\n// Fetch data over WebRTC\n//\n// ------------------------------------------------------------------\n\nconst text = Http.fetch('http://localhost:5000').then(r =\u003e r.text())\n```\n\n## Install\n\n```bash\n$ npm install @sinclair/smoke\n```\n\n## Overview\n\nSmoke is an experimental browser networking and storage framework that provides Http, Tcp, and WebSocket emulation over WebRTC, as well as large file storage using IndexedDB. It is designed as a foundation for developing peer-to-peer web services directly in the browser, with each browser accessible through an application-controlled virtual network.\n\nSmoke reshapes WebRTC into standard Http compatible interfaces enabling traditional web server applications to be made portable between server and browser environments. It is developed in support of alternative software architectures where user centric services can be moved away from the cloud and run peer to peer in the browser.\n\nLicence MIT\n\n## Contents\n\n- [Network](#Network)\n  - [Private](#Network-Private)\n  - [Public](#Network-Public)\n- [Http](#Http)\n  - [Listen](#Http-Listen)\n  - [Fetch](#Http-Fetch)\n  - [Upgrade](#Http-Upgrade)\n  - [Connect](#Http-Connect)\n- [Net](#Net)\n  - [Listen](#Net-Listen)\n  - [Connect](#Net-Connect)\n- [Media](#Media)\n  - [Listen](#Media-Listen)\n  - [Send](#Media-Send)\n  - [Audio](#Media-Audio)\n  - [Video](#Media-Video)\n  - [Pattern](#Media-Pattern)\n- [Proxy](#Proxy)\n  - [Listen](#Proxy-Listen)\n  - [Worker](#Proxy-Worker)\n- [FileSystem](#FileSystem)\n  - [Open](#FileSystem-Open)\n  - [Stat](#FileSystem-Stat)\n  - [Exists](#FileSystem-Exists)\n  - [Mkdir](#FileSystem-Mkdir)\n  - [Readdir](#FileSystem-Readdir)\n  - [Blob](#FileYstem-Blob)\n  - [Read](#FileSystem-Read)\n  - [Write](#FileSystem-Write)\n  - [Delete](#FileSystem-Delete)\n  - [Rename](#FileSystem-Rename)\n  - [Copy](#FileSystem-Copy)\n  - [Move](#FileSystem-Move)\n  - [Watch](#FileSystem-Watch)\n- [Contribute](#Contribute)\n\n\n\u003ca name=\"Network\"\u003e\u003c/a\u003e\n## Network\n\nSmoke networking API's are provided by way of Network objects. A Network object represents an active connection to a shared signalling Hub and exposes the Http, Net and Media functionality used to communicate with other Network objects connected to the same Hub.\n\n```typescript\nimport { Network, Hubs } from '@sinclair/smoke'\n\nconst { Http, Net, Media, Hub } = new Network({ hub: new Hubs.Private() })\n\nconst address = await Hub.address() // The address of this Network object.\n```\n\n\u003ca name=\"Network-Private\"\u003e\u003c/a\u003e\n### Private\n\nA Private hub is an in-memory relay that forwards WebRTC ICE messages by way of the browser's BroadcastChannel API. A private hub can only relay messages to the page and other tabs running within the same browser process. Because private hubs cannot facilitate connections made outside the current page, it is considered private. This Hub is the default.\n\n```typescript\nimport { Network, Hubs } from '@sinclair/smoke'\n\nconst { Http } = new Network({ hub: new Hubs.Private() })\n\n```\n\n\u003ca name=\"Network-Public\"\u003e\u003c/a\u003e\n### Public \n\nThe implementation of this hub is currently pending.\n\n```typescript\nimport { Network, Hubs } from '@sinclair/smoke'\n\nconst { Http } = new Network({ hub: new Hubs.Public('ws://server/hub') })\n```\n\n\u003ca name=\"Http\"\u003e\u003c/a\u003e\n## Http\n\nThe Http API supports Http listen and fetch over WebRTC. It also provides WebSocket emulation.\n\n```typescript\nconst { Http } = new Network()\n```\n\n\u003ca name=\"Http-Listen\"\u003e\u003c/a\u003e\n### Listen\n\nUse the listen function to receive Http requests from remote peers.\n\n```typescript\nHttp.listen({ port: 5000 }, request =\u003e new Response('hello'))\n```\n\n\u003ca name=\"Http-Fetch\"\u003e\u003c/a\u003e\n### Fetch\n\nUse the fetch function to make a Http request to remote peers.\n\n```typescript\nconst response = await Http.fetch('http://localhost:5000')\n\nconst message = await response.text()\n```\n\n\u003ca name=\"Http-Upgrade\"\u003e\u003c/a\u003e\n### Upgrade\n\nUse the upgrade function to convert a Http request into a WebSocket\n\n```typescript\nHttp.listen({ port: 5000 }, request =\u003e Http.upgrade(request, (socket) =\u003e socket.send('hello')))\n```\n\n\u003ca name=\"Http-Connect\"\u003e\u003c/a\u003e\n### Connect\n\nUse the connect function to connect to a remote WebSocket server.\n\n```typescript\nconst socket = await Http.connect('ws://localhost:5000')\n\nsocket.on('message', (event) =\u003e console.log(event.data))\n\nsocket.on('error', (event) =\u003e console.log(event))\n\nsocket.on('close', (event) =\u003e console.log(event))\n```\n\n\u003ca name=\"Net\"\u003e\u003c/a\u003e\n## Net\n\nThe Net API provides Tcp emulation over RTCDataChannel\n\n```typescript\nconst { Net } = new Network()\n```\n\n\u003ca name=\"Net-Listen\"\u003e\u003c/a\u003e\n### Listen\n\nUse the listen function to accept an incoming socket.\n\n```typescript\nNet.listen({ port: 5000 }, async socket =\u003e {\n\n  const data = await socket.read()\n\n  await socket.write(data)\n\n  await socket.close()\n})\n```\n\n\u003ca name=\"Net-Connect\"\u003e\u003c/a\u003e\n### Connect\n\nUse the connect function to establish a Net connection to a remote listener.\n\n```typescript\nconst socket = await Net.connect({ hostname: 'localhost', port: 5000 })\n\nawait socket.write(new Uint8Array(1000))\n\nconst data = await socket.read() // Uint8Array()\n\nconst end = await socket.read() // null\n```\n\n\u003ca name=\"Media\"\u003e\u003c/a\u003e\n## Media\n\nThe Media API provides functionality to send and receive MediaStream objects over WebRTC.\n\n```typescript\nconst { Media } = new Network()\n```\n\n\u003ca name=\"Media-Listen\"\u003e\u003c/a\u003e\n### Listen\n\nUse the listen function to listen for incoming MediaStream objects\n\n```typescript\nMedia.listen({ port: 6000 }, (receiver) =\u003e {\n  \n  const video = document.createElement('video')\n  \n  video.srcObject = receiver.mediastream\n  \n  video.play()\n\n  document.body.appendChild(video)\n\n  receiver.on('close', () =\u003e document.removeChild(video))\n})\n```\n\n\u003ca name=\"Media-Send\"\u003e\u003c/a\u003e\n### Send\n\nUse the send function to send a MediaStream to a listener\n\n```typescript\nconst sender = await Media.send({ hostname: 'localhost', port: 6000 }, new MediaStream([...]))\n\nsender.close() // stop sending live media\n```\n\n\u003ca name=\"Media-Audio\"\u003e\u003c/a\u003e\n### Audio\n\nUse the audio function to create a streamable AudioSource.\n\n```typescript\nconst audio = Media.audio({ src: './audio.mp3' })\n\nconst sender = Media.send({ hostname: 'localhost', port: 6000 }, audio.mediastream)\n```\n\n\u003ca name=\"Media-Video\"\u003e\u003c/a\u003e\n### Video\n\nUse the video function to create a streamable VideoSource.\n\n```typescript\nconst video = Media.video({ src: './video.mp4' })\n\nconst sender = Media.send({ hostname: 'localhost', port: 6000 }, video.mediastream)\n```\n\n\u003ca name=\"Media-Pattern\"\u003e\u003c/a\u003e\n### Pattern\n\nUse the pattern function to generate a MediaStream test pattern. This function can be useful for testing live media streaming without web cameras or other media sources.\n\n```typescript\nconst pattern = Media.pattern()\n\nconst sender = Media.send({ port: 5000 }, pattern.mediastream)\n```\n\n\u003ca name=\"Proxy\"\u003e\u003c/a\u003e\n## Proxy\n\nA Smoke Proxy enables a web page to intercept outbound HTTP requests. It uses a Service Worker to redirect these requests back to the calling page, allowing the page to handle its own requests. This functionality supports both fetch requests and referenced assets embedded in HTML. Currently, the Smoke Proxy is supported only in Chromium-based browsers.\n\n\u003ca name=\"Proxy-Listen\"\u003e\u003c/a\u003e\n### Listen\n\nUse the listen function to intercept Http requests made to a given path.\n\n```typescript\nimport { Proxy } from '@sinclair/smoke'\n\nProxy.listen({ path: '/some-path', workerPath: 'worker.js' }, request =\u003e {\n\n  return new Response('hello world')\n})\n\n// ...\n\nconst result = await fetch('/some-path/foo').then(res =\u003e res.text())\n\n```\n\n\u003ca name=\"Proxy-Worker\"\u003e\u003c/a\u003e\n### Worker\n\nThe Proxy requires a Service Worker script to be loaded at the root path of the website. Smoke provides a prebuilt worker script that you can copy into the website's root directory.\n\n```bash\n# Copy this JavaScript file to the website root.\n\nnode_modules/@sinclair/smoke/worker.js\n```\n\n\u003ca name=\"FileSystem\"\u003e\u003c/a\u003e\n## FileSystem\n\nSmoke provides a hierarchical file system able to store large files within the browser. The file system is backed by IndexedDB and has support for streaming read and write, directory enumeration, copy, move, rename as well as file and directory watch events. It is designed to act as a static file store for network services but can be used as a general purpose file system for applications needing to store large files in the browser.\n\n\u003ca name=\"FileSystem-Open\"\u003e\u003c/a\u003e\n### Open\n\nUse the open function to open a file system with the given database name. If the database does not exist it is created.\n\n```typescript\nimport { FileSystem } from '@sinclair/smoke'\n\nconst Fs = await FileSystem.open('\u003cdatabase-name\u003e')\n```\n\u003ca name=\"FileSystem-Stat\"\u003e\u003c/a\u003e\n### Stat\n\nUse the stat function to return information about a file or directory.\n\n```typescript\nconst stat = await Fs.write('/path/file.txt')\n```\n\n\u003ca name=\"FileSystem-Exists\"\u003e\u003c/a\u003e\n### Exists\n\nUse the exists function to check a path exists.\n\n```typescript\nconst exists = await Fs.exists('/path/file.txt')\n```\n\n\u003ca name=\"FileSystem-Mkdir\"\u003e\u003c/a\u003e\n### Mkdir\n\nUse the mkdir function to create a directory.\n\n```typescript\nawait Fs.mkdir('/media/videos')\n```\n\n\u003ca name=\"FileSystem-Readdir\"\u003e\u003c/a\u003e\n### Readdir\n\nUse the readdir function to return stat objects for the given directory path.\n\n```typescript\nconst stats = await Fs.readdir('/media/videos')\n```\n\n\u003ca name=\"FileSystem-Blob\"\u003e\u003c/a\u003e\n### Blob\n\nUse the blob function to return a Blob object to a file path.\n\n```typescript\nconst blob = await Fs.readdir('/video.mp4')\n\nconst url = URL.createObjectUrl(blob)\n```\n\n\u003ca name=\"FileSystem-Write\"\u003e\u003c/a\u003e\n### Write\n\nUse the write and writeText functions to write file content.\n\n```typescript\nawait Fs.write('/path/file.dat', new Uint8Array([1, 2, 3, 4]))\n\nawait Fs.writeText('/path/file.txt', 'hello world')\n```\n\n\u003ca name=\"FileSystem-Read\"\u003e\u003c/a\u003e\n### Read\n\nUse the read and readText functions will read content from a file.\n\n```typescript\nconst buffer = await fs.read('/path/file.dat')\n\nconst content = await Fs.readText('/path/file.txt')\n```\n\n\u003ca name=\"FileSystem-Delete\"\u003e\u003c/a\u003e\n### Delete\n\nUse the delete function to delete a file or directory.\n\n```typescript\nawait Fs.delete('/path/file.txt')\n```\n\n\u003ca name=\"FileSystem-Rename\"\u003e\u003c/a\u003e\n### Rename\n\nUse the rename function to rename a file or directory.\n\n```typescript\nawait Fs.writeText('/path/fileA.txt', '...')\n\nawait Fs.rename('/path/fileA.txt', 'fileB.txt')\n```\n\n\u003ca name=\"FileSystem-Copy\"\u003e\u003c/a\u003e\n### Copy\n\nUse the copy function to copy a file or directory into a target directory.\n\n```typescript\nawait Fs.writeText('/path/fileA.txt', '...')\n\nawait Fs.copy('/path/fileA.txt', '/backup')\n```\n\n\u003ca name=\"FileSystem-Move\"\u003e\u003c/a\u003e\n### Move\n\nUse the move function to move a file or directory into a target directory.\n\n```typescript\nawait Fs.writeText('/path/fileA.txt', '...')\n\nawait Fs.move('/path/fileA.txt', '/backup')\n```\n\n\u003ca name=\"FileSystem-Watch\"\u003e\u003c/a\u003e\n### Watch\n\nUse the watch function to watch for file and directory events.\n\n```typescript\nFs.watch('/dir', event =\u003e console.log(event))\n```\n\n## Contribute\n\nSmoke is open to community contribution. Please ensure you submit an open issue before submitting your pull request. The Smoke project prefers open community discussion before accepting new features.","funding_links":[],"categories":["TypeScript","websocket"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsinclairzx81%2Fsmoke","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsinclairzx81%2Fsmoke","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsinclairzx81%2Fsmoke/lists"}