Skip to content

GoPlausible/liquid-auth-js

Liquid Auth Client (TypeScript)

@goplausible/liquid-client

TypeScript browser client library for Liquid Auth — passwordless FIDO2/WebAuthn authentication with Algorand wallet binding and WebRTC peer-to-peer signaling.

Improved by GoPlausible, originated by the Algorand Foundation's Liquid Auth Client reference design. Redesigned from the ground up with a lighter footprint: native WebSocket instead of Socket.io, zero transport dependencies, and a simple { event, data } JSON protocol that works directly with Cloudflare Durable Objects.

Related Projects

Project Package / App Description
Liquid Auth Cloud @goplausible/liquid-auth-cloud Cloudflare Workers auth server — edge-deployed, serverless, zero idle cost
Liquid Auth Android Android wallet app FIDO2 + WebRTC with native OkHttp WebSocket
Rocca Wallet React Native / Expo app Cross-platform wallet — FIDO2 passkeys + WebRTC, links to @goplausible/liquid-client via file: dependency
WebRTC Payment SDK @goplausible/webrtc-payment-sdk Used in — micropayment-gated WebRTC streams on Algorand

What Makes This Different

  • No Socket.io — uses the browser's native WebSocket API directly, eliminating engine.io transport negotiation and ~200KB of dependencies
  • Cloud-native protocol{ event, data } JSON envelope designed for Cloudflare Durable Objects (WalletRoom Hibernation API)
  • Room routing via URLSignalClient connects to /ws?requestId=xxx, so both peers land in the same Durable Object instance without any room-join handshake
  • Lighter build — ships as pure ESM, tree-shakable, with no runtime dependencies beyond eventemitter3, qr-code-styling, and uuid
  • React Native compatible — QR code styling uses dynamic import (lazy loading) so the library works in React Native environments where qr-code-styling is not available
  • Pure base32 address encoding — Algorand address codec with no algokit-utils dependency

Subpath Exports

The package exposes granular subpath exports for tree-shaking:

Subpath Purpose
@goplausible/liquid-client Main entry — SignalClient, encoding, etc.
@goplausible/liquid-client/signal SignalClient only
@goplausible/liquid-client/encoding Base64URL / Base32 / address utilities
@goplausible/liquid-client/assertion FIDO2 assertion (sign-in) helpers
@goplausible/liquid-client/assertion/encoder Assertion response encoding
@goplausible/liquid-client/attestation FIDO2 attestation (registration) helpers

Install

npm install @goplausible/liquid-client --save

Usage

import { SignalClient, encoding, attestation, assertion } from "@goplausible/liquid-client";
const client = new SignalClient("https://liquidauth.goplausible.xyz");

Create a new account and passkey

const testAccount = algosdk.generateAccount();
await client.attestation(
  async (challenge: Uint8Array) => ({
    type: "algorand",
    address: testAccount.addr,
    signature: encoding.toBase64URL(nacl.sign.detached(challenge, testAccount.sk)),
    requestId: "019097ff-bb8d-7f68-9062-89543625aca5",
    device: "Demo Web Wallet",
  }),
);

Sign in with an existing passkey

await client.assertion(credentialId);

Offer side (Dapp / Desktop)

const requestId = SignalClient.generateRequestId();
client.peer(requestId, "offer").then((dataChannel: RTCDataChannel) => {
  dataChannel.onmessage = (event) => console.log(event.data);
});
const qrBlob = await client.qrCode();

Answer side (Wallet / Mobile)

client.peer(requestId, "answer").then((dataChannel: RTCDataChannel) => {
  dataChannel.onmessage = (event) => console.log(event.data);
});

WebSocket Protocol

All messages use a JSON envelope:

{ event: string, data: any }
Event Description
link Register interest in a requestId / auth completed notification
offer-description WebRTC SDP offer
offer-candidate WebRTC ICE candidate (offer side)
answer-description WebRTC SDP answer
answer-candidate WebRTC ICE candidate (answer side)

Interface

class SignalClient extends EventEmitter {
  readonly url: string;
  ws: WebSocket | null;
  type: "offer" | "answer" | null;
  authenticated: boolean;
  requestId?: string;
  peerClient?: RTCPeerConnection;

  connect(requestId?: string): void;
  attestation(onChallenge, options?, debug?): Promise<User>;
  assertion(credId, debug?): Promise<User | null>;
  peer(requestId, type, config?): Promise<RTCDataChannel>;
  link(requestId): Promise<LinkMessage>;
  signal(type): Promise<RTCSessionDescriptionInit>;
  qrCode(): Promise<string>;
  deepLink(requestId?): string;
  close(disconnect?): void;
}

Development

# From the monorepo root
npm run build:vendor          # Build the library with Vite
npm run lint:client           # Lint with oxlint
npm run fmt:client            # Format with oxfmt

License

MIT

About

Liquid Auth JS client — native WebSocket, Algorand FIDO2 wallet

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors