Skip to content

Commit 7232170

Browse files
fix: Modify utils functions for browser (box/box-codegen#686) (#585)
1 parent a7dcaf0 commit 7232170

29 files changed

+7834
-108
lines changed

.codegen.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "engineHash": "15d82b5", "specHash": "c303afc", "version": "1.15.1" }
1+
{ "engineHash": "4148197", "specHash": "c303afc", "version": "1.15.1" }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: build-and-test
2+
on:
3+
pull_request:
4+
branches:
5+
- main
6+
push:
7+
branches:
8+
- main
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
browser: ['chrome', 'firefox']
15+
name: Browser ${{ matrix.browser }}
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v3
19+
- name: Setup Node
20+
uses: actions/setup-node@v3
21+
with:
22+
node-version: '18'
23+
- name: Install dependencies and build
24+
run: |
25+
npm install
26+
npm run build
27+
- name: Run tests
28+
run: |
29+
cd test-browser
30+
npm install
31+
npx start-server-and-test build-run 3000 'cypress run --browser ${{ matrix.browser }}'
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }}
35+
ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }}
36+
CLIENT_ID: ${{ secrets.CLIENT_ID }}
37+
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
38+
USER_ID: ${{ secrets.USER_ID }}
39+
ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }}
40+
BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }}
41+
BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }}
42+
WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }}
43+
APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }}
44+
APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }}
45+
APP_ITEM_SHARED_LINK: ${{ secrets.APP_ITEM_SHARED_LINK }}

package-lock.json

Lines changed: 90 additions & 90 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal/utils.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ import {
2020
Buffer,
2121
FormData,
2222
generateReadableStreamFromFile,
23+
getEnvVar,
24+
setEnvVar,
2325
utilLib,
2426
} from './utilsNode';
27+
import { MultipartItem } from '../networking';
2528
import { sanitizedValue } from '../serialization/json';
2629

2730
export type HashName = 'sha1';
@@ -44,6 +47,8 @@ export {
4447
jsonStringifyWithEscapedUnicode,
4548
computeWebhookSignature,
4649
calculateMD5Hash,
50+
getEnvVar,
51+
setEnvVar,
4752
utilLib,
4853
};
4954

@@ -133,10 +138,6 @@ export function hexStrToBase64(hex: string) {
133138
return base64;
134139
}
135140

136-
export function getEnvVar(name: string) {
137-
return process.env[name] || '';
138-
}
139-
140141
export function generateByteStream(size: number): ByteStream {
141142
return generateByteStreamFromBuffer(generateByteBuffer(size));
142143
}
@@ -243,6 +244,42 @@ export function random(min: number, max: number): number {
243244
return Math.random() * (max - min) + min;
244245
}
245246

