Skip to content

feat: @profullstack/pairux-embed, E2EE calls for partner apps - #133

Merged
ralyodio merged 3 commits into
masterfrom
feat/pairux-embed
Oct 6, 2026
Merged

ralyodio merged 3 commits into
masterfrom
feat/pairux-embed

Conversation

@ralyodio

@ralyodio ralyodio commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

This adds @profullstack/pairux-embed, a framework-agnostic library any web app can drop in for voice, video and screen sharing on PairUX's SFU, with end-to-end encryption via LiveKit E2EE. It also adds the partner token endpoint that lets another app's server use it. The first partner is qrypt.chat.

The library (packages/embed)

PairuxCall

  • It sets the host-supplied media key and turns E2EE on before connecting, so no frame ever leaves unencrypted.
  • A browser that can't do insertable streams is refused (E2EEUnsupportedError) rather than downgraded to a plaintext call.
  • Methods: setMic, setCamera, setScreen, rotateKey, leave.
  • Events: participants (each participant carries an encrypted flag from LiveKit's encryption status), track, state, error.

mountCall(el, call)

  • Tiles and controls in plain DOM, no framework.
  • Themeable with CSS custom properties.
  • Shows 🔒 per participant and an "End-to-end encrypted" status once everyone is encrypted.

Other: newMediaKey() returns 32 random bytes.

The E2EE worker: it defaults to livekit-client/e2ee-worker, which Vite, webpack 5 and Next resolve. Apps can pass their own.

Partner tokens (POST /api/v1/partner/token)

  • Request: the partner's server sends Authorization: Bearer pux_pk_… with { room, identity, name }.
  • Response: a 6h LiveKit token, plus url, iceServers and e2ee: true.
  • Namespacing:
    • Rooms are always partner-<id>-<room>; the room id is restricted to [A-Za-z0-9_-], so it can't escape the namespace.
    • Identities are <id>:<user>.
  • Configuration: partners live in PAIRUX_PARTNERS, which holds only a SHA-256 of each key, compared in constant time. No database migration.
  • Media keys never come here. Partners share them between their own participants, so PairUX forwards ciphertext only.
  • No recording or analysis: egress, restreaming, recording and call analysis only act on session-* rooms, so partner rooms are never recorded or analysed.
  • Rate limits apply per IP and per partner.

Not in this PR

  • TURN credentials are still static. They're already public (NEXT_PUBLIC_*), so this endpoint exposes nothing new. Switching coturn to time-limited credentials (use-auth-secret) is a separate infra change.
  • Remote-control data packets aren't covered by media E2EE. The embed doesn't use remote control.
  • Publishing to npm and the qrypt.chat integration come next.

Gates

I ran these by hand, because the pre-commit hook OOMs on this box:

  • pnpm --filter @pairux/web typecheck
  • tsc for the embed package
  • eslint on the changed files (0 errors)
  • repo-wide pnpm format:check
  • vitest: embed 5/5 and partner token 5/5

🤖 Generated with Claude Code

A framework-agnostic library any web app can drop in for voice, video and
screen sharing on PairUX's SFU, end-to-end encrypted with LiveKit E2EE:

- packages/embed: PairuxCall (key set + E2EE on BEFORE connect; refuses a
  browser that cannot encrypt instead of downgrading to plaintext; rotateKey,
  mic/camera/screen, participants with per-participant encryption status)
  and mountCall, a plain-DOM tile grid + controls, themeable via CSS vars.
- POST /api/v1/partner/token: a partner's server (Bearer pux_pk_…) mints a
  6h LiveKit token for one of its users in its own namespace
  (partner-<id>-<room>, identity <id>:<user>). Partners are configured in
  PAIRUX_PARTNERS with only a SHA-256 of each key; constant-time compare.
  The media key never reaches PairUX. Recording, restreaming and call
  analysis only act on session-* rooms, so partner rooms are never touched.

First partner: qrypt.chat, which will share the media key ML-KEM-1024
encrypted to each participant.

Gates run by hand (the pre-commit hook OOMs on this box): embed + web
typecheck, eslint on changed files (0 errors), repo-wide format:check,
vitest (embed 5, partner token 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

47 finding(s)

HIGH/CRITICAL: 4 | MEDIUM: 27 | LOW: 16

Severity Rule Location
HIGH sh-eval-expansion .githooks/pre-commit:33
HIGH js-electron-node-integration apps/desktop/src/main/window.ts:49
HIGH sh-unquoted-expansion-destructive apps/installer/scripts/install.sh:715
HIGH sh-unquoted-expansion-destructive apps/installer/scripts/install.sh:917
MEDIUM sh-remote-script-execution apps/installer/scripts/install.sh:691
MEDIUM sh-remote-script-execution apps/installer/scripts/install.sh:820
MEDIUM sh-remote-script-execution apps/installer/scripts/install.sh:822
MEDIUM sh-remote-script-execution apps/installer/scripts/install.sh:1078
MEDIUM sh-remote-script-execution apps/installer/scripts/install.sh:1080
MEDIUM sh-remote-script-execution apps/livekit/setup-livekit-server.sh:93
MEDIUM sh-remote-script-execution apps/turn/deploy-droplet.sh:62
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:48
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:73
MEDIUM js-unescaped-html-sink apps/web/src/app/c/[handle]/page.tsx:196
MEDIUM js-open-redirect apps/web/src/app/cli/authorize/consent.tsx:66
MEDIUM js-unescaped-html-sink apps/web/src/app/l/[joinCode]/page.tsx:129
MEDIUM js-unescaped-html-sink apps/web/src/app/l/[joinCode]/page.tsx:213
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:141
MEDIUM js-unescaped-html-sink apps/web/src/app/live/page.tsx:145
MEDIUM js-unescaped-html-sink apps/web/src/app/page.tsx:123
MEDIUM js-unescaped-html-sink apps/web/src/app/pricing/page.tsx:286
MEDIUM js-open-redirect apps/web/src/app/pricing/UpgradeButton.tsx:50
MEDIUM js-unescaped-html-sink apps/web/src/app/u/[username]/page.tsx:282
MEDIUM js-open-redirect apps/web/src/hooks/useDesktopHandoff.ts:24
MEDIUM redos-nested-quantifier apps/web/src/lib/deliverable.ts:11
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:124
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:393
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:396
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:405
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:608
MEDIUM sql-template-interpolation packages/ai-core/src/prompts.ts:36
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:138
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:139
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:340
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:341
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:371
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:372
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:383
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:397
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:398
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:405
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:415
LOW secret-generic-credential apps/desktop/src/main/agent-cli/commands.test.ts:416
LOW secret-generic-credential apps/livekit/fly.toml:12
LOW secret-generic-credential apps/turn/fly.toml:11
LOW secret-generic-credential docs/API.md:747
LOW secret-generic-credential docs/API.md:753

Snippets are redacted; ThreatCrush never prints matched credential material.

ralyodio and others added 2 commits October 6, 2026 08:45
…gitleaks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 0961832 into master Oct 6, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant