Skip to content

Introduction

Realtime Pub/Sub (also referred to as "the platform") is a SaaS solution that enables your applications with real-time Publish/Subscribe capabilities through WebSockets.

By adopting real-time messaging, applications and services can deliver better integrations and user experience; whether you're building a live chat application, a stock market tracking platform, IoT applications or a social network, the platform enables you to receive and deliver information in real-time.

Get started today:

Open Admin Console

<script>
  const APP_ID = 'YOUR APP ID HERE'
  const ACCESS_TOKEN = 'VALID JWT TOKEN HERE' // verifiable via "app/configuration/authKey"

  const ws = new WebSocket(`wss://genesis.r7.21no.de/apps/${APP_ID}?access_token=${ACCESS_TOKEN}`)
  ws.onmessage = async (event) => {
    // Parse incoming WebSocket messages
    const { topic, messageType, data } = event.data instanceof Blob
      ? JSON.parse(await event.data.text()) // compression is enabled
      : JSON.parse(event.data)

    // Check if it's a welcome message from the 'main' topic
    if (topic === 'main' && messageType === 'welcome') {
      console.log('> Connected 🔗')
    }

    // PROCESSING LOGIC HERE... (e.g. update UI, etc.)
    console.log('> Incoming message:', { topic, messageType, data })
  }
  ws.onclose = ({ code, reason }) => {
    // Handle and log WebSocket connection closure
    console.log('Connection closed: ', {
      code,
      reason
    })
  }
</script>
const APP_ID = 'YOUR_APP_ID'
const AUTH_TOKEN = 'Bearer AUTH_TOKEN_HERE' // verifiable via "app/configuration/adminKey"

const reqHeaders = new Headers()
reqHeaders.append('Accept', 'application/json')
reqHeaders.append('Content-Type', 'application/json')
reqHeaders.append('Authorization', AUTH_TOKEN)

fetch(`https://genesis.r7.21no.de/api/topics/${APP_ID}/publish`, {
  method: 'POST',
  headers: reqHeaders,
  body: JSON.stringify({
    topic: 'main',
    message: {
      msg: 'Hello subscribers in main topic 👋',
      time: Date.now()
    }
  })
})
  .then(() => console.log('Message published!'))
  .catch(console.error)

Use cases

Realtime Pub/Sub is ideal for workflows where the clients and servers have to exchange instant messages. The following categories are great use-cases:

  • Low-latency and energy-efficient communication (like multiplayer games or communicating with IoT devices)
  • Sending messages to servers (like chat apps or IoT devices sharing sensor info)
  • Delivering messages to clients (like UI alerts or commands to IoT gadgets)
  • Live dashboards (like stock market graphs)
  • Multimedia uses (like messaging, games, or team projects)

High-level architecture overview

Realtime Pub/Sub follows a distributed architecture that is resilient and highly scalable:

Architecture Overview

Main features

Our core uses and support projects of the 21no.de organization. By using Realtime Pub/Sub, you are also supporting the development and maintenance of open-source projects 💚

Enable real-time capabilities anywhere

Easily integrate real-time communication into any workflow with R7. Our platform empowers you to effortlessly incorporate WebSocket-based real-time Pub/Sub capabilities into your applications, no matter where or when you need them. Using our high-performance APIs, you can seamlessly publish messages into your application topics, ensuring that your users receive them instantly.
By enabling inbound WebSocket-based communication, your application clients can efficiently deliver instant messages to backend services.

High performance at scale

Realtime Pub/Sub enables lightning-fast communication with our low-latency messaging solution. Under the hood, you will find Node.js, uWebSockets.js and Redis powering optimal WebSocket performance for your users.
Our infrastructure is built to manage heavy traffic smoothly and scale to meet growing demands efficiently.

Open WebSocket Roundtrip Latency Benchmarker

Standard Web Protocols

Realtime Pub/Sub leverages the well-established WebSocket protocol for real-time communication, eliminating the need for proprietary or custom interfaces. By utilizing standard implementations, you can be confident in the reliability and compatibility of our solution.
Additionally, publishers and administrators/operators can communicate with our platform through a simple and easy-to-use REST API, providing a seamless experience for any runtime.

Secure by design

Our platform ensures secure communication with the latest in encryption technology, utilizing secure TLS(1.2 and 1.3) implementations all ingress endpoints, making sure that data transmitted between internet clients and our servers is safeguarded during transit.
Furthermore, we mandate JSON Web Token (JWT) authentication for all communication endpoints, adding an additional layer of security. This strategy also embraces Public Key Cryptography, facilitating zero trust authentication to enhance overall security measures.

We take the heavy lifting off your shoulders

Managing and scaling long-living TCP connections at scale can be complex and challenging due to several factors, including:

  • Resource management: Managing the resources required to handle a large number of simultaneous connections can be a challenge. As the number of connections increases, the memory, CPU, and network bandwidth required to maintain the connections also increases.
  • Network latency: As the number of connections grows, the network latency can become a bottleneck. This can result in slow data transfer rates and decreased performance, making it difficult to maintain real-time functionality.
  • Resilience and failure handling: In a high-scale system, failures can happen at any time. It's important to have a robust architecture in place to handle such failures and minimize downtime.
  • Load balancing: In a system that processes that many connections and messages, it's important to balance the load between multiple servers to avoid overloading any single server. This can be a complex process, especially in real-time systems where the load can change rapidly.
  • Security and Isolation: Ensuring data security and isolation is critical in real-time systems. Without proper measures, data breaches and unauthorized access can occur. Isolation between connections is vital to prevent interference.

Realtime Pub/Sub takes care of all these challenges for you, providing a reliable and scalable platform for real-time communication.

Administration in one place

Applications management and analytics is available through our administration console: https://admin.r7.21no.de:

Centralized Management

Building beyond our console?

Through our REST APIs, you have the ability to create custom monitoring solutions tailored to your specific needs. Whether you need to monitor usage, or track volume and costs, our APIs provide the foundation for you to build the solution you require.

Read more: API Docs and Accessing Application Telemetry via REST

Deploy on your infrastructure

If you are interested in hosting and managing the Realtime Pub/Sub on your own infrastructure, please contact us at realtime.contact@21no.de

Examples

Connecting to your apps (WebSockets)

JavaScript WebSocket API

The following example demonstrates how to connect to applications using the WebSocket API:

const APP_ID = 'YOUR APP ID HERE'
const ACCESS_TOKEN = 'VALID JWT TOKEN HERE' // verifiable via "app/configuration/authKey"

const ws = new WebSocket(`wss://genesis.r7.21no.de/apps/${APP_ID}?access_token=${ACCESS_TOKEN}`)
ws.onopen = () => {
  console.log('Client connected!')
}
ws.onmessage = (event) => {
  // Parse incoming WebSocket messages
  const { topic, messageType, data } = JSON.parse(event.data)

  if (topic === 'main' && messageType === 'welcome') {
    // SESSION IS NOW READY, YOU CAN SUBSCRIBE TO APP TOPICS...

    ws.send(JSON.stringify({
      type: 'subscribe',
      data: {
        topic: 'dashboard1' // example topic name
      }
    }))
  }
}

WebSocket API support by web browsers and JavaScript engines: WebSocket API Compatibility

Kotlin (Ktor WebSockets)

import com.google.gson.Gson
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.runBlocking

data class WebSocketClientConfig(
  val url: String, val onMessage: suspend (msg: IncomingMessage, send: suspend (s: Subscription) -> Unit) -> Unit
)

data class IncomingMessage(
  val topic: String, val messageType: String, val data: Any
)

enum class SubscriptionType(val type: String) {
  SUBSCRIBE("subscribe"), UNSUBSCRIBE("unsubscribe")
}

class Subscription {
  private data class Data(val topic: String, val auth: String? = null)

  private val type: String
  private val data: Data

  constructor(subscriptionType: SubscriptionType, topic: String, auth: String? = null) {
    this.type = subscriptionType.type
    this.data = Data(topic, auth)
  }
}

class WebSocketClient {
  private val json = Gson()
  private val client: HttpClient
  private val config: WebSocketClientConfig

  constructor(config: WebSocketClientConfig) {
    this.config = config

    client = HttpClient(OkHttp) {
      install(WebSockets) {}
    }
  }

  fun connect() {
    runBlocking {
      client.webSocket(config.url) {
        while (true) {
          val raw = incoming.receive() as? Frame.Text
          val msg = json.fromJson(raw?.readText(), IncomingMessage::class.java)

          config.onMessage(msg) {
            send(json.toJson(it))
          }
        }
      }
    }
  }

  fun close() {
    client.close()
  }
}
fun main() {
  val appId = "YOUR APP ID HERE"
  val accessToken = "VALID JWT TOKEN HERE" // verifiable via "app/configuration/authKey"
  val url = "wss://genesis.r7.21no.de/apps/${appId}?access_token=${accessToken}"
  val ws = WebSocketClient(WebSocketClientConfig(url) { msg, send ->
    run {
      println(msg)

      if (msg.topic == "main" && msg.messageType == "welcome") {
        println("> subscribing...")
        runBlocking {
          send(Subscription(SubscriptionType.SUBSCRIBE, "dashboard1"))
        }
      }
    }
  })

  ws.connect()
}

Publishing messages in your apps topics (REST)

The following example demonstrates how to publish messages into an application topic using the Node.js and Kotlin. The example includes the issuing of JWT authentication tokens using RS256 signing algorithm:

JavaScript

const fs = require('fs')

publish(
  {
    serverUrl: 'genesis.r7.21no.de',
    appId: '00000d863vb5baa987e8768ed3drrr09',
    // private key of "adminKey.key" (public.pem)
    adminSigningKey: fs.readFileSync('./private.pem')
  },
  'main',
  'Hello from JavaScript!'
).then(() => console.log('Message published!')).catch(err => console.error(err))
const REQUIRED_PUBLISHER_ROLE = 'Publisher'
const axios = require('axios').default
const jwt = require('jsonwebtoken')

function publish (config, topic, message) {
  console.info(`Publishing message into ${config.appId}/${topic} topic...`)

  const AUTH_TOKEN = getAuthToken(config.adminSigningKey, 5)
  const options = {
    method: 'POST',
    url: `https://${config.serverUrl}/api/topics/${config.appId}/publish`,
    headers: { 'Content-Type': 'application/json', Authorization: AUTH_TOKEN },
    data: { topic, message, compress: false }
  }

  return axios.request(options)
}

function getAuthToken (secret, expiresIn) {
  return 'Bearer ' + jwt.sign({ roles: [REQUIRED_PUBLISHER_ROLE], allowedTopics: ['*'] }, secret, {
    algorithm: 'RS256',
    expiresIn,
    issuer: 'https://signer.example.com',
    subject: 'service-name'
  })
}

Kotlin (Ktor HTTP Client)

fun main() {
  java.security.Security.addProvider(
    org.bouncycastle.jce.provider.BouncyCastleProvider()
  )

  val appId = "00000d863vb5baa987e8768ed3drrr09"

  val publisher = Publisher(
    PublisherConfig(
      appId = appId,
      serverUrl = "genesis.r7.21no.de",
      adminSigningKey = File(ClassLoader.getSystemResource("rsa256-private.pem").file).readText(Charsets.UTF_8)
    )
  )

  runBlocking {
    val response = publisher.publish("main", "Hello from Kotlin!")
    if (response.status.isSuccess()) {
      println("Message published!")
    }
  }
}
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.google.gson.Gson
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import org.bouncycastle.util.io.pem.PemReader
import java.io.StringReader
import java.security.KeyFactory
import java.security.interfaces.RSAPrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.time.Instant
import java.util.*

data class PublisherConfig(val appId: String, val serverUrl: String, val adminSigningKey: String)

class Publisher(private val config: PublisherConfig) {
  private val json = Gson()
  private val client: HttpClient = HttpClient(OkHttp) {}

  suspend fun publish(topic: String, message: Any, compress: Boolean = false): HttpResponse {
    println("Publishing message into ${config.appId}/${topic} topic...")
    val authToken = getAuthToken(5)

    return client.request("https://${config.serverUrl}/api/topics/${config.appId}/publish") {
      method = HttpMethod.Post
      headers {
        append(HttpHeaders.ContentType, "application/json")
        append(HttpHeaders.Authorization, "Bearer $authToken")
      }
      setBody(json.toJson(mapOf("topic" to "main", "message" to message, "compress" to compress)))
    }
  }

  fun getAuthToken(expiresIn: Long): String {
    val pemReader = PemReader(StringReader(config.adminSigningKey))
    val spec = PKCS8EncodedKeySpec(pemReader.readPemObject().content)
    val privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec)
    val algorithm: Algorithm = Algorithm.RSA256(privateKey as RSAPrivateKey)

    return JWT.create()
      .withExpiresAt(Instant.from(Date().toInstant().plusSeconds(expiresIn)))
      .withPayload(mapOf("roles" to listOf("Publisher"), "allowedTopics" to listOf("*")))
      .withIssuer("https://signer.example.com")
      .withSubject("service-name")
      .sign(algorithm)
  }
}

You can easily create RSA keys for JWT RS256 signing and verification using https://jwt-keys.21no.de:

curl "https://jwt-keys.21no.de/api/generate/RS256?bits=2048" | jq '.'

Community

Follow and engage in community discussions at: https://github.com/BackendStack21/realtime-forum

If you'd like to help us reach our next milestone sooner, consider making a donation to support this project through PayPal: https://www.paypal.me/kyberneees. Your contribution is greatly appreciated!