247+
export async function multipartStreamToBuffer(
248+
multipart: readonly MultipartItem[],
249+
): Promise<(MultipartItem & { fileStreamBuffer?: Buffer })[]> {
250+
return await Promise.all(
251+
multipart.map(async (item) => {
252+
if (!item.fileStream) {
253+
return item;
254+
}
255+
return {
256+
...item,
257+
fileStream: undefined,
258+
fileStreamBuffer: item.fileStream
259+
? await readByteStream(item.fileStream)
260+
: undefined,
261+
};
262+
}),
263+
);
264+
}
265+
266+
export function multipartBufferToStream(
267+
multipart: (MultipartItem & { fileStreamBuffer?: Buffer })[],
268+
): readonly MultipartItem[] {
269+
return multipart.map((item) => {
270+
if (!item.fileStreamBuffer) {
271+
return item;
272+
}
273+
return {
274+
...item,
275+
fileStreamBuffer: undefined,
276+
fileStream: item.fileStreamBuffer
277+
? generateByteStreamFromBuffer(item.fileStreamBuffer)
278+
: undefined,
279+
} as MultipartItem;
280+
});
281+
}
282+
246283
/**
247284
* Sanitize a map by replacing sensitive values with a placeholder.
248285
* @param dictionary The map to sanitize

src/internal/utilsBrowser.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,17 @@ export class Hash {
5757
}
5858

5959
export function generateByteBuffer(size: number): Buffer {
60+
// Maximum size for crypto.getRandomValues is 65536 bytes
61+
const MAX_CHUNK_SIZE = 65536;
6062
const buffer = new Uint8Array(size);
61-
window.crypto.getRandomValues(buffer);
63+
64+
for (let offset = 0; offset < size; offset += MAX_CHUNK_SIZE) {
65+
const length = Math.min(MAX_CHUNK_SIZE, size - offset);
66+
const chunk = new Uint8Array(length);
67+
window.crypto.getRandomValues(chunk);
68+
buffer.set(chunk, offset);
69+
}
70+
6271
return Buffer.from(buffer);
6372
}
6473

@@ -124,6 +133,27 @@ export function stringToByteStream(data: string): ReadableStream {
124133
});
125134
}
126135

136+
export function getEnvVar(name: string): string {
137+
if (
138+
typeof window !== 'undefined' &&
139+
(window as any).env &&
140+
(window as any).env[name]
141+
) {
142+
return (window as any).env[name];
143+
}
144+
return '';
145+
}
146+
147+
export function setEnvVar(name: string, value: string): void {
148+
if (typeof window === 'undefined') {
149+
throw new Error('This function requires a browser environment');
150+
}
151+
if (!(window as any).env) {
152+
(window as any).env = {};
153+
}
154+
(window as any).env[name] = value;
155+
}
156+
127157
export async function readByteStream(byteStream: ByteStream): Promise<Buffer> {
128158
const buffers: Buffer[] = [];
129159

src/internal/utilsNode.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,17 @@ export function random(min: number, max: number): number {
271271
export async function calculateMD5Hash(data: string | Buffer): Promise<string> {
272272
return crypto.createHash('sha1').update(data).digest('hex');
273273
}
274+
275+
export function getEnvVar(name: string): string {
276+
if (typeof process === 'undefined' || !process.env) {
277+
throw new Error('This function requires a Node.js environment');
278+
}
279+
return process.env[name] || '';
280+
}
281+
282+
export function setEnvVar(name: string, value: string): void {
283+
if (typeof process === 'undefined' || !process.env) {
284+
throw new Error('This function requires a Node.js environment');
285+
}
286+
process.env[name] = value;
287+
}

src/networking/boxNetworkClient.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
isBrowser,
99
readByteStream,
1010
calculateMD5Hash,
11+
multipartStreamToBuffer,
12+
multipartBufferToStream,
1113
} from '../internal/utils';
1214
import { sdkVersion } from './version';
1315
import { NetworkClient } from './networkClient.generated';
@@ -26,7 +28,15 @@ import { NetworkSession } from './network.generated';
2628
export const userAgentHeader = `Box JavaScript generated SDK v${sdkVersion} (${
2729
isBrowser() ? navigator.userAgent : `Node ${process.version}`
2830
})`;
31+
2932
export const xBoxUaHeader = constructBoxUAHeader();
33+
export const shouldIncludeBoxUaHeader = (options: FetchOptions) => {
34+
return !(
35+
isBrowser() &&
36+
(options.responseFormat === 'binary' ||
37+
options.responseFormat === 'no_content')
38+
);
39+
};
3040

3141
export interface MultipartItem {
3242
readonly partName: string;
@@ -118,15 +128,18 @@ async function createRequestInit(options: FetchOptions): Promise<RequestInit> {
118128
return {
119129
method,
120130
headers: {
121-
...contentHeaders,
131+
// Only set content type if it is not a GET request
132+
...(method != 'GET' && contentHeaders),
122133
...headers,
123134
...(options.auth && {
124135
Authorization: await options.auth.retrieveAuthorizationHeader(
125136
options.networkSession,
126137
),
127138
}),
128-
'User-Agent': userAgentHeader,
129-
'X-Box-UA': xBoxUaHeader,
139+
...(shouldIncludeBoxUaHeader(options) && {
140+
'User-Agent': userAgentHeader,
141+
'X-Box-UA': xBoxUaHeader,
142+
}),
130143
// Additional headers will override the default headers
131144
...options.networkSession?.additionalHeaders,
132145
},
@@ -157,11 +170,18 @@ export class BoxNetworkClient implements NetworkClient {
157170
const fileStreamBuffer = fetchOptions.fileStream
158171
? await readByteStream(fetchOptions.fileStream)
159172
: void 0;
173+
const multipartBuffer = fetchOptions.multipartData
174+
? await multipartStreamToBuffer(fetchOptions.multipartData)
175+
: void 0;
176+
160177
const requestInit = await createRequestInit({
161178
...fetchOptions,
162179
fileStream: fileStreamBuffer
163180
? generateByteStreamFromBuffer(fileStreamBuffer)
164181
: void 0,
182+
multipartData: multipartBuffer
183+
? multipartBufferToStream(multipartBuffer)
184+
: void 0,
165185
});
166186

167187
const { params = {} } = fetchOptions;
@@ -220,7 +240,16 @@ export class BoxNetworkClient implements NetworkClient {
220240
numRetries,
221241
);
222242
await new Promise((resolve) => setTimeout(resolve, retryTimeout));
223-
return this.fetch({ ...options, numRetries: numRetries + 1 });
243+
return this.fetch({
244+
...options,
245+
numRetries: numRetries + 1,
246+
fileStream: fileStreamBuffer
247+
? generateByteStreamFromBuffer(fileStreamBuffer)
248+
: void 0,
249+
multipartData: multipartBuffer
250+
? multipartBufferToStream(multipartBuffer)
251+
: void 0,
252+
});
224253
}
225254

226255
if (

src/test/commons.generated.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ import { BoxClient } from '../client.generated.js';
8080
import { ClassificationTemplate } from '../schemas/classificationTemplate.generated.js';
8181
import { FileFull } from '../schemas/fileFull.generated.js';
8282
import { TermsOfService } from '../schemas/termsOfService.generated.js';
83+
import { Authentication } from '../networking/auth.generated.js';
84+
import { BoxCcgAuth } from '../box/ccgAuth.generated.js';
85+
import { CcgConfig } from '../box/ccgAuth.generated.js';
86+
import { isBrowser } from '../internal/utils.js';
8387
import { BoxJwtAuth } from '../box/jwtAuth.generated.js';
8488
import { JwtConfig } from '../box/jwtAuth.generated.js';
8589
import { toString } from '../internal/utils.js';
@@ -91,6 +95,15 @@ import { sdIsNumber } from '../serialization/json.js';
9195
import { sdIsString } from '../serialization/json.js';
9296
import { sdIsList } from '../serialization/json.js';
9397
import { sdIsMap } from '../serialization/json.js';
98+
export function getCcgAuth(): BoxCcgAuth {
99+
const ccgConfig: CcgConfig = new CcgConfig({
100+
clientId: getEnvVar('CLIENT_ID'),
101+
clientSecret: getEnvVar('CLIENT_SECRET'),
102+
enterpriseId: getEnvVar('ENTERPRISE_ID'),
103+
});
104+
const auth: BoxCcgAuth = new BoxCcgAuth({ config: ccgConfig });
105+
return auth;
106+
}
94107
export function getJwtAuth(): BoxJwtAuth {
95108
const jwtConfig: JwtConfig = JwtConfig.fromConfigJsonString(
96109
decodeBase64(getEnvVar('JWT_CONFIG_BASE_64')),
@@ -99,12 +112,19 @@ export function getJwtAuth(): BoxJwtAuth {
99112
return auth;
100113
}
101114
export function getDefaultClientWithUserSubject(userId: string): BoxClient {
115+
if (isBrowser()) {
116+
const ccgAuth: BoxCcgAuth = getCcgAuth();
117+
const ccgAuthUser: BoxCcgAuth = ccgAuth.withUserSubject(userId);
118+
return new BoxClient({ auth: ccgAuthUser });
119+
}
102120
const auth: BoxJwtAuth = getJwtAuth();
103121
const authUser: BoxJwtAuth = auth.withUserSubject(userId);
104122
return new BoxClient({ auth: authUser });
105123
}
106124
export function getDefaultClient(): BoxClient {
107-
const client: BoxClient = new BoxClient({ auth: getJwtAuth() });
125+
const client: BoxClient = new BoxClient({
126+
auth: isBrowser() ? getCcgAuth() : getJwtAuth(),
127+
});
108128
return client;
109129
}
110130
export async function createNewFolder(): Promise<FolderFull> {

src/test/files.generated.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { UploadFileRequestBody } from '../managers/uploads.generated.js';
3333
import { UploadFileRequestBodyAttributesField } from '../managers/uploads.generated.js';
3434
import { UploadFileRequestBodyAttributesParentField } from '../managers/uploads.generated.js';
3535
import { GetFileThumbnailUrlExtension } from '../managers/files.generated.js';
36+
import { Buffer } from '../internal/utils.js';
3637
import { GetFileThumbnailByIdExtension } from '../managers/files.generated.js';
3738
import { GetFileByIdQueryParams } from '../managers/files.generated.js';
3839
import { GetFileByIdHeaders } from '../managers/files.generated.js';
@@ -46,6 +47,8 @@ import { CopyFileRequestBodyParentField } from '../managers/files.generated.js';
4647
import { getUuid } from '../internal/utils.js';
4748
import { generateByteStream } from '../internal/utils.js';
4849
import { readByteStream } from '../internal/utils.js';
50+
import { generateByteStreamFromBuffer } from '../internal/utils.js';
51+
import { generateByteBuffer } from '../internal/utils.js';
4952
import { bufferEquals } from '../internal/utils.js';
5053
import { ByteStream } from '../internal/utils.js';
5154
import { createNull } from '../internal/utils.js';
@@ -93,7 +96,9 @@ test('testGetFileThumbnailUrl', async function testGetFileThumbnailUrl(): Promis
9396
});
9497
test('testGetFileThumbnail', async function testGetFileThumbnail(): Promise<any> {
9598
const thumbnailFileName: string = getUuid();
96-
const thumbnailContentStream: ByteStream = generateByteStream(1024 * 1024);
99+
const thumbnailBuffer: Buffer = generateByteBuffer(1024 * 1024);
100+
const thumbnailContentStream: ByteStream =
101+
generateByteStreamFromBuffer(thumbnailBuffer);
97102
const thumbnailFile: FileFull = await uploadFile(
98103
thumbnailFileName,
99104
thumbnailContentStream,
@@ -104,12 +109,7 @@ test('testGetFileThumbnail', async function testGetFileThumbnail(): Promise<any>
104109
'png' as GetFileThumbnailByIdExtension,
105110
);
106111
if (
107-
!!(
108-
bufferEquals(
109-
await readByteStream(thumbnail!),
110-
await readByteStream(thumbnailContentStream),
111-
) == true
112-
)
112+
!!(bufferEquals(await readByteStream(thumbnail!), thumbnailBuffer) == true)
113113
) {
114114
throw new Error('Assertion failed');
115115
}

0 commit comments

Comments
 (0)