dánwòdánwò

Realtime

Pusher

Pusher Channels, the API your server triggers events on and the socket pusher-js keeps open, with private and presence channels, subscription counting, signed webhooks and Beams.

danwo's Pusher emulator answers the Channels API, the socket pusher-js connects to, and Beams. Your server's pusher package and the browser's pusher-js work against it unchanged. An event your server triggers reaches the page danwo drives.

Set up

Add pusherService. With no seed it serves one app, PUSHER_DEFAULTS.app; hand its credentials to your app:

danwo.config.ts
import { appEnvironment, defineConfig, PUSHER_DEFAULTS, pusherService } from "@danwo/sdk"

const { app } = PUSHER_DEFAULTS

export default defineConfig({
  testDir: "tests",
  readiness: { mode: "page+backend", settle: true },
  environment: () =>
    appEnvironment({
      app: {
        command: "npm run dev",
        url: "http://localhost:3000",
        env: {
          PUSHER_APP_ID: app.id,
          PUSHER_KEY: app.key,
          PUSHER_SECRET: app.secret,
          PUSHER_CLUSTER: app.cluster,
          NEXT_PUBLIC_PUSHER_KEY: app.key,
          NEXT_PUBLIC_PUSHER_CLUSTER: app.cluster,
        },
      },
      services: [pusherService()],
    }),
})

Seed it

danwo.config.ts · the seed
pusherService({
  seed: {
    apps: [
      {
        id: "1000001",
        key: "my-key",
        secret: "my-secret",
        cluster: "eu",
        subscriptionCounting: true,
        webhooks: [
          { url: "http://localhost:3000/api/pusher", events: ["channel_occupied", "member_added"] },
        ],
      },
    ],
    beams: [{ instanceId: "my-instance", secretKey: "my-beams-secret" }],
  },
})
FieldWhat it seeds
appsChannels apps: id, key, secret, cluster, and their webhooks.
apps[].subscriptionCountingTurns on subscription counting, so channel info carries subscription_count and the subscription_count webhook fires. Off by default, as on a new app.
beamsBeams instances and their secret keys.

Use it in a test

Place an order, then check the page heard of it live, and that the server published it once:

tests/live-orders.danwo.ts
import { type PusherFixtures, pusherChannels, test as base } from "@danwo/sdk"

const test = base.extend<PusherFixtures>()

test("shows a new order without a reload", async ({ app, screen, expect, pusher }) => {
  await app.open("/orders")
  await screen.getByRole("button", { name: "Place order" }).click()

  await expect(screen.getByRole("status")).toHaveText("Order o_1 placed")

  const event = await pusherChannels(pusher).waitFor("orders", "order.placed")

  expect(event.delivered_to).toHaveLength(1)
  expect(pusherChannels(pusher).events({ channel: "orders" })).toHaveLength(1)
})
HelperWhat it's for
pusherChannels(pusher).events(filter)Every event, and which sockets it was delivered to.
pusherChannels(pusher).waitFor(channel, name)Wait for one event.
pusherChannels(pusher).connections()The live sockets.
pusherChannels(pusher).trigger(…)Publish from the test.
pusherBeams(pusher)Beams publishes and registered devices.

What's covered

AreaCovered
Channels APIEvents and batches, channel info, presence users, terminating a user's connections.
SocketPublic, private and presence channels, client events, user sign-in.
Subscription countingSubscription counts in channel info and webhooks, when turned on.
WebhooksChannels occupied and vacated, members added and removed, client events, counts.
BeamsPublishing to interests and users, and devices.

Faults

CallFaults the matrix tries
POST /apps/:id/events429, 500, connection reset, answer lost after Pusher published it
The socketthe upgrade refused, and the connection dropped for one person
Every webhook deliveryarrives twice, arrives 10 minutes late, forged signature, 1 hour stale

A dropped connection is the test that matters most: does the page reconnect, and does it catch up on what it missed?

Webhooks

Webhooks carry X-Pusher-Key and X-Pusher-Signature, an HMAC of the body under the app's secret, so pusher.webhook(request).isValid() accepts them.

Fidelity

Live. danwo fidelity pusher runs 5 scenarios against a real app. 4 match; the subscription-counting scenario with counting on needs the feature enabled on the real app first.

Not covered yet

  • Encrypted channels (private-encrypted-…)
  • Cache channels
  • Watchlist events

Next

On this page

Run it on every pull request

A GitHub check and one comment per pull request, with the flight records attached.

Set up CI