Skip to content

Commit

Permalink
Add browser js support
Browse files Browse the repository at this point in the history
tgpholly committed Oct 24, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent dd3eb43 commit d12d971
Showing 7 changed files with 461 additions and 41 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/browser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Build for Browser

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
# Don't run updateCheck for now
#- run: npm run dev:updateCheck
- run: npm run buildweb
#- run: npm test
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI
name: Build for NodeJS

on:
push:
389 changes: 389 additions & 0 deletions base/BufferShim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,389 @@
// Copyright (c) Holly Stubbs (tgpholly) - Licensed under MIT
// Check LICENSE in repository root for more information.

// This is a mostly 1:1 feature complete implementation of node's Buffer class for browsers.
// It's missing some of the static stuff but everything in the object *should* be there.

class BrowserBuffer {
public buffer:Uint8Array;
private dataView:DataView;

public constructor(dataOrSize: Array<number> | ArrayBufferLike | number) {
if (typeof(dataOrSize) === "number") {
this.buffer = new Uint8Array(dataOrSize);
} else if (typeof(dataOrSize) === "object") {
this.buffer = new Uint8Array(dataOrSize); // Convert whatever comes in to a Uint8Array.
} else {
this.buffer = new Uint8Array(0); // Fallback
}

this.dataView = new DataView(this.buffer.buffer);
}

public static allocUnsafe(size:number) { return this.alloc(size); }
public static allocUnsafeSlow(size:number) { return this.alloc(size); }
public static alloc(size:number) {
return new BrowserBuffer(size);
}

public static concat(buffers:Array<BrowserBuffer>, totalLength?:number) {
let joinedArrays:Uint8Array;
if (totalLength !== undefined) {
joinedArrays = new Uint8Array(totalLength);
} else {
let arraysLength = 0;
for (const buffer of buffers) {
arraysLength += buffer.length;
}
joinedArrays = new Uint8Array(arraysLength);
}

let offset = 0;
for (const buffer of buffers) {
joinedArrays.set(buffer.buffer, offset);
offset += buffer.length;
}

return new BrowserBuffer(joinedArrays);
}

public static from(data: Array<number> | ArrayBufferLike | string) {
if (typeof(data) === "string") {
throw new Error("BrowserBuffer does not currently support creating buffers from strings.");
}

return new BrowserBuffer(data);
}

// NOTE: Here to match node buffer, has no use.
public static readonly poolSize:number = 8192;

// TODO: Implement
public static of() {}
public static isBuffer() {}
public static isEncoding() {}
public static byteLength() {}
public static copyBytesFrom() {}
public static compare() {}

public get length() {
return this.buffer.length;
}

private checkRanged(value:number|bigint, valueName:string, lowRange:number|bigint, highRange:number|bigint) {
if (value < lowRange || value > highRange) {
throw new Error(`The value of "${valueName}" is out of range. It must be >= ${lowRange} and <= ${highRange}. Received ${value}`);
}
}

private checkValue(value:number|bigint, low:number|bigint, high:number|bigint) {
this.checkRanged(value, "value", low, high);
}

private checkOffset(offset?:number) {
if (offset) {
this.checkRanged(offset, "offset", 0, this.buffer.length - 1);
}
}

// Writing methods
public writeInt8(value:number, offset:number) {
this.checkValue(value, -128, 127);
this.checkOffset(offset);

this.dataView.setInt8(offset, value);

return this;
}

public writeUInt8(value:number, offset:number) {
this.checkValue(value, 0, 255);
this.checkOffset(offset);

this.dataView.setUint8(offset, value);

return this;
}

// Little Endian

public writeInt16LE(value:number, offset:number) {
this.checkValue(value, -32768, 32767);
this.checkOffset(offset);

this.dataView.setInt16(offset, value, true);

return this;
}

public writeUInt16LE(value:number, offset:number) {
this.checkValue(value, 0, 65535);
this.checkOffset(offset);

this.dataView.setUint16(offset, value, true);

return this;
}

public writeInt32LE(value:number, offset:number) {
this.checkValue(value, -2147483648, 2147483647);
this.checkOffset(offset);

this.dataView.setInt32(offset, value, true);

return this;
}

public writeUInt32LE(value:number, offset:number) {
this.checkValue(value, 0, 4294967295);
this.checkOffset(offset);

this.dataView.setUint32(offset, value, true);

return this;
}

public writeBigInt64LE(value:bigint|number, offset:number) {
if (typeof(value) === "number") {
value = BigInt(value);
}

this.checkValue(value, -(2n ** 63n), 2n ** 63n);
this.checkOffset(offset);

this.dataView.setBigInt64(offset, value, true);

return this;
}

public writeBigUint64LE(value:bigint|number, offset:number) {
if (typeof(value) === "number") {
value = BigInt(value);
}

this.checkValue(value, 0n, 2n ** 64n);
this.checkOffset(offset);

this.dataView.setBigUint64(offset, value, true);

return this;
}

public writeFloatLE(value:number, offset:number) {
this.checkOffset(offset);

this.dataView.setFloat32(offset, value, true);

return this;
}

public writeDoubleLE(value:number, offset:number) {
this.checkOffset(offset);

this.dataView.setFloat64(offset, value, true);

return this;
}

// Big Endian
public writeInt16BE(value:number, offset:number) {
this.checkValue(value, -32768, 32767);
this.checkOffset(offset);

this.dataView.setInt16(offset, value, false);

return this;
}

public writeUInt16BE(value:number, offset:number) {
this.checkValue(value, 0, 65535);
this.checkOffset(offset);

this.dataView.setUint16(offset, value, false);

return this;
}

public writeInt32BE(value:number, offset:number) {
this.checkValue(value, -2147483648, 2147483647);
this.checkOffset(offset);

this.dataView.setInt32(offset, value, false);

return this;
}

public writeUInt32BE(value:number, offset:number) {
this.checkValue(value, 0, 4294967295);
this.checkOffset(offset);

this.dataView.setUint32(offset, value, false);

return this;
}

public writeBigInt64BE(value:bigint|number, offset:number) {
if (typeof(value) === "number") {
value = BigInt(value);
}

this.checkValue(value, -(2n ** 63n), 2n ** 63n);
this.checkOffset(offset);

this.dataView.setBigInt64(offset, value, false);

return this;
}

public writeBigUint64BE(value:bigint|number, offset:number) {
if (typeof(value) === "number") {
value = BigInt(value);
}

this.checkValue(value, 0n, 2n ** 64n);
this.checkOffset(offset);

this.dataView.setBigUint64(offset, value, false);

return this;
}

public writeFloatBE(value:number, offset:number) {
this.checkOffset(offset);

this.dataView.setFloat32(offset, value, false);

return this;
}

public writeDoubleBE(value:number, offset:number) {
this.checkOffset(offset);

this.dataView.setFloat64(offset, value, false);

return this;
}

// Reading methods
public readInt8(offset:number) {
this.checkOffset(offset);

return this.dataView.getInt8(offset);
}

public readUInt8(offset:number) {
this.checkOffset(offset);

return this.dataView.getUint8(offset);
}

// Little Endian

public readInt16LE(offset:number) {
this.checkOffset(offset);

return this.dataView.getInt16(offset, true);
}

public readUInt16LE(offset:number) {
this.checkOffset(offset);

return this.dataView.getUint16(offset, true);
}

public readInt32LE(offset:number) {
this.checkOffset(offset);

return this.dataView.getInt32(offset, true);
}

public readUInt32LE(offset:number) {
this.checkOffset(offset);

return this.dataView.getUint32(offset, true);
}

public readBigInt64LE(offset:number) {
this.checkOffset(offset);

return this.dataView.getBigInt64(offset, true);
}

public readBigUint64LE(offset:number) {
this.checkOffset(offset);

return this.dataView.getBigUint64(offset, true);
}

public readFloatLE(offset:number) {
this.checkOffset(offset);

return this.dataView.getFloat32(offset, true);
}

public readDoubleLE(offset:number) {
this.checkOffset(offset);

return this.dataView.getFloat64(offset, true);
}

// Big Endian
public readInt16BE(offset:number) {
this.checkOffset(offset);

return this.dataView.getInt16(offset, false);
}

public readUInt16BE(offset:number) {
this.checkOffset(offset);

return this.dataView.getUint16(offset, false);
}

public readInt32BE(offset:number) {
this.checkOffset(offset);

return this.dataView.getInt32(offset, false);
}

public readUInt32BE(offset:number) {
this.checkOffset(offset);

return this.dataView.getUint32(offset, false);
}

public readBigInt64BE(offset:number) {
this.checkOffset(offset);

return this.dataView.getBigInt64(offset, false);
}

public readBigUint64BE(offset:number) {
this.checkOffset(offset);

return this.dataView.getBigUint64(offset, false);
}

public readFloatBE(offset:number) {
this.checkOffset(offset);

return this.dataView.getFloat32(offset, false);
}

public readDoubleBE(offset:number) {
this.checkOffset(offset);

return this.dataView.getFloat64(offset, false);
}
}

