Skip to content

Commit 31583a0

Browse files
committed
fix: don't reference globalThis.Buffer when env=browser (stephenh#967)
The generated bytesFromBase64 and base64FromBytes functions now only include code that's required for the specified env: For env=node, the functions now exclusively use globalThis.Buffer to de-/encode to and from JSON For env=browser, globalThis.btoa/atob is used For env=both, the two functions use either the node or browser implementations depending on whether globalThis.Buffer exists
1 parent 061eaf8 commit 31583a0

File tree

13 files changed

+271
-57
lines changed

13 files changed

+271
-57
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Message } from './message';
2+
3+
describe('bytes-as-base64', () => {
4+
type TestData = [string, Uint8Array];
5+
const testData: TestData[] = [
6+
['3q2+7w==', Uint8Array.from([0xDE, 0xAD, 0xBE, 0xEF])],
7+
['AAAAAAAAAAAAAAAAAAAAAA==', new Uint8Array(16).fill(0x00)],
8+
['/////////////////////w==', new Uint8Array(16).fill(0xFF)],
9+
['AAECAwQFBgcICQoLDA0ODw==', new Uint8Array(16).map((_, i) => i)],
10+
];
11+
12+
it('fromJSON can decode bytes from base64', () => {
13+
for (const entry of testData) {
14+
const message = Message.fromJSON({ data: entry[0] });
15+
expect(message).toEqual({ data: entry[1] });
16+
}
17+
});
18+
19+
it('toJSON can encode bytes as base64', () => {
20+
for (const entry of testData) {
21+
const message = Message.toJSON({ data: entry[1] });
22+
expect(message).toEqual({ data: entry[0] });
23+
}
24+
});
25+
26+
it('fromJSON and toJSON can handle "large" bytes fields', () => {
27+
const LENGTH = 1000000; // 1 MB
28+
const messageA = { data: new Uint8Array(LENGTH).fill(0xFF) };
29+
const json = Message.toJSON(messageA);
30+
expect(json).toHaveProperty('data');
31+
const messageB = Message.fromJSON(json);
32+
expect(messageA).toEqual(messageB);
33+
});
34+
});
181 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
syntax = "proto3";
2+
3+
message Message {
4+
bytes data = 1;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* eslint-disable */
2+
3+
export const protobufPackage = "";
4+
5+
export interface Message {
6+
data: Uint8Array;
7+
}
8+
9+
function createBaseMessage(): Message {
10+
return { data: new Uint8Array(0) };
11+
}
12+
13+
export const Message = {
14+
fromJSON(object: any): Message {
15+
return { data: isSet(object.data) ? bytesFromBase64(object.data) : new Uint8Array(0) };
16+
},
17+
18+
toJSON(message: Message): unknown {
19+
const obj: any = {};
20+
if (message.data.length !== 0) {
21+
obj.data = base64FromBytes(message.data);
22+
}
23+
return obj;
24+
},
25+
26+
create<I extends Exact<DeepPartial<Message>, I>>(base?: I): Message {
27+
return Message.fromPartial(base ?? ({} as any));
28+
},
29+
fromPartial<I extends Exact<DeepPartial<Message>, I>>(object: I): Message {
30+
const message = createBaseMessage();
31+
message.data = object.data ?? new Uint8Array(0);
32+
return message;
33+
},
34+
};
35+
36+
function bytesFromBase64(b64: string): Uint8Array {
37+
const bin = globalThis.atob(b64);
38+
const arr = new Uint8Array(bin.length);
39+
for (let i = 0; i < bin.length; ++i) {
40+
arr[i] = bin.charCodeAt(i);
41+
}
42+
return arr;
43+
}
44+
45+
function base64FromBytes(arr: Uint8Array): string {
46+
const bin: string[] = [];
47+
arr.forEach((byte) => {
48+
bin.push(globalThis.String.fromCharCode(byte));
49+
});
50+
return globalThis.btoa(bin.join(""));
51+
}
52+
53+
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
54+
55+
export type DeepPartial<T> = T extends Builtin ? T
56+
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
57+
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
58+
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
59+
: Partial<T>;
60+
61+
type KeysOfUnion<T> = T extends T ? keyof T : never;
62+
export type Exact<P, I extends P> = P extends Builtin ? P
63+
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
64+
65+
function isSet(value: any): boolean {
66+
return value !== null && value !== undefined;
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
outputEncodeMethods=false,outputJsonMethods=true,env=browser
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Message } from './message';
2+
3+
describe('bytes-as-base64', () => {
4+
type TestData = [string, Buffer];
5+
const testData: TestData[] = [
6+
['3q2+7w==', Buffer.from([0xDE, 0xAD, 0xBE, 0xEF])],
7+
['AAAAAAAAAAAAAAAAAAAAAA==', Buffer.alloc(16).fill(0x00)],
8+
['/////////////////////w==', Buffer.alloc(16).fill(0xFF)],
9+
['AAECAwQFBgcICQoLDA0ODw==', Buffer.from(Array.from({length: 16}).map((_, i) => i))],
10+
];
11+
12+
it('fromJSON can decode bytes from base64', () => {
13+
for (const entry of testData) {
14+
const message = Message.fromJSON({ data: entry[0] });
15+
expect(message).toEqual({ data: entry[1] });
16+
}
17+
});
18+
19+
it('toJSON can encode bytes as base64', () => {
20+
for (const entry of testData) {
21+
const message = Message.toJSON({ data: entry[1] });
22+
expect(message).toEqual({ data: entry[0] });
23+
}
24+
});
25+
26+
it('fromJSON and toJSON can handle "large" bytes fields', () => {
27+
const LENGTH = 1000000; // 1 MB
28+
const messageA = { data: Buffer.alloc(LENGTH).fill(0xFF) };
29+
const json = Message.toJSON(messageA);
30+
expect(json).toHaveProperty('data');
31+
const messageB = Message.fromJSON(json);
32+
expect(messageA).toEqual(messageB);
33+
});
34+
});
181 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
syntax = "proto3";
2+
3+
message Message {
4+
bytes data = 1;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* eslint-disable */
2+
3+
export const protobufPackage = "";
4+
5+
export interface Message {
6+
data: Buffer;
7+
}
8+
9+
function createBaseMessage(): Message {
10+
return { data: Buffer.alloc(0) };
11+
}
12+
13+
export const Message = {
14+
fromJSON(object: any): Message {
15+
return { data: isSet(object.data) ? Buffer.from(bytesFromBase64(object.data)) : Buffer.alloc(0) };
16+
},
17+
18+
toJSON(message: Message): unknown {
19+
const obj: any = {};
20+
if (message.data.length !== 0) {
21+
obj.data = base64FromBytes(message.data);
22+
}
23+
return obj;
24+
},
25+
26+
create<I extends Exact<DeepPartial<Message>, I>>(base?: I): Message {
27+
return Message.fromPartial(base ?? ({} as any));
28+
},
29+
fromPartial<I extends Exact<DeepPartial<Message>, I>>(object: I): Message {
30+
const message = createBaseMessage();
31+
message.data = object.data ?? Buffer.alloc(0);
32+
return message;
33+
},
34+
};
35+
36+
function bytesFromBase64(b64: string): Uint8Array {
37+
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
38+
}
39+
40+
function base64FromBytes(arr: Uint8Array): string {
41+
return globalThis.Buffer.from(arr).toString("base64");
42+
}
43+
44+
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
45+
46+
export type DeepPartial<T> = T extends Builtin ? T
47+
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
48+
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
49+
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
50+
: Partial<T>;
51+
52+
type KeysOfUnion<T> = T extends T ? keyof T : never;
53+
export type Exact<P, I extends P> = P extends Builtin ? P
54+
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
55+
56+
function isSet(value: any): boolean {
57+
return value !== null && value !== undefined;
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
outputEncodeMethods=false,outputJsonMethods=true,env=node

integration/bytes-node/google/protobuf/wrappers.ts

+2-19
Original file line numberDiff line numberDiff line change
@@ -608,28 +608,11 @@ export const BytesValue = {
608608
};
609609

610610
function bytesFromBase64(b64: string): Uint8Array {
611-
if (globalThis.Buffer) {
612-
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
613-
} else {
614-
const bin = globalThis.atob(b64);
615-
const arr = new Uint8Array(bin.length);
616-
for (let i = 0; i < bin.length; ++i) {
617-
arr[i] = bin.charCodeAt(i);
618-
}
619-
return arr;
620-
}
611+
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
621612
}
622613

623614
function base64FromBytes(arr: Uint8Array): string {
624-
if (globalThis.Buffer) {
625-
return globalThis.Buffer.from(arr).toString("base64");
626-
} else {
627-
const bin: string[] = [];
628-
arr.forEach((byte) => {
629-
bin.push(globalThis.String.fromCharCode(byte));
630-
});
631-
return globalThis.btoa(bin.join(""));
632-
}
615+
return globalThis.Buffer.from(arr).toString("base64");
633616
}
634617

635618
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

integration/bytes-node/point.ts

+2-19
Original file line numberDiff line numberDiff line change
@@ -84,28 +84,11 @@ export const Point = {
8484
};
8585

8686
function bytesFromBase64(b64: string): Uint8Array {
87-
if (globalThis.Buffer) {
88-
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
89-
} else {
90-
const bin = globalThis.atob(b64);
91-
const arr = new Uint8Array(bin.length);
92-
for (let i = 0; i < bin.length; ++i) {
93-
arr[i] = bin.charCodeAt(i);
94-
}
95-
return arr;
96-
}
87+
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
9788
}
9889

9990
function base64FromBytes(arr: Uint8Array): string {
100-
if (globalThis.Buffer) {
101-
return globalThis.Buffer.from(arr).toString("base64");
102-
} else {
103-
const bin: string[] = [];
104-
arr.forEach((byte) => {
105-
bin.push(globalThis.String.fromCharCode(byte));
106-
});
107-
return globalThis.btoa(bin.join(""));
108-
}
91+
return globalThis.Buffer.from(arr).toString("base64");
10992
}
11093

11194
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

src/main.ts

+62-19
Original file line numberDiff line numberDiff line change
@@ -550,36 +550,79 @@ function makeByteUtils(options: Options) {
550550
);
551551
const globalThis = options.globalThisPolyfill ? globalThisPolyfill : conditionalOutput("globalThis", code``);
552552

553+
function getBytesFromBase64Snippet() {
554+
const bytesFromBase64NodeSnippet = code`
555+
return Uint8Array.from(${globalThis}.Buffer.from(b64, 'base64'));
556+
`;
557+
558+
const bytesFromBase64BrowserSnippet = code`
559+
const bin = ${globalThis}.atob(b64);
560+
const arr = new Uint8Array(bin.length);
561+
for (let i = 0; i < bin.length; ++i) {
562+
arr[i] = bin.charCodeAt(i);
563+
}
564+
return arr;
565+
`;
566+
567+
switch (options.env) {
568+
case EnvOption.NODE:
569+
return bytesFromBase64NodeSnippet;
570+
case EnvOption.BROWSER:
571+
return bytesFromBase64BrowserSnippet;
572+
default:
573+
return code`
574+
if (${globalThis}.Buffer) {
575+
${bytesFromBase64NodeSnippet}
576+
} else {
577+
${bytesFromBase64BrowserSnippet}
578+
}
579+
`;
580+
}
581+
}
582+
553583
const bytesFromBase64 = conditionalOutput(
554584
"bytesFromBase64",
555585
code`
556586
function bytesFromBase64(b64: string): Uint8Array {
557-
if (${globalThis}.Buffer) {
558-
return Uint8Array.from(${globalThis}.Buffer.from(b64, 'base64'));
559-
} else {
560-
const bin = ${globalThis}.atob(b64);
561-
const arr = new Uint8Array(bin.length);
562-
for (let i = 0; i < bin.length; ++i) {
563-
arr[i] = bin.charCodeAt(i);
564-
}
565-
return arr;
566-
}
587+
${getBytesFromBase64Snippet()}
567588
}
568589
`,
569590
);
591+
592+
function getBase64FromBytesSnippet() {
593+
const base64FromBytesNodeSnippet = code`
594+
return ${globalThis}.Buffer.from(arr).toString('base64');
595+
`;
596+
597+
const base64FromBytesBrowserSnippet = code`
598+
const bin: string[] = [];
599+
arr.forEach((byte) => {
600+
bin.push(${globalThis}.String.fromCharCode(byte));
601+
});
602+
return ${globalThis}.btoa(bin.join(''));
603+
`;
604+
605+
switch (options.env) {
606+
case EnvOption.NODE:
607+
return base64FromBytesNodeSnippet;
608+
case EnvOption.BROWSER:
609+
return base64FromBytesBrowserSnippet;
610+
default:
611+
return code`
612+
if (${globalThis}.Buffer) {
613+
${base64FromBytesNodeSnippet}
614+
} else {
615+
${base64FromBytesBrowserSnippet}
616+
}
617+
`
618+
}
619+
}
620+
570621
const base64FromBytes = conditionalOutput(
571622
"base64FromBytes",
572623
code`
573624
function base64FromBytes(arr: Uint8Array): string {
574-
if (${globalThis}.Buffer) {
575-
return ${globalThis}.Buffer.from(arr).toString('base64')
576-
} else {
577-
const bin: string[] = [];
578-
arr.forEach((byte) => {
579-
bin.push(${globalThis}.String.fromCharCode(byte));
580-
});
581-
return ${globalThis}.btoa(bin.join(''));
582-
}
625+
${getBase64FromBytesSnippet()}
583626
}
584627
`,
585628
);

0 commit comments

Comments
 (0)