{"id":25970569,"url":"https://github.com/elijah-bodden/membrane","last_synced_at":"2025-09-19T22:56:25.056Z","repository":{"id":46330867,"uuid":"507599036","full_name":"Elijah-Bodden/Membrane","owner":"Elijah-Bodden","description":"A robust, minimal-server-interaction API for peer routing in the browser","archived":false,"fork":false,"pushed_at":"2024-12-20T19:27:17.000Z","size":4483,"stargazers_count":23,"open_issues_count":4,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-24T13:07:36.282Z","etag":null,"topics":["chat-application","distributed-systems","djikstra","graph-algorithms","javascript","js","p2p","peer-to-peer","sigmajs","vanilla-javascript","webapp","webrtc","webrtc-experiments","webrtc-signaling"],"latest_commit_sha":null,"homepage":"https://membranexus.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Elijah-Bodden.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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,"zenodo":null}},"created_at":"2022-06-26T14:39:37.000Z","updated_at":"2024-11-29T04:30:52.000Z","dependencies_parsed_at":"2024-08-27T04:04:21.790Z","dependency_job_id":"2d5aca1b-11b0-4a98-ba00-746fbd376668","html_url":"https://github.com/Elijah-Bodden/Membrane","commit_stats":{"total_commits":350,"total_committers":4,"mean_commits":87.5,"dds":0.38,"last_synced_commit":"feeb3ce4020fdfb349ff11fb3b67b698f0834539"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/Elijah-Bodden/Membrane","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elijah-Bodden%2FMembrane","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elijah-Bodden%2FMembrane/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elijah-Bodden%2FMembrane/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elijah-Bodden%2FMembrane/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Elijah-Bodden","download_url":"https://codeload.github.com/Elijah-Bodden/Membrane/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elijah-Bodden%2FMembrane/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276012602,"owners_count":25569845,"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","status":"online","status_checked_at":"2025-09-19T02:00:09.700Z","response_time":108,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["chat-application","distributed-systems","djikstra","graph-algorithms","javascript","js","p2p","peer-to-peer","sigmajs","vanilla-javascript","webapp","webrtc","webrtc-experiments","webrtc-signaling"],"created_at":"2025-03-04T23:17:50.183Z","updated_at":"2025-09-19T22:56:25.021Z","avatar_url":"https://github.com/Elijah-Bodden.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Membrane](./Assets/Membrane-banner.svg)\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://img.shields.io/github/license/Elijah-Bodden/membrane?color=blue\u0026label=License\"/\u003e\n\u003cimg src=\"https://img.shields.io/github/languages/code-size/Elijah-Bodden/membrane?color=%20%23d0a011%20\u0026label=Raw%20Code%20Size\"/\u003e\n\u003cimg src=\"https://img.shields.io/maintenance/yes/2025?label=Maintained\"/\u003e\n\u003ca href=\"https://twitter.com/intent/tweet?text=An+unkillable%2C+browser-based+p2p+network.\u0026url=https%3A%2F%2Fgithub.com%2FElijah-Bodden%2FMembrane\u0026hashtags=webrtc+opensource+p2p+peer2peer+github+projectMembrane\u0026original_referer=http%3A%2F%2Fgithub.com%2F\u0026tw_p=tweetbutton\" target=\"_blank\"\u003e\n  \u003cimg src=\"http://jpillora.com/github-twitter-button/img/tweet.png\" title=\"An unkillable browser-based p2p network.\"\u003e\u003c/img\u003e\n\u003c/a\u003e\n\u003c/br\u003e\n\u003ca href = https://github.com/Elijah-Bodden/Membrane/blob/main/docs.md\u003e\n\u003cimg src=\"https://img.shields.io/badge/Documentation-here-green?style=for-the-badge\u0026logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAkCAMAAADfNcjQAAAACXBIWXMAAAPoAAAD6AG1e1JrAAABI1BMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRGyDUAAAAYHRSTlMADsnu1gXVAQQD0YN0VgJ2Y2Fk73WFVdfzB9QGEhqEWoJndwwhc85iGNM62+uGz9LLyghgQFgxgewRXowiF3FXX/L4D7cbUhDl5FDqVCnYFGWJCjiy9Isdxyfd4QnQ9Ul2HlhyAAABVklEQVQ4y8WTxXLDMABEt41t2U2jpHaYoYGGy8zMzKT//4racqYxNse+g09vVrsaGc135k36EhQ6b0wQEoKThHTFDkAMgUkpRXYh4jqQEMGFNDypBKQUkamRoH+II4AQjGkzhkd0gZhn2dCF9snFWfweXMBGOTwkGq42gONTvqVjCBRFx0QV2D/XbjtPbWYm5OpTFuo7WQw6vDD/DgEhhczsn4IIeXxwxHR00kJ0axM2gSLoLmlPOIyokSHqfNYhjOxgERQO4FmyGi5VPBMsJRfMR+KamVM5pcW5kR18Loooym87T2Fkwr8LIlVcyFjVJNkUet7/BVYGCXu7sWI+FgvayS+vsxq4UGZ+rOn3pwtoFuKhVvLmaDtkJV5Y0psaAt/QT965S1ACWX/VoDJVaskGFGKHrzWOMOg+PiMjOsnIH30uUDywdHfCzWePfZnCa8tnifb9A/asZNZ24Y66AAAAAElFTkSuQmCC\"/\u003e\n\u003c/a\u003e\n\u003ca href =\"https://membranexus.com\"\u003e\n\u003cimg src=\"https://img.shields.io/badge/Demo-here-blue?style=for-the-badge\"/\u003e\n\u003c/a\u003e\n\u003c/br\u003e\nRobust browser-based peer to peer networks  \n\u003c/br\u003e\n\u003ci\u003eHappily announcing Membrane v2 with overhauled developer experience and reliability!\u003c/i\u003e\n\u003c/br\u003e\n\n## What's this?\nMembrane is a protocol that takes WebRTC signalling to the browser with living peer networks. With minimal server-based bootstrapping, it makes self-sufficient peer-to-peer networks that can operate without servers. Any member browser can create a connection to any other by signaling across its peers, avoiding clumsy, centralized signaling servers. The network automatically stabilizes its topology and coordinates using broadcast \"gossip\".\n\nDesigned to be agnostic and easy to use, membrane is ideal for web clients for distributed chat, hosting, torrenting, computing, and so much more!  \n| ![](./Assets/demo.gif) |\n|:--:|\n| *[Membranexus.com](https://membranexus.com), built using Membrane. After a first-contact server signal, clients never need to interact with a server again - all network functions can happen on the peer level.* |  \n\nMembrane leverages `RTCPeerConnection`'s agnosticism about signalling. You could just as well communicate `ICE` connection data through smoke signals or quantum teleportation as through a standard signalling server. In many cases, signaling servers are an unreliable and vulnerable approach. Membrane attempts to implement a better alternative protocol. With each membrane network acting as a giant, decentralized router, unconnected members on opposite sides of the earth can exchange arbitrary data in milliseconds, with no clumsy intermediary server or risk of downtime.\n\nOf course, this approach isn't perfect. The benefits of decentralization are ultimately also the fatal flaw. What it gains in robustness and decentralization, it loses in making spoofing and manipulation easier. A public key ledger for cryptographic signing is WIP, which should largely remedy these issues (it will likely be served up by the signaling server on initial connectioen, which isn't a problem, since new peers can only be added when the server's up). And for non trust-critical operations, the existing implementation should be good enough.  \n\n## Installation and Integation\n### Installing the Demo or Building From Source\n  #### Prerequisites\n  - `npm`\n  - `npx`\n  - `node.js`  \n### \nPaste the following commands into a terminal to build a complete directory structure and initialize the demo on 127.0.0.1:8000 anywhere with the prerequisities installed.\n  ```shell\n  wget Membrane-current.tar.gz https://github.com/Elijah-Bodden/Membrane/tarball/v2.1.1\n  tar xfv Membrane-current.tar.gz --transform 's!^[^/]\\+\\($\\|/\\)!Membrane-current\\1!'\n  cd Membrane-current/src/source/frontend\n  npm install\n  cd ../server\n  npm install\n  npm run deploy\n  rm ../../../../Membrane-current.tar.gz\n  ```\n  To kill the pm2 instance this creates, run `npm run kill`. To re-spawn it, run `npm run deploy` in `/src/source/server`.\n\n### Deploying a New First-Contact Signalling Server\nInstalling the pre-made server from `/src/source/server/index.noStatic.js` is a piece of cake! Just run `npm i membrane-server` at the root of your node project, sit back, and relax while it installs.  \nTo deploy the server on pm2, run `npm explore membrane-server -- npm run deploy`. To kill the pm2 instance, run `npx pm2 kill`. Now just remember to replace the websocket addresses in your client's config with your new server's.\n\nYou will likely also want to set up coturn TURN and STUN servers and replace the addresses in the frontend's `defaultConfig` with those. And since this is a standalone signaling server, you'll have to serve your frontend (in the demo's case, `/src/source/frontend/dist`) separately.\n### Build Your Own App!\nUsing the library for your own use-case is relatively simple. Here's a typical integration process. Find the delivery method you like below, then head down below to the general next steps  \n| Delivery Method | Instructions |\n---- | ----\n| npm + Webpack | Run `npm install @elijah-bodden/membrane \\| cd node-modules/elijah-bodden/membrane` in the root of your webpack project |\n| HTML script tag | Go to the directory you serve static files from and find where you want to save the script. Run `wget https://raw.githubusercontent.com/Elijah-Bodden/Membrane/main/lib/index.js -o membrane.js`, and finally paste `\u003cscript src=\"/path/to/membrane.js\"/\u003e` into your HTML head. |\n| Jsdelivr CDN (recommended) | Simply paste this tag into your HTML head: `\u003cscript src=\"https://cdn.jsdelivr.net/npm/@elijah-bodden/membrane@2.1.4/index.js\"\u003e\u003c/scrípt\u003e` |\n\n\u003cp align=center\u003e\u003ci\u003ethen\u003c/i\u003e\u003c/p\u003e\n\n\u003c!--List Break--\u003e\nNow comes the fun part - building the thing! You'll likely want to set up your own first-contact signaling server (like [this](https://github.com/Elijah-Bodden/Membrane#deploying-a-new-signalling-server)) and host it publicly so people can use your app from all different networks. Don't forget to edit the websocket urls in `CONFIG.serverLink` to point to it (or pass a config loader into init and do that without editing the source - how to do that next)!\n\nThe library has lots of things to play with, but here are some of the most useful for high-level apps. Read the [docs](https://github.com/Elijah-Bodden/Membrane/blob/main/docs.md) to find out how to use them!: `init`, `gossipTransport.addType`, `gossipTransport.addParser`, `*.addGossip`, `networkMap.nodes`, `livePeers`, `authPeers`, `CONFIG`, `deauthPeer`, `onAuthRejected`, `onServerError`, `onNewAuthPeer`, `onNewPeer`, `onLostAuthPeer`, `onLostPeer`, `sendConsumable`, `onAuth`, `makeConnection`, `onNetworkMapUpdate`, `onConsumable`\n#### Custom App Demo\nHere's a small demo that shows off how to build an app on membrane. It's a tiny distributed self-hosting platform, where clients can publish resources (for example websites) and advertise them to the rest of the network through a custom gossip transport. If a peer wants a resource, it creates an auth route to the target, and the two use an ad-hoc `consumable` formatting method to handle request and response. Notably this is all peer-to-peer - aside from a tiny signaling server (which is only needed for adding new members), it should be able to scale infinitely without hardware overhead. The actual structure of the network is screened off from the user, so as far as they're concerned, they're directly connected to every single node. It runs on vanilla Membrane with a standard signalling server. With redundant hosting, this could be fleshed out into a pretty usable p2p version of the web or a social media. Here's the demo in action:  \n\n\n![Demo](./Assets/membrane-minimal-demo.gif)\n\n\nAnd here's the code\n```js\n// index.js\nvar selfHostingTransport\nvar resourceLocations = {}\nvar myResources = {}\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n    resourceList = document.getElementById(\"resource-list\")\n    resourceDisplay = document.getElementById(\"resource-display\")\n})\n\nasync function configLoadFunction() {\n    return {\n            \"serverLink.initBindURL\": \"ws://localhost:8777/bind?originatingSDP=*\",\n            \"serverLink.reconnectURL\": \"ws://localhost:8777/reconnect\",\n            \"communication.publicAlias\": \"Random person\",\n        }\n}\n\n// Can only add gossip transports after init\ninit(configLoadFunction).then(async () =\u003e {\n    // This transport will broadcast each peer's content list\n    selfHostingTransport = gossipTransport.addType(\"contentList\");\n    // When we get gossip about content lists, edit our content list\n    gossipTransport.addParser(\n        \"contentList\",\n        // Means we use the default pre-parser. Automatically handles passing gossip along for us\n        true,\n        // block = all gossip; commitable = all gossip, but only the \"constantParameter\" fields - ones that the transport uses to decide if gossip is new; unknown = all new gossip\n        async function (_block, _committable, unknown) {\n            unknown.forEach((fact) =\u003e {\n                if (networkMap.nodes[fact.alias]) {\n                    resourceLocations[fact.alias] = fact.content\n                    // Rerender resource list if it's currently visible\n                    reRenderResourceList()\n                }\n            });\n        },\n        // constantParameters - in this case each gossip has a uniqe ID to make sure it doesn't circulate the network infinitely\n        [\"UID\"]\n    );\n})\n\nonNetworkMapUpdate((kind, info) =\u003e {\n    // Will be a tuple of aliases if it's an edge change, but we're only interested in node changes\n    let alias = info\n    if (kind === \"addNode\") {\n        // Add a content tracker for every new node\n        resourceLocations[alias] = []\n    }\n    if (kind === \"removeNode\") {\n        // Delete our tracker for a node's content when it leaves\n        delete resourceLocations[alias]\n    }\n    // Rerender the resource list every time the network map updates\n    // If the DOM hasn't loaded yet, we'll have to wait until it does\n    if (!document.body) {\n        document.addEventListener(\"DOMContentLoaded\", () =\u003e {\n            reRenderResourceList()\n        })\n        return\n    }\n    reRenderResourceList()\n});\n\nonNewAuthPeer((alias) =\u003e {\n    // Give it a callback for consumable packages\n    onConsumable(alias, (request) =\u003e {\n        // If it's a lookup request (defined by our arbitrary formatting protocol), give a response\n        if (request.startsWith(\"lookup: \")) {\n            lookupResponse(alias, myResources[request.slice(8)])\n        }\n        else if (request.startsWith(\"resource: \")) {\n            // And render if it's a lookup response\n            renderResourceDisplay(request.slice(9))\n        }\n        else {\n            console.error(\"Unknown consumable package: \" + request)\n        }\n    })\n})\n\nasync function lookupRequest(alias, location) {\n    if (alias === CONFIG.communication.hiddenAlias) {\n        renderResourceDisplay(myResources[location])\n        return\n    }\n    // Make a route to the resource host if we don't already have one. This is screened off from the user.\n    // If we already have a connection, this will do nothing\n    await makeConnection(alias, true)\n    onAuth(alias, () =\u003e {\n        sendConsumable(alias, \"lookup: \" + location)\n    })\n}\n\nasync function lookupResponse(alias, resource) {\n    sendConsumable(alias, \"resource: \" + resource)\n}\n\nasync function publishResource(location, resource) {\n    // Add the resource to our list\n    myResources[location] = resource\n}\n\n// Broadcast our resource list every 1 second\nsetInterval(() =\u003e {\n    selfHostingTransport.addGossip({alias: CONFIG.communication.hiddenAlias, content: Object.keys(myResources), UID: Math.random().toString().slice(2, 17)})\n}, 1000);\n\nfunction renderResourceList() {\n    resourceList.innerHTML = \"\"\n    resourceDisplay.innerHTML = \"\"\n    for (let i in resourceLocations) {\n        resourceList.innerHTML += `\u003cp\u003e${hiddenAliasLookup[i]}\u003c/p\u003e\u003cul\u003e`\n        for (let j of resourceLocations[i]) {\n            resourceList.innerHTML += `\u003cli\u003e\u003ca href=\"#\" onclick=\"lookupRequest('${i}', '${j}')\"\u003e${j}\u003c/a\u003e\u003c/li\u003e`\n        }   \n    }\n}\n\nfunction renderResourceDisplay(resource) {\n    resourceDisplay.innerHTML = resource\n    resourceList.innerHTML = \"\"\n    resourceDisplay.innerHTML += \"\u003cbr\u003e\u003cbutton onclick='renderResourceList()'\u003eBack\u003c/button\u003e\"\n}\n\nfunction reRenderResourceList() {\n    if (resourceList.innerHTML !== \"\") {\n        renderResourceList()\n    }\n}\n```\n```html\n\u003c!-- index.html --\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n    \u003chead\u003e\n        \u003cmeta charset=\"UTF-8\"\u003e\n        \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"\u003e\n        \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n        \u003ctitle\u003eP2P hosting\u003c/title\u003e\n        \u003cscript src=\"https://cdn.jsdelivr.net/npm/@elijah-bodden/membrane@2.1.3/index.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\n        \u003cscript src=\"index.js\"\u003e\u003c/script\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003ch1\u003eP2P self-hosting\u003c/h1\u003e\n        \u003cdiv id=\"resource-list\"\u003e(No sites up right now)\u003c/div\u003e\n        \u003cdiv id=\"resource-display\"\u003e\u003c/div\u003e\n        \u003ctextarea id=\"resource-writer\" rows=\"5\" cols=\"50\" placeholder=\"Your site\"\u003e\u003c/textarea\u003e\n        \u003ctextarea id=\"name-field\" rows=\"1\" cols=\"10\" placeholder=\"Site name\"\u003e\u003c/textarea\u003e\n        \u003cbutton onclick=\"publishResource(document.getElementById('name-field').value, document.getElementById('resource-writer').value)\"\u003ePublish\u003c/button\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n## Contributing\nI appreciate any contributions! If you see something you think you can improve in the code, please make a PR. If you just have an idea or spot a bug, that's great too! Please, open an `issue` with either the `bug` or `enhancement` tag. And if you just want to show some love to the project, it'd mean a ton if you left a star!  \n## Authors\n* **Elijah Bodden** - *Initial work* - [Elijah-Bodden](https://github.com/Elijah-Bodden)\n## License\nMIT License. See the [LICENSE](LICENSE) file for details.\n\n## Built With\n- The core module - 100% [Vanilla.js](http://vanilla-js.com/). Also check out [kNow](https://github.com/Elijah-Bodden/kNow), which I spun off of membrane's homebrew event handler.\n- Demo frontend - HTML/SCSS/JS (because i'm stupid), plus [Sigma.js](https://github.com/jacomyal/sigma.js)+[Graphology](https://github.com/graphology/graphology) to power the gorgeous network visualization graph (and some pretty Font Awesome icons!)\n- Backend - [Node](https://github.com/nodejs/node) using lots of libraries, but especially [Winston](https://github.com/winstonjs/winston) for logging and [WS](https://github.com/websockets/ws) for websockets\n\n## Contact\nElijah Bodden - elijahbodden@protonmail.com / admin@membranexus.com  \nProject - https://github.com/Elijah-Bodden/Membrane  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felijah-bodden%2Fmembrane","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felijah-bodden%2Fmembrane","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felijah-bodden%2Fmembrane/lists"}