Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: don't reference globalThis.Buffer when env=browser (#967) #999

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Message } from './message';

describe('bytes-as-base64', () => {
type TestData = [string, Uint8Array];
const testData: TestData[] = [
['3q2+7w==', Uint8Array.from([0xDE, 0xAD, 0xBE, 0xEF])],
['AAAAAAAAAAAAAAAAAAAAAA==', new Uint8Array(16).fill(0x00)],
['/////////////////////w==', new Uint8Array(16).fill(0xFF)],
['AAECAwQFBgcICQoLDA0ODw==', new Uint8Array(16).map((_, i) => i)],
];

it('fromJSON can decode bytes from base64', () => {
for (const entry of testData) {
const message = Message.fromJSON({ data: entry[0] });
expect(message).toEqual({ data: entry[1] });
}
});

it('toJSON can encode bytes as base64', () => {
for (const entry of testData) {
const message = Message.toJSON({ data: entry[1] });
expect(message).toEqual({ data: entry[0] });
}
});

it('fromJSON and toJSON can handle "large" bytes fields', () => {
const LENGTH = 1000000; // 1 MB
const messageA = { data: new Uint8Array(LENGTH).fill(0xFF) };
const json = Message.toJSON(messageA);
expect(json).toHaveProperty('data');
const messageB = Message.fromJSON(json);
expect(messageA).toEqual(messageB);
});
});
Binary file added integration/bytes-as-base64-browser/message.bin
Binary file not shown.
5 changes: 5 additions & 0 deletions integration/bytes-as-base64-browser/message.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message Message {
bytes data = 1;
}
67 changes: 67 additions & 0 deletions integration/bytes-as-base64-browser/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* eslint-disable */

export const protobufPackage = "";

export interface Message {
data: Uint8Array;
}

function createBaseMessage(): Message {
return { data: new Uint8Array(0) };
}

export const Message = {
fromJSON(object: any): Message {
return { data: isSet(object.data) ? bytesFromBase64(object.data) : new Uint8Array(0) };
},

toJSON(message: Message): unknown {
const obj: any = {};
if (message.data.length !== 0) {
obj.data = base64FromBytes(message.data);
}
return obj;
},

create<I extends Exact<DeepPartial<Message>, I>>(base?: I): Message {
return Message.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<Message>, I>>(object: I): Message {
const message = createBaseMessage();
message.data = object.data ?? new Uint8Array(0);
return message;
},
};

function bytesFromBase64(b64: string): Uint8Array {
const bin = globalThis.atob(b64);
const arr = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
arr[i] = bin.charCodeAt(i);
}
return arr;
}

function base64FromBytes(arr: Uint8Array): string {
const bin: string[] = [];
arr.forEach((byte) => {
bin.push(globalThis.String.fromCharCode(byte));
});
return globalThis.btoa(bin.join(""));
}

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

export type DeepPartial<T> = T extends Builtin ? T
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
1 change: 1 addition & 0 deletions integration/bytes-as-base64-browser/parameters.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
outputEncodeMethods=false,outputJsonMethods=true,env=browser
34 changes: 34 additions & 0 deletions integration/bytes-as-base64-node/bytes-as-base64-node-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Message } from './message';

describe('bytes-as-base64', () => {
type TestData = [string, Buffer];
const testData: TestData[] = [
['3q2+7w==', Buffer.from([0xDE, 0xAD, 0xBE, 0xEF])],
['AAAAAAAAAAAAAAAAAAAAAA==', Buffer.alloc(16).fill(0x00)],
['/////////////////////w==', Buffer.alloc(16).fill(0xFF)],
['AAECAwQFBgcICQoLDA0ODw==', Buffer.from(Array.from({length: 16}).map((_, i) => i))],
];

it('fromJSON can decode bytes from base64', () => {
for (const entry of testData) {
const message = Message.fromJSON({ data: entry[0] });
expect(message).toEqual({ data: entry[1] });
}
});

it('toJSON can encode bytes as base64', () => {
for (const entry of testData) {
const message = Message.toJSON({ data: entry[1] });
expect(message).toEqual({ data: entry[0] });
}
});

it('fromJSON and toJSON can handle "large" bytes fields', () => {
const LENGTH = 1000000; // 1 MB
const messageA = { data: Buffer.alloc(LENGTH).fill(0xFF) };
const json = Message.toJSON(messageA);
expect(json).toHaveProperty('data');
const messageB = Message.fromJSON(json);
expect(messageA).toEqual(messageB);
});
});
Binary file added integration/bytes-as-base64-node/message.bin
Binary file not shown.
5 changes: 5 additions & 0 deletions integration/bytes-as-base64-node/message.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message Message {
bytes data = 1;
}
58 changes: 58 additions & 0 deletions integration/bytes-as-base64-node/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable */

export const protobufPackage = "";

export interface Message {
data: Buffer;
}

function createBaseMessage(): Message {
return { data: Buffer.alloc(0) };
}

export const Message = {
fromJSON(object: any): Message {
return { data: isSet(object.data) ? Buffer.from(bytesFromBase64(object.data)) : Buffer.alloc(0) };
},

toJSON(message: Message): unknown {
const obj: any = {};
if (message.data.length !== 0) {
obj.data = base64FromBytes(message.data);
}
return obj;
},

create<I extends Exact<DeepPartial<Message>, I>>(base?: I): Message {
return Message.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<Message>, I>>(object: I): Message {
const message = createBaseMessage();
message.data = object.data ?? Buffer.alloc(0);
return message;
},
};

function bytesFromBase64(b64: string): Uint8Array {
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
}

function base64FromBytes(arr: Uint8Array): string {
return globalThis.Buffer.from(arr).toString("base64");
}

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

export type DeepPartial<T> = T extends Builtin ? T
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
1 change: 1 addition & 0 deletions integration/bytes-as-base64-node/parameters.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
outputEncodeMethods=false,outputJsonMethods=true,env=node
21 changes: 2 additions & 19 deletions integration/bytes-node/google/protobuf/wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,28 +608,11 @@ export const BytesValue = {
};

function bytesFromBase64(b64: string): Uint8Array {
if (globalThis.Buffer) {
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
} else {
const bin = globalThis.atob(b64);
const arr = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
arr[i] = bin.charCodeAt(i);
}
return arr;
}
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
}

function base64FromBytes(arr: Uint8Array): string {
if (globalThis.Buffer) {
return globalThis.Buffer.from(arr).toString("base64");
} else {
const bin: string[] = [];
arr.forEach((byte) => {
bin.push(globalThis.String.fromCharCode(byte));
});
return globalThis.btoa(bin.join(""));
}
return globalThis.Buffer.from(arr).toString("base64");
}

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
Expand Down
21 changes: 2 additions & 19 deletions integration/bytes-node/point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,28 +84,11 @@ export const Point = {
};

function bytesFromBase64(b64: string): Uint8Array {
if (globalThis.Buffer) {
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
} else {
const bin = globalThis.atob(b64);
const arr = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
arr[i] = bin.charCodeAt(i);
}
return arr;
}
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
}

function base64FromBytes(arr: Uint8Array): string {
if (globalThis.Buffer) {
return globalThis.Buffer.from(arr).toString("base64");
} else {
const bin: string[] = [];
arr.forEach((byte) => {
bin.push(globalThis.String.fromCharCode(byte));
});
return globalThis.btoa(bin.join(""));
}
return globalThis.Buffer.from(arr).toString("base64");
}

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
Expand Down
81 changes: 62 additions & 19 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,36 +550,79 @@ function makeByteUtils(options: Options) {
);
const globalThis = options.globalThisPolyfill ? globalThisPolyfill : conditionalOutput("globalThis", code``);

function getBytesFromBase64Snippet() {
const bytesFromBase64NodeSnippet = code`
return Uint8Array.from(${globalThis}.Buffer.from(b64, 'base64'));
`;

const bytesFromBase64BrowserSnippet = code`
const bin = ${globalThis}.atob(b64);
const arr = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
arr[i] = bin.charCodeAt(i);
}
return arr;
`;

switch (options.env) {
case EnvOption.NODE:
return bytesFromBase64NodeSnippet;
case EnvOption.BROWSER:
return bytesFromBase64BrowserSnippet;
default:
return code`
if (${globalThis}.Buffer) {
${bytesFromBase64NodeSnippet}
} else {
${bytesFromBase64BrowserSnippet}
}
`;
}
}

const bytesFromBase64 = conditionalOutput(
"bytesFromBase64",
code`
function bytesFromBase64(b64: string): Uint8Array {
if (${globalThis}.Buffer) {
return Uint8Array.from(${globalThis}.Buffer.from(b64, 'base64'));
} else {
const bin = ${globalThis}.atob(b64);
const arr = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
arr[i] = bin.charCodeAt(i);
}
return arr;
}
${getBytesFromBase64Snippet()}
}
`,
);

function getBase64FromBytesSnippet() {
const base64FromBytesNodeSnippet = code`
return ${globalThis}.Buffer.from(arr).toString('base64');
`;

const base64FromBytesBrowserSnippet = code`
const bin: string[] = [];
arr.forEach((byte) => {
bin.push(${globalThis}.String.fromCharCode(byte));
});
return ${globalThis}.btoa(bin.join(''));
`;

switch (options.env) {
case EnvOption.NODE:
return base64FromBytesNodeSnippet;
case EnvOption.BROWSER:
return base64FromBytesBrowserSnippet;
default:
return code`
if (${globalThis}.Buffer) {
${base64FromBytesNodeSnippet}
} else {
${base64FromBytesBrowserSnippet}
}
`;
}
}

const base64FromBytes = conditionalOutput(
"base64FromBytes",
code`
function base64FromBytes(arr: Uint8Array): string {
if (${globalThis}.Buffer) {
return ${globalThis}.Buffer.from(arr).toString('base64')
} else {
const bin: string[] = [];
arr.forEach((byte) => {
bin.push(${globalThis}.String.fromCharCode(byte));
});
return ${globalThis}.btoa(bin.join(''));
}
${getBase64FromBytesSnippet()}
}
`,
);
Expand Down
Loading