• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

box / box-node-sdk / 26924385648

03 Jun 2026 12:44PM UTC coverage: 41.016% (-0.06%) from 41.079%
26924385648

push

github

web-flow
chore: release version 10.11.1 (#1503)

4939 of 20841 branches covered (23.7%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

47 existing lines in 7 files now uncovered.

17983 of 35045 relevant lines covered (51.31%)

169.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.78
/src/internal/utilsNode.ts
1
import { Buffer } from 'buffer';
246✔
2
import { Readable } from 'stream';
246✔
3
import { SignJWT, importPKCS8 } from 'jose';
246✔
4
import { default as crypto } from 'crypto';
246✔
5
import { default as fs } from 'fs';
246✔
6
import { ProxyAgent } from 'proxy-agent';
246✔
7
import { default as FormData } from 'form-data';
246✔
8
import util from 'util';
246✔
9

10
export { Buffer, Readable as ByteStream, FormData, util as utilLib };
729✔
11
export { default as nodeFetch } from 'node-fetch';
3,024✔
12
export type { RequestInit } from 'node-fetch';
13
export type AgentOptions = any;
14
export type Agent = any;
15
export type HashName = 'sha1';
16
export type DigestHashType = 'base64';
17

18
export class Hash {
246✔
19
  hash: any;
20
  algorithm: HashName;
21

22
  constructor({ algorithm }: { algorithm: HashName }) {
23
    this.algorithm = algorithm;
36✔
24
    this.hash = crypto.createHash(algorithm);
36✔
25
  }
26

27
  async updateHash(data: Buffer) {
28
    this.hash.update(data);
54✔
29
  }
30

31
  async digestHash(encoding: DigestHashType = 'base64'): Promise<string> {
36!
32
    return this.hash.digest(encoding);
36✔
33
  }
34
}
35

36
export function generateByteBuffer(size: number): Buffer {
246✔
37
  return crypto.randomBytes(size);
201✔
38
}
39

40
export function generateReadableStreamFromFile(
246✔
41
  file: any,
42
  chunkSize: number = 1024 * 1024,
×
43
): ReadableStream {
UNCOV
44
  throw new Error('This function is only supported in the browser');
×
45
}
46

47
export function generateByteStreamFromBuffer(
246✔
48
  buffer: Buffer | ArrayBuffer,
49
): Readable {
50
  const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
5,352✔
51
  return Readable.from(buf);
5,352✔
52
}
53

54
export function decodeBase64ByteStream(data: string): Readable {
246✔
55
  return Readable.from(Buffer.from(data, 'base64'));
3✔
56
}
57

58
export function stringToByteStream(data: string): Readable {
246✔
59
  return Readable.from(Buffer.from(data, 'ascii'));
9✔
60
}
61

62
export async function readByteStream(byteStream: Readable): Promise<Buffer> {
246✔
63
  const buffers: Buffer[] = [];
477✔
64
  for await (const data of byteStream) {
25,332✔
65
    buffers.push(data);
12,666✔
66
  }
67
  return Buffer.concat(buffers);
477✔
68
}
69

70
export async function* iterateChunks(
246✔
71
  stream: Readable,
72
  chunkSize: number,
73
  fileSize: number,
74
): AsyncGenerator<Readable> {
75
  let buffers: Buffer[] = [];
9✔
76
  let totalSize = 0;
9✔
77
  let consumedSize = 0;
9✔
78
  while (consumedSize < fileSize && !stream.readableEnded) {
9✔
79
    for await (const chunk of stream) {
27✔
80
      const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
9!
81
      if (!Buffer.isBuffer(data)) {
9!
UNCOV
82
        throw new Error('Expecting a chunk of stream to be a Buffer');
×
83
      }
84
      consumedSize += data.length;
9✔
85
      buffers.push(data);
9✔
86
      totalSize += data.length;
9✔
87

88
      if (totalSize < chunkSize) {
9!
UNCOV
89
        continue;
×
90
      }
91

92
      const buffer = Buffer.concat(buffers);
9✔
93

94
      let start = 0;
9✔
95
      while (totalSize >= chunkSize) {
9✔
96
        yield generateByteStreamFromBuffer(
18✔
97
          buffer.subarray(start, start + chunkSize),
98
        );
99
        start += chunkSize;
18✔
100
        totalSize -= chunkSize;
18✔
101
      }
102

103
      buffers = totalSize > 0 ? [buffer.subarray(start)] : [];
9!
104
    }
105
  }
106

107
  if (consumedSize !== fileSize) {
9!
UNCOV
108
    throw new Error(
×
109
      `Stream size ${consumedSize} does not match expected file size ${fileSize}`,
110
    );
111
  }
112
  if (totalSize > 0) {
9✔
113
    yield generateByteStreamFromBuffer(Buffer.concat(buffers));
9✔
114
  }
115
}
116

117
/**
118
 * Interface used for private key decryption in JWT auth.
119
 */
120
export interface PrivateKeyDecryptor {
121
  /**
122
   * Decrypts private key using a passphrase.
123
   */
124
  decryptPrivateKey(encryptedPrivateKey: string, passphrase: string): string;
125
}
126

127
export class DefaultPrivateKeyDecryptor implements PrivateKeyDecryptor {
246✔
128
  constructor(fields: Omit<DefaultPrivateKeyDecryptor, 'decryptPrivateKey'>) {}
129
  decryptPrivateKey(encryptedPrivateKey: string, passphrase: string): string {
130
    const privateKey = crypto.createPrivateKey({
426✔
131
      key: encryptedPrivateKey,
132
      format: 'pem',
133
      type: 'pkcs8',
134
      passphrase: passphrase,
135
    });
136
    const pem = privateKey.export({ type: 'pkcs8', format: 'pem' }).toString();
426✔
137

138
    return pem;
426✔
139
  }
140
}
141

142
export type JwtKey = {
143
  key: string;
144
  passphrase: string;
145
};
146

147
export type JwtAlgorithm =
148
  | 'HS256'
149
  | 'HS384'
150
  | 'HS512'
151
  | 'RS256'
152
  | 'RS384'
153
  | 'RS512'
154
  | 'ES256'
155
  | 'ES384'
156
  | 'ES512'
157
  | 'PS256'
158
  | 'PS384'
159
  | 'PS512'
160
  | 'none';
161

162
export type JwtSignOptions = {
163
  algorithm?: JwtAlgorithm;
164
  keyid?: string | undefined;
165
  expiresIn?: string | number | undefined;
166
  notBefore?: string | number | undefined;
167
  audience?: string | string[] | undefined;
168
  subject?: string | undefined;
169
  issuer?: string | undefined;
170
  jwtid?: string | undefined;
171
  privateKeyDecryptor: PrivateKeyDecryptor | undefined;
172
};
173

174
/**
175
 * Creates a JWT assertion.
176
 *
177
 * @param claims
178
 * @param key
179
 * @param options
180
 * @returns
181
 */
182
export async function createJwtAssertion(
246✔
183
  claims: {
184
    readonly [key: string]: any;
185
  },
186
  key: JwtKey,
187
  options: JwtSignOptions,
188
): Promise<string> {
189
  const pem = options.privateKeyDecryptor?.decryptPrivateKey(
426!
190
    key.key,
191
    key.passphrase,
192
  );
193
  if (!pem) {
426!
UNCOV
194
    throw new Error(`Decrypted jwt private key is empty`);
×
195
  }
196
  const pkcs8 = await importPKCS8(pem, options.algorithm || 'RS256');
426!
197
  let signer = new SignJWT(claims);
426✔
198
  signer = options.audience ? signer.setAudience(options.audience) : signer;
426!
199
  signer = options.expiresIn
426!
200
    ? signer.setExpirationTime(options.expiresIn)
201
    : signer;
202
  signer = options.issuer ? signer.setIssuer(options.issuer) : signer;
426!
203
  signer = options.jwtid ? signer.setJti(options.jwtid) : signer;
426!
204
  signer = options.notBefore ? signer.setNotBefore(options.notBefore) : signer;
426!
205
  signer = options.subject ? signer.setSubject(options.subject) : signer;
426!
206
  signer = options.algorithm
426!
207
    ? signer.setProtectedHeader({ alg: options.algorithm })
208
    : signer;
209
  signer = signer.setIssuedAt();
426✔
210
  return await signer.sign(pkcs8);
426✔
211
}
212

213
/**
214
 * Reads a text file and returns its content.
215
 */
216
export function readTextFromFile(filepath: string): string {
246✔
UNCOV
217
  return fs.readFileSync(filepath, 'utf8');
×
218
}
219

220
/**
221
 * Create web agent from proxy agent options.
222
 */
223
export function createAgent(options?: AgentOptions, proxyConfig?: any): Agent {
246✔
224
  let agentOptions = options;
45,486✔
225

226
  if (proxyConfig && proxyConfig.url) {
45,486!
UNCOV
227
    if (!proxyConfig.url.startsWith('http')) {
×
UNCOV
228
      throw new Error('Invalid proxy URL');
×
229
    }
230

231
    const proxyHost = proxyConfig.url.split('//')[1];
×
232
    const proxyAuth =
UNCOV
233
      proxyConfig.username && proxyConfig.password
×
234
        ? `${proxyConfig.username}:${proxyConfig.password}@`
235
        : '';
236
    const proxyUrl = `http://${proxyAuth}${proxyHost}`;
×
UNCOV
237
    agentOptions = Object.assign(
×
UNCOV
238
      { getProxyForUrl: (url: string) => proxyUrl },
×
239
      options || {},
×
240
    );
241
  }
242

243
  return agentOptions ? new ProxyAgent(agentOptions) : new ProxyAgent();
45,486!
244
}
245

246
/**
247
 * Stringify JSON with escaped multibyte Unicode characters and slashes to ensure computed signatures match PHP's default behavior
248
 *
249
 * @param {Object} body - The parsed JSON object
250
 * @returns {string} - Stringified JSON with escaped multibyte Unicode characters
251
 * @private
252
 */
253
export function jsonStringifyWithEscapedUnicode(body: string) {
246✔
254
  return body
69✔
255
    .replace(
256
      /[\u007f-\uffff]/g,
257
      (char) => `\\u${`0000${char.charCodeAt(0).toString(16)}`.slice(-4)}`,
195✔
258
    )
259
    .replace(/(?<!\\)\//g, '\\/');
260
}
261

262
/**
263
 * Compute the message signature
264
 * @see {@Link https://developer.box.com/en/guides/webhooks/handle/setup-signatures/}
265
 *
266
 * @param {string} body - The request body of the webhook message
267
 * @param {Object} headers - The request headers of the webhook message
268
 * @param {string} signatureKey - The signature to verify the message with
269
 * @param {string} escapeBody - Indicates if payload should be escaped or left as is
270
 * @returns {?string} - The message signature (or null, if it can't be computed)
271
 * @private
272
 */
273
export async function computeWebhookSignature(
246✔
274
  body: string,
99✔
275
  headers: {
276
    [key: string]: string;
277
  },
278
  signatureKey: string,
279
  escapeBody: boolean = false,
×
280
): Promise<string | null> {
281
  if (headers['box-signature-version'] !== '1') {
99!
UNCOV
282
    return null;
×
283
  }
284
  if (headers['box-signature-algorithm'] !== 'HmacSHA256') {
99!
UNCOV
285
    return null;
×
286
  }
287
  let signature: string | null = null;
99✔
288

289
  const escapedBody = escapeBody ? jsonStringifyWithEscapedUnicode(body) : body;
99✔
290
  let hmac = crypto.createHmac('sha256', signatureKey);
99✔
291
  hmac.update(escapedBody);
99✔
292
  hmac.update(headers['box-delivery-timestamp']);
99✔
293
  signature = hmac.digest('base64');
99✔
294
  return signature;
99✔
295
}
296

297
export async function compareSignatures(
246✔
298
  expectedSignature: string | null,
299
  receivedSignature: string | null,
300
): Promise<boolean> {
301
  if (!expectedSignature || !receivedSignature) {
75!
UNCOV
302
    return false;
×
303
  }
304

305
  const expectedBuffer = Buffer.from(expectedSignature, 'base64');
75✔
306
  const receivedBuffer = Buffer.from(receivedSignature, 'base64');
75✔
307

308
  if (expectedBuffer.length !== receivedBuffer.length) {
75!
UNCOV
309
    return false;
×
310
  }
311

312
  return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
75✔
313
}
314

315
export function random(min: number, max: number): number {
246✔
UNCOV
316
  return Math.random() * (max - min) + min;
×
317
}
318

319
export async function calculateMD5Hash(data: string | Buffer): Promise<string> {
246✔
320
  return crypto.createHash('sha1').update(data).digest('hex');
201✔
321
}
322

323
export function getEnvVar(name: string): string {
246✔
324
  if (typeof process === 'undefined' || !process.env) {
729!
UNCOV
325
    throw new Error('This function requires a Node.js environment');
×
326
  }
327
  return process.env[name] || '';
729!
328
}
329

330
export function setEnvVar(name: string, value: string): void {
246✔
UNCOV
331
  if (typeof process === 'undefined' || !process.env) {
×
332
    throw new Error('This function requires a Node.js environment');
×
333
  }
UNCOV
334
  process.env[name] = value;
×
335
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc