Skip to content

darthwalsh/FireSocket

Repository files navigation

npm

FireSocket

A drop-in replacement for WebSocket using Firebase Realtime Database.

Examples

An example showing dynamically switching between FireSocket and WebSocket is in example/.

A full example is running at https://firesocketexample.appspot.com/.

Code changes

Before with WebSocket

server:

const express = require("express");
const ws = require("ws");
const app = express();
const wss = new ws.Server({server});

wss.on("connection", ws => ...

client:

const ws = new WebSocket(window.location.href.replace("http", "ws"));
ws.addEventListener("open", () => ...

Using FireSocket

server:

const firesocket = require("firesocket");
const express = require("express");
const app = express();
const wss = firesocket.Server.createFromCreds(databaseUrl, {app, firebaseConfig});

wss.on("connection", ws => ...

client:

const ws = new FireSocket(); // Args are ignored
ws.addEventListener("open", () => ...

Firebase setup

  • Run npm install firesocket
  • Install firebase cli
  • Set up Realtime Database security rules
    • Run firebase login
    • Run firebase init
      • Create new project or use existing project created in Console
      • Select Database
      • For security rules, use the path node_modules/firesocket/database.rules.json
      • Don't delete the existing file
    • Run firebase deploy --only database
  • Set up Authentication
  • Set up Admin Authentication using a Service Account
    • Open IAM Service accounts
    • Permissing needed is "Firebase Realtime Database Admin": roles/firebasedatabase.admin
    • If running in GCP, add admin policy to your default service account
    • OR
    • Create new service account, name i.e. "server", give admin policy
    • For non-GCP servers or local testing, create service account credentials and store it securely
      • gcloud iam service-accounts keys create .test-creds.json --iam-account example@example.com
      • set $env:GOOGLE_APPLICATION_CREDENTIALS=".test-creds.json"

App setup

  • On the web server, change WebSocket to FireSocket
    • require("firesocket")
    • (TODO split up browser part) If clients are browser-based, use firesocket.Server.createFromCreds factory to create Server.
    • The returned FireSocket.Server object is API-compatible with WebSocket.Server
  • Download the Firebase client public JSON config for web app
    • The web server is expected to serve this JSON at /firebase-config.json
    • (TODO API) the firesocket.Server helper will add this to your express server
  • Add the script /firesocket.js before your web app's logic
    • Replace WebSocket with FireSocket (constructor args are ignored)
    • The FireSocket object is API-compatible with WebSocket
    • (TODO API) the firesocket.Server helper will serve this script

Roadmap to release 1.0

  • Basic parity with ws functionality
    • message
    • send
    • readyState
    • open waits until server connects
    • close
  • A/B testing source compatibility between FireSocket and WebSocket
  • Server admin authentication, supporting example server app and cli
  • Database read/write limitations on user/server
  • Server lib for wiping some/all message state
  • Client authentication
    • web
    • cli
  • Example express server setup with HTTP serving the script file
  • npm limit files for pack release
  • bot to update dependencies
  • generate .d.ts prepack or something
  • set up CI/CD that builds/tests/publishes to npm
  • firebase disconnect messages using onDisconnect
  • Get vs code typing for firesocket.Server working
  • Any TODOs left in README
  • Any TODOs left in code, maybe won't fix

Future improvements

  • Authentication is pluggable, so app can swap in email/SMS/OAuth sign-in
  • Generate type declarations instead of relying on cast to WebSocket

Testing

Run npm test to run all tests. This includes unit tests, and mock tests using the firebase emulator and a local WebSocket server. See spec/ for more information.

The example/ can be useful for manually debugging changes.

Motivation for client-server communication

WebSocket

The default client-server communication, and by far the fastest. It's pretty simple for a web game to just update state and issue questions based on player events.

Downsides:

  • Cheap hosting servers don't allow many simultaneous connections, if any
  • Messages are not persisted, so a server reboot will wipe out any app state

Firebase Database

Using Firebase Realtime Database, it is possible to emulate WebSocket messages.

This fixes both limitations:

  • Firebase is free for 100 connections users, with pay-as-you-go up to 200k.
  • When the server restarts it can event-source state from the database

Realtime Database was picked over Firestore because the average round trip latency of 600ms is fast enough to barely be noticed by users, while Firestore is noticeably slower at 1500ms. medium.com

Guide: Info on Data Model, Auth, Queues

Database Schema

Each game is composed of messages from server to client, and v/v.

Each firebase client listens for child_added on their message queue.

Here, user* is a userID from Firebase Authentication

user:
    userWQ3mVT:
        0: Message: Connected to Game
        1: Choice: New Game, Refresh
        2: Message: Cards
        3: Choice: Play Copper, Buy Copper
    user7f8pR:
        0: Message: Connected to Game
        1: Choice: New Game, Refresh, alice's game
server:
    userWQ3mVT:
        0: Name: alice
        1: Choice: New Game
        2: Start: Militia, Moat, ...
    user7f8pR:
        0: Name: bob
        1: Choice: Refresh