Posted on 2 Comments

Creating A WebTransport NPM Package – ShakePort

  1. Intro
  2. What Is HTTP/3
  3. Dissecting The WebTransport Class
  4. WebTransport Use Cases
  5. Why I Created ShakePort
  6. The Code

People Are SLEEPING on WebTransport!

Of all my favorite HTML APIs the WebTransport API is definitely TOP 3. MDN explains the WebTransport API as such:

The WebTransport interface of the WebTransport API provides functionality to enable a user agent to connect to an HTTP/3 server, initiate reliable and unreliable transport in either or both directions, and close the connection once it is no longer needed.

https://developer.mozilla.org/en-US/docs/Web/API/WebTransport

It allows a web page to connect to a bidirectional HTTP/3 server to send and receive UDP datagrams either reliably or unreliably. Seriously why is no one talking about this?! In this article/tutorial, I will go through the process of creating an NPM package called ShakePort

What Is HTTP/3

HTTP/3 is the third major version of the Hypertext Transfer Protocol used to exchange information on the World Wide Web, complementing the widely-deployed HTTP/1.1 and HTTP/2. Unlike previous versions which relied on the well-established TCP (published in 1974),[1] HTTP/3 uses QUIC, a multiplexed transport protocol built on UDP.[2] On 6 June 2022, IETF published HTTP/3 as a Proposed Standard in RFC9114.[3]

https://en.wikipedia.org/wiki/HTTP/3

HTTP/3 is 3x faster than HTTP/1.1 and has much less latency than its predecessors. Built using QUIC for data transfer using UDP instead of TCP. What this means for real-time sensitive applications is faster data faster execution.

Listen To Some Hacker Music While You Code

Follow me on Spotify I make Tech Trap music

Dissecting The WebTransport Class

Constructor

The constructor for the WebTransport takes in a url and an options parameter. The URL points to an instance of a HTTP/3 server to connect to. The options parameter is an optional variable that passes in a JSON object

url

A string representing the URL of the HTTP/3 server to connect to. The scheme needs to be HTTPS, and the port number needs to be explicitly specified.options Optional

An object containing the following properties:serverCertificateHashes Optional

An array of WebTransportHash objects. If specified, it allows the website to connect to a server by authenticating the certificate against the expected certificate hash instead of using the Web public key infrastructure (PKI). This feature allows Web developers to connect to WebTransport servers that would normally find obtaining a publicly trusted certificate challenging, such as hosts that are not publicly routable, or ephemeral hosts like virtual machines.

WebTransportHash objects contain two properties:algorithm

A string representing the algorithm to use to verify the hash. Any hash using an unknown algorithm will be ignored.value

BufferSource representing the hash value.

We instantiate a new instance of WebTransport with the following command

new WebTransport(url, options) 

Instance Properties

The closed read-only property of the WebTransport interface returns a promise that resolves when the transport is closed.

The datagrams read-only property of the WebTransport interface returns a WebTransportDatagramDuplexStream instance that can be used to send and receive datagrams — unreliable data transmission.

“Unreliable” means that transmission of data is not guaranteed, nor is arrival in a specific order. This is fine in some situations and provides very fast delivery. For example, you might want to transmit regular game state updates where each message supersedes the last one that arrives, and order is not important.

The incomingBidirectionalStreams read-only property of the WebTransport interface represents one or more bidirectional streams opened by the server. Returns a ReadableStream of WebTransportBidirectionalStream objects. Each one can be used to reliably read data from the server and write data back to it.

The incomingUnidirectionalStreams read-only property of the WebTransport interface represents one or more unidirectional streams opened by the server. Returns a ReadableStream of WebTransportReceiveStream objects. Each one can be used to reliably read data from the server.

The ready read-only property of the WebTransport interface returns a promise that resolves when the transport is ready to use.

Instance Methods

The close() method of the WebTransport interface closes an ongoing WebTransport session.

The createBidirectionalStream() method of the WebTransport interface opens a bidirectional stream; it returns a WebTransportBidirectionalStream object containing readable and writable properties, which can be used to reliably read from and write to the server.

The createUnidirectionalStream() method of the WebTransport interface opens a unidirectional stream; it returns a WritableStream object that can be used to reliably write data to the server.

Use Cases For WebTransport

Web Gaming

WebTransport allows not only for better performance web games in multiplayer, but it also allows for CROSS-SYSTEM PLAY! Since WebTransport is an HTTP/3 web standard you can implement it on the web, PlayStation, Xbox, and whatever else may come in the future! You may be wondering why use WebTransport instead of WebRTC. For 1 v 1 multiplayer games WebRTC will do just fine, but what if you want to build a Battle Royale style battle game that’s 50 v 50? If you are using WebRTC you will run into latency issues because WebRTC is peer-to-peer whereas WebTransport is client-server. Using UDP packets so order does not matter which is what you want for gaming.

Free hand holding gaming console
WebTransport is a major win for gaming in the web.

IOT

With WebTransport communicating with IOT devices via the web just got a whole lot easier. You can manage a fleet of hardware devices, get analytical data and issue commands in real-time. I am currently using a WebTransport server on my Raspberry Pi 4 and a Vue frontend to remotely control my Pi!

Machine Learning

Machine learning requires large datasets. By utilizing WebTransport you can send user data to your server in real-time to get better insights on your machine learning models. Take for example you are building a recommendation engine. As the user browses the site, you are sending data in real time based on what they are looking at, etc. The faster you can collect and analyze data, the more profitable your company can become.

Pub/Sub

Using the WebTransport API you can implement a pub/sub system. The simplest use case would be a notification engine (think game HUD updates in multiplayer). You can also do things like implement real-time tickers instead of relying on long-polling techniques.

Why I Created ShakePort

