Low cost, low effort P2P WebRTC serverless signalling using Cloudflare Workers

Overview

image

P2PCF

P2PCF enables free (or cheap) serverless WebRTC signalling using a Cloudflare worker and a Cloudflare R2 bucket. The API is inspired by P2PT, but instead of using WebTorrent trackers, which may go down, a custom Cloudflare worker is provided whose level of I/O aims to be free for most use-cases, and otherwise very cheap.

The point is to allow people to deploy WebRTC-enabled applications without having to manage or worry (much) about a signalling server. Out of the box the library will "just work" using a free public worker (run by the author) that is subject to quota. However, setting up your own worker is easy and takes just a few minutes. Once it is deployed, signalling will work without any further maintenance.

P2PCF also has some additional features:

  • Room-based keying for easy connection management + acquisition
  • Minimal initial signalling (1 or 2 signalling messages) using the technique put together by @evan_brass
  • Subsequent signalling over DataChannels
  • Efficient chunking + delivery of DataChannel messages that exceed the ~16k limit
  • Peers handed back from the API are tiny-simple-peer instances which provides a simple API for interacting with the underlying PeerConnections (adding + removing media tracks, etc.)

Example + Usage

Out of the box, P2PCF will use a free public worker.

Once you're ready, you should set up your own worker on Cloudflare: https://github.com/gfodor/p2pcf/blob/master/INSTALL.md

A basic chat + video sharing example demonstrates the library at https://gfodor.github.io/p2pcf-demo (source)

Basic usage:

import P2PCF from 'p2pcf'

const client_id = 'MyUsername'
const room_id = 'MyRoom'

const p2pcf = new P2PCF(client_id, room_id, {
  // Worker URL (optional) - if left out, will use a public worker
  workerUrl: '<your worker url>',

  // STUN ICE servers (optional)
  // If left out, will use public STUN from Google + Twilio
  stunIceServers: { ... },
  
  // TURN ICE servers (optional)
  // If left out, will use openrelay public TURN servers from metered.ca
  turnIceServers: { ... },
  
  // Network change poll interval (milliseconds, optional, default: 15000, 15 seconds)
  // Interval to poll STUN for network changes + reconnect
  networkChangePollIntervalMs: ...,
  
  // State expiration interval (milliseconds, optional, default: 120000, 2 minutes)
  // Timeout interval for peers during polling
  stateExpirationIntervalMs: ...,
  
  // State heartbeat interval (milliseconds, optional, default: 30000, 30 seconds)
  // Time before expiration to heartbeat
  stateHeartbeatWindowMs: ...,
  
  // Fast polling rate (milliseconds, optional, default: 750)
  // Polling rate during state transitions
  fastPollingRateMs: ...,
  
  // Slow polling rate (milliseconds, optional, default: 1500, 1.5 seconds)
  // Polling rate when state is idle
  slowPollingRateMs: ...,

  // Options to pass to RTCPeerConnection constructor (optional)
  rtcPeerConnectionOptions: {},

  // Proprietary constraints to pass to RTCPeerConnection constructor (optional)
  rtcPeerConnectionProprietaryConstraints: {},

  // SDP transform function (optional)
  sdpTransform: sdp => sdp
});

// Start polling
p2pcf.start()

p2pcf.on('peerconnect', peer => {
  // New peer connected
  
  // Peer is an instance of simple-peer (https://github.com/feross/simple-peer)
  //
  // The peer has two custom fields:
  // - id (a per session unique id)
  // - client_id (which was passed to their P2PCF constructor)
  
  console.log("New peer:", peer.id, peer.client_id)
  
  peer.on('track', (track, stream) => {
    // New media track + stream from peer
  })
  
  // Add a media stream to the peer to start sending it
  peer.addStream(new MediaStream(...))
})

p2pcf.on('peerclose', peer => {
  // Peer has disconnected
})

p2pcf.on('msg', (peer, data) => {
  // Received data from peer (data is an ArrayBuffer)
})

// Broadcast a message via data channel to all peers
p2pcf.broadcast(new ArrayBuffer(...))

// To send a message via data channel to just one peer:
p2pcf.send(peer, new ArrayBuffer(...))

// To stop polling + shut down (not necessary to call this typically, page transition suffices.)
p2pcf.destroy()

stunIceServers and turnIceServers are optional, but if provided, should be in the format of the iceServers option passed to RTCPeerConnection.

When a new peer joins, it can take up to slowPollingRateMs before negotiation will begin. If you want peers to connect more quickly, you can adjust slowPollingRateMs but it will result in increased worker requests and R2 reads.

Note that peers who are both on symmetric NATs (or one symmetric NAT + one port restricted NAT) will use TURN. If you do not specify a TURN server then the TURN server provided by Open Relay will be used. It's estimated 8% of visitors require TURN.

Estimated Cloudflare Usage

The worker provides signalling via HTTP polling (with backoff when the room is idle), and each request to the server performs a small number of reads from R2. Each join of a peer to the room will do at least 1 write to R2 and up to N + 1 writes (one for each peer, and a metadata update) in the worst-case where all peers are behind symmetric NATs and need to perform bi-directional hole punching to establish their initial DataChannel. Subsequent renegoations are performed over the DataChannel and so do not incur any R2 writes. Clients also heartbeat to maintain livliness every 90 seconds, which incurs an additional write each time.

R2's free tier offers 1M writes per month and 10M reads per month. Cloudflare workers offer ~3M free requests per month. In general, these free tiers should support any modest WebRTC application's signalling needs without the need to rely upon public signalling servers.

Comments
  • perf: use crypto and ESM

    perf: use crypto and ESM

    note: #2 needs to be merged first!

    • replaces the not random randomstring with the crypto API [which was already used by randombytes], a drawback of this is that it doesn't specify the string length, [is this needed?] ~~- replaces some deps with the built-in atob/btoa which is deprecated in node, but is supported in browsers and is consistent with CF's implementation~~
    • drops require making the module itself ESM compliant
    opened by ThaUnknown 24
  • perf: migrate from simple-peer to polite-peer

    perf: migrate from simple-peer to polite-peer

    Please consider migrating from simple-peer to polite-peer: https://github.com/jimmywarting/jimmy.warting.se/blob/master/packages/peer/perfect-negotiation.js [there isn't an NPM package for this but I could make one]

    Why this matters: simple-peer relies on stream which in turn relies on buffer, and process which relies on setTimeout. This is an important issue because those require to be polyfilled, and those polyfills are VERY slow. Since WebTorrent was mentioned, from my testing more than half of the app's CPU time was consumed by node's stream and buffer or actually it's polyfills. Another massive issue is the reliance on the slow process polyfill of most bundlers which uses setTimeouts for delying tasks instead of promises or microtask.

    Problems: polite-peer is a much simper package than simple-peer [oh the irony] and as such doesn't expose that many wrappers so it might require extra work. Another issue might be the immaturity of the package as very little people actually used it [not to be confused with it wasn't used a lot, because it was used in public projects a lot] so unexpected issues might arise.

    opened by ThaUnknown 20
  • bug: peerclose fires without emitting peerconnect

    bug: peerclose fires without emitting peerconnect

    If a peer fails to connect [not sure why that happens yet], it will emit close, without ever emitting peerconnect

    This case needs to be considered

    code:

    const map = {}
    p2pcf.on('peerconnect', peer => {
      map[peer.id] = true
    })
    p2pcf.on('peerclose', peer => {
      if (map[peer.id]) {
        // handle
      } else {
        // this should never occur but does
      }
    })
    

    should there be a separate event for failed to init peer?

    opened by ThaUnknown 15
  • fix: drop pako

    fix: drop pako

    This drops pako as a dependency. From my [limited] testing, pako is only used for ~5% of all requests and saving just a few bytes, while making up almost 40% of the bundle size for the module. The size optimization brought in by pako likely won't actually reduce the amt of data transferred over the network as the packet size would most likely be too small to split it anyways.

    opened by ThaUnknown 13
  • critical: handle destroy state correctly

    critical: handle destroy state correctly

    this fixes an issue which would leak step and network intervals, which would accumulate over usage, especially for SPA's

    this happened when a p2pcf instance was destroyed promptly after being created

    opened by ThaUnknown 3
  • perf: use process-fast

    perf: use process-fast

    this is a very quick and easy fix for major performance bottlenecks caused by the process until #1 is resolved, as it uses queueMicrotask instead of setTimeout

    opened by ThaUnknown 2
  • Use tiny-simple-peer

    Use tiny-simple-peer

    This migrates p2pcf to use a small fork of simple-peer I created called tiny-simple-peer, which drops all the node polyfills in exchange for removing the Peer's stream.Duplex API. This was based on the feedback in https://github.com/gfodor/p2pcf/issues/1

    I'm not going to cut this as a major release even though it's technically a breaking API change, since this library is so new that I doubt we have any downstream consumers or ever will of the stream.Duplex API.

    opened by gfodor 1
  • On a network change, re-create the peer gracefully when it closes

    On a network change, re-create the peer gracefully when it closes

    Instead of forcibly disconnecting all peers on a network change, mark all peers as needing a restart. This results in no forced disconnects in the case of false positives but will also cause the peer to come back online again once it closes. The downside of this is there will be a bunch of dead peers floating around if IPs change benignly and any of the active peers disconnect eventually, since they'll be re-created.

    opened by gfodor 0
  • bug: cf worker shouldn't disconnect peers

    bug: cf worker shouldn't disconnect peers

    the CF worker really shouldn't disconnect peers, this should happen between the peers themselves

    the peer list often takes time to propagate tru cloudflare, or sometimes doesn't at all, leading to people randomly disconnecting with eachother

    opened by ThaUnknown 9
  • bug: cf worker error 1101 causing JSON.parse error

    bug: cf worker error 1101 causing JSON.parse error

    Error 1101 Ray ID: 73427f0fcf93b363 • 2022-08-01 23:53:59 UTC
    Worker threw exception
    What happened?
    You've requested a page on a website (p2pcf.minddrop.workers.dev) that is on the Cloudflare network. An unknown error occurred while rendering the page.
    
    What can I do?
    If you are the owner of this website:
    you should login to Cloudflare and check the error logs for p2pcf.minddrop.workers.dev.
    

    POST https://p2pcf.minddrop.workers.dev/ 500

    causes:

    SyntaxError: Unexpected token < in JSON at position 0

    at: https://github.com/gfodor/p2pcf/blob/3b3988592a60e3f1e9ce59fc2cf866d4c26829ed/src/p2pcf.js#L356

    opened by ThaUnknown 5