export function getBufferClass() : BufferConstructor {
if (typeof(Buffer) === "undefined") {
console.log("Using BrowserBuffer implementation.");
// @ts-ignore
return BrowserBuffer;
} else {
console.log("Using native Buffer implementation.");
return Buffer;
}
}
4 changes: 3 additions & 1 deletion base/WriterBase.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Copyright (c) Holly Stubbs (tgpholly) - Licensed under MIT
// Check LICENSE in repository root for more information.

import { getBufferClass } from "./BufferShim";

export class WriterBase {
public buffer:Buffer;
public offset:number;
public readonly resizable:boolean;

public constructor(size:number = 0) {
this.buffer = Buffer.alloc(size);
this.buffer = getBufferClass().alloc(size);
this.offset = 0;
this.resizable = size === 0;
}
53 changes: 19 additions & 34 deletions package-lock.json
15 changes: 11 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
{
"name": "bufferstuff",
"version": "1.3.0",
"version": "1.3.1",
"description": "A set of utility classes for reading and writing binary data in NodeJS and the browser",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"updateCheck": "check-outdated",
"_clean": "tsc --build --clean",

"build": "npm-run-all build:*",
"build:smash": "ts-node ./tooling/fileSmasher.ts",
"build:build": "tsc --build",
"build:cleanup": "ts-node ./tooling/cleanup.ts",
"_clean": "tsc --build --clean"

"buildweb": "npm-run-all buildweb:*",
"buildweb:smash": "ts-node ./tooling/fileSmasher.ts forweb",
"buildweb:build": "tsc --build",
"buildweb:cleanup": "ts-node ./tooling/cleanup.ts"
},
"repository": {
"type": "git",
@@ -25,9 +31,10 @@
"homepage": "https://github.com/tgpholly/bufferStuff#readme",
"devDependencies": {
"check-outdated": "^2.11.0",
"npm-run-all": "^4.1.5",
"terser": "^5.22.0",
"ts-loader": "^9.4.4",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"npm-run-all": "^4.1.5"
"typescript": "^5.1.6"
}
}
6 changes: 5 additions & 1 deletion tooling/fileSmasher.ts
Original file line number Diff line number Diff line change
@@ -63,7 +63,11 @@ for (const line of splitLines) {
continue;
}
// Fix up classes, interfaces and such.
resultLines.push(checkForMatchAndReplace(line));
if (process.argv[2] === "forweb") {
resultLines.push(line.replace("export class", "class").replace("export function", "function").replace("export enum", "enum").replace("export interface", "interface"));
} else {
resultLines.push(checkForMatchAndReplace(line));
}
}

writeFileSync("./combined.ts", resultLines.join("\n"));

0 comments on commit d12d971

Please sign in to comment.