@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 +21 -0
- package/README.md +351 -0
- package/dist/index.cjs +1154 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6994 -0
- package/dist/index.d.ts +6994 -0
- package/dist/index.js +1107 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
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)
|