@unofficialbox/box-open-sdk 0.1.2 → 0.3.1
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 +47 -11
- package/dist/cjs/chunked_upload.js +108 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/managers/chunked_uploads.js +4 -4
- package/dist/cjs/managers/search.js +1 -1
- package/dist/cjs/managers/transfer.js +1 -1
- package/dist/cjs/managers/uploads.js +2 -2
- package/dist/esm/chunked_upload.js +104 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/managers/chunked_uploads.js +4 -4
- package/dist/esm/managers/search.js +1 -1
- package/dist/esm/managers/transfer.js +1 -1
- package/dist/esm/managers/uploads.js +2 -2
- package/dist/types/chunked_upload.d.ts +21 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/managers/chunked_uploads.d.ts +2 -2
- package/dist/types/managers/search.d.ts +1 -1
- package/dist/types/managers/transfer.d.ts +3 -3
- package/dist/types/managers/uploads.d.ts +6 -6
- package/dist/types/models/schemas.d.ts +2 -2
- package/docs/auth.md +10 -10
- package/docs/errors.md +2 -2
- package/docs/managers/ai.md +30 -0
- package/docs/managers/ai_studio.md +32 -0
- package/docs/managers/app_item_associations.md +16 -0
- package/docs/managers/archives.md +26 -0
- package/docs/managers/authorization.md +24 -0
- package/docs/managers/automate_workflows.md +14 -0
- package/docs/managers/avatars.md +18 -0
- package/docs/managers/chunked_uploads.md +48 -4
- package/docs/managers/classifications.md +24 -0
- package/docs/managers/collaboration_allowlist_entries.md +26 -0
- package/docs/managers/collaboration_allowlist_exempt_targets.md +26 -0
- package/docs/managers/collections.md +22 -0
- package/docs/managers/comments.md +32 -0
- package/docs/managers/device_pinners.md +20 -0
- package/docs/managers/docgen.md +28 -0
- package/docs/managers/docgen_template.md +42 -0
- package/docs/managers/downloads.md +6 -0
- package/docs/managers/email_aliases.md +18 -0
- package/docs/managers/enterprise_configurations.md +6 -0
- package/docs/managers/events.md +12 -0
- package/docs/managers/external_users.md +6 -0
- package/docs/managers/file_classifications.md +24 -0
- package/docs/managers/file_metadata.md +30 -0
- package/docs/managers/file_requests.md +24 -0
- package/docs/managers/file_version_legal_holds.md +14 -0
- package/docs/managers/file_version_retentions.md +14 -0
- package/docs/managers/file_versions.md +32 -0
- package/docs/managers/file_watermarks.md +18 -0
- package/docs/managers/files.md +30 -0
- package/docs/managers/folder_classifications.md +24 -0
- package/docs/managers/folder_locks.md +18 -0
- package/docs/managers/folder_metadata.md +30 -0
- package/docs/managers/folder_watermarks.md +18 -0
- package/docs/managers/folders.md +38 -0
- package/docs/managers/groups.md +32 -0
- package/docs/managers/hub_collaborations.md +32 -0
- package/docs/managers/hub_document.md +16 -0
- package/docs/managers/hub_items.md +14 -0
- package/docs/managers/hubs.md +46 -0
- package/docs/managers/integration_mappings.md +50 -0
- package/docs/managers/invites.md +12 -0
- package/docs/managers/legal_hold_policies.md +32 -0
- package/docs/managers/legal_hold_policy_assignments.md +42 -0
- package/docs/managers/list_collaborations.md +32 -0
- package/docs/managers/memberships.md +40 -0
- package/docs/managers/metadata_cascade_policies.md +32 -0
- package/docs/managers/metadata_taxonomies.md +96 -0
- package/docs/managers/metadata_templates.md +54 -0
- package/docs/managers/notes.md +6 -0
- package/docs/managers/recent_items.md +8 -0
- package/docs/managers/retention_policies.md +32 -0
- package/docs/managers/retention_policy_assignments.md +42 -0
- package/docs/managers/search.md +13 -1
- package/docs/managers/session_termination.md +12 -0
- package/docs/managers/shared_links_app_items.md +6 -0
- package/docs/managers/shared_links_files.md +30 -0
- package/docs/managers/shared_links_folders.md +30 -0
- package/docs/managers/shared_links_web_links.md +30 -0
- package/docs/managers/shield_information_barrier_reports.md +20 -0
- package/docs/managers/shield_information_barrier_segment_members.md +26 -0
- package/docs/managers/shield_information_barrier_segment_restrictions.md +26 -0
- package/docs/managers/shield_information_barrier_segments.md +32 -0
- package/docs/managers/shield_information_barriers.md +26 -0
- package/docs/managers/shield_lists.md +30 -0
- package/docs/managers/sign_requests.md +32 -0
- package/docs/managers/sign_templates.md +14 -0
- package/docs/managers/skills.md +30 -0
- package/docs/managers/storage_policies.md +14 -0
- package/docs/managers/storage_policy_assignments.md +32 -0
- package/docs/managers/task_assignments.md +30 -0
- package/docs/managers/tasks.md +30 -0
- package/docs/managers/terms_of_service_user_statuses.md +18 -0
- package/docs/managers/terms_of_services.md +24 -0
- package/docs/managers/transfer.md +7 -1
- package/docs/managers/trashed_files.md +18 -0
- package/docs/managers/trashed_folders.md +18 -0
- package/docs/managers/trashed_items.md +8 -0
- package/docs/managers/trashed_web_links.md +18 -0
- package/docs/managers/uploads.md +20 -2
- package/docs/managers/user_collaborations.md +24 -0
- package/docs/managers/users.md +38 -0
- package/docs/managers/web_links.md +24 -0
- package/docs/managers/webhooks.md +32 -0
- package/docs/managers/workflows.md +14 -0
- package/docs/managers/zip_downloads.md +18 -0
- package/package.json +3 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kyle @ Unofficial Box
|
|
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
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/@unofficialbox/box-open-sdk)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
An **open source, community-built** Box API client for TypeScript — fully typed models
|
|
9
9
|
for the whole Box surface, one manager per API area behind a single `Client`,
|
|
10
10
|
and a `fetch`-based runtime with retry, backoff, and token refresh. Ships as a
|
|
11
11
|
**dual ESM/CJS** package with bundled `.d.ts` declarations; no runtime
|
|
@@ -22,20 +22,56 @@ npm install @unofficialbox/box-open-sdk
|
|
|
22
22
|
|
|
23
23
|
## Quickstart
|
|
24
24
|
|
|
25
|
+
Authenticate, look up the current user, create a folder, upload a file, extract
|
|
26
|
+
its fields with Box AI, tag it with metadata, and query for it — end to end:
|
|
27
|
+
|
|
25
28
|
```ts
|
|
26
|
-
import { Client,
|
|
29
|
+
import { Client, auth } from '@unofficialbox/box-open-sdk';
|
|
27
30
|
|
|
28
|
-
//
|
|
29
|
-
|
|
31
|
+
// Client Credentials Grant (server-to-server); developer token, OAuth, and JWT
|
|
32
|
+
// also live in the `auth` namespace.
|
|
33
|
+
const client = new Client(auth.clientCredentials({
|
|
34
|
+
clientId: 'CLIENT_ID',
|
|
35
|
+
clientSecret: 'CLIENT_SECRET',
|
|
36
|
+
enterpriseId: 'ENTERPRISE_ID',
|
|
37
|
+
}));
|
|
30
38
|
|
|
31
|
-
//
|
|
39
|
+
// The current user.
|
|
32
40
|
const me = await client.users.getMe();
|
|
33
|
-
console.log(me);
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
41
|
+
console.log(`authenticated as ${me.id}`);
|
|
42
|
+
|
|
43
|
+
// Create a folder at the account root ("0").
|
|
44
|
+
const folder = await client.folders.create({
|
|
45
|
+
name: 'Invoices',
|
|
46
|
+
parent: { id: '0' },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Upload a file into it.
|
|
50
|
+
const uploaded = await client.uploads.uploadFile({
|
|
51
|
+
attributes: { name: 'invoice.pdf', parent: { id: folder.id } },
|
|
52
|
+
file: new Blob(['<file bytes>']),
|
|
53
|
+
});
|
|
54
|
+
const fileId = uploaded.entries![0].id;
|
|
55
|
+
|
|
56
|
+
// Extract fields from the file with Box AI.
|
|
57
|
+
const answer = await client.ai.extract({
|
|
58
|
+
prompt: 'Extract the invoice number and total amount.',
|
|
59
|
+
items: [{ id: fileId, type: 'file' }],
|
|
60
|
+
});
|
|
61
|
+
console.log(answer);
|
|
62
|
+
|
|
63
|
+
// Attach that metadata to the file (an enterprise template).
|
|
64
|
+
await client.fileMetadata.createFileMetadata(fileId, 'enterprise', 'invoiceData', {
|
|
65
|
+
invoiceNumber: 'INV-0042',
|
|
66
|
+
total: 1250,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Query for files carrying that metadata.
|
|
70
|
+
const results = await client.search.queryByMetadata({
|
|
71
|
+
from: 'enterprise_0.invoiceData',
|
|
72
|
+
ancestor_folder_id: folder.id,
|
|
73
|
+
});
|
|
74
|
+
console.log(results);
|
|
39
75
|
```
|
|
40
76
|
|
|
41
77
|
## Authentication
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Code generated by box-gantry. DO NOT EDIT.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ChunkedUpload = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* ChunkedUpload orchestrates a Box chunked (multipart) upload over a Client: it
|
|
7
|
+
* creates an upload session, uploads the content's parts with bounded
|
|
8
|
+
* concurrency, and commits — for a new file (upload) or a new version of an
|
|
9
|
+
* existing file (uploadVersion). Box requires chunked upload for files at or
|
|
10
|
+
* above its minimum session size; smaller files use the single-shot endpoints.
|
|
11
|
+
*/
|
|
12
|
+
class ChunkedUpload {
|
|
13
|
+
client;
|
|
14
|
+
/** Bounds parts in flight, capping peak memory. */
|
|
15
|
+
maxConcurrent = 4;
|
|
16
|
+
constructor(client) {
|
|
17
|
+
this.client = client;
|
|
18
|
+
}
|
|
19
|
+
/** Upload content as a new file named fileName into folderId. */
|
|
20
|
+
async upload(content, fileName, folderId) {
|
|
21
|
+
const session = await this.client.chunkedUploads.createFileUploadSession({
|
|
22
|
+
folder_id: folderId,
|
|
23
|
+
file_size: content.length,
|
|
24
|
+
file_name: fileName,
|
|
25
|
+
});
|
|
26
|
+
return this.finish(session, content);
|
|
27
|
+
}
|
|
28
|
+
/** Upload content as a new version of the existing file fileId. */
|
|
29
|
+
async uploadVersion(content, fileName, fileId) {
|
|
30
|
+
const session = await this.client.chunkedUploads.createFileVersionUploadSession(fileId, {
|
|
31
|
+
file_size: content.length,
|
|
32
|
+
file_name: fileName,
|
|
33
|
+
});
|
|
34
|
+
return this.finish(session, content);
|
|
35
|
+
}
|
|
36
|
+
async finish(session, content) {
|
|
37
|
+
if (session.id === undefined) {
|
|
38
|
+
throw new Error('gantry: upload session returned no id');
|
|
39
|
+
}
|
|
40
|
+
if (session.part_size === undefined || session.part_size <= 0) {
|
|
41
|
+
throw new Error('gantry: upload session returned a non-positive part_size');
|
|
42
|
+
}
|
|
43
|
+
const id = session.id;
|
|
44
|
+
const partSize = session.part_size;
|
|
45
|
+
const total = content.length;
|
|
46
|
+
const offsets = [];
|
|
47
|
+
for (let off = 0; off < total; off += partSize) {
|
|
48
|
+
offsets.push(off);
|
|
49
|
+
}
|
|
50
|
+
const parts = new Array(offsets.length);
|
|
51
|
+
// A bounded worker pool: each of maxConcurrent workers claims the next
|
|
52
|
+
// offset (the `next++` read is atomic between awaits on a single thread).
|
|
53
|
+
// Once any part fails, `failed` stops the others from starting new parts,
|
|
54
|
+
// so a failure early in a large upload doesn't push every remaining part.
|
|
55
|
+
let next = 0;
|
|
56
|
+
let failed = false;
|
|
57
|
+
const worker = async () => {
|
|
58
|
+
for (;;) {
|
|
59
|
+
if (failed) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const i = next++;
|
|
63
|
+
if (i >= offsets.length) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const start = offsets[i];
|
|
67
|
+
const end = Math.min(start + partSize, total);
|
|
68
|
+
try {
|
|
69
|
+
parts[i] = await this.uploadPart(id, content, start, end, total);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
failed = true;
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const workerCount = Math.min(this.maxConcurrent, offsets.length);
|
|
78
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
79
|
+
const digest = 'sha=' + (await sha1Base64(content));
|
|
80
|
+
return this.client.chunkedUploads.commitFileUploadSession(id, digest, { parts });
|
|
81
|
+
}
|
|
82
|
+
async uploadPart(id, content, start, end, total) {
|
|
83
|
+
const slice = content.subarray(start, end);
|
|
84
|
+
const digest = 'sha=' + (await sha1Base64(slice));
|
|
85
|
+
const contentRange = `bytes ${start}-${end - 1}/${total}`;
|
|
86
|
+
// The `as BlobPart` cast bridges the `Uint8Array<ArrayBufferLike>` vs
|
|
87
|
+
// `BufferSource` (ArrayBuffer-only) generic gap in the DOM lib, exactly as
|
|
88
|
+
// the runtime casts its `fetch` body; a plain `Uint8Array` is a valid part.
|
|
89
|
+
const uploaded = await this.client.chunkedUploads.updateFileUploadSession(id, digest, contentRange, new Blob([slice]));
|
|
90
|
+
if (uploaded.part === undefined) {
|
|
91
|
+
throw new Error('gantry: upload part returned no part');
|
|
92
|
+
}
|
|
93
|
+
return uploaded.part;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.ChunkedUpload = ChunkedUpload;
|
|
97
|
+
/** Base64 of the SHA-1 of data — the Box `Digest: sha=…` header value. Uses Web
|
|
98
|
+
* Crypto (browser and Node 18+), so it adds no dependency. */
|
|
99
|
+
async function sha1Base64(data) {
|
|
100
|
+
// `as BufferSource` bridges the `Uint8Array<ArrayBufferLike>` vs ArrayBuffer-only
|
|
101
|
+
// generic gap in the DOM lib (as the runtime does for its `fetch` body).
|
|
102
|
+
const digest = new Uint8Array(await crypto.subtle.digest('SHA-1', data));
|
|
103
|
+
let binary = '';
|
|
104
|
+
for (const byte of digest) {
|
|
105
|
+
binary += String.fromCharCode(byte);
|
|
106
|
+
}
|
|
107
|
+
return btoa(binary);
|
|
108
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -34,10 +34,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.buildinfo = exports.runtime = exports.managers = exports.models = exports.Client = void 0;
|
|
37
|
+
exports.ChunkedUpload = exports.buildinfo = exports.auth = exports.runtime = exports.managers = exports.models = exports.Client = void 0;
|
|
38
38
|
var client_js_1 = require("./client.js");
|
|
39
39
|
Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_js_1.Client; } });
|
|
40
40
|
exports.models = __importStar(require("./models/index.js"));
|
|
41
41
|
exports.managers = __importStar(require("./managers/index.js"));
|
|
42
42
|
exports.runtime = __importStar(require("./runtime.js"));
|
|
43
|
+
exports.auth = __importStar(require("./auth.js"));
|
|
43
44
|
exports.buildinfo = __importStar(require("./buildinfo.js"));
|
|
45
|
+
var chunked_upload_js_1 = require("./chunked_upload.js");
|
|
46
|
+
Object.defineProperty(exports, "ChunkedUpload", { enumerable: true, get: function () { return chunked_upload_js_1.ChunkedUpload; } });
|
|
@@ -43,7 +43,7 @@ class ChunkedUploadsManager {
|
|
|
43
43
|
constructor(session) {
|
|
44
44
|
this.session = session;
|
|
45
45
|
}
|
|
46
|
-
async
|
|
46
|
+
async createFileUploadSession(body) {
|
|
47
47
|
let url = this.session.baseUrl("upload");
|
|
48
48
|
url += "/files";
|
|
49
49
|
url += "/upload_sessions";
|
|
@@ -54,7 +54,7 @@ class ChunkedUploadsManager {
|
|
|
54
54
|
const data = runtime.responseBytes(resp);
|
|
55
55
|
return JSON.parse(new TextDecoder().decode(data));
|
|
56
56
|
}
|
|
57
|
-
async
|
|
57
|
+
async createFileVersionUploadSession(fileId, body) {
|
|
58
58
|
let url = this.session.baseUrl("upload");
|
|
59
59
|
url += "/files";
|
|
60
60
|
url += '/' + (0, internal_js_1.pathEscape)(fileId);
|
|
@@ -84,8 +84,8 @@ class ChunkedUploadsManager {
|
|
|
84
84
|
let req = this.session.newRequest("PUT", url);
|
|
85
85
|
req = runtime.withHeader(req, "digest", digest);
|
|
86
86
|
req = runtime.withHeader(req, "content-range", contentRange);
|
|
87
|
-
|
|
88
|
-
req = runtime.withStreamBody(req, runtime.Stream
|
|
87
|
+
const fileBytes = new Uint8Array(await body.arrayBuffer());
|
|
88
|
+
req = runtime.withStreamBody(req, new runtime.Stream(fileBytes), 'application/octet-stream');
|
|
89
89
|
const resp = await this.session.fetch(req);
|
|
90
90
|
const data = runtime.responseBytes(resp);
|
|
91
91
|
return JSON.parse(new TextDecoder().decode(data));
|
|
@@ -43,7 +43,7 @@ class SearchManager {
|
|
|
43
43
|
constructor(session) {
|
|
44
44
|
this.session = session;
|
|
45
45
|
}
|
|
46
|
-
async
|
|
46
|
+
async queryByMetadata(body) {
|
|
47
47
|
let url = this.session.baseUrl("api");
|
|
48
48
|
url += "/metadata_queries";
|
|
49
49
|
url += "/execute_read";
|
|
@@ -43,7 +43,7 @@ class TransferManager {
|
|
|
43
43
|
constructor(session) {
|
|
44
44
|
this.session = session;
|
|
45
45
|
}
|
|
46
|
-
async
|
|
46
|
+
async transferFolders(userId, body, opts) {
|
|
47
47
|
let url = this.session.baseUrl("api");
|
|
48
48
|
url += "/users";
|
|
49
49
|
url += '/' + (0, internal_js_1.pathEscape)(userId);
|
|
@@ -43,7 +43,7 @@ class UploadsManager {
|
|
|
43
43
|
constructor(session) {
|
|
44
44
|
this.session = session;
|
|
45
45
|
}
|
|
46
|
-
async
|
|
46
|
+
async uploadFileVersion(fileId, body, opts) {
|
|
47
47
|
let url = this.session.baseUrl("upload");
|
|
48
48
|
url += "/files";
|
|
49
49
|
url += '/' + (0, internal_js_1.pathEscape)(fileId);
|
|
@@ -65,7 +65,7 @@ class UploadsManager {
|
|
|
65
65
|
const data = runtime.responseBytes(resp);
|
|
66
66
|
return JSON.parse(new TextDecoder().decode(data));
|
|
67
67
|
}
|
|
68
|
-
async
|
|
68
|
+
async uploadFile(body, opts) {
|
|
69
69
|
let url = this.session.baseUrl("upload");
|
|
70
70
|
url += "/files";
|
|
71
71
|
url += "/content";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Code generated by box-gantry. DO NOT EDIT.
|
|
2
|
+
/**
|
|
3
|
+
* ChunkedUpload orchestrates a Box chunked (multipart) upload over a Client: it
|
|
4
|
+
* creates an upload session, uploads the content's parts with bounded
|
|
5
|
+
* concurrency, and commits — for a new file (upload) or a new version of an
|
|
6
|
+
* existing file (uploadVersion). Box requires chunked upload for files at or
|
|
7
|
+
* above its minimum session size; smaller files use the single-shot endpoints.
|
|
8
|
+
*/
|
|
9
|
+
export class ChunkedUpload {
|
|
10
|
+
client;
|
|
11
|
+
/** Bounds parts in flight, capping peak memory. */
|
|
12
|
+
maxConcurrent = 4;
|
|
13
|
+
constructor(client) {
|
|
14
|
+
this.client = client;
|
|
15
|
+
}
|
|
16
|
+
/** Upload content as a new file named fileName into folderId. */
|
|
17
|
+
async upload(content, fileName, folderId) {
|
|
18
|
+
const session = await this.client.chunkedUploads.createFileUploadSession({
|
|
19
|
+
folder_id: folderId,
|
|
20
|
+
file_size: content.length,
|
|
21
|
+
file_name: fileName,
|
|
22
|
+
});
|
|
23
|
+
return this.finish(session, content);
|
|
24
|
+
}
|
|
25
|
+
/** Upload content as a new version of the existing file fileId. */
|
|
26
|
+
async uploadVersion(content, fileName, fileId) {
|
|
27
|
+
const session = await this.client.chunkedUploads.createFileVersionUploadSession(fileId, {
|
|
28
|
+
file_size: content.length,
|
|
29
|
+
file_name: fileName,
|
|
30
|
+
});
|
|
31
|
+
return this.finish(session, content);
|
|
32
|
+
}
|
|
33
|
+
async finish(session, content) {
|
|
34
|
+
if (session.id === undefined) {
|
|
35
|
+
throw new Error('gantry: upload session returned no id');
|
|
36
|
+
}
|
|
37
|
+
if (session.part_size === undefined || session.part_size <= 0) {
|
|
38
|
+
throw new Error('gantry: upload session returned a non-positive part_size');
|
|
39
|
+
}
|
|
40
|
+
const id = session.id;
|
|
41
|
+
const partSize = session.part_size;
|
|
42
|
+
const total = content.length;
|
|
43
|
+
const offsets = [];
|
|
44
|
+
for (let off = 0; off < total; off += partSize) {
|
|
45
|
+
offsets.push(off);
|
|
46
|
+
}
|
|
47
|
+
const parts = new Array(offsets.length);
|
|
48
|
+
// A bounded worker pool: each of maxConcurrent workers claims the next
|
|
49
|
+
// offset (the `next++` read is atomic between awaits on a single thread).
|
|
50
|
+
// Once any part fails, `failed` stops the others from starting new parts,
|
|
51
|
+
// so a failure early in a large upload doesn't push every remaining part.
|
|
52
|
+
let next = 0;
|
|
53
|
+
let failed = false;
|
|
54
|
+
const worker = async () => {
|
|
55
|
+
for (;;) {
|
|
56
|
+
if (failed) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const i = next++;
|
|
60
|
+
if (i >= offsets.length) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const start = offsets[i];
|
|
64
|
+
const end = Math.min(start + partSize, total);
|
|
65
|
+
try {
|
|
66
|
+
parts[i] = await this.uploadPart(id, content, start, end, total);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
failed = true;
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const workerCount = Math.min(this.maxConcurrent, offsets.length);
|
|
75
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
76
|
+
const digest = 'sha=' + (await sha1Base64(content));
|
|
77
|
+
return this.client.chunkedUploads.commitFileUploadSession(id, digest, { parts });
|
|
78
|
+
}
|
|
79
|
+
async uploadPart(id, content, start, end, total) {
|
|
80
|
+
const slice = content.subarray(start, end);
|
|
81
|
+
const digest = 'sha=' + (await sha1Base64(slice));
|
|
82
|
+
const contentRange = `bytes ${start}-${end - 1}/${total}`;
|
|
83
|
+
// The `as BlobPart` cast bridges the `Uint8Array<ArrayBufferLike>` vs
|
|
84
|
+
// `BufferSource` (ArrayBuffer-only) generic gap in the DOM lib, exactly as
|
|
85
|
+
// the runtime casts its `fetch` body; a plain `Uint8Array` is a valid part.
|
|
86
|
+
const uploaded = await this.client.chunkedUploads.updateFileUploadSession(id, digest, contentRange, new Blob([slice]));
|
|
87
|
+
if (uploaded.part === undefined) {
|
|
88
|
+
throw new Error('gantry: upload part returned no part');
|
|
89
|
+
}
|
|
90
|
+
return uploaded.part;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Base64 of the SHA-1 of data — the Box `Digest: sha=…` header value. Uses Web
|
|
94
|
+
* Crypto (browser and Node 18+), so it adds no dependency. */
|
|
95
|
+
async function sha1Base64(data) {
|
|
96
|
+
// `as BufferSource` bridges the `Uint8Array<ArrayBufferLike>` vs ArrayBuffer-only
|
|
97
|
+
// generic gap in the DOM lib (as the runtime does for its `fetch` body).
|
|
98
|
+
const digest = new Uint8Array(await crypto.subtle.digest('SHA-1', data));
|
|
99
|
+
let binary = '';
|
|
100
|
+
for (const byte of digest) {
|
|
101
|
+
binary += String.fromCharCode(byte);
|
|
102
|
+
}
|
|
103
|
+
return btoa(binary);
|
|
104
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -3,4 +3,6 @@ export { Client } from './client.js';
|
|
|
3
3
|
export * as models from './models/index.js';
|
|
4
4
|
export * as managers from './managers/index.js';
|
|
5
5
|
export * as runtime from './runtime.js';
|
|
6
|
+
export * as auth from './auth.js';
|
|
6
7
|
export * as buildinfo from './buildinfo.js';
|
|
8
|
+
export { ChunkedUpload } from './chunked_upload.js';
|
|
@@ -7,7 +7,7 @@ export class ChunkedUploadsManager {
|
|
|
7
7
|
constructor(session) {
|
|
8
8
|
this.session = session;
|
|
9
9
|
}
|
|
10
|
-
async
|
|
10
|
+
async createFileUploadSession(body) {
|
|
11
11
|
let url = this.session.baseUrl("upload");
|
|
12
12
|
url += "/files";
|
|
13
13
|
url += "/upload_sessions";
|
|
@@ -18,7 +18,7 @@ export class ChunkedUploadsManager {
|
|
|
18
18
|
const data = runtime.responseBytes(resp);
|
|
19
19
|
return JSON.parse(new TextDecoder().decode(data));
|
|
20
20
|
}
|
|
21
|
-
async
|
|
21
|
+
async createFileVersionUploadSession(fileId, body) {
|
|
22
22
|
let url = this.session.baseUrl("upload");
|
|
23
23
|
url += "/files";
|
|
24
24
|
url += '/' + pathEscape(fileId);
|
|
@@ -48,8 +48,8 @@ export class ChunkedUploadsManager {
|
|
|
48
48
|
let req = this.session.newRequest("PUT", url);
|
|
49
49
|
req = runtime.withHeader(req, "digest", digest);
|
|
50
50
|
req = runtime.withHeader(req, "content-range", contentRange);
|
|
51
|
-
|
|
52
|
-
req = runtime.withStreamBody(req, runtime.Stream
|
|
51
|
+
const fileBytes = new Uint8Array(await body.arrayBuffer());
|
|
52
|
+
req = runtime.withStreamBody(req, new runtime.Stream(fileBytes), 'application/octet-stream');
|
|
53
53
|
const resp = await this.session.fetch(req);
|
|
54
54
|
const data = runtime.responseBytes(resp);
|
|
55
55
|
return JSON.parse(new TextDecoder().decode(data));
|
|
@@ -7,7 +7,7 @@ export class TransferManager {
|
|
|
7
7
|
constructor(session) {
|
|
8
8
|
this.session = session;
|
|
9
9
|
}
|
|
10
|
-
async
|
|
10
|
+
async transferFolders(userId, body, opts) {
|
|
11
11
|
let url = this.session.baseUrl("api");
|
|
12
12
|
url += "/users";
|
|
13
13
|
url += '/' + pathEscape(userId);
|
|
@@ -7,7 +7,7 @@ export class UploadsManager {
|
|
|
7
7
|
constructor(session) {
|
|
8
8
|
this.session = session;
|
|
9
9
|
}
|
|
10
|
-
async
|
|
10
|
+
async uploadFileVersion(fileId, body, opts) {
|
|
11
11
|
let url = this.session.baseUrl("upload");
|
|
12
12
|
url += "/files";
|
|
13
13
|
url += '/' + pathEscape(fileId);
|
|
@@ -29,7 +29,7 @@ export class UploadsManager {
|
|
|
29
29
|
const data = runtime.responseBytes(resp);
|
|
30
30
|
return JSON.parse(new TextDecoder().decode(data));
|
|
31
31
|
}
|
|
32
|
-
async
|
|
32
|
+
async uploadFile(body, opts) {
|
|
33
33
|
let url = this.session.baseUrl("upload");
|
|
34
34
|
url += "/files";
|
|
35
35
|
url += "/content";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Client } from './client.js';
|
|
2
|
+
import type * as models from './models/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* ChunkedUpload orchestrates a Box chunked (multipart) upload over a Client: it
|
|
5
|
+
* creates an upload session, uploads the content's parts with bounded
|
|
6
|
+
* concurrency, and commits — for a new file (upload) or a new version of an
|
|
7
|
+
* existing file (uploadVersion). Box requires chunked upload for files at or
|
|
8
|
+
* above its minimum session size; smaller files use the single-shot endpoints.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ChunkedUpload {
|
|
11
|
+
private readonly client;
|
|
12
|
+
/** Bounds parts in flight, capping peak memory. */
|
|
13
|
+
private readonly maxConcurrent;
|
|
14
|
+
constructor(client: Client);
|
|
15
|
+
/** Upload content as a new file named fileName into folderId. */
|
|
16
|
+
upload(content: Uint8Array, fileName: string, folderId: string): Promise<models.schemas.Files>;
|
|
17
|
+
/** Upload content as a new version of the existing file fileId. */
|
|
18
|
+
uploadVersion(content: Uint8Array, fileName: string, fileId: string): Promise<models.schemas.Files>;
|
|
19
|
+
private finish;
|
|
20
|
+
private uploadPart;
|
|
21
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,4 +2,6 @@ export { Client } from './client.js';
|
|
|
2
2
|
export * as models from './models/index.js';
|
|
3
3
|
export * as managers from './managers/index.js';
|
|
4
4
|
export * as runtime from './runtime.js';
|
|
5
|
+
export * as auth from './auth.js';
|
|
5
6
|
export * as buildinfo from './buildinfo.js';
|
|
7
|
+
export { ChunkedUpload } from './chunked_upload.js';
|
|
@@ -14,8 +14,8 @@ export interface ChunkedUploadsManagerCommitFileUploadSessionOptions {
|
|
|
14
14
|
export declare class ChunkedUploadsManager {
|
|
15
15
|
private readonly session;
|
|
16
16
|
constructor(session: runtime.Client);
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
createFileUploadSession(body: models.schemas.FileUploadSessionCreateRequest): Promise<models.schemas.UploadSession>;
|
|
18
|
+
createFileVersionUploadSession(fileId: string, body: models.schemas.FileVersionUploadSessionCreateRequest): Promise<models.schemas.UploadSession>;
|
|
19
19
|
getFileUploadSession(uploadSessionId: string): Promise<models.schemas.UploadSession>;
|
|
20
20
|
updateFileUploadSession(uploadSessionId: string, digest: string, contentRange: string, body: Blob): Promise<models.schemas.UploadedPart>;
|
|
21
21
|
deleteFileUploadSession(uploadSessionId: string): Promise<void>;
|
|
@@ -28,6 +28,6 @@ export interface SearchManagerGetOptions {
|
|
|
28
28
|
export declare class SearchManager {
|
|
29
29
|
private readonly session;
|
|
30
30
|
constructor(session: runtime.Client);
|
|
31
|
-
|
|
31
|
+
queryByMetadata(body: models.schemas.MetadataQuery): Promise<models.schemas.MetadataQueryResults>;
|
|
32
32
|
get(opts?: SearchManagerGetOptions): Promise<models.schemas.SearchResultsResponse>;
|
|
33
33
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as runtime from '../runtime.js';
|
|
2
2
|
import type * as models from '../models/index.js';
|
|
3
|
-
/** Optional parameters for TransferManager.
|
|
4
|
-
export interface
|
|
3
|
+
/** Optional parameters for TransferManager.transferFolders. */
|
|
4
|
+
export interface TransferManagerTransferFoldersOptions {
|
|
5
5
|
fields?: Array<string>;
|
|
6
6
|
notify?: boolean;
|
|
7
7
|
}
|
|
@@ -9,5 +9,5 @@ export interface TransferManagerUpdateUserFolder0Options {
|
|
|
9
9
|
export declare class TransferManager {
|
|
10
10
|
private readonly session;
|
|
11
11
|
constructor(session: runtime.Client);
|
|
12
|
-
|
|
12
|
+
transferFolders(userId: string, body: models.schemas.UserFolder0UpdateRequest, opts?: TransferManagerTransferFoldersOptions): Promise<models.schemas.FolderFull>;
|
|
13
13
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as runtime from '../runtime.js';
|
|
2
2
|
import type * as models from '../models/index.js';
|
|
3
|
-
/** Optional parameters for UploadsManager.
|
|
4
|
-
export interface
|
|
3
|
+
/** Optional parameters for UploadsManager.uploadFileVersion. */
|
|
4
|
+
export interface UploadsManagerUploadFileVersionOptions {
|
|
5
5
|
"if-match"?: string;
|
|
6
6
|
fields?: Array<string>;
|
|
7
7
|
"content-md5"?: string;
|
|
8
8
|
}
|
|
9
|
-
/** Optional parameters for UploadsManager.
|
|
10
|
-
export interface
|
|
9
|
+
/** Optional parameters for UploadsManager.uploadFile. */
|
|
10
|
+
export interface UploadsManagerUploadFileOptions {
|
|
11
11
|
fields?: Array<string>;
|
|
12
12
|
"content-md5"?: string;
|
|
13
13
|
}
|
|
@@ -15,7 +15,7 @@ export interface UploadsManagerCreateFileContent2Options {
|
|
|
15
15
|
export declare class UploadsManager {
|
|
16
16
|
private readonly session;
|
|
17
17
|
constructor(session: runtime.Client);
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
uploadFileVersion(fileId: string, body: models.schemas.FileIdContentCreateRequest, opts?: UploadsManagerUploadFileVersionOptions): Promise<models.schemas.Files>;
|
|
19
|
+
uploadFile(body: models.schemas.FileContentCreateRequest, opts?: UploadsManagerUploadFileOptions): Promise<models.schemas.Files>;
|
|
20
20
|
optionFileContent(body: models.schemas.OptionFileContentRequest): Promise<models.schemas.UploadUrl>;
|
|
21
21
|
}
|
|
@@ -3059,12 +3059,12 @@ export interface OptionFileContentRequest {
|
|
|
3059
3059
|
size?: number;
|
|
3060
3060
|
parent?: PostFileIdParent;
|
|
3061
3061
|
}
|
|
3062
|
-
export interface
|
|
3062
|
+
export interface FileUploadSessionCreateRequest {
|
|
3063
3063
|
folder_id: string;
|
|
3064
3064
|
file_size: number;
|
|
3065
3065
|
file_name: string;
|
|
3066
3066
|
}
|
|
3067
|
-
export interface
|
|
3067
|
+
export interface FileVersionUploadSessionCreateRequest {
|
|
3068
3068
|
file_size: number;
|
|
3069
3069
|
file_name?: string;
|
|
3070
3070
|
}
|
package/docs/auth.md
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
Build a `Client` from one of the four Box auth flows. Each flow is an `Auth`
|
|
5
5
|
the runtime uses to fetch a bearer token; the network layer refreshes on a
|
|
6
|
-
`401` and retries once. The flow builders live in the
|
|
7
|
-
passed straight to `new Client(...)`.
|
|
6
|
+
`401` and retries once. The flow builders live in the `auth` namespace (JWT on
|
|
7
|
+
the Node-only `./jwt` subpath) and are passed straight to `new Client(...)`.
|
|
8
8
|
|
|
9
9
|
## Developer Token
|
|
10
10
|
|
|
11
11
|
A fixed token from the Developer Console, for quick local testing:
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import {
|
|
15
|
-
const client = new Client(developerToken('DEVELOPER_TOKEN'));
|
|
14
|
+
import { auth } from '@unofficialbox/box-open-sdk';
|
|
15
|
+
const client = new Client(auth.developerToken('DEVELOPER_TOKEN'));
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Client Credentials Grant (server auth, no key)
|
|
@@ -21,8 +21,8 @@ Set exactly one subject — `enterpriseId` for the service account, or `userId`
|
|
|
21
21
|
to act as a managed user:
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import {
|
|
25
|
-
const client = new Client(clientCredentials({
|
|
24
|
+
import { auth } from '@unofficialbox/box-open-sdk';
|
|
25
|
+
const client = new Client(auth.clientCredentials({
|
|
26
26
|
clientId: 'CLIENT_ID',
|
|
27
27
|
clientSecret: 'CLIENT_SECRET',
|
|
28
28
|
enterpriseId: 'ENTERPRISE_ID',
|
|
@@ -36,7 +36,7 @@ loudly at construction. It reaches `node:crypto`, so it is a Node-only leaf
|
|
|
36
36
|
imported from the runtime's `./jwt` entry point and returns a promise:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
import { jwtAuth } from 'box-
|
|
39
|
+
import { jwtAuth } from '@unofficialbox/box-open-sdk/jwt';
|
|
40
40
|
const auth = await jwtAuth({
|
|
41
41
|
clientId: 'CLIENT_ID',
|
|
42
42
|
clientSecret: 'CLIENT_SECRET',
|
|
@@ -53,8 +53,8 @@ Redirect the user to the authorize URL, exchange the returned code, then reuse
|
|
|
53
53
|
the rotated refresh token on later runs (persist it via `onRefresh`):
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
|
-
import {
|
|
56
|
+
import { auth } from '@unofficialbox/box-open-sdk';
|
|
57
57
|
// later runs: resume from the stored refresh token.
|
|
58
|
-
const
|
|
59
|
-
const client = new Client(
|
|
58
|
+
const source = auth.oauth({ clientId: 'CLIENT_ID', clientSecret: 'CLIENT_SECRET' }, storedRefreshToken);
|
|
59
|
+
const client = new Client(source);
|
|
60
60
|
```
|