@arbidocs/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Arbitration City
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,351 @@
1
+ # @arbidocs/sdk
2
+
3
+ TypeScript SDK for the ARBI API — zero-knowledge auth, E2E encryption, and type-safe REST client.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @arbidocs/sdk
9
+ ```
10
+
11
+ For Node.js environments (no browser), also install the IndexedDB polyfill:
12
+
13
+ ```bash
14
+ npm install fake-indexeddb
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import { createArbiClient } from '@arbidocs/sdk'
21
+
22
+ const arbi = createArbiClient({
23
+ baseUrl: 'https://box-201.arbibox.com',
24
+ deploymentDomain: 'box-201.arbibox.com',
25
+ })
26
+
27
+ // Login
28
+ const session = await arbi.auth.login({
29
+ email: 'user@example.com',
30
+ password: 'mypassword',
31
+ })
32
+
33
+ // Type-safe API calls — auto-injects Bearer token
34
+ const { data: workspaces } = await arbi.fetch.GET('/api/user/workspaces')
35
+ console.log(workspaces) // WorkspaceResponse[]
36
+ ```
37
+
38
+ ## Examples
39
+
40
+ ### Register + Login (Node.js)
41
+
42
+ ```javascript
43
+ // Polyfill IndexedDB for Node.js (must be first import)
44
+ require('fake-indexeddb/auto')
45
+ const { createArbiClient } = require('@arbidocs/sdk')
46
+
47
+ async function main() {
48
+ const arbi = createArbiClient({
49
+ baseUrl: 'https://box-201.arbibox.com',
50
+ deploymentDomain: 'box-201.arbibox.com',
51
+ credentials: 'omit', // No cookies in Node.js
52
+ })
53
+
54
+ // Register a new user
55
+ const { signingPrivateKey } = await arbi.auth.register({
56
+ email: 'alice@example.com',
57
+ password: 'SecurePass123!',
58
+ verificationCode: '...', // From /api/user/verify-email flow
59
+ firstName: 'Alice',
60
+ lastName: 'Smith',
61
+ })
62
+
63
+ // Login
64
+ const session = await arbi.auth.login({
65
+ email: 'alice@example.com',
66
+ password: 'SecurePass123!',
67
+ })
68
+
69
+ console.log('Logged in as', session.userExtId)
70
+ console.log('Token:', session.accessToken.slice(0, 20) + '...')
71
+ }
72
+
73
+ main()
74
+ ```
75
+
76
+ ### Create a Workspace and Upload a Document
77
+
78
+ ```javascript
79
+ require('fake-indexeddb/auto')
80
+ const fs = require('fs')
81
+ const {
82
+ createArbiClient,
83
+ getSession,
84
+ sealedBoxDecrypt,
85
+ deriveEncryptionKeypairFromSigning,
86
+ createWorkspaceKeyHeader,
87
+ } = require('@arbidocs/sdk')
88
+
89
+ async function main() {
90
+ const arbi = createArbiClient({
91
+ baseUrl: 'https://box-201.arbibox.com',
92
+ deploymentDomain: 'box-201.arbibox.com',
93
+ credentials: 'omit',
94
+ })
95
+
96
+ // Login
97
+ const { accessToken, serverSessionKey } = await arbi.auth.login({
98
+ email: 'alice@example.com',
99
+ password: 'SecurePass123!',
100
+ })
101
+
102
+ // Create workspace
103
+ const { data: workspace } = await arbi.fetch.POST('/api/workspace/create_protected', {
104
+ body: {
105
+ name: 'My Case Files',
106
+ description: 'Documents for Case #1234',
107
+ is_public: false,
108
+ },
109
+ })
110
+
111
+ // Decrypt the workspace key and generate the auth header.
112
+ // The server wraps the workspace symmetric key with your public key
113
+ // during creation. You unwrap it here to prove you own the workspace.
114
+ const session = await getSession()
115
+ const pubKey = session.signingPrivateKey.slice(32, 64)
116
+ const encKP = deriveEncryptionKeypairFromSigning({
117
+ publicKey: pubKey,
118
+ secretKey: session.signingPrivateKey,
119
+ })
120
+ const workspaceKey = sealedBoxDecrypt(workspace.wrapped_key, encKP.secretKey)
121
+ const wsHeader = await createWorkspaceKeyHeader(workspaceKey, serverSessionKey)
122
+
123
+ // Tell the SDK which workspace is active (middleware auto-injects the header)
124
+ arbi.session.setSelectedWorkspace(workspace.external_id)
125
+ arbi.session.setCachedWorkspaceHeader(workspace.external_id, wsHeader)
126
+
127
+ // Upload a PDF (multipart — use raw fetch since openapi-fetch
128
+ // doesn't handle multipart/form-data)
129
+ const formData = new FormData()
130
+ const pdf = fs.readFileSync('./contract.pdf')
131
+ formData.append('files', new Blob([pdf], { type: 'application/pdf' }), 'contract.pdf')
132
+
133
+ const uploadRes = await fetch(
134
+ `https://box-201.arbibox.com/api/document/upload?workspace_ext_id=${workspace.external_id}`,
135
+ {
136
+ method: 'POST',
137
+ headers: {
138
+ Authorization: `Bearer ${accessToken}`,
139
+ 'workspace-key': wsHeader,
140
+ },
141
+ body: formData,
142
+ }
143
+ )
144
+
145
+ const { doc_ext_ids } = await uploadRes.json()
146
+ console.log('Uploaded document:', doc_ext_ids[0])
147
+
148
+ // List documents (workspace-key header injected automatically by middleware)
149
+ const { data: docs } = await arbi.fetch.GET(
150
+ '/api/workspace/{workspace_ext_id}/documents',
151
+ { params: { path: { workspace_ext_id: workspace.external_id } } }
152
+ )
153
+
154
+ for (const doc of docs) {
155
+ console.log(` ${doc.file_name} — ${doc.status}`)
156
+ }
157
+ }
158
+
159
+ main()
160
+ ```
161
+
162
+ ### Build an MCP Server or CLI Tool
163
+
164
+ ```typescript
165
+ import 'fake-indexeddb/auto'
166
+ import { createArbiClient, getSession, sealedBoxDecrypt,
167
+ deriveEncryptionKeypairFromSigning, createWorkspaceKeyHeader } from '@arbidocs/sdk'
168
+
169
+ // Reusable helper: select a workspace and set up the crypto header
170
+ async function selectWorkspace(arbi, workspaceId, serverSessionKey) {
171
+ // Fetch workspaces to get the wrapped key
172
+ const { data: workspaces } = await arbi.fetch.GET('/api/user/workspaces')
173
+ const ws = workspaces.find(w => w.external_id === workspaceId)
174
+ if (!ws?.wrapped_key) throw new Error(`Workspace ${workspaceId} not found`)
175
+
176
+ const session = await getSession()
177
+ const pubKey = session.signingPrivateKey.slice(32, 64)
178
+ const encKP = deriveEncryptionKeypairFromSigning({
179
+ publicKey: pubKey,
180
+ secretKey: session.signingPrivateKey,
181
+ })
182
+ const wsKey = sealedBoxDecrypt(ws.wrapped_key, encKP.secretKey)
183
+ const header = await createWorkspaceKeyHeader(wsKey, serverSessionKey)
184
+
185
+ arbi.session.setSelectedWorkspace(workspaceId)
186
+ arbi.session.setCachedWorkspaceHeader(workspaceId, header)
187
+ }
188
+
189
+ // Example: CLI that searches documents
190
+ async function searchDocuments(query: string) {
191
+ const arbi = createArbiClient({
192
+ baseUrl: process.env.ARBI_API_URL!,
193
+ deploymentDomain: process.env.ARBI_DOMAIN!,
194
+ credentials: 'omit',
195
+ })
196
+
197
+ const { serverSessionKey } = await arbi.auth.login({
198
+ email: process.env.ARBI_EMAIL!,
199
+ password: process.env.ARBI_PASSWORD!,
200
+ })
201
+
202
+ await selectWorkspace(arbi, process.env.ARBI_WORKSPACE!, serverSessionKey)
203
+
204
+ // Use the assistant API to ask a question about your documents
205
+ const { data } = await arbi.fetch.POST('/api/assistant/query', {
206
+ body: {
207
+ workspace_ext_id: process.env.ARBI_WORKSPACE!,
208
+ query,
209
+ },
210
+ })
211
+
212
+ console.log(data)
213
+ }
214
+
215
+ searchDocuments('What are the key terms of the contract?')
216
+ ```
217
+
218
+ ### Subscribe to Session State Changes
219
+
220
+ The SDK's `SessionManager` supports a subscribe pattern for reactive updates:
221
+
222
+ ```typescript
223
+ const arbi = createArbiClient({ baseUrl, deploymentDomain })
224
+
225
+ // React to session changes (useful for custom UI frameworks)
226
+ const unsubscribe = arbi.session.subscribe((state) => {
227
+ console.log('Session changed:', {
228
+ loggedIn: !!state.accessToken,
229
+ email: state.userEmail,
230
+ workspace: state.selectedWorkspaceId,
231
+ })
232
+ })
233
+
234
+ // Later: stop listening
235
+ unsubscribe()
236
+ ```
237
+
238
+ ## API Reference
239
+
240
+ ### `createArbiClient(options)`
241
+
242
+ Creates an SDK client instance.
243
+
244
+ ```typescript
245
+ interface ArbiClientOptions {
246
+ baseUrl: string // ARBI API URL (e.g. "https://box-201.arbibox.com")
247
+ deploymentDomain: string // Used for deterministic key derivation
248
+ credentials?: RequestCredentials // Default: 'include'. Use 'omit' for Node.js
249
+ ssoTokenProvider?: { getToken(): Promise<string | null> } | null
250
+ onReloginSuccess?: (data) => void
251
+ }
252
+ ```
253
+
254
+ Returns an `ArbiClient` with these namespaces:
255
+
256
+ | Namespace | Description |
257
+ |-----------|-------------|
258
+ | `arbi.fetch` | Type-safe `openapi-fetch` client with auto-auth middleware |
259
+ | `arbi.session` | In-memory session state (token, user, workspace) |
260
+ | `arbi.auth` | High-level auth: `register`, `login`, `loginWithKey`, `logout`, `changePassword`, `relogin` |
261
+ | `arbi.crypto` | Crypto utilities: key derivation, signing, encryption, base64 |
262
+ | `arbi.storage` | IndexedDB session persistence: `getSession`, `saveSession` |
263
+
264
+ ### Auth Methods
265
+
266
+ ```typescript
267
+ // Register
268
+ await arbi.auth.register({ email, password, verificationCode, firstName?, lastName? })
269
+
270
+ // Login with password
271
+ await arbi.auth.login({ email, password })
272
+
273
+ // Login with recovery key (Uint8Array)
274
+ await arbi.auth.loginWithKey({ email, signingPrivateKey })
275
+
276
+ // Change password
277
+ await arbi.auth.changePassword({ email, currentPassword, newPassword })
278
+
279
+ // Logout (clears session + IndexedDB)
280
+ await arbi.auth.logout()
281
+
282
+ // Re-login using stored private key (auto-called by middleware on 401)
283
+ await arbi.auth.relogin()
284
+ ```
285
+
286
+ ### Middleware (Auto-Applied)
287
+
288
+ The `arbi.fetch` client has three middleware layers applied automatically:
289
+
290
+ 1. **Bearer Auth** — injects `Authorization: Bearer <token>` on every request
291
+ 2. **Workspace Key** — injects `workspace-key` header for workspace-scoped endpoints
292
+ 3. **Auto-Relogin** — on 401, re-derives credentials from stored key and retries
293
+
294
+ ### Standalone Exports
295
+
296
+ All building blocks are individually importable for advanced use:
297
+
298
+ ```typescript
299
+ import {
300
+ // Crypto primitives
301
+ initSodium, signMessage, sealedBoxDecrypt, sealedBoxEncrypt,
302
+ deriveEncryptionKeypairFromSigning, createWorkspaceKeyHeader,
303
+ generateUserKeypairs, generateLoginCredentials,
304
+ encryptMessage, decryptMessage,
305
+ base64Encode, base64Decode, base64ToBytes, bytesToBase64,
306
+
307
+ // Storage
308
+ getSession, saveSession, clearAllData, hasSession,
309
+
310
+ // Middleware (compose your own client)
311
+ createBearerAuthMiddleware, createWorkspaceKeyMiddleware, createAutoReloginMiddleware,
312
+
313
+ // Relogin handler
314
+ createReloginHandler,
315
+
316
+ // Session manager
317
+ createSessionManager,
318
+
319
+ // OpenAPI schema types
320
+ type paths, type components, type operations,
321
+ } from '@arbidocs/sdk'
322
+ ```
323
+
324
+ ## How It Works
325
+
326
+ ### Zero-Knowledge Auth
327
+
328
+ ARBI uses Ed25519 signatures for authentication. Your password never leaves the client:
329
+
330
+ 1. **Key derivation**: `Argon2id(email + password + deploymentDomain)` → Ed25519 seed → signing keypair
331
+ 2. **Registration**: Public key sent to server, private key stays local
332
+ 3. **Login**: Client signs a timestamp, server verifies the signature
333
+ 4. **Session**: Server returns an encrypted session key for workspace operations
334
+
335
+ ### Workspace Encryption
336
+
337
+ Each workspace has a symmetric key (NaCl SecretBox). The key is:
338
+ - Generated server-side on workspace creation
339
+ - Wrapped with your X25519 public key (derived from your Ed25519 signing key)
340
+ - Unwrapped client-side using `sealedBoxDecrypt`
341
+ - Used to generate per-request `workspace-key` headers (HMAC proof of key possession)
342
+
343
+ ### IndexedDB Storage
344
+
345
+ 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.
346
+
347
+ ## Requirements
348
+
349
+ - **Browser**: Any modern browser (Chrome, Firefox, Safari, Edge)
350
+ - **Node.js**: v18+ (needs `fetch`, `FormData`, `Blob` globals). Install `fake-indexeddb` for IndexedDB.
351
+ - **TypeScript**: 5.0+ (optional — works with plain JS too)