I created ShakePort as a basis for my real-time apps and more importantly, I’m building a game and need multiplayer networking. I decided at the beginning of the year I would post on average 1 open source package a month. So far I’m at 4! My philosophy is if you find yourself doing certain code over and over, just package that sh!t up and release it to the world. Chances are there are other developers who you are helping OR you could find devs to help make your package even better! The ShakePort suite is made up of a client (ShakePort Client) and a server (ShakePort Server) this tutorial will focus on the ShakePort Client, I will post the ShakePort Server tutorial later in the year once I finish.

The Code

This package is actually very simple, it only consists of two files:

  • A WebWorker file
  • The ShakePortClient class

Almost all of the heavy work is offloaded to the WebWorker to optimize speed and performance and utilizes window.postMessage() to send data to the main application. This way the developer has custom control on how to deal with the datagrams.

Scaffolding

Create a new directory and run the npm init command to create a new NPM package
mkdir shakeport-client

cd shakeport-client && npm init

The WebWorker

Create a worker.js file in the root of the project and input the following:

let transport, stream = null
onmessage = (e) => {
  try {
    switch(e.data.event) {
      case 'start':
        transport = initTransport(e.data.url, e.data.options)
        postMessage({event:'start', transport:transport})
      break;

      case 'setup-bidirectional':
      stream = setUpBidirectional()
      readData(stream.readable)
      break;

      case 'write-bidirectional':
      writeData(stream.writable, e.data.data)
      break;

      case 'data':

      break;

      case 'stop':
        closeTransport(e.transport)
      break;
    }
  } catch {

    postMessage({event: 'error'});
  }
}
async function initTransport(url, options = {}) {
  // Initialize transport connection
  const transport = new WebTransport(url, options);

  // The connection can be used once ready fulfills
  await transport.ready;

  return transport
}

async function readData(readable) {
  const reader = readable.getReader();
  while (true) {
    const {value, done} = await reader.read();
    if (done) {
      break;
    }
    // value is a Uint8Array.
    postMessage({event: 'data-read', data:value});
  }
}

async function writeData(writable, data) {
  const writer = writable.getWriter();
  writer.write(data)
  postMessage({event: 'data-written', data: data})
}

async function setUpBidirectional() {
  const stream = await transport.createBidirectionalStream();
  // stream is a WebTransportBidirectionalStream
  // stream.readable is a ReadableStream
  // stream.writable is a WritableStream
  return stream
}

async function setUpUnidirectional() {
  const stream = await transport.createUnidirectionalStream();
  // stream is a WebTransportBidirectionalStream
  // stream.readable is a ReadableStream
  // stream.writable is a WritableStream
  return stream
}

async function closeTransport(transport) {
    // Respond to connection closing
  try {
    await transport.closed;
    console.log(`The HTTP/3 connection to ${url} closed gracefully.`);
  } catch(error) {
    console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`);
  }
}

The ShakePortClient Class

Create a class file called ShakePortClient.js in the root directory and fill it in:


export default class ShakePortClient {
  transport = null
  constructor() {
    const worker = require('./worker.js')
    console.log(worker)
    this.worker = new Worker(worker)
  }

  startClient(data = {url, options: {}}) {
    this.worker.postMessage({event:'start', ...data})
    this.worker.onmessage = (event) => {
      switch(event.data.event) {
        case 'start':
          console.log('Started')
          this.transport = event.data.transport
        break;
        case 'stop':
          this.transport = null
        break;
        case 'error':
          console.log(event.data.error)
        break;
        default:
          window.postMessage(event.data)
        break;
      }
    }
  }

  stopClient () {
    this.worker.postMessage({event:'stop'})
  }


  setUpBidirectional () {
    this.worker.postMessage({event:'setup-bidirectional'})
  }

  setUpUnidirectional () {
    this.worker.postMessage({event:'setup-unidirectional'})
  }

  writeBidirectional (data) {
    this.worker.postMessage({event:'write-bidirectional', data: data})
  }

  writeData (data) {
    this.worker.postMessage({event:'write-data', data: data})
  }

  writeUndirectional (data) {
    this.worker.postMessage({event:'write-unidirectional', data: data})
  }
  
}

Using The Package

npm install @mastashake08/shakeport-client
Then import and use

Import In Your Project

import ShakePortClient from '@mastashake08/shakeport-client'
const spc = new ShakePortClient();
spc.startClient({
  url:'<webtransport_server_url>'
})

Responding To Messages

window.addEventListener("message", (event) => {
  // Do we trust the sender of this message?  (might be
  // different from what we originally opened, for example).
  if (event.origin !== "http://example.com")
    return;

  // event.source is popup
  // event.data is "hi there yourself!  the secret response is: rheeeeet!"
}, false);

Follow Me On Social Media

Follow Me On Youtube!

Follow my YouTube account

Get Your Next Domain Cheap & Support The Channel

I use Namecheap for all of my domains! Whenever I need a cheap solution for a proof-of-concept project I grab a domain name for as little as $1! When you sign up and buy your first domain with Namecheap I get a commission, it’s a great way to get a quality service and support this platform!

Get Your Next Domain Cheap
CLICK HERE

Become A Sponsor

Open-source work is free to use but it is not free to develop. If you enjoy my content and would like to see more please consider becoming a sponsor on Github or Patreon! Not only do you support me but you are funding tech programs for at risk youth in Louisville, Kentucky.

Join The Newsletter

By joining the newsletter, you get first access to all of my blogs, events, and other brand-related content delivered directly to your inbox. It’s 100% free and you can opt out at any time!

Check The Shop

You can also consider visiting the official #CodeLife shop! I have my own clothing/accessory line for techies as well as courses designed by me covering a range of software engineering topics.

2 thoughts on “Creating A WebTransport NPM Package – ShakePort

Leave a Reply