npm.io
0.4.17 • Published 1h ago

@arbidocs/client

Licence
MIT
Version
0.4.17
Deps
0
Vulns
0
Weekly
0

@arbidocs/client

TypeScript client for the ARBI API — zero-knowledge auth, E2E encryption, and type-safe REST client.

Install

npm install @arbidocs/client

For Node.js environments (no browser), also install the IndexedDB polyfill:

npm install fake-indexeddb

Quick Start

import { createArbiClient } from '@arbidocs/client'

const arbi = createArbiClient({
  baseUrl: 'https://www.arbidocs.com',
  deploymentDomain: 'www.arbidocs.com',
})

// Login
const session = await arbi.auth.login({
  email: 'user@example.com',
  password: 'mypassword',
})

// Type-safe API calls — auto-injects Bearer token
const { data: workspaces } = await arbi.fetch.GET('/api/user/workspaces')
console.log(workspaces) // WorkspaceResponse[]

Examples

Register + Login (Node.js)
// Polyfill IndexedDB for Node.js (must be first import)
require('fake-indexeddb/auto')
const { createArbiClient } = require('@arbidocs/client')

async function main() {
  const arbi = createArbiClient({
    baseUrl: 'https://www.arbidocs.com',
    deploymentDomain: 'www.arbidocs.com',
    credentials: 'omit', // No cookies in Node.js
  })

  // Register a new user
  const { signingPrivateKey } = await arbi.auth.register({
    email: 'alice@example.com',
    password: 'SecurePass123!',
    verificationCode: '...', // From /api/user/verify-email flow
    firstName: 'Alice',
    lastName: 'Smith',
  })

  // Login
  const session = await arbi.auth.login({
    email: 'alice@example.com',
    password: 'SecurePass123!',
  })

  console.log('Logged in as', session.userExtId)
  console.log('Token:', session.accessToken.slice(0, 20) + '...')
}

main()
Create a Workspace and Upload a Document
require('fake-indexeddb/auto')
const fs = require('fs')
const {
  createArbiClient,
  getSession,
  sealedBoxDecrypt,
  deriveEncryptionKeypairFromSigning,
  createWorkspaceKeyHeader,
} = require('@arbidocs/client')

async function main() {
  const arbi = createArbiClient({
    baseUrl: 'https://www.arbidocs.com',
    deploymentDomain: 'www.arbidocs.com',
    credentials: 'omit',
  })

  // Login
  const { accessToken, serverSessionKey } = await arbi.auth.login({
    email: 'alice@example.com',
    password: 'SecurePass123!',
  })

  // Create workspace
  const { data: workspace } = await arbi.fetch.POST('/api/workspace/create_protected', {
    body: {
      name: 'My Case Files',
      description: 'Documents for Case #1234',
      is_public: false,
    },
  })

  // Decrypt the workspace key and generate the auth header.
  // The server wraps the workspace symmetric key with your public key
  // during creation. You unwrap it here to prove you own the workspace.
  const session = await getSession()
  const pubKey = session.signingPrivateKey.slice(32, 64)
  const encKP = deriveEncryptionKeypairFromSigning({
    publicKey: pubKey,
    secretKey: session.signingPrivateKey,
  })
  const workspaceKey = sealedBoxDecrypt(workspace.wrapped_key, encKP.secretKey)
  const wsHeader = await createWorkspaceKeyHeader(workspaceKey, serverSessionKey)

  // Tell the SDK which workspace is active (middleware auto-injects the header)
  arbi.session.setSelectedWorkspace(workspace.external_id)
  arbi.session.setCachedWorkspaceHeader(workspace.external_id, wsHeader)

  // Upload a PDF (multipart — use raw fetch since openapi-fetch
  // doesn't handle multipart/form-data)
  const formData = new FormData()
  const pdf = fs.readFileSync('./contract.pdf')
  formData.append('files', new Blob([pdf], { type: 'application/pdf' }), 'contract.pdf')

  const uploadRes = await fetch(
    `https://www.arbidocs.com/api/document/upload?workspace_ext_id=${workspace.external_id}`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'workspace-key': wsHeader,
      },
      body: formData,
    }
  )

  const { doc_ext_ids } = await uploadRes.json()
  console.log('Uploaded document:', doc_ext_ids[0])

  // List documents (workspace-key header injected automatically by middleware)
  const { data: docs } = await arbi.fetch.GET(
    '/api/workspace/{workspace_ext_id}/documents',
    { params: { path: { workspace_ext_id: workspace.external_id } } }
  )

  for (const doc of docs) {
    console.log(`  ${doc.file_name} — ${doc.status}`)
  }
}

