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:
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
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" }],
},
})| Field | What it seeds |
|---|---|
apps | Channels apps: id, key, secret, cluster, and their webhooks. |
apps[].subscriptionCounting | Turns on subscription counting, so channel info carries subscription_count and the subscription_count webhook fires. Off by default, as on a new app. |
beams | Beams 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:
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)
})| Helper | What 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
| Area | Covered |
|---|---|
| Channels API | Events and batches, channel info, presence users, terminating a user's connections. |
| Socket | Public, private and presence channels, client events, user sign-in. |
| Subscription counting | Subscription counts in channel info and webhooks, when turned on. |
| Webhooks | Channels occupied and vacated, members added and removed, client events, counts. |
| Beams | Publishing to interests and users, and devices. |
Faults
| Call | Faults the matrix tries |
|---|---|
POST /apps/:id/events | 429, 500, connection reset, answer lost after Pusher published it |
| The socket | the upgrade refused, and the connection dropped for one person |
| Every webhook delivery | arrives 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
Unipile
Unipile's hosted auth links, connected LinkedIn, WhatsApp and mail accounts, chats, messages and email, with webhooks when the other side writes back.
Novu
Novu's server API, workflows run through your app's bridge or from seeded templates, the Inbox API and socket the browser uses, and emails sent through the Resend emulator.