@unofficialbox/box-open-sdk 0.8.0 → 0.9.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.
@@ -181,10 +181,13 @@ class Client {
181
181
  webhooks;
182
182
  workflows;
183
183
  zipDownloads;
184
+ #session;
184
185
  /** Build a client for an authentication flow; `options` configures the
185
- * runtime session (retries, `fetch`, default headers, base URLs). */
186
+ * runtime session (retries, `fetch`, default headers, base URLs). An
187
+ * already configured runtime session is used as is. */
186
188
  constructor(auth, options = {}) {
187
- const session = new runtime_js_1.Client(auth, options);
189
+ const session = auth instanceof runtime_js_1.Client ? auth : new runtime_js_1.Client(auth, options);
190
+ this.#session = session;
188
191
  this.ai = new ai_js_1.AiManager(session);
189
192
  this.aiStudio = new ai_studio_js_1.AiStudioManager(session);
190
193
  this.appItemAssociations = new app_item_associations_js_1.AppItemAssociationsManager(session);
@@ -274,5 +277,11 @@ class Client {
274
277
  this.workflows = new workflows_js_1.WorkflowsManager(session);
275
278
  this.zipDownloads = new zip_downloads_js_1.ZipDownloadsManager(session);
276
279
  }
280
+ /** A new client whose every API call acts as the user `userId` (Box's
281
+ * `As-User` header). It shares this client's auth and configuration;
282
+ * this client is unchanged. */
283
+ withAsUserHeader(userId) {
284
+ return new Client(this.#session.withAsUserHeader(userId));
285
+ }
277
286
  }
278
287
  exports.Client = Client;
@@ -57,8 +57,9 @@ function apiErrorFromResponse(status, body) {
57
57
  const value = payload[key];
58
58
  return typeof value === 'string' ? value : undefined;
59
59
  };
60
- const code = field('code');
61
- const boxMessage = field('message');
60
+ // A Box error object, or from the token endpoint an OAuth 2.0 error.
61
+ const code = field('code') ?? field('error');
62
+ const boxMessage = field('message') ?? field('error_description');
62
63
  const requestId = field('request_id');
63
64
  let message = `box: status ${status}`;
64
65
  if (code !== undefined) {
@@ -145,5 +145,20 @@ class AuthorizationManager {
145
145
  req = runtime.withFormBody(req, new TextEncoder().encode((0, internal_js_1.formEncode)(form)));
146
146
  await this.session.fetch(req);
147
147
  }
148
+ /** Exchange the session's current access token for one limited to `scopes`
149
+ * and, when given, to one item (`resource`: its API URL, e.g.
150
+ * `https://api.box.com/2.0/files/123`) or a shared link. The result is a plain
151
+ * access token with no refresh token: build a client from it with
152
+ * `auth.developerToken`. */
153
+ async downscopeToken(scopes, resource, sharedLink) {
154
+ return this.createOauth2Token({
155
+ grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
156
+ subject_token: await this.session.accessToken(),
157
+ subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
158
+ scope: scopes.join(' '),
159
+ ...(resource === undefined ? {} : { resource: resource }),
160
+ ...(sharedLink === undefined ? {} : { box_shared_link: sharedLink }),
161
+ });
162
+ }
148
163
  }
149
164
  exports.AuthorizationManager = AuthorizationManager;
@@ -104,6 +104,20 @@ class Client {
104
104
  this.fetchImpl = options.fetch ?? fetch;
105
105
  this.defaultHeaders = { ...(options.headers ?? {}) };
106
106
  }
107
+ /** A new client that sends `As-User: userId` with every API request, so Box
108
+ * acts as that user (an admin, or a service account whose app may "make API
109
+ * calls using the as-user header"). It shares this client's auth flow and
110
+ * token cache, `fetch`, base URLs, and retry policy; this client is
111
+ * unchanged. Token requests never carry the header. */
112
+ withAsUserHeader(userId) {
113
+ const headers = Object.fromEntries(Object.entries(this.defaultHeaders).filter(([name]) => name.toLowerCase() !== 'as-user'));
114
+ return new Client(this.auth, {
115
+ maxRetries: this.maxRetries,
116
+ baseUrls: this.baseUrls,
117
+ fetch: this.fetchImpl,
118
+ headers: { ...headers, 'As-User': userId },
119
+ });
120
+ }
107
121
  /** The configured base URL for a D-106 class, without a trailing slash. */
108
122
  baseUrl(name) {
109
123
  return this.baseUrls[name] ?? '';
@@ -178,10 +178,13 @@ export class Client {
178
178
  webhooks;
179
179
  workflows;
180
180
  zipDownloads;
181
+ #session;
181
182
  /** Build a client for an authentication flow; `options` configures the
182
- * runtime session (retries, `fetch`, default headers, base URLs). */
183
+ * runtime session (retries, `fetch`, default headers, base URLs). An
184
+ * already configured runtime session is used as is. */
183
185
  constructor(auth, options = {}) {
184
- const session = new Session(auth, options);
186
+ const session = auth instanceof Session ? auth : new Session(auth, options);
187
+ this.#session = session;
185
188
  this.ai = new AiManager(session);
186
189
  this.aiStudio = new AiStudioManager(session);
187
190
  this.appItemAssociations = new AppItemAssociationsManager(session);
@@ -271,4 +274,10 @@ export class Client {
271
274
  this.workflows = new WorkflowsManager(session);
272
275
  this.zipDownloads = new ZipDownloadsManager(session);
273
276
  }
277
+ /** A new client whose every API call acts as the user `userId` (Box's
278
+ * `As-User` header). It shares this client's auth and configuration;
279
+ * this client is unchanged. */
280
+ withAsUserHeader(userId) {
281
+ return new Client(this.#session.withAsUserHeader(userId));
282
+ }
274
283
  }
@@ -51,8 +51,9 @@ export function apiErrorFromResponse(status, body) {
51
51
  const value = payload[key];
52
52
  return typeof value === 'string' ? value : undefined;
53
53
  };
54
- const code = field('code');
55
- const boxMessage = field('message');
54
+ // A Box error object, or from the token endpoint an OAuth 2.0 error.
55
+ const code = field('code') ?? field('error');
56
+ const boxMessage = field('message') ?? field('error_description');
56
57
  const requestId = field('request_id');
57
58
  let message = `box: status ${status}`;
58
59
  if (code !== undefined) {
@@ -109,4 +109,19 @@ export class AuthorizationManager {
109
109
  req = runtime.withFormBody(req, new TextEncoder().encode(formEncode(form)));
110
110
  await this.session.fetch(req);
111
111
  }
112
+ /** Exchange the session's current access token for one limited to `scopes`
113
+ * and, when given, to one item (`resource`: its API URL, e.g.
114
+ * `https://api.box.com/2.0/files/123`) or a shared link. The result is a plain
115
+ * access token with no refresh token: build a client from it with
116
+ * `auth.developerToken`. */
117
+ async downscopeToken(scopes, resource, sharedLink) {
118
+ return this.createOauth2Token({
119
+ grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
120
+ subject_token: await this.session.accessToken(),
121
+ subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
122
+ scope: scopes.join(' '),
123
+ ...(resource === undefined ? {} : { resource: resource }),
124
+ ...(sharedLink === undefined ? {} : { box_shared_link: sharedLink }),
125
+ });
126
+ }
112
127
  }
@@ -69,6 +69,20 @@ export class Client {
69
69
  this.fetchImpl = options.fetch ?? fetch;
70
70
  this.defaultHeaders = { ...(options.headers ?? {}) };
71
71
  }
72
+ /** A new client that sends `As-User: userId` with every API request, so Box
73
+ * acts as that user (an admin, or a service account whose app may "make API
74
+ * calls using the as-user header"). It shares this client's auth flow and
75
+ * token cache, `fetch`, base URLs, and retry policy; this client is
76
+ * unchanged. Token requests never carry the header. */
77
+ withAsUserHeader(userId) {
78
+ const headers = Object.fromEntries(Object.entries(this.defaultHeaders).filter(([name]) => name.toLowerCase() !== 'as-user'));
79
+ return new Client(this.auth, {
80
+ maxRetries: this.maxRetries,
81
+ baseUrls: this.baseUrls,
82
+ fetch: this.fetchImpl,
83
+ headers: { ...headers, 'As-User': userId },
84
+ });
85
+ }
72
86
  /** The configured base URL for a D-106 class, without a trailing slash. */
73
87
  baseUrl(name) {
74
88
  return this.baseUrls[name] ?? '';
@@ -1,4 +1,4 @@
1
- import { Auth, ClientOptions } from './runtime.js';
1
+ import { Auth, Client as Session, ClientOptions } from './runtime.js';
2
2
  import { AiManager } from './managers/ai.js';
3
3
  import { AiStudioManager } from './managers/ai_studio.js';
4
4
  import { AppItemAssociationsManager } from './managers/app_item_associations.js';
@@ -89,6 +89,7 @@ import { WorkflowsManager } from './managers/workflows.js';
89
89
  import { ZipDownloadsManager } from './managers/zip_downloads.js';
90
90
  /** The Box SDK client: one manager per API area over a shared session. */
91
91
  export declare class Client {
92
+ #private;
92
93
  readonly ai: AiManager;
93
94
  readonly aiStudio: AiStudioManager;
94
95
  readonly appItemAssociations: AppItemAssociationsManager;
@@ -178,6 +179,11 @@ export declare class Client {
178
179
  readonly workflows: WorkflowsManager;
179
180
  readonly zipDownloads: ZipDownloadsManager;
180
181
  /** Build a client for an authentication flow; `options` configures the
181
- * runtime session (retries, `fetch`, default headers, base URLs). */
182
- constructor(auth: Auth, options?: ClientOptions);
182
+ * runtime session (retries, `fetch`, default headers, base URLs). An
183
+ * already configured runtime session is used as is. */
184
+ constructor(auth: Auth | Session, options?: ClientOptions);
185
+ /** A new client whose every API call acts as the user `userId` (Box's
186
+ * `As-User` header). It shares this client's auth and configuration;
187
+ * this client is unchanged. */
188
+ withAsUserHeader(userId: string): Client;
183
189
  }
@@ -14,4 +14,10 @@ export declare class AuthorizationManager {
14
14
  createOauth2Token(body: models.schemas.PostOAuth2Token): Promise<models.schemas.AccessToken>;
15
15
  createOauth2TokenRefresh(body: models.schemas.PostOAuth2TokenRefreshAccessToken): Promise<models.schemas.AccessToken>;
16
16
  revokeOauth2(body: models.schemas.PostOAuth2Revoke): Promise<void>;
17
+ /** Exchange the session's current access token for one limited to `scopes`
18
+ * and, when given, to one item (`resource`: its API URL, e.g.
19
+ * `https://api.box.com/2.0/files/123`) or a shared link. The result is a plain
20
+ * access token with no refresh token: build a client from it with
21
+ * `auth.developerToken`. */
22
+ downscopeToken(scopes: readonly string[], resource?: string, sharedLink?: string): Promise<models.schemas.AccessToken>;
17
23
  }
@@ -53,6 +53,12 @@ export declare class Client {
53
53
  private readonly fetchImpl;
54
54
  private readonly defaultHeaders;
55
55
  constructor(auth: Auth, options?: ClientOptions);
56
+ /** A new client that sends `As-User: userId` with every API request, so Box
57
+ * acts as that user (an admin, or a service account whose app may "make API
58
+ * calls using the as-user header"). It shares this client's auth flow and
59
+ * token cache, `fetch`, base URLs, and retry policy; this client is
60
+ * unchanged. Token requests never carry the header. */
61
+ withAsUserHeader(userId: string): Client;
56
62
  /** The configured base URL for a D-106 class, without a trailing slash. */
57
63
  baseUrl(name: string): string;
58
64
  /** A valid access token for the configured auth flow. The `signal` is
package/docs/auth.md CHANGED
@@ -23,9 +23,9 @@ to act as a managed user:
23
23
  ```ts
24
24
  import { auth } from '@unofficialbox/box-open-sdk';
25
25
  const client = new Client(auth.clientCredentials({
26
- clientId: 'CLIENT_ID',
27
- clientSecret: 'CLIENT_SECRET',
28
- enterpriseId: 'ENTERPRISE_ID',
26
+ clientId: 'CLIENT_ID',
27
+ clientSecret: 'CLIENT_SECRET',
28
+ enterpriseId: 'ENTERPRISE_ID',
29
29
  }));
30
30
  ```
31
31
 
@@ -38,11 +38,11 @@ imported from the runtime's `./jwt` entry point and returns a promise:
38
38
  ```ts
39
39
  import { jwtAuth } from '@unofficialbox/box-open-sdk/jwt';
40
40
  const auth = await jwtAuth({
41
- clientId: 'CLIENT_ID',
42
- clientSecret: 'CLIENT_SECRET',
43
- publicKeyId: 'PUBLIC_KEY_ID',
44
- privateKeyPem: privateKeyPem, // optionally encrypted; set passphrase
45
- enterpriseId: 'ENTERPRISE_ID',
41
+ clientId: 'CLIENT_ID',
42
+ clientSecret: 'CLIENT_SECRET',
43
+ publicKeyId: 'PUBLIC_KEY_ID',
44
+ privateKeyPem: privateKeyPem, // optionally encrypted; set passphrase
45
+ enterpriseId: 'ENTERPRISE_ID',
46
46
  });
47
47
  const client = new Client(auth);
48
48
  ```
@@ -58,3 +58,30 @@ import { auth } from '@unofficialbox/box-open-sdk';
58
58
  const source = auth.oauth({ clientId: 'CLIENT_ID', clientSecret: 'CLIENT_SECRET' }, storedRefreshToken);
59
59
  const client = new Client(source);
60
60
  ```
61
+
62
+ ## Acting as another user
63
+
64
+ An admin, or a service account whose app may *make API calls using the as-user
65
+ header*, can act as another user. `withAsUserHeader` returns a new client whose
66
+ every call sends Box's `As-User` header; it shares the original's auth (and
67
+ token cache) and configuration, and the original is unchanged:
68
+
69
+ ```ts
70
+ const userClient = client.withAsUserHeader('USER_ID');
71
+ const me = await userClient.users.getMe(); // the user USER_ID
72
+ ```
73
+
74
+ ## Downscoping a token
75
+
76
+ Exchange the session's access token for one limited to `scopes` and,
77
+ optionally, to one item (its API URL) or shared link — e.g. to hand a
78
+ browser-side preview a narrow token. The result has no refresh token; build
79
+ a client from it as a developer token:
80
+
81
+ ```ts
82
+ const down = await client.authorization.downscopeToken(
83
+ ['item_preview'],
84
+ 'https://api.box.com/2.0/files/123',
85
+ );
86
+ const preview = new Client(auth.developerToken(down.access_token!));
87
+ ```
@@ -7,7 +7,7 @@ that yields one entry at a time, threading the cursor for you — the list metho
7
7
 
8
8
  ```ts
9
9
  for await (const item of client.folders.listItems(folderId)) {
10
- // use item
10
+ // use item
11
11
  }
12
12
  ```
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unofficialbox/box-open-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Box API client for TypeScript (open source, community, punk rock) — typed models, async managers, and a fetch runtime with retry, backoff, and token refresh.",
5
5
  "keywords": ["box", "box-api", "sdk", "api-client", "typescript", "unofficial"],
6
6
  "license": "MIT",