Releases(v1.3.10)
  • v1.3.10(Nov 3, 2022)

  • v1.3.9(Nov 2, 2022)

  • v1.3.8(Oct 29, 2022)

    • Add 15s timeout for overall initial connection time, and retry if it fails. The ICE timeout built into simplepeer is not useful as it is falling through to the iceComplete handler de494f2

    https://github.com/gfodor/p2pcf/compare/v1.3.7...v1.3.8

    Source code(tar.gz)
    Source code(zip)
Owner
Greg Fodor
Greg Fodor
JavaScript Package Size Cost like bundlephobia or import-cost

JavaScript Page Size Cost The @esmj/size show you javascript cost for your defined packages. The packages is bundled with Webpack. Then show the bundl

Miroslav Jancarik 4 Nov 17, 2022
Functional-style Cloudflare Durable Objects with direct API calls from Cloudflare Workers and TypeScript support.

durable-apis Simplifies usage of Cloudflare Durable Objects, allowing a functional programming style or class style, lightweight object definitions, a

Dabble 12 Jan 2, 2023
基于 gh-proxy + Jsdelivr+ cnpmjs + cloudflare workers 的 GitHub Serverless API 工具。

better-github-api Better, Eazy, Access Anywhere 介绍 基于 gh-proxy + Jsdelivr + cnpmjs + cloudflare workers 的 GitHub Serverless API 工具。 cdn.js:仅含 gh-proxy

One Studio 11 Nov 23, 2022
ToolJet an open-source low-code framework to build and deploy internal tools quickly without much effort from the engineering teams

ToolJet is an open-source low-code framework to build and deploy internal tools quickly without much effort from the engineering teams. You can connect to your data sources, such as databases (like PostgreSQL, MongoDB, Elasticsearch, etc), API endpoints (ToolJet supports importing OpenAPI spec & OAuth2 authorization), and external services (like Stripe, Slack, Google Sheets, Airtable) and use our pre-built UI widgets to build internal tools.

