Skip to Content
DocumentationBackend SDKAdaptersOverview

Adapters

An adapter translates between a runtime and the core. It exposes three things — POST /enroll, POST /enroll/status, and the WS /ws session socket — and it never parses a protocol frame. The core drives the entire handshake, acks, resync and command dispatch.

Every packaged adapter ships with a fake-node run over its own transport, so a phone pairs against any of them identically. None of them required a change to @luno-oss/core.

The contract

Whichever runtime you are on, an integration is these four calls. Everything else on these pages is the glue a particular framework needs to make them.

  1. Route POST /enroll and POST /enroll/status to luno.http.handle
  2. Authorise the WS /ws upgrade with luno.connections.authorizebefore upgrading
  3. Open a session with luno.connections.open(device, sink) and pump frames into session.receive
  4. Call session.close() when the socket ends

Pick your runtime

Not every platform can hold a socket

This is the one thing worth understanding before you choose. The protocol is built around a long-lived, bidirectional connection. Several popular platforms are request-scoped and cannot hold one — a property of those platforms, not of Luno.

RuntimeEnrolmentSession socketPackage
Hono (Node, Deno, Bun)YesYes@luno-oss/hono
ExpressYesYes@luno-oss/express
FastifyYesYes@luno-oss/fastify
NestJSYesYes@luno-oss/nestjs
Cloudflare WorkersYesYes — Durable Objects@luno-oss/cloudflare
Next.js, self-hostedYesYes — custom servernone needed
Next.js on VercelYesNonone needed
Firebase Cloud RunYesYes@luno-oss/express
Firebase FunctionsYesNonone needed
Deno Deploy / Supabase EdgeYesPartly — ephemeral instances@luno-oss/hono

Where the socket column says no, enrolment still works and your application code is unchanged; the socket needs a companion process that can stay alive. See Next.js and Firebase for the concrete shapes.

The HTTP fallback transport is designed, not built. v1 implements sockets only. FrameSink is defined so a long-poll or SSE buffer satisfies it without change, and the additive versioning rules mean the fallback can land later without a redesign — but today, a socketless platform needs a companion service.

Authentication at the boundary

An unknown device credential fails the WS /ws upgrade with an HTTP 401. The authorisation runs before the socket is upgraded, deliberately: the node treats a 401 as “re-enrolment required” and pauses, whereas a post-upgrade close looks like a transient failure and it would reconnect through it forever.

If you write your own integration, authorise before upgrading. Accepting the socket and closing it on bad credentials turns every revoked device into a reconnect loop against your server.

Operator and API auth for your own dashboard routes is your application’s concern — adapters only handle the node credential. See the two axes.

Why the duplication is deliberate

The socket bridge — authorise before upgrade, then hand off to connections.open — is roughly the same thirty-five lines in each adapter. That repetition is a choice: each adapter stays self-contained and copy-pasteable, rather than introducing a shared dependency between them that would have to absorb every runtime’s quirks.