SDK Reference

@encra/react

React hooks for end-to-end encryption. Three hooks cover the most common use cases: real-time chat, file transfer, and form submission. All share the same cryptographic identity — one key pair per user, stored in IndexedDB.

Installation

bash
npm install @encra/react
@encra/react requires react ≥ 18 as a peer dependency.@encra/core is bundled automatically.

useE2EChat()

Real-time end-to-end encrypted messaging over WebSocket. Handles key generation, server registration, connection management, Double Ratchet encryption, and automatic reconnection with exponential backoff.

Chat.tsxtsx
import { useE2EChat } from '@encra/react'

function Chat({ userId, recipientId }) {
  const {
    messages,
    isReady,
    isConnecting,
    sendMessage,
    error,
  } = useE2EChat({
    apiKey: process.env.NEXT_PUBLIC_ENCRA_API_KEY!,
    userId,
  })

  return (
    <div>
      {isConnecting && <p>Connecting…</p>}
      {error && <p>Error: {error.message}</p>}

      {messages.map((msg, i) => (
        <div key={i} className={msg.from === userId ? 'mine' : 'theirs'}>
          <strong>{msg.from}</strong>: {msg.text}
        </div>
      ))}

      <button
        disabled={!isReady}
        onClick={() => sendMessage(recipientId, 'Hello!')}
      >
        Send
      </button>
    </div>
  )
}

Options

PropTypeDefaultDescription
apiKey*stringYour Encra API key from the dashboard. Prefix with NEXT_PUBLIC_ in browser apps.
userId*stringUnique identifier for the current user. Used as the key registration ID.
serverUrlstringEncra serverKey server URL. Defaults to the Encra managed server. Override for self-hosting.
onError(err: Error) => voidCalled on recoverable errors such as decryption failures or WebSocket errors.

Return values

PropTypeDefaultDescription
messagesMessage[]Array of decrypted messages (sent + received), newest last. Capped at 200.
isReadybooleanTrue once the key pair is registered and the WebSocket is connected.
isConnectingbooleanTrue while connecting or reconnecting after a disconnect.
sendMessage(to: string, text: string) => Promise<void>Encrypt and send a message to another userId. Throws if not connected.
errorError | nullSet if key registration fails. Recoverable errors are emitted via onError instead.
typescript
interface Message {
  from:      string  // sender's userId
  text:      string  // decrypted plaintext
  timestamp: number  // Date.now() at receipt
}

E2EChatProvider

Avoid passing apiKey and serverUrl to every hook by wrapping your app in the provider. Each useE2EChat call still requires a userId.

app/layout.tsxtsx
import { E2EChatProvider } from '@encra/react'

export default function Layout({ children }) {
  return (
    <E2EChatProvider apiKey={process.env.NEXT_PUBLIC_ENCRA_API_KEY!}>
      {children}
    </E2EChatProvider>
  )
}

useE2EFile()

Encrypt and decrypt File or Blob objects end-to-end. Uses the same X25519 key pair as useE2EChat — no extra setup needed. Files up to 50 MB are supported. The encrypted bytes can be uploaded anywhere (S3, your own server, IPFS) — only the recipient can decrypt them.

FileTransfer.tsxtsx
import { useE2EFile } from '@encra/react'

function FileTransfer({ userId }) {
  const { encryptFile, decryptFile, isReady, error } = useE2EFile({
    apiKey: process.env.NEXT_PUBLIC_ENCRA_API_KEY!,
    userId,
  })

  async function handleUpload(file: File, recipientId: string) {
    // Encrypt on the sender's device
    const encrypted = await encryptFile(file, recipientId)

    // encrypted.ciphertext — Uint8Array, upload this
    // encrypted.nonce      — Uint8Array, store alongside ciphertext
    // encrypted.name       — original filename
    // encrypted.mimeType   — original MIME type
    // encrypted.size       — original size in bytes

    await uploadToServer(encrypted)
  }

  async function handleDownload(encrypted, senderId: string) {
    // Decrypt on the recipient's device
    const file = await decryptFile(encrypted, senderId)
    const url  = URL.createObjectURL(file)
    // file.name and file.type are restored automatically
  }
}

Options

PropTypeDefaultDescription
apiKey*stringYour Encra API key.
userId*stringCurrent user's ID. Shares the same key pair as useE2EChat and useE2EForm.
serverUrlstringEncra serverKey server URL. Override for self-hosting.
onError(err: Error) => voidCalled on any initialisation error.

Return values

PropTypeDefaultDescription
encryptFile(file: File | Blob, to: string) => Promise<EncryptedFile>Encrypt a file for a recipient. Throws if the file exceeds 50 MB or the recipient's key cannot be fetched.
decryptFile(encrypted: EncryptedFile, from: string) => Promise<File>Decrypt an EncryptedFile. Returns a File with the original name and MIME type restored.
isReadybooleanTrue once the key pair is initialised and registered.
errorError | nullSet if key registration fails on mount.
typescript
interface EncryptedFile {
  ciphertext: Uint8Array  // XSalsa20-Poly1305 ciphertext
  nonce:      Uint8Array  // random 24-byte nonce
  name:       string      // original filename
  mimeType:   string      // original MIME type
  size:       number      // original size in bytes
}

// Maximum accepted file size
import { MAX_FILE_BYTES } from '@encra/react'
// MAX_FILE_BYTES === 50 * 1024 * 1024 (50 MB)

Metadata is plaintext

name, mimeType, and size are stored in plaintext so the recipient can display a preview before downloading. Only the file bytes are encrypted. If you need filename privacy, overwrite name before storing.

useE2EForm()

Encrypt individual form field values before submission. Each field is encrypted independently with a unique random nonce, using an X25519 shared secret derived from the sender and recipient's key pairs. Field names (keys) are sent in plaintext — only the values are encrypted.

Ideal for HIPAA forms, legal submissions, secure surveys, or any case where your server must store data but must not be able to read it.

MedicalForm.tsxtsx
import { useE2EForm } from '@encra/react'

function MedicalForm({ userId }) {
  const { encryptFields, decryptFields, isReady } = useE2EForm({
    apiKey: process.env.NEXT_PUBLIC_ENCRA_API_KEY!,
    userId,
  })

  async function handleSubmit(formData: FormData) {
    const payload = await encryptFields(
      {
        name:  formData.get('name') as string,
        ssn:   formData.get('ssn') as string,
        notes: formData.get('notes') as string,
      },
      'doctor-userId',  // recipient who can decrypt
    )

    // payload.name  = { ciphertext: '...', nonce: '...' }
    // payload.ssn   = { ciphertext: '...', nonce: '...' }
    // payload.notes = { ciphertext: '...', nonce: '...' }

    await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify(payload),
    })
  }

  // On the recipient's side:
  async function handleRead(payload, patientId: string) {
    const fields = await decryptFields(payload, patientId)
    console.log(fields.ssn)   // '123-45-6789'
    console.log(fields.notes) // 'Private notes'
  }
}

Options

PropTypeDefaultDescription
apiKey*stringYour Encra API key.
userId*stringCurrent user's ID. Shares the same key pair as useE2EChat and useE2EFile.
serverUrlstringEncra serverKey server URL. Override for self-hosting.
onError(err: Error) => voidCalled on any initialisation error.

Return values

PropTypeDefaultDescription
encryptFields(fields: Record<string, string>, to: string) => Promise<EncryptedFields>Encrypt a flat object of string values for a recipient. Each field gets a unique nonce.
decryptFields(encrypted: EncryptedFields, from: string) => Promise<Record<string, string>>Decrypt an EncryptedFields object. Returns the original key→plaintext mapping.
isReadybooleanTrue once the key pair is initialised and registered.
errorError | nullSet if key registration fails on mount.
typescript
// Each field is independently encrypted
type EncryptedFields = Record<string, {
  ciphertext: string  // URL-safe base64
  nonce:      string  // URL-safe base64
}>

💡 Field names are plaintext

Only field values are encrypted. If your field names are sensitive (e.g. "hiv_status"), hash them before passing toencryptFields.

TypeScript types

typescript
import type {
  // useE2EChat
  Message,
  WireEvent,
  UseE2EChatOptions,
  UseE2EChatResult,
  E2EChatConfig,

  // useE2EFile
  EncryptedFile,
  UseE2EFileOptions,
  UseE2EFileResult,

  // useE2EForm
  EncryptedFields,
  UseE2EFormOptions,
  UseE2EFormResult,
} from '@encra/react'

Examples

useE2EChat with NextAuth / Better Auth

tsx
import { useSession } from 'next-auth/react'
import { useE2EChat } from '@encra/react'

function Chat({ recipientId }) {
  const { data: session } = useSession()

  const { messages, isReady, sendMessage } = useE2EChat({
    apiKey: process.env.NEXT_PUBLIC_ENCRA_API_KEY!,
    userId: session?.user?.email ?? '',
  })
}

Send on Enter

tsx
const [text, setText] = useState('')

<input
  value={text}
  onChange={e => setText(e.target.value)}
  onKeyDown={async e => {
    if (e.key === 'Enter' && text.trim() && isReady) {
      await sendMessage(recipientId, text)
      setText('')
    }
  }}
/>

useE2EFile with S3 upload

tsx
import { useE2EFile } from '@encra/react'

function SecureUpload({ userId, recipientId }) {
  const { encryptFile, isReady } = useE2EFile({
    apiKey: process.env.NEXT_PUBLIC_ENCRA_API_KEY!,
    userId,
  })

  async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0]
    if (!file || !isReady) return

    const enc = await encryptFile(file, recipientId)

    // Serialize for upload — ciphertext and nonce are Uint8Arrays
    const form = new FormData()
    form.append('ciphertext', new Blob([enc.ciphertext]))
    form.append('nonce',      new Blob([enc.nonce]))
    form.append('meta',       JSON.stringify({
      name: enc.name, mimeType: enc.mimeType, size: enc.size,
    }))

    await fetch('/api/upload', { method: 'POST', body: form })
  }

  return <input type="file" onChange={handleChange} disabled={!isReady} />
}

One key pair per user

All three hooks — useE2EChat, useE2EFile, anduseE2EForm — share the same IndexedDB key pair for a given userId. A user has one cryptographic identity across all Encra hooks.

Encra AI

Ask me anything · docs, code, troubleshooting

Hi, I'm Encra AI

I can explain concepts, generate starter code, troubleshoot errors, and guide your setup.

May make mistakes · verify critical crypto details