This commit is contained in:
2026-03-25 14:14:07 +01:00
parent d6b31e2ef7
commit a0073b4fb1
10368 changed files with 2214340 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
dist/
node_modules/

View File

@@ -0,0 +1,8 @@
{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}

View File

@@ -0,0 +1,19 @@
# 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: Bun CI
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: bun install --frozen-lockfile
- run: bun run build
- run: bun test
- run: bun run check

View File

@@ -0,0 +1,34 @@
# 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
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.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
- run: npm run build
- run: npm run coverage
- name: Publish code coverage to CodeClimate
uses: paambaati/codeclimate-action@v5.0.0
env:
CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}

View File

@@ -0,0 +1,16 @@
name: Check JavaScript for conformance with Prettier
on:
push:
pull_request:
jobs:
biome:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v1
- name: Run Biome
run: biome ci .

View File

@@ -0,0 +1,25 @@
name: Release
on:
push:
branches:
- rc
- stable
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build
run: bun run build
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GH_TOKEN }}
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }}
run: bun run semantic-release

View File

@@ -0,0 +1,17 @@
{
"branches": [
"stable",
{
"name": "rc",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/git",
"@semantic-release/github"
]
}

View File

@@ -0,0 +1,30 @@
# [2.1.0](https://github.com/peers/js-binarypack/compare/v2.0.0...v2.1.0) (2023-12-03)
### Features
* re-add Blob support ([cd17d63](https://github.com/peers/js-binarypack/commit/cd17d638e06dd4388145ac62f24c109030ee0e36)), closes [#61](https://github.com/peers/js-binarypack/issues/61)
* re-add Blob support ([#62](https://github.com/peers/js-binarypack/issues/62)) ([e79d1ee](https://github.com/peers/js-binarypack/commit/e79d1eef75a6a9e6f07f5af0aa031925583359ad)), closes [#61](https://github.com/peers/js-binarypack/issues/61)
# [2.0.0](https://github.com/peers/js-binarypack/compare/v1.0.2...v2.0.0) (2023-06-22)
### Bug Fixes
* empty TypedArray can now be packed ([3475f45](https://github.com/peers/js-binarypack/commit/3475f450a7bc97b757325cd54bc7ba7ffc84118b))
* undefined will stay undefined instead of null ([83af274](https://github.com/peers/js-binarypack/commit/83af274ea82fdd44d93546f18cbcf547abe77804))
### Features
* return `ArrayBuffer` instead of `Blob` ([6b70875](https://github.com/peers/js-binarypack/commit/6b70875b4d7db791fdd14a1f3ff3776d12febfb2))
### Reverts
* Revert "fix: undefined will stay undefined instead of null" ([da49137](https://github.com/peers/js-binarypack/commit/da4913787d9ab96845bd8e512d5f501574746a35))
### BREAKING CHANGES
* Return type of `pack` is now `ArrayBuffer`

View File

@@ -0,0 +1,22 @@
Copyright (c) 2012 Eric Zhang, http://binaryjs.com
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,8 @@
# BinaryPack
BinaryPack is 95% MessagePack. The protocol has been extended to support distinct string and binary types.
This library is mainly used by [PeerJS](https://peerjs.com) to transfer objects via data channels.
Bug reports and fixes are welcome, but we are unlikely to implement new features.
Inspired by: https://github.com/cuzic/MessagePack-JS

View File

@@ -0,0 +1,29 @@
import { expect, describe, it } from "@jest/globals";
import { packAndUnpack } from "./util";
import data, { blob, objWithBlob } from "./data";
import { pack, unpack } from "../lib/binarypack";
describe("Blobs", () => {
it("replaces Blobs with ArrayBuffer ", async () => {
expect(await packAndUnpack(blob)).toStrictEqual(await blob.arrayBuffer());
});
it("replaces Blobs with ArrayBuffer in objects ", async () => {
const objWithAB = {
...objWithBlob,
blob: await objWithBlob.blob.arrayBuffer(),
};
expect(await packAndUnpack(objWithBlob)).toStrictEqual(objWithAB);
});
it("keep Text decodable", async () => {
for (const commit of data) {
const json = JSON.stringify(commit);
const blob = new Blob([json], { type: "application/json" });
const decoded = new TextDecoder().decode(
await packAndUnpack<ArrayBuffer>(blob),
);
expect(decoded).toStrictEqual(json);
}
});
});

View File

@@ -0,0 +1,16 @@
import { expect, describe, it } from "@jest/globals";
import { packAndUnpack } from "./util";
describe("Bugs", () => {
describe("Objects", () => {
it("replaces undefined with null ", async () => {
expect(packAndUnpack(undefined)).toBe(null);
});
});
describe("Numbers", () => {
it("gives back wrong value on INT64_MAX ", async () => {
expect(packAndUnpack(0x7fffffffffffffff)).not.toEqual(0x7fffffffffffffff);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
import { expect, describe, it } from "@jest/globals";
import { packAndUnpack } from "./util";
describe("Binarypack", () => {
it("should keep valid UTF-8", async () => {
const values = [
0,
1,
-1,
//
Math.PI,
-Math.PI,
//8 bit
0x7f,
0x0f,
//16 bit
0x7fff,
0x0fff,
//32 bit
0x7fffffff,
0x0fffffff,
//64 bit
// 0x7FFFFFFFFFFFFFFF,
0x0fffffffffffffff,
];
// expect.assertions(values.length);
for (const v of values) {
expect(packAndUnpack(v)).toEqual(v);
}
});
});

View File

@@ -0,0 +1,113 @@
import { expect, describe, it } from "@jest/globals";
import commit_data from "./data";
import { packAndUnpack } from "./util";
describe("Binarypack", () => {
it("should keep objects intact", async () => {
const values = commit_data;
// expect.assertions(values.length);
for (const v of values) {
expect(packAndUnpack(v)).toEqual(v);
}
});
it("should keep very large object intact", async () => {
const v: { [key: number]: number } = {};
for (let i = 0; i < 0xffff; i++) {
v[i] = i;
}
expect(packAndUnpack(v)).toEqual(v);
});
it("should keep arrays of objects intact", async () => {
expect(packAndUnpack(commit_data)).toEqual(commit_data);
});
it("should keep empty and very large arrays intact", async () => {
const values = [[], Array(0xffff).fill(0)];
// expect.assertions(values.length);
for (const v of values) {
expect(packAndUnpack(v)).toEqual(v);
}
});
it("should keep null", async () => {
expect(packAndUnpack(null)).toEqual(null);
});
it("should transfer Uint8Array views correctly", async () => {
const arr = new Uint8Array(8);
for (let i = 0; i < 8; i++) arr[i] = i;
const v = new Uint8Array(arr.buffer, 4); // Half the array
const result = packAndUnpack<ArrayBuffer>(v);
expect(result).toBeInstanceOf(ArrayBuffer);
if (result instanceof ArrayBuffer)
expect(new Uint8Array(result)).toEqual(v);
});
it("should transfer Uint8Array as ArrayBuffer", async () => {
const values = [
new Uint8Array(),
new Uint8Array([0]),
new Uint8Array([0, 1, 2, 3, 4, 6, 7]),
new Uint8Array([0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15]),
new Uint8Array([
0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 30, 31,
]),
];
// expect.assertions(values.length);
for (const v of values) {
const result = packAndUnpack<ArrayBuffer>(v);
expect(result).toBeInstanceOf(ArrayBuffer);
if (result instanceof ArrayBuffer)
expect(new Uint8Array(result)).toEqual(v);
}
});
it("should transfer Int32Array as ArrayBuffer", async () => {
const values = [
new Int32Array([0].map((x) => -x)),
new Int32Array([0, 1, 2, 3, 4, 6, 7].map((x) => -x)),
new Int32Array(
[0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15].map((x) => -x),
),
new Int32Array(
[
0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 30, 31,
].map((x) => -x),
),
];
// expect.assertions(values.length);
for (const v of values) {
const result = packAndUnpack<ArrayBuffer>(v);
expect(result).toBeInstanceOf(ArrayBuffer);
if (result instanceof ArrayBuffer)
expect(new Int32Array(result)).toEqual(v);
}
});
it("should keep ArrayBuffers", async () => {
const values = [
new Uint8Array([]).buffer,
new Uint8Array([0]).buffer,
new Uint8Array([0, 1, 2, 3, 4, 6, 7]).buffer,
new Uint8Array([0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15]).buffer,
new Uint8Array([
0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 30, 31,
]).buffer,
];
// expect.assertions(values.length);
for (const v of values) {
expect(packAndUnpack<ArrayBuffer>(v)).toEqual(v);
}
});
it("should transfer Dates as String", async () => {
const values = [new Date(), new Date(Date.UTC(1, 1, 1, 1, 1, 1, 1))];
// expect.assertions(values.length);
for (const v of values) {
expect(packAndUnpack(v)).toEqual(v.toString());
}
});
});

View File

@@ -0,0 +1,52 @@
import { expect, describe, it } from "@jest/globals";
import { packAndUnpack } from "./util";
describe("Binarypack", () => {
it("should keep valid UTF-8", async () => {
const values = [
"",
"hello",
"café",
"中文",
"broccoli🥦līp𨋢grin😃ok",
"\u{10ffff}",
];
// expect.assertions(values.length);
for (const v of values) {
expect(packAndUnpack(v)).toEqual(v);
}
});
/**
* A Javascript string with unpaired surrogates is not actually valid
* UTF-16, and so it cannot be round-tripped to UTF-8 and back.
* The recommended way to handle this is to replace each unpaired surrogate
* with \uFFFD (the "replacement character").
*
* Note a surrogate pair means two adjacent Javascript characters where the
* first is in the range \uD800 - \uDBFF and the second is in the
* range \uDC00 - \uDFFF.
* To be valid UTF-16, Javascript characters from these ranges must *only*
* appear in surrogate pairs. An *unpaired* surrogate means any such
* Javascript character that is not paired up properly.
*
* https://github.com/peers/js-binarypack/issues/11#issuecomment-1445129237
*/
it("should replace unpaired surrogates", async () => {
const v = "un\ud800paired\udfffsurrogates";
const expected = v.replace(
/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g,
"\ufffd",
);
expect(await packAndUnpack(v)).toEqual(expected);
});
it("should encode very large strings", async () => {
const chunk = "ThisIsÁTèstString";
const v = chunk.repeat(1000);
expect(await packAndUnpack(v)).toEqual(v);
});
});

View File

@@ -0,0 +1,9 @@
import { pack, Packable, unpack, Unpackable } from "../lib/binarypack";
export const packAndUnpack = <T extends Unpackable>(data: Packable) => {
const encoded = pack(data);
if (encoded instanceof Promise) {
return encoded.then(unpack<T>);
}
return unpack<T>(encoded);
};

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
"files": {
"ignore": ["dist", "node_modules", "package-lock.json"]
},
"organizeImports": {
"enabled": false
},
"linter": {
"enabled": false,
"rules": {
"recommended": true
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,564 @@
import { BufferBuilder } from "./bufferbuilder";
export type Packable =
| null
| undefined
| string
| number
| boolean
| Date
| ArrayBuffer
| Blob
| Array<Packable>
| { [key: string]: Packable }
| ({ BYTES_PER_ELEMENT: number } & ArrayBufferView);
export type Unpackable =
| null
| undefined
| string
| number
| boolean
| ArrayBuffer
| Array<Unpackable>
| { [key: string]: Unpackable };
export function unpack<T extends Unpackable>(data: ArrayBuffer) {
const unpacker = new Unpacker(data);
return unpacker.unpack() as T;
}
export function pack(data: Packable) {
const packer = new Packer();
const res = packer.pack(data);
if (res instanceof Promise) {
return res.then(() => packer.getBuffer());
}
return packer.getBuffer();
}
class Unpacker {
private index: number;
private readonly dataBuffer: ArrayBuffer;
private readonly dataView: Uint8Array;
private readonly length: number;
constructor(data: ArrayBuffer) {
this.index = 0;
this.dataBuffer = data;
this.dataView = new Uint8Array(this.dataBuffer);
this.length = this.dataBuffer.byteLength;
}
unpack(): Unpackable {
const type = this.unpack_uint8();
if (type < 0x80) {
return type;
} else if ((type ^ 0xe0) < 0x20) {
return (type ^ 0xe0) - 0x20;
}
let size;
if ((size = type ^ 0xa0) <= 0x0f) {
return this.unpack_raw(size);
} else if ((size = type ^ 0xb0) <= 0x0f) {
return this.unpack_string(size);
} else if ((size = type ^ 0x90) <= 0x0f) {
return this.unpack_array(size);
} else if ((size = type ^ 0x80) <= 0x0f) {
return this.unpack_map(size);
}
switch (type) {
case 0xc0:
return null;
case 0xc1:
return undefined;
case 0xc2:
return false;
case 0xc3:
return true;
case 0xca:
return this.unpack_float();
case 0xcb:
return this.unpack_double();
case 0xcc:
return this.unpack_uint8();
case 0xcd:
return this.unpack_uint16();
case 0xce:
return this.unpack_uint32();
case 0xcf:
return this.unpack_uint64();
case 0xd0:
return this.unpack_int8();
case 0xd1:
return this.unpack_int16();
case 0xd2:
return this.unpack_int32();
case 0xd3:
return this.unpack_int64();
case 0xd4:
return undefined;
case 0xd5:
return undefined;
case 0xd6:
return undefined;
case 0xd7:
return undefined;
case 0xd8:
size = this.unpack_uint16();
return this.unpack_string(size);
case 0xd9:
size = this.unpack_uint32();
return this.unpack_string(size);
case 0xda:
size = this.unpack_uint16();
return this.unpack_raw(size);
case 0xdb:
size = this.unpack_uint32();
return this.unpack_raw(size);
case 0xdc:
size = this.unpack_uint16();
return this.unpack_array(size);
case 0xdd:
size = this.unpack_uint32();
return this.unpack_array(size);
case 0xde:
size = this.unpack_uint16();
return this.unpack_map(size);
case 0xdf:
size = this.unpack_uint32();
return this.unpack_map(size);
}
}
unpack_uint8() {
const byte = this.dataView[this.index] & 0xff;
this.index++;
return byte;
}
unpack_uint16() {
const bytes = this.read(2);
const uint16 = (bytes[0] & 0xff) * 256 + (bytes[1] & 0xff);
this.index += 2;
return uint16;
}
unpack_uint32() {
const bytes = this.read(4);
const uint32 =
((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3];
this.index += 4;
return uint32;
}
unpack_uint64() {
const bytes = this.read(8);
const uint64 =
((((((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]) *
256 +
bytes[4]) *
256 +
bytes[5]) *
256 +
bytes[6]) *
256 +
bytes[7];
this.index += 8;
return uint64;
}
unpack_int8() {
const uint8 = this.unpack_uint8();
return uint8 < 0x80 ? uint8 : uint8 - (1 << 8);
}
unpack_int16() {
const uint16 = this.unpack_uint16();
return uint16 < 0x8000 ? uint16 : uint16 - (1 << 16);
}
unpack_int32() {
const uint32 = this.unpack_uint32();
return uint32 < 2 ** 31 ? uint32 : uint32 - 2 ** 32;
}
unpack_int64() {
const uint64 = this.unpack_uint64();
return uint64 < 2 ** 63 ? uint64 : uint64 - 2 ** 64;
}
unpack_raw(size: number) {
if (this.length < this.index + size) {
throw new Error(
`BinaryPackFailure: index is out of range ${this.index} ${size} ${this.length}`,
);
}
const buf = this.dataBuffer.slice(this.index, this.index + size);
this.index += size;
return buf;
}
unpack_string(size: number) {
const bytes = this.read(size);
let i = 0;
let str = "";
let c;
let code;
while (i < size) {
c = bytes[i];
// The length of a UTF-8 sequence is specified in the first byte:
// 0xxxxxxx means length 1,
// 110xxxxx means length 2,
// 1110xxxx means length 3,
// 11110xxx means length 4.
// 10xxxxxx is for non-initial bytes.
if (c < 0xa0) {
// One-byte sequence: bits 0xxxxxxx
code = c;
i++;
} else if ((c ^ 0xc0) < 0x20) {
// Two-byte sequence: bits 110xxxxx 10xxxxxx
code = ((c & 0x1f) << 6) | (bytes[i + 1] & 0x3f);
i += 2;
} else if ((c ^ 0xe0) < 0x10) {
// Three-byte sequence: bits 1110xxxx 10xxxxxx 10xxxxxx
code =
((c & 0x0f) << 12) |
((bytes[i + 1] & 0x3f) << 6) |
(bytes[i + 2] & 0x3f);
i += 3;
} else {
// Four-byte sequence: bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
code =
((c & 0x07) << 18) |
((bytes[i + 1] & 0x3f) << 12) |
((bytes[i + 2] & 0x3f) << 6) |
(bytes[i + 3] & 0x3f);
i += 4;
}
str += String.fromCodePoint(code);
}
this.index += size;
return str;
}
unpack_array(size: number) {
const objects = new Array<Unpackable>(size);
for (let i = 0; i < size; i++) {
objects[i] = this.unpack();
}
return objects;
}
unpack_map(size: number) {
const map: { [key: string]: Unpackable } = {};
for (let i = 0; i < size; i++) {
const key = this.unpack() as string;
map[key] = this.unpack();
}
return map;
}
unpack_float() {
const uint32 = this.unpack_uint32();
const sign = uint32 >> 31;
const exp = ((uint32 >> 23) & 0xff) - 127;
const fraction = (uint32 & 0x7fffff) | 0x800000;
return (sign === 0 ? 1 : -1) * fraction * 2 ** (exp - 23);
}
unpack_double() {
const h32 = this.unpack_uint32();
const l32 = this.unpack_uint32();
const sign = h32 >> 31;
const exp = ((h32 >> 20) & 0x7ff) - 1023;
const hfrac = (h32 & 0xfffff) | 0x100000;
const frac = hfrac * 2 ** (exp - 20) + l32 * 2 ** (exp - 52);
return (sign === 0 ? 1 : -1) * frac;
}
read(length: number) {
const j = this.index;
if (j + length <= this.length) {
return this.dataView.subarray(j, j + length);
} else {
throw new Error("BinaryPackFailure: read index out of range");
}
}
}
export class Packer {
private _bufferBuilder = new BufferBuilder();
private _textEncoder = new TextEncoder();
getBuffer() {
return this._bufferBuilder.toArrayBuffer();
}
pack(value: Packable) {
if (typeof value === "string") {
this.pack_string(value);
} else if (typeof value === "number") {
if (Math.floor(value) === value) {
this.pack_integer(value);
} else {
this.pack_double(value);
}
} else if (typeof value === "boolean") {
if (value === true) {
this._bufferBuilder.append(0xc3);
} else if (value === false) {
this._bufferBuilder.append(0xc2);
}
} else if (value === undefined) {
this._bufferBuilder.append(0xc0);
} else if (typeof value === "object") {
if (value === null) {
this._bufferBuilder.append(0xc0);
} else {
const constructor = value.constructor;
if (value instanceof Array) {
const res = this.pack_array(value);
if (res instanceof Promise) {
return res.then(() => this._bufferBuilder.flush());
}
} else if (value instanceof ArrayBuffer) {
this.pack_bin(new Uint8Array(value));
} else if ("BYTES_PER_ELEMENT" in value) {
const v = value as unknown as DataView;
this.pack_bin(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));
} else if (value instanceof Date) {
this.pack_string(value.toString());
} else if (value instanceof Blob) {
return value.arrayBuffer().then((buffer) => {
this.pack_bin(new Uint8Array(buffer));
this._bufferBuilder.flush();
});
// this.pack_bin(new Uint8Array(await value.arrayBuffer()));
} else if (
constructor == Object ||
constructor.toString().startsWith("class")
) {
const res = this.pack_object(value);
if (res instanceof Promise) {
return res.then(() => this._bufferBuilder.flush());
}
} else {
throw new Error(`Type "${constructor.toString()}" not yet supported`);
}
}
} else {
throw new Error(`Type "${typeof value}" not yet supported`);
}
this._bufferBuilder.flush();
}
pack_bin(blob: Uint8Array) {
const length = blob.length;
if (length <= 0x0f) {
this.pack_uint8(0xa0 + length);
} else if (length <= 0xffff) {
this._bufferBuilder.append(0xda);
this.pack_uint16(length);
} else if (length <= 0xffffffff) {
this._bufferBuilder.append(0xdb);
this.pack_uint32(length);
} else {
throw new Error("Invalid length");
}
this._bufferBuilder.append_buffer(blob);
}
pack_string(str: string) {
const encoded = this._textEncoder.encode(str);
const length = encoded.length;
if (length <= 0x0f) {
this.pack_uint8(0xb0 + length);
} else if (length <= 0xffff) {
this._bufferBuilder.append(0xd8);
this.pack_uint16(length);
} else if (length <= 0xffffffff) {
this._bufferBuilder.append(0xd9);
this.pack_uint32(length);
} else {
throw new Error("Invalid length");
}
this._bufferBuilder.append_buffer(encoded);
}
pack_array(ary: Packable[]) {
const length = ary.length;
if (length <= 0x0f) {
this.pack_uint8(0x90 + length);
} else if (length <= 0xffff) {
this._bufferBuilder.append(0xdc);
this.pack_uint16(length);
} else if (length <= 0xffffffff) {
this._bufferBuilder.append(0xdd);
this.pack_uint32(length);
} else {
throw new Error("Invalid length");
}
const packNext = (index: number): Promise<void> | void => {
if (index < length) {
const res = this.pack(ary[index]);
if (res instanceof Promise) {
return res.then(() => packNext(index + 1));
}
return packNext(index + 1);
}
};
return packNext(0);
}
pack_integer(num: number) {
if (num >= -0x20 && num <= 0x7f) {
this._bufferBuilder.append(num & 0xff);
} else if (num >= 0x00 && num <= 0xff) {
this._bufferBuilder.append(0xcc);
this.pack_uint8(num);
} else if (num >= -0x80 && num <= 0x7f) {
this._bufferBuilder.append(0xd0);
this.pack_int8(num);
} else if (num >= 0x0000 && num <= 0xffff) {
this._bufferBuilder.append(0xcd);
this.pack_uint16(num);
} else if (num >= -0x8000 && num <= 0x7fff) {
this._bufferBuilder.append(0xd1);
this.pack_int16(num);
} else if (num >= 0x00000000 && num <= 0xffffffff) {
this._bufferBuilder.append(0xce);
this.pack_uint32(num);
} else if (num >= -0x80000000 && num <= 0x7fffffff) {
this._bufferBuilder.append(0xd2);
this.pack_int32(num);
} else if (num >= -0x8000000000000000 && num <= 0x7fffffffffffffff) {
this._bufferBuilder.append(0xd3);
this.pack_int64(num);
} else if (num >= 0x0000000000000000 && num <= 0xffffffffffffffff) {
this._bufferBuilder.append(0xcf);
this.pack_uint64(num);
} else {
throw new Error("Invalid integer");
}
}
pack_double(num: number) {
let sign = 0;
if (num < 0) {
sign = 1;
num = -num;
}
const exp = Math.floor(Math.log(num) / Math.LN2);
const frac0 = num / 2 ** exp - 1;
const frac1 = Math.floor(frac0 * 2 ** 52);
const b32 = 2 ** 32;
const h32 =
(sign << 31) | ((exp + 1023) << 20) | ((frac1 / b32) & 0x0fffff);
const l32 = frac1 % b32;
this._bufferBuilder.append(0xcb);
this.pack_int32(h32);
this.pack_int32(l32);
}
pack_object(obj: { [key: string]: Packable }) {
const keys = Object.keys(obj);
const length = keys.length;
if (length <= 0x0f) {
this.pack_uint8(0x80 + length);
} else if (length <= 0xffff) {
this._bufferBuilder.append(0xde);
this.pack_uint16(length);
} else if (length <= 0xffffffff) {
this._bufferBuilder.append(0xdf);
this.pack_uint32(length);
} else {
throw new Error("Invalid length");
}
const packNext = (index: number): Promise<void> | void => {
if (index < keys.length) {
const prop = keys[index];
// eslint-disable-next-line no-prototype-builtins
if (obj.hasOwnProperty(prop)) {
this.pack(prop);
const res = this.pack(obj[prop]);
if (res instanceof Promise) {
return res.then(() => packNext(index + 1));
}
}
return packNext(index + 1);
}
};
return packNext(0);
}
pack_uint8(num: number) {
this._bufferBuilder.append(num);
}
pack_uint16(num: number) {
this._bufferBuilder.append(num >> 8);
this._bufferBuilder.append(num & 0xff);
}
pack_uint32(num: number) {
const n = num & 0xffffffff;
this._bufferBuilder.append((n & 0xff000000) >>> 24);
this._bufferBuilder.append((n & 0x00ff0000) >>> 16);
this._bufferBuilder.append((n & 0x0000ff00) >>> 8);
this._bufferBuilder.append(n & 0x000000ff);
}
pack_uint64(num: number) {
const high = num / 2 ** 32;
const low = num % 2 ** 32;
this._bufferBuilder.append((high & 0xff000000) >>> 24);
this._bufferBuilder.append((high & 0x00ff0000) >>> 16);
this._bufferBuilder.append((high & 0x0000ff00) >>> 8);
this._bufferBuilder.append(high & 0x000000ff);
this._bufferBuilder.append((low & 0xff000000) >>> 24);
this._bufferBuilder.append((low & 0x00ff0000) >>> 16);
this._bufferBuilder.append((low & 0x0000ff00) >>> 8);
this._bufferBuilder.append(low & 0x000000ff);
}
pack_int8(num: number) {
this._bufferBuilder.append(num & 0xff);
}
pack_int16(num: number) {
this._bufferBuilder.append((num & 0xff00) >> 8);
this._bufferBuilder.append(num & 0xff);
}
pack_int32(num: number) {
this._bufferBuilder.append((num >>> 24) & 0xff);
this._bufferBuilder.append((num & 0x00ff0000) >>> 16);
this._bufferBuilder.append((num & 0x0000ff00) >>> 8);
this._bufferBuilder.append(num & 0x000000ff);
}
pack_int64(num: number) {
const high = Math.floor(num / 2 ** 32);
const low = num % 2 ** 32;
this._bufferBuilder.append((high & 0xff000000) >>> 24);
this._bufferBuilder.append((high & 0x00ff0000) >>> 16);
this._bufferBuilder.append((high & 0x0000ff00) >>> 8);
this._bufferBuilder.append(high & 0x000000ff);
this._bufferBuilder.append((low & 0xff000000) >>> 24);
this._bufferBuilder.append((low & 0x00ff0000) >>> 16);
this._bufferBuilder.append((low & 0x0000ff00) >>> 8);
this._bufferBuilder.append(low & 0x000000ff);
}
}

View File

@@ -0,0 +1,53 @@
class BufferBuilder {
private _pieces: number[];
private readonly _parts: ArrayBufferView[];
constructor() {
this._pieces = [];
this._parts = [];
}
append_buffer(data: ArrayBufferView) {
this.flush();
this._parts.push(data);
}
append(data: number) {
this._pieces.push(data);
}
flush() {
if (this._pieces.length > 0) {
const buf = new Uint8Array(this._pieces);
this._parts.push(buf);
this._pieces = [];
}
}
private encoder = new TextEncoder();
public toArrayBuffer() {
const buffer = [];
for (const part of this._parts) {
buffer.push(part);
}
return concatArrayBuffers(buffer).buffer;
}
}
export { BufferBuilder };
function concatArrayBuffers(bufs: ArrayBufferView[]) {
let size = 0;
for (const buf of bufs) {
size += buf.byteLength;
}
const result = new Uint8Array(size);
let offset = 0;
for (const buf of bufs) {
const view = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
result.set(view, offset);
offset += buf.byteLength;
}
return result;
}

View File

@@ -0,0 +1,118 @@
{
"name": "peerjs-js-binarypack",
"version": "2.1.0",
"description": "BinaryPack serialization",
"homepage": "https://github.com/peers/js-binarypack",
"main": "dist/binarypack.cjs",
"module": "dist/binarypack.mjs",
"source": "lib/binarypack.ts",
"types": "dist/binarypack.d.ts",
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/binarypack.d.ts",
"default": "./dist/binarypack.mjs"
},
"require": {
"types": "./dist/binarypack.d.ts",
"default": "./dist/binarypack.cjs"
}
}
},
"scripts": {
"watch": "parcel watch",
"build": "parcel build",
"format": "biome format --write .",
"lint": "eslint --ext .js,.ts .",
"check": "tsc --noEmit"
},
"repository": {
"type": "git",
"url": "https://github.com/peers/js-binarypack"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/peer"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/peer"
},
"author": "Eric Zhang",
"contributors": [
{
"name": "Eric Zhang",
"email": "really.ez@gmail.com"
},
{
"name": "Jonas Gloning",
"email": "34194370+jonasgloning@users.noreply.github.com"
},
{
"name": "afrokick",
"email": "devbyru@gmail.com"
},
{
"name": "manvalls",
"email": "manolo@chatiku.com"
},
{
"name": "Michelle Bu",
"email": "michelle@michellebu.com"
},
{
"name": "Liu Cong",
"email": "leehom2001@gmail.com"
},
{
"name": "Michelle Bu",
"email": "michelle@stripe.com"
},
{
"name": "lmb",
"email": "i@lmb.io"
},
{
"name": "orcaman",
"email": "orhiltch@gmail.com"
},
{
"name": "Godfrey Chan",
"email": "godfreykfc@gmail.com"
},
{
"name": "Jarrett Cruger",
"email": "jcrugzz@gmail.com"
},
{
"name": "Rossi Lorenzo",
"email": "snowycoder@gmail.com"
},
{
"name": "divec",
"email": "david@troi.org"
},
{
"name": "renovate[bot]",
"email": "29139614+renovate[bot]@users.noreply.github.com"
}
],
"devDependencies": {
"@biomejs/biome": "1.4.1",
"@jest/globals": "^29.7.0",
"@parcel/packager-ts": "^2.8.3",
"@parcel/transformer-typescript-types": "^2.8.3",
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/git": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"eslint": "^8.34.0",
"parcel": "^2.8.3",
"semantic-release": "^20.1.0",
"typescript": "^5.0.0"
},
"license": "MIT",
"engines": {
"node": ">= 14.0.0"
}
}

View File

@@ -0,0 +1,28 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", ":assignAndReview(jonasgloning)"],
"labels": ["dependencies"],
"assignees": ["jonasgloning"],
"major": {
"dependencyDashboardApproval": true
},
"packageRules": [
{
"matchDepTypes": ["devDependencies"],
"addLabels": ["dev-dependencies"],
"automerge": true,
"automergeType": "branch"
},
{
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": "!/^0/",
"automerge": true,
"automergeType": "branch"
}
],
"lockFileMaintenance": {
"enabled": true,
"automerge": true,
"automergeType": "branch"
}
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "es2021",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
}
}