@arbidocs/client
v0.3.19
Published
TypeScript SDK for the ARBI API — zero-knowledge auth, E2E encryption, and type-safe REST client
Downloads
1,265
Maintainers
Readme
@arbidocs/client
TypeScript client for the ARBI API — zero-knowledge auth, E2E encryption, and type-safe REST client.
Install
npm install @arbidocs/clientFor Node.js environments (no browser), also install the IndexedDB polyfill:
npm install fake-indexeddbQuick 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: '[email protected]',
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: '[email protected]',
password: 'SecurePass123!',
verificationCode: '...', // From /api/user/verify-email flow
firstName: 'Alice',
lastName: 'Smith',
})
// Login
const session = await arbi.auth.login({
email: '[email protected]',
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: '[email protected]',
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:
- Bearer Auth — injects
Authorization: Bearer <token>on every request - Workspace Key — injects
workspace-keyheader for workspace-scoped endpoints - 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:
- Key derivation:
Argon2id(email + password + deploymentDomain)→ Ed25519 seed → signing keypair - Registration: Public key sent to server, private key stays local
- Login: Client signs a timestamp, server verifies the signature
- 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-keyheaders (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,Blobglobals). Installfake-indexeddbfor IndexedDB. - TypeScript: 5.0+ (optional — works with plain JS too)
