From d12d971ef52712d6397ce884396bc6cb76e5e2c1 Mon Sep 17 00:00:00 2001 From: Holly Date: Tue, 24 Oct 2023 12:04:32 +0100 Subject: [PATCH] Add browser js support --- .github/workflows/browser.yml | 33 +++ .github/workflows/node.js.yml | 2 +- base/BufferShim.ts | 389 ++++++++++++++++++++++++++++++++++ base/WriterBase.ts | 4 +- package-lock.json | 53 ++--- package.json | 15 +- tooling/fileSmasher.ts | 6 +- 7 files changed, 461 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/browser.yml create mode 100644 base/BufferShim.ts diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml new file mode 100644 index 0000000..bfb31b0 --- /dev/null +++ b/.github/workflows/browser.yml @@ -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 diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 8a61823..aebd7a7 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -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: diff --git a/base/BufferShim.ts b/base/BufferShim.ts new file mode 100644 index 0000000..96d3fe3 --- /dev/null +++ b/base/BufferShim.ts @@ -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 | 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, 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 | 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; + } +} \ No newline at end of file diff --git a/base/WriterBase.ts b/base/WriterBase.ts index 38387a6..735e9b0 100644 --- a/base/WriterBase.ts +++ b/base/WriterBase.ts @@ -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; } diff --git a/package-lock.json b/package-lock.json index 4cc5122..1f08a19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "bufferstuff", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bufferstuff", - "version": "1.3.0", + "version": "1.3.1", "license": "MIT", "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" @@ -33,7 +34,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -57,7 +57,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -67,7 +66,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -496,8 +494,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/call-bind": { "version": "1.0.2", @@ -593,8 +590,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/concat-map": { "version": "0.0.1", @@ -1905,7 +1901,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -1915,7 +1910,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -2058,14 +2052,13 @@ } }, "node_modules/terser": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", - "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", + "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", "dev": true, - "peer": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -2463,7 +2456,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, - "peer": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -2480,15 +2472,13 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "peer": true + "dev": true }, "@jridgewell/source-map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dev": true, - "peer": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -2866,8 +2856,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "peer": true + "dev": true }, "call-bind": { "version": "1.0.2", @@ -2928,8 +2917,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true + "dev": true }, "concat-map": { "version": "0.0.1", @@ -3908,15 +3896,13 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true + "dev": true }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "peer": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -4026,14 +4012,13 @@ "dev": true }, "terser": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", - "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", + "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", "dev": true, - "peer": true, "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" } diff --git a/package.json b/package.json index d7bc1da..18ce75d 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/tooling/fileSmasher.ts b/tooling/fileSmasher.ts index f17c0ee..8c2ec64 100644 --- a/tooling/fileSmasher.ts +++ b/tooling/fileSmasher.ts @@ -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"));