ToolJet 15.6k Jan 3, 2023
Use pulsar to make custom trading strategy that uses Flashloans at low cost. No contract deployment required.

PULSAR Pulsar is a contract that will let you execute custom call on the blockchain and let you make use of the Flasloans in your trading sequences. Y

idecentralize.finance 9 Jun 6, 2022
Connect to a Postgres database from a Cloudflare Worker, using Cloudflare Tunnel

Cloudflare Workers Postgres Client This is an experimental module. Heavily based on cloudflare/worker-template-postgres, but cleaned up and bundled in

BubblyDoo 17 Dec 22, 2022
we try to make a tiny p2p client spec, maybe for sigchain gossip thing, maybe for simple blockchain thing

mininode Mininode is a tiny p2p client for prototyping p2p protocols. It is a specification for a set of interfaces that I made to make it easier to t

Nikolai Mushegian 8 Nov 23, 2022
API for P2P file sharing web application, Zed

zed-sharing-node Backend for file sharing app built with the MERN Stack Report Bug · Request Feature About The Project ??‍??️ This is the API for Zed,

Quavo 9 Nov 29, 2022
Learn Web 2.0 and Web 3.0 Development using Next.js, Typescript, AWS CDK, AWS Serverless, Ethereum and AWS Aurora Serverless

Learn Web 2.0 Cloud and Web 3.0 Development in Baby Steps In this course repo we will learn Web 2.0 cloud development using the latest state of the ar

Panacloud Multi-Cloud Internet-Scale Modern Global Apps 89 Jan 3, 2023
A Serverless GraphQL Sample project using Apollo and Serverless Framework with TypeScript and Webpack.

Serverless GraphQL Boilerplate This is a base project with a structure that includes Serverless Framework, Apollo, TypeScript and Webpack. It can be d

Ravi Souza 5 Aug 23, 2022
A URL shortener that runs on Cloudflare Workers

ITP Works A URL shortener that runs on Cloudflare Workers. It stores the rules in Cloudflare KV storage and sends a 301 redirect when a matched pathna

Yifei Gao 3 Mar 4, 2022
Starting template for building a Remix site with CloudFlare Workers (ES Modules Syntax)

Starting template for building a Remix site with CloudFlare Workers (ES Modules Syntax)

null 12 May 20, 2022
Google-Drive-Directory-Index | Combining the power of Cloudflare Workers and Google Drive API will allow you to index your Google Drive files on the browser.

?? Google-Drive-Directory-Index Combining the power of Cloudflare Workers and Google Drive will allow you to index your Google Drive files on the brow

Aicirou 127 Jan 2, 2023
Remix + Cloudflare Workers + DO + Turborepo

Remix + Cloudflare Workers + DO + Turborepo A starter to get you up and going with Remix on Cloudflare with all the latest and greatest. What's inside

Jacob Ebey 38 Dec 12, 2022
Remix + Cloudflare Workers + Wrangler2 + Tailwind + ESLint + Prettier + Vitest + Playwright

Welcome to Remix! Remix Docs Development You will be running two processes during development: The Miniflare server (miniflare is a local environment

null 48 Dec 19, 2022
Server-side rendering blog runs on Cloudflare workers

Serverside rendered blog I have tried something completely against to current web trends. What are these? I am using the React as backend framework wi

null 3 Jun 24, 2022
A collection of useful tools for building web apps on Cloudflare Workers.

Keywork is a batteries-included, magic-free, library for building web apps on Cloudflare Workers. Features ?? Written in TypeScript ?? Modules Support

Nirrius Studio 28 Dec 22, 2022
Abusing Cloudflare Workers to establish persistence and exfiltrate sensitive data at the edge.

Abusing Cloudflare Workers This repository contains companion code for the blog post MITM at the Edge: Abusing Cloudflare Workers. malicious-worker/ c

Christophe Tafani-Dereeper 10 Sep 16, 2022
Lightweight universal Cloudflare API client library for Node.js, Browser, and CF Workers

Cloudflare API Client Lightweight universal HTTP client for Cloudflare API based on Fetch API that works in Node.js, browser, and CF Workers environme

Kriasoft 15 Nov 13, 2022