@arbidocs/client 0.3.9

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,295 @@
1
+ # @arbidocs/client
2
+
3
+ TypeScript client for the ARBI API — zero-knowledge auth, E2E encryption, and type-safe REST client.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @arbidocs/client
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/client'
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/client')
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/client')
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
+ ### Subscribe to Session State Changes
163
+
164
+ The SDK's `SessionManager` supports a subscribe pattern for reactive updates:
165
+
166
+ ```typescript
167
+ const arbi = createArbiClient({ baseUrl, deploymentDomain })
168
+
169
+ // React to session changes (useful for custom UI frameworks)
170
+ const unsubscribe = arbi.session.subscribe((state) => {
171
+ console.log('Session changed:', {
172
+ loggedIn: !!state.accessToken,
173
+ email: state.userEmail,
174
+ workspace: state.selectedWorkspaceId,
175
+ })
176
+ })
177
+
178
+ // Later: stop listening
179
+ unsubscribe()
180
+ ```
181
+
182
+ ## API Reference
183
+
184
+ ### `createArbiClient(options)`
185
+
186
+ Creates an SDK client instance.
187
+
188
+ ```typescript
189
+ interface ArbiClientOptions {
190
+ baseUrl: string // ARBI API URL (e.g. "https://box-201.arbibox.com")
191
+ deploymentDomain: string // Used for deterministic key derivation
192
+ credentials?: RequestCredentials // Default: 'include'. Use 'omit' for Node.js
193
+ ssoTokenProvider?: { getToken(): Promise<string | null> } | null
194
+ onReloginSuccess?: (data) => void
195
+ }
196
+ ```
197
+
198
+ Returns an `ArbiClient` with these namespaces:
199
+
200
+ | Namespace | Description |
201
+ |-----------|-------------|
202
+ | `arbi.fetch` | Type-safe `openapi-fetch` client with auto-auth middleware |
203
+ | `arbi.session` | In-memory session state (token, user, workspace) |
204
+ | `arbi.auth` | High-level auth: `register`, `login`, `loginWithKey`, `logout`, `changePassword`, `relogin` |
205
+ | `arbi.crypto` | Crypto utilities: key derivation, signing, encryption, base64 |
206
+ | `arbi.storage` | IndexedDB session persistence: `getSession`, `saveSession` |
207
+
208
+ ### Auth Methods
209
+
210
+ ```typescript
211
+ // Register
212
+ await arbi.auth.register({ email, password, verificationCode, firstName?, lastName? })
213
+
214
+ // Login with password
215
+ await arbi.auth.login({ email, password })
216
+
217
+ // Login with recovery key (Uint8Array)
218
+ await arbi.auth.loginWithKey({ email, signingPrivateKey })
219
+
220
+ // Change password
221
+ await arbi.auth.changePassword({ email, currentPassword, newPassword })
222
+
223
+ // Logout (clears session + IndexedDB)
224
+ await arbi.auth.logout()
225
+
226
+ // Re-login using stored private key (auto-called by middleware on 401)
227
+ await arbi.auth.relogin()
228
+ ```
229
+
230
+ ### Middleware (Auto-Applied)
231
+
232
+ The `arbi.fetch` client has three middleware layers applied automatically:
233
+
234
+ 1. **Bearer Auth** — injects `Authorization: Bearer <token>` on every request
235
+ 2. **Workspace Key** — injects `workspace-key` header for workspace-scoped endpoints
236
+ 3. **Auto-Relogin** — on 401, re-derives credentials from stored key and retries
237
+
238
+ ### Standalone Exports
239
+
240
+ All building blocks are individually importable for advanced use:
241
+
242
+ ```typescript
243
+ import {
244
+ // Crypto primitives
245
+ initSodium, signMessage, sealedBoxDecrypt, sealedBoxEncrypt,
246
+ deriveEncryptionKeypairFromSigning, createWorkspaceKeyHeader,
247
+ generateUserKeypairs, generateLoginCredentials,
248
+ encryptMessage, decryptMessage,
249
+ base64Encode, base64Decode, base64ToBytes, bytesToBase64,
250
+
251
+ // Storage
252
+ getSession, saveSession, clearAllData, hasSession,
253
+
254
+ // Middleware (compose your own client)
255
+ createBearerAuthMiddleware, createWorkspaceKeyMiddleware, createAutoReloginMiddleware,
256
+
257
+ // Relogin handler
258
+ createReloginHandler,
259
+
260
+ // Session manager
261
+ createSessionManager,
262
+
263
+ // OpenAPI schema types
264
+ type paths, type components, type operations,
265
+ } from '@arbidocs/client'
266
+ ```
267
+
268
+ ## How It Works
269
+
270
+ ### Zero-Knowledge Auth
271
+
272
+ ARBI uses Ed25519 signatures for authentication. Your password never leaves the client:
273
+
274
+ 1. **Key derivation**: `Argon2id(email + password + deploymentDomain)` → Ed25519 seed → signing keypair
275
+ 2. **Registration**: Public key sent to server, private key stays local
276
+ 3. **Login**: Client signs a timestamp, server verifies the signature
277
+ 4. **Session**: Server returns an encrypted session key for workspace operations
278
+
279
+ ### Workspace Encryption
280
+
281
+ Each workspace has a symmetric key (NaCl SecretBox). The key is:
282
+ - Generated server-side on workspace creation
283
+ - Wrapped with your X25519 public key (derived from your Ed25519 signing key)
284
+ - Unwrapped client-side using `sealedBoxDecrypt`
285
+ - Used to generate per-request `workspace-key` headers (HMAC proof of key possession)
286
+
287
+ ### IndexedDB Storage
288
+
289
+ 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.
290
+
291
+ ## Requirements
292
+
293
+ - **Browser**: Any modern browser (Chrome, Firefox, Safari, Edge)
294
+ - **Node.js**: v18+ (needs `fetch`, `FormData`, `Blob` globals). Install `fake-indexeddb` for IndexedDB.
295
+ - **TypeScript**: 5.0+ (optional — works with plain JS too)