main()
Subscribe to Session State Changes

The SDK's SessionManager supports a subscribe pattern for reactive updates:

const arbi = createArbiClient({ baseUrl, deploymentDomain })

// React to session changes (useful for custom UI frameworks)
const unsubscribe = arbi.session.subscribe((state) => {
  console.log('Session changed:', {
    loggedIn: !!state.accessToken,
    email: state.userEmail,
    workspace: state.selectedWorkspaceId,
  })
})

// Later: stop listening
unsubscribe()

API Reference

createArbiClient(options)

Creates an SDK client instance.

interface ArbiClientOptions {
  baseUrl: string              // ARBI API URL (e.g. "https://www.arbidocs.com")
  deploymentDomain: string     // Used for deterministic key derivation
  credentials?: RequestCredentials  // Default: 'include'. Use 'omit' for Node.js
  ssoTokenProvider?: { getToken(): Promise<string | null> } | null
  onReloginSuccess?: (data) => void
}

Returns an ArbiClient with these namespaces:

Namespace Description
arbi.fetch Type-safe openapi-fetch client with auto-auth middleware
arbi.session In-memory session state (token, user, workspace)
arbi.auth High-level auth: register, login, loginWithKey, logout, changePassword, relogin
arbi.crypto Crypto utilities: key derivation, signing, encryption, base64
arbi.storage IndexedDB session persistence: getSession, saveSession
Auth Methods
// Register
await arbi.auth.register({ email, password, verificationCode, firstName?, lastName? })

// Login with password
await arbi.auth.login({ email, password })

// Login with recovery key (Uint8Array)
await arbi.auth.loginWithKey({ email, signingPrivateKey })

// Change password
await arbi.auth.changePassword({ email, currentPassword, newPassword })

// Logout (clears session + IndexedDB)
await arbi.auth.logout()

// Re-login using stored private key (auto-called by middleware on 401)
await arbi.auth.relogin()
Middleware (Auto-Applied)

The arbi.fetch client has three middleware layers applied automatically:

  1. Bearer Auth — injects Authorization: Bearer <token> on every request
  2. Workspace Key — injects workspace-key header for workspace-scoped endpoints
  3. Auto-Relogin — on 401, re-derives credentials from stored key and retries
Standalone Exports

All building blocks are individually importable for advanced use:

import {
  // Crypto primitives
  initSodium, signMessage, sealedBoxDecrypt, sealedBoxEncrypt,
  deriveEncryptionKeypairFromSigning, createWorkspaceKeyHeader,
  generateUserKeypairs, generateLoginCredentials,
  encryptMessage, decryptMessage,
  base64Encode, base64Decode, base64ToBytes, bytesToBase64,

  // Storage
  getSession, saveSession, clearAllData, hasSession,

  // Middleware (compose your own client)
  createBearerAuthMiddleware, createWorkspaceKeyMiddleware, createAutoReloginMiddleware,

  // Relogin handler
  createReloginHandler,

  // Session manager
  createSessionManager,

  // OpenAPI schema types
  type paths, type components, type operations,
} from '@arbidocs/client'

How It Works

Zero-Knowledge Auth

ARBI uses Ed25519 signatures for authentication. Your password never leaves the client:

  1. Key derivation: Argon2id(email + password + deploymentDomain) → Ed25519 seed → signing keypair
  2. Registration: Public key sent to server, private key stays local
  3. Login: Client signs a timestamp, server verifies the signature
  4. Session: Server returns an encrypted session key for workspace operations
Workspace Encryption

Each workspace has a symmetric key (NaCl SecretBox). The key is:

  • Generated server-side on workspace creation
  • Wrapped with your X25519 public key (derived from your Ed25519 signing key)
  • Unwrapped client-side using sealedBoxDecrypt
  • Used to generate per-request workspace-key headers (HMAC proof of key possession)
IndexedDB Storage

The SDK stores the signing private key and session key in IndexedDB, encrypted with a non-extractable AES-GCM key from the Web Crypto API. In Node.js, use fake-indexeddb as a polyfill.

Requirements

  • Browser: Any modern browser (Chrome, Firefox, Safari, Edge)
  • Node.js: v18+ (needs fetch, FormData, Blob globals). Install fake-indexeddb for IndexedDB.
  • TypeScript: 5.0+ (optional — works with plain JS too)

Keywords