├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ ├── bun.yml │ ├── node.js.yml.deactivated │ ├── prettier.yml │ └── release.yml ├── .gitignore ├── .releaserc.json ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __test__ ├── blobs.spec.ts ├── bugs.spec.ts ├── data.ts ├── numbers.spec.ts ├── objects.spec.ts ├── strings.spec.ts └── util.ts ├── biome.json ├── bun.lockb ├── lib ├── binarypack.ts └── bufferbuilder.ts ├── package.json ├── renovate.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/bun.yml: -------------------------------------------------------------------------------- 1 | # 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 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Bun CI 5 | 6 | on: 7 | push: 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: oven-sh/setup-bun@v1 16 | - run: bun install --frozen-lockfile 17 | - run: bun run build 18 | - run: bun test 19 | - run: bun run check 20 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml.deactivated: -------------------------------------------------------------------------------- 1 | # 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 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: ["master"] 9 | pull_request: 10 | branches: ["master"] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [16.x, 18.x, 20.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: "npm" 28 | - run: npm ci 29 | - run: npm run build 30 | - run: npm run coverage 31 | - name: Publish code coverage to CodeClimate 32 | uses: paambaati/codeclimate-action@v5.0.0 33 | env: 34 | CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} 35 | -------------------------------------------------------------------------------- /.github/workflows/prettier.yml: -------------------------------------------------------------------------------- 1 | name: Check JavaScript for conformance with Prettier 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | biome: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Setup Biome 14 | uses: biomejs/setup-biome@v2 15 | - name: Run Biome 16 | run: biome ci . -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - rc 6 | - stable 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - uses: oven-sh/setup-bun@v1 17 | - name: Install dependencies 18 | run: bun install --frozen-lockfile 19 | - name: Build 20 | run: bun run build 21 | - name: Release 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GH_TOKEN }} 24 | NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }} 25 | run: bun run semantic-release 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .parcel-cache 2 | coverage 3 | node_modules/ 4 | .cache/ 5 | .idea/ 6 | *.log 7 | dist/ 8 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "stable", 4 | { 5 | "name": "rc", 6 | "prerelease": true 7 | } 8 | ], 9 | "plugins": [ 10 | "@semantic-release/commit-analyzer", 11 | "@semantic-release/release-notes-generator", 12 | "@semantic-release/changelog", 13 | "@semantic-release/npm", 14 | "@semantic-release/git", 15 | "@semantic-release/github" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.0.0](https://github.com/peers/js-binarypack/compare/v1.0.2...v2.0.0) (2023-06-22) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * empty TypedArray can now be packed ([3475f45](https://github.com/peers/js-binarypack/commit/3475f450a7bc97b757325cd54bc7ba7ffc84118b)) 7 | * undefined will stay undefined instead of null ([83af274](https://github.com/peers/js-binarypack/commit/83af274ea82fdd44d93546f18cbcf547abe77804)) 8 | 9 | 10 | ### Features 11 | 12 | * return `ArrayBuffer` instead of `Blob` ([6b70875](https://github.com/peers/js-binarypack/commit/6b70875b4d7db791fdd14a1f3ff3776d12febfb2)) 13 | 14 | 15 | ### Reverts 16 | 17 | * Revert "fix: undefined will stay undefined instead of null" ([da49137](https://github.com/peers/js-binarypack/commit/da4913787d9ab96845bd8e512d5f501574746a35)) 18 | 19 | 20 | ### BREAKING CHANGES 21 | 22 | * Return type of `pack` is now `ArrayBuffer` 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Eric Zhang, http://binaryjs.com 2 | 3 | (The MIT License) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BinaryPack 2 | 3 | BinaryPack is 95% MessagePack. The protocol has been extended to support distinct string and binary types. 4 | 5 | This library is mainly used by [PeerJS](https://peerjs.com) to transfer objects via data channels. 6 | Bug reports and fixes are welcome, but we are unlikely to implement new features. 7 | 8 | Inspired by: https://github.com/cuzic/MessagePack-JS 9 | -------------------------------------------------------------------------------- /__test__/blobs.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "@jest/globals"; 2 | 3 | import { packAndUnpack } from "./util"; 4 | 5 | import data, { blob, objWithBlob } from "./data"; 6 | import { pack, unpack } from "../lib/binarypack"; 7 | 8 | describe("Blobs", () => { 9 | it("replaces Blobs with ArrayBuffer ", async () => { 10 | expect(await packAndUnpack(blob)).toStrictEqual(await blob.arrayBuffer()); 11 | }); 12 | it("replaces Blobs with ArrayBuffer in objects ", async () => { 13 | const objWithAB = { 14 | ...objWithBlob, 15 | blob: await objWithBlob.blob.arrayBuffer(), 16 | }; 17 | expect(await packAndUnpack(objWithBlob)).toStrictEqual(objWithAB); 18 | }); 19 | it("keep Text decodable", async () => { 20 | for (const commit of data) { 21 | const json = JSON.stringify(commit); 22 | const blob = new Blob([json], { type: "application/json" }); 23 | const decoded = new TextDecoder().decode( 24 | await packAndUnpack(blob), 25 | ); 26 | expect(decoded).toStrictEqual(json); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /__test__/bugs.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "@jest/globals"; 2 | 3 | import { packAndUnpack } from "./util"; 4 | 5 | describe("Bugs", () => { 6 | describe("Objects", () => { 7 | it("replaces undefined with null ", async () => { 8 | expect(packAndUnpack(undefined)).toBe(null); 9 | }); 10 | }); 11 | describe("Numbers", () => { 12 | it("gives back wrong value on INT64_MAX ", async () => { 13 | expect(packAndUnpack(0x7fffffffffffffff)).not.toEqual(0x7fffffffffffffff); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /__test__/data.ts: -------------------------------------------------------------------------------- 1 | const data = [ 2 | { 3 | created_at: "2013-11-07T19:41:25-08:00", 4 | payload: { issue_id: 22111847, comment_id: 28032260 }, 5 | public: true, 6 | type: "IssueCommentEvent", 7 | url: "https://github.com/peers/peerjs/issues/101#issuecomment-28032260", 8 | actor: "ericz", 9 | actor_attributes: { 10 | login: "ericz", 11 | type: "User", 12 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 13 | name: "Eric Zhang", 14 | company: "Lever", 15 | blog: "https://twitter.com/reallyez", 16 | location: "Berkeley", 17 | email: "eric@ericzhang.com", 18 | }, 19 | repository: { 20 | id: 7292898, 21 | name: "peerjs", 22 | url: "https://github.com/peers/peerjs", 23 | description: "Peer-to-peer data in the browser.", 24 | homepage: "https://peerjs.com", 25 | watchers: 1647, 26 | stargazers: 1647, 27 | forks: 145, 28 | fork: false, 29 | size: 2188, 30 | owner: "peers", 31 | private: false, 32 | open_issues: 20, 33 | has_issues: true, 34 | has_downloads: true, 35 | has_wiki: true, 36 | language: "JavaScript", 37 | created_at: "2012-12-22T23:28:47-08:00", 38 | pushed_at: "2013-11-09T22:58:31-08:00", 39 | master_branch: "master", 40 | organization: "peers", 41 | }, 42 | }, 43 | { 44 | created_at: "2013-11-07T19:35:50-08:00", 45 | payload: { issue_id: 22196839, comment_id: 28031970 }, 46 | public: true, 47 | type: "IssueCommentEvent", 48 | url: "https://github.com/peers/peerjs/issues/103#issuecomment-28031970", 49 | actor: "ericz", 50 | actor_attributes: { 51 | login: "ericz", 52 | type: "User", 53 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 54 | name: "Eric Zhang", 55 | company: "Lever", 56 | blog: "https://twitter.com/reallyez", 57 | location: "Berkeley", 58 | email: "eric@ericzhang.com", 59 | }, 60 | repository: { 61 | id: 7292898, 62 | name: "peerjs", 63 | url: "https://github.com/peers/peerjs", 64 | description: "Peer-to-peer data in the browser.", 65 | homepage: "https://peerjs.com", 66 | watchers: 1647, 67 | stargazers: 1647, 68 | forks: 145, 69 | fork: false, 70 | size: 2188, 71 | owner: "peers", 72 | private: false, 73 | open_issues: 20, 74 | has_issues: true, 75 | has_downloads: true, 76 | has_wiki: true, 77 | language: "JavaScript", 78 | created_at: "2012-12-22T23:28:47-08:00", 79 | pushed_at: "2013-11-09T22:58:31-08:00", 80 | master_branch: "master", 81 | organization: "peers", 82 | }, 83 | }, 84 | { 85 | created_at: "2013-11-02T15:18:51-07:00", 86 | payload: { 87 | ref: "0.3.3", 88 | ref_type: "tag", 89 | master_branch: "master", 90 | description: "Peer-to-peer data in the browser.", 91 | }, 92 | public: true, 93 | type: "CreateEvent", 94 | url: "https://github.com/peers/peerjs/tree/0.3.3", 95 | actor: "ericz", 96 | actor_attributes: { 97 | login: "ericz", 98 | type: "User", 99 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 100 | name: "Eric Zhang", 101 | company: "Lever", 102 | blog: "https://twitter.com/reallyez", 103 | location: "Berkeley", 104 | email: "eric@ericzhang.com", 105 | }, 106 | repository: { 107 | id: 7292898, 108 | name: "peerjs", 109 | url: "https://github.com/peers/peerjs", 110 | description: "Peer-to-peer data in the browser.", 111 | homepage: "https://peerjs.com", 112 | watchers: 1647, 113 | stargazers: 1647, 114 | forks: 145, 115 | fork: false, 116 | size: 2188, 117 | owner: "peers", 118 | private: false, 119 | open_issues: 20, 120 | has_issues: true, 121 | has_downloads: true, 122 | has_wiki: true, 123 | language: "JavaScript", 124 | created_at: "2012-12-22T23:28:47-08:00", 125 | pushed_at: "2013-11-09T22:58:31-08:00", 126 | master_branch: "master", 127 | organization: "peers", 128 | }, 129 | }, 130 | { 131 | created_at: "2013-11-02T15:18:49-07:00", 132 | payload: { 133 | shas: [ 134 | [ 135 | "9976990c61c0f9c3c44f1de2a997ff8f21013d2a", 136 | "really.ez@gmail.com", 137 | "Bump to 0.3.3", 138 | "ericz", 139 | true, 140 | ], 141 | ], 142 | size: 1, 143 | ref: "refs/heads/master", 144 | head: "9976990c61c0f9c3c44f1de2a997ff8f21013d2a", 145 | }, 146 | public: true, 147 | type: "PushEvent", 148 | url: "https://github.com/peers/peerjs/compare/c9adf5076e...9976990c61", 149 | actor: "ericz", 150 | actor_attributes: { 151 | login: "ericz", 152 | type: "User", 153 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 154 | name: "Eric Zhang", 155 | company: "Lever", 156 | blog: "https://twitter.com/reallyez", 157 | location: "Berkeley", 158 | email: "eric@ericzhang.com", 159 | }, 160 | repository: { 161 | id: 7292898, 162 | name: "peerjs", 163 | url: "https://github.com/peers/peerjs", 164 | description: "Peer-to-peer data in the browser.", 165 | homepage: "https://peerjs.com", 166 | watchers: 1647, 167 | stargazers: 1647, 168 | forks: 145, 169 | fork: false, 170 | size: 2188, 171 | owner: "peers", 172 | private: false, 173 | open_issues: 20, 174 | has_issues: true, 175 | has_downloads: true, 176 | has_wiki: true, 177 | language: "JavaScript", 178 | created_at: "2012-12-22T23:28:47-08:00", 179 | pushed_at: "2013-11-09T22:58:31-08:00", 180 | master_branch: "master", 181 | organization: "peers", 182 | }, 183 | }, 184 | { 185 | created_at: "2013-11-02T15:15:18-07:00", 186 | payload: { 187 | shas: [ 188 | [ 189 | "ec1424c0f264ea5d8ce08ac2cc9ea2bd027a6a71", 190 | "really.ez@gmail.com", 191 | "Errant comma", 192 | "ericz", 193 | true, 194 | ], 195 | [ 196 | "c9adf5076ea41df60231e11d032f371939228609", 197 | "really.ez@gmail.com", 198 | "Dont throw exception on failures", 199 | "ericz", 200 | true, 201 | ], 202 | ], 203 | size: 2, 204 | ref: "refs/heads/master", 205 | head: "c9adf5076ea41df60231e11d032f371939228609", 206 | }, 207 | public: true, 208 | type: "PushEvent", 209 | url: "https://github.com/peers/peerjs/compare/bb1045bf92...c9adf5076e", 210 | actor: "ericz", 211 | actor_attributes: { 212 | login: "ericz", 213 | type: "User", 214 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 215 | name: "Eric Zhang", 216 | company: "Lever", 217 | blog: "https://twitter.com/reallyez", 218 | location: "Berkeley", 219 | email: "eric@ericzhang.com", 220 | }, 221 | repository: { 222 | id: 7292898, 223 | name: "peerjs", 224 | url: "https://github.com/peers/peerjs", 225 | description: "Peer-to-peer data in the browser.", 226 | homepage: "https://peerjs.com", 227 | watchers: 1647, 228 | stargazers: 1647, 229 | forks: 145, 230 | fork: false, 231 | size: 2188, 232 | owner: "peers", 233 | private: false, 234 | open_issues: 20, 235 | has_issues: true, 236 | has_downloads: true, 237 | has_wiki: true, 238 | language: "JavaScript", 239 | created_at: "2012-12-22T23:28:47-08:00", 240 | pushed_at: "2013-11-09T22:58:31-08:00", 241 | master_branch: "master", 242 | organization: "peers", 243 | }, 244 | }, 245 | { 246 | created_at: "2013-11-02T15:02:02-07:00", 247 | payload: { 248 | shas: [ 249 | [ 250 | "bb1045bf92fbaef6e2156c3ac47015f70af5d866", 251 | "really.ez@gmail.com", 252 | "Fix errant comma", 253 | "ericz", 254 | true, 255 | ], 256 | ], 257 | size: 1, 258 | ref: "refs/heads/master", 259 | head: "bb1045bf92fbaef6e2156c3ac47015f70af5d866", 260 | }, 261 | public: true, 262 | type: "PushEvent", 263 | url: "https://github.com/peers/peerjs/compare/2db1c59998...bb1045bf92", 264 | actor: "ericz", 265 | actor_attributes: { 266 | login: "ericz", 267 | type: "User", 268 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 269 | name: "Eric Zhang", 270 | company: "Lever", 271 | blog: "https://twitter.com/reallyez", 272 | location: "Berkeley", 273 | email: "eric@ericzhang.com", 274 | }, 275 | repository: { 276 | id: 7292898, 277 | name: "peerjs", 278 | url: "https://github.com/peers/peerjs", 279 | description: "Peer-to-peer data in the browser.", 280 | homepage: "https://peerjs.com", 281 | watchers: 1647, 282 | stargazers: 1647, 283 | forks: 145, 284 | fork: false, 285 | size: 2188, 286 | owner: "peers", 287 | private: false, 288 | open_issues: 20, 289 | has_issues: true, 290 | has_downloads: true, 291 | has_wiki: true, 292 | language: "JavaScript", 293 | created_at: "2012-12-22T23:28:47-08:00", 294 | pushed_at: "2013-11-09T22:58:31-08:00", 295 | master_branch: "master", 296 | organization: "peers", 297 | }, 298 | }, 299 | { 300 | created_at: "2013-11-02T14:56:14-07:00", 301 | payload: { 302 | shas: [ 303 | [ 304 | "2db1c599987753079d197f73272a9e40e9290f73", 305 | "really.ez@gmail.com", 306 | "Remove errant comma", 307 | "ericz", 308 | true, 309 | ], 310 | ], 311 | size: 1, 312 | ref: "refs/heads/master", 313 | head: "2db1c599987753079d197f73272a9e40e9290f73", 314 | }, 315 | public: true, 316 | type: "PushEvent", 317 | url: "https://github.com/peers/peerjs/compare/214a14cc10...2db1c59998", 318 | actor: "ericz", 319 | actor_attributes: { 320 | login: "ericz", 321 | type: "User", 322 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 323 | name: "Eric Zhang", 324 | company: "Lever", 325 | blog: "https://twitter.com/reallyez", 326 | location: "Berkeley", 327 | email: "eric@ericzhang.com", 328 | }, 329 | repository: { 330 | id: 7292898, 331 | name: "peerjs", 332 | url: "https://github.com/peers/peerjs", 333 | description: "Peer-to-peer data in the browser.", 334 | homepage: "https://peerjs.com", 335 | watchers: 1647, 336 | stargazers: 1647, 337 | forks: 145, 338 | fork: false, 339 | size: 2188, 340 | owner: "peers", 341 | private: false, 342 | open_issues: 20, 343 | has_issues: true, 344 | has_downloads: true, 345 | has_wiki: true, 346 | language: "JavaScript", 347 | created_at: "2012-12-22T23:28:47-08:00", 348 | pushed_at: "2013-11-09T22:58:31-08:00", 349 | master_branch: "master", 350 | organization: "peers", 351 | }, 352 | }, 353 | { 354 | created_at: "2013-11-02T14:56:13-07:00", 355 | payload: { 356 | shas: [ 357 | [ 358 | "841921c349aff234b022bb966774d00ae22fef5e", 359 | "really.ez@gmail.com", 360 | "Errant comma", 361 | "ericz", 362 | true, 363 | ], 364 | ], 365 | size: 1, 366 | ref: "refs/heads/better-supports", 367 | head: "841921c349aff234b022bb966774d00ae22fef5e", 368 | }, 369 | public: true, 370 | type: "PushEvent", 371 | url: "https://github.com/peers/peerjs/compare/ccd80612ae...841921c349", 372 | actor: "ericz", 373 | actor_attributes: { 374 | login: "ericz", 375 | type: "User", 376 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 377 | name: "Eric Zhang", 378 | company: "Lever", 379 | blog: "https://twitter.com/reallyez", 380 | location: "Berkeley", 381 | email: "eric@ericzhang.com", 382 | }, 383 | repository: { 384 | id: 7292898, 385 | name: "peerjs", 386 | url: "https://github.com/peers/peerjs", 387 | description: "Peer-to-peer data in the browser.", 388 | homepage: "https://peerjs.com", 389 | watchers: 1647, 390 | stargazers: 1647, 391 | forks: 145, 392 | fork: false, 393 | size: 2188, 394 | owner: "peers", 395 | private: false, 396 | open_issues: 20, 397 | has_issues: true, 398 | has_downloads: true, 399 | has_wiki: true, 400 | language: "JavaScript", 401 | created_at: "2012-12-22T23:28:47-08:00", 402 | pushed_at: "2013-11-09T22:58:31-08:00", 403 | master_branch: "master", 404 | organization: "peers", 405 | }, 406 | }, 407 | { 408 | created_at: "2013-11-01T11:41:34-07:00", 409 | payload: { 410 | shas: [ 411 | [ 412 | "37350aaef3763d5a1bc63c4903f0f34ea9780d36", 413 | "really.ez@gmail.com", 414 | "Fix", 415 | "Eric Zhang", 416 | true, 417 | ], 418 | ], 419 | size: 1, 420 | ref: "refs/heads/master", 421 | head: "37350aaef3763d5a1bc63c4903f0f34ea9780d36", 422 | }, 423 | public: true, 424 | type: "PushEvent", 425 | url: "https://github.com/HackBerkeley/ascam/compare/7d76223acc...37350aaef3", 426 | actor: "ericz", 427 | actor_attributes: { 428 | login: "ericz", 429 | type: "User", 430 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 431 | name: "Eric Zhang", 432 | company: "Lever", 433 | blog: "https://twitter.com/reallyez", 434 | location: "Berkeley", 435 | email: "eric@ericzhang.com", 436 | }, 437 | repository: { 438 | id: 5557070, 439 | name: "ascam", 440 | url: "https://github.com/HackBerkeley/ascam", 441 | description: "Ascii Webcam", 442 | homepage: "", 443 | watchers: 1, 444 | stargazers: 1, 445 | forks: 1, 446 | fork: true, 447 | size: 1973, 448 | owner: "HackBerkeley", 449 | private: false, 450 | open_issues: 0, 451 | has_issues: false, 452 | has_downloads: true, 453 | has_wiki: true, 454 | language: "JavaScript", 455 | created_at: "2012-08-25T20:19:01-07:00", 456 | pushed_at: "2013-11-01T11:41:33-07:00", 457 | master_branch: "master", 458 | organization: "HackBerkeley", 459 | }, 460 | }, 461 | { 462 | created_at: "2013-10-31T10:56:20-07:00", 463 | payload: { issue_id: 13813188, comment_id: 27509702 }, 464 | public: true, 465 | type: "IssueCommentEvent", 466 | url: "https://github.com/peers/peerjs/pull/45#issuecomment-27509702", 467 | actor: "ericz", 468 | actor_attributes: { 469 | login: "ericz", 470 | type: "User", 471 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 472 | name: "Eric Zhang", 473 | company: "Lever", 474 | blog: "https://twitter.com/reallyez", 475 | location: "Berkeley", 476 | email: "eric@ericzhang.com", 477 | }, 478 | repository: { 479 | id: 7292898, 480 | name: "peerjs", 481 | url: "https://github.com/peers/peerjs", 482 | description: "Peer-to-peer data in the browser.", 483 | homepage: "https://peerjs.com", 484 | watchers: 1647, 485 | stargazers: 1647, 486 | forks: 145, 487 | fork: false, 488 | size: 2188, 489 | owner: "peers", 490 | private: false, 491 | open_issues: 20, 492 | has_issues: true, 493 | has_downloads: true, 494 | has_wiki: true, 495 | language: "JavaScript", 496 | created_at: "2012-12-22T23:28:47-08:00", 497 | pushed_at: "2013-11-09T22:58:31-08:00", 498 | master_branch: "master", 499 | organization: "peers", 500 | }, 501 | }, 502 | { 503 | created_at: "2013-10-26T22:53:49-07:00", 504 | payload: { issue_id: 21646195, comment_id: 27163465 }, 505 | public: true, 506 | type: "IssueCommentEvent", 507 | url: "https://github.com/peers/peerjs-server/issues/25#issuecomment-27163465", 508 | actor: "ericz", 509 | actor_attributes: { 510 | login: "ericz", 511 | type: "User", 512 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 513 | name: "Eric Zhang", 514 | company: "Lever", 515 | blog: "https://twitter.com/reallyez", 516 | location: "Berkeley", 517 | email: "eric@ericzhang.com", 518 | }, 519 | repository: { 520 | id: 7452705, 521 | name: "peerjs-server", 522 | url: "https://github.com/peers/peerjs-server", 523 | description: "Server for PeerJS", 524 | homepage: "https://peerjs.com", 525 | watchers: 328, 526 | stargazers: 328, 527 | forks: 56, 528 | fork: false, 529 | size: 389, 530 | owner: "peers", 531 | private: false, 532 | open_issues: 12, 533 | has_issues: true, 534 | has_downloads: true, 535 | has_wiki: true, 536 | language: "JavaScript", 537 | created_at: "2013-01-04T22:49:08-08:00", 538 | pushed_at: "2013-10-24T00:40:28-07:00", 539 | master_branch: "master", 540 | organization: "peers", 541 | }, 542 | }, 543 | { 544 | created_at: "2013-10-25T11:04:07-07:00", 545 | payload: { action: "closed", issue: 16608955, number: 68 }, 546 | public: true, 547 | type: "IssuesEvent", 548 | url: "https://github.com/peers/peerjs/issues/68", 549 | actor: "ericz", 550 | actor_attributes: { 551 | login: "ericz", 552 | type: "User", 553 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 554 | name: "Eric Zhang", 555 | company: "Lever", 556 | blog: "https://twitter.com/reallyez", 557 | location: "Berkeley", 558 | email: "eric@ericzhang.com", 559 | }, 560 | repository: { 561 | id: 7292898, 562 | name: "peerjs", 563 | url: "https://github.com/peers/peerjs", 564 | description: "Peer-to-peer data in the browser.", 565 | homepage: "https://peerjs.com", 566 | watchers: 1647, 567 | stargazers: 1647, 568 | forks: 145, 569 | fork: false, 570 | size: 2188, 571 | owner: "peers", 572 | private: false, 573 | open_issues: 20, 574 | has_issues: true, 575 | has_downloads: true, 576 | has_wiki: true, 577 | language: "JavaScript", 578 | created_at: "2012-12-22T23:28:47-08:00", 579 | pushed_at: "2013-11-09T22:58:31-08:00", 580 | master_branch: "master", 581 | organization: "peers", 582 | }, 583 | }, 584 | { 585 | created_at: "2013-10-25T11:02:50-07:00", 586 | payload: { issue_id: 21379154, comment_id: 27113277 }, 587 | public: true, 588 | type: "IssueCommentEvent", 589 | url: "https://github.com/peers/peerjs/issues/91#issuecomment-27113277", 590 | actor: "ericz", 591 | actor_attributes: { 592 | login: "ericz", 593 | type: "User", 594 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 595 | name: "Eric Zhang", 596 | company: "Lever", 597 | blog: "https://twitter.com/reallyez", 598 | location: "Berkeley", 599 | email: "eric@ericzhang.com", 600 | }, 601 | repository: { 602 | id: 7292898, 603 | name: "peerjs", 604 | url: "https://github.com/peers/peerjs", 605 | description: "Peer-to-peer data in the browser.", 606 | homepage: "https://peerjs.com", 607 | watchers: 1647, 608 | stargazers: 1647, 609 | forks: 145, 610 | fork: false, 611 | size: 2188, 612 | owner: "peers", 613 | private: false, 614 | open_issues: 20, 615 | has_issues: true, 616 | has_downloads: true, 617 | has_wiki: true, 618 | language: "JavaScript", 619 | created_at: "2012-12-22T23:28:47-08:00", 620 | pushed_at: "2013-11-09T22:58:31-08:00", 621 | master_branch: "master", 622 | organization: "peers", 623 | }, 624 | }, 625 | { 626 | created_at: "2013-10-25T11:01:53-07:00", 627 | payload: { action: "closed", issue: 21531959, number: 96 }, 628 | public: true, 629 | type: "IssuesEvent", 630 | url: "https://github.com/peers/peerjs/issues/96", 631 | actor: "ericz", 632 | actor_attributes: { 633 | login: "ericz", 634 | type: "User", 635 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 636 | name: "Eric Zhang", 637 | company: "Lever", 638 | blog: "https://twitter.com/reallyez", 639 | location: "Berkeley", 640 | email: "eric@ericzhang.com", 641 | }, 642 | repository: { 643 | id: 7292898, 644 | name: "peerjs", 645 | url: "https://github.com/peers/peerjs", 646 | description: "Peer-to-peer data in the browser.", 647 | homepage: "https://peerjs.com", 648 | watchers: 1647, 649 | stargazers: 1647, 650 | forks: 145, 651 | fork: false, 652 | size: 2188, 653 | owner: "peers", 654 | private: false, 655 | open_issues: 20, 656 | has_issues: true, 657 | has_downloads: true, 658 | has_wiki: true, 659 | language: "JavaScript", 660 | created_at: "2012-12-22T23:28:47-08:00", 661 | pushed_at: "2013-11-09T22:58:31-08:00", 662 | master_branch: "master", 663 | organization: "peers", 664 | }, 665 | }, 666 | { 667 | created_at: "2013-10-25T11:01:25-07:00", 668 | payload: { issue_id: 21379154, comment_id: 27113184 }, 669 | public: true, 670 | type: "IssueCommentEvent", 671 | url: "https://github.com/peers/peerjs/issues/91#issuecomment-27113184", 672 | actor: "ericz", 673 | actor_attributes: { 674 | login: "ericz", 675 | type: "User", 676 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 677 | name: "Eric Zhang", 678 | company: "Lever", 679 | blog: "https://twitter.com/reallyez", 680 | location: "Berkeley", 681 | email: "eric@ericzhang.com", 682 | }, 683 | repository: { 684 | id: 7292898, 685 | name: "peerjs", 686 | url: "https://github.com/peers/peerjs", 687 | description: "Peer-to-peer data in the browser.", 688 | homepage: "https://peerjs.com", 689 | watchers: 1647, 690 | stargazers: 1647, 691 | forks: 145, 692 | fork: false, 693 | size: 2188, 694 | owner: "peers", 695 | private: false, 696 | open_issues: 20, 697 | has_issues: true, 698 | has_downloads: true, 699 | has_wiki: true, 700 | language: "JavaScript", 701 | created_at: "2012-12-22T23:28:47-08:00", 702 | pushed_at: "2013-11-09T22:58:31-08:00", 703 | master_branch: "master", 704 | organization: "peers", 705 | }, 706 | }, 707 | { 708 | created_at: "2013-10-25T10:42:32-07:00", 709 | payload: { issue_id: 21531959, comment_id: 27111740 }, 710 | public: true, 711 | type: "IssueCommentEvent", 712 | url: "https://github.com/peers/peerjs/issues/96#issuecomment-27111740", 713 | actor: "ericz", 714 | actor_attributes: { 715 | login: "ericz", 716 | type: "User", 717 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 718 | name: "Eric Zhang", 719 | company: "Lever", 720 | blog: "https://twitter.com/reallyez", 721 | location: "Berkeley", 722 | email: "eric@ericzhang.com", 723 | }, 724 | repository: { 725 | id: 7292898, 726 | name: "peerjs", 727 | url: "https://github.com/peers/peerjs", 728 | description: "Peer-to-peer data in the browser.", 729 | homepage: "https://peerjs.com", 730 | watchers: 1647, 731 | stargazers: 1647, 732 | forks: 145, 733 | fork: false, 734 | size: 2188, 735 | owner: "peers", 736 | private: false, 737 | open_issues: 20, 738 | has_issues: true, 739 | has_downloads: true, 740 | has_wiki: true, 741 | language: "JavaScript", 742 | created_at: "2012-12-22T23:28:47-08:00", 743 | pushed_at: "2013-11-09T22:58:31-08:00", 744 | master_branch: "master", 745 | organization: "peers", 746 | }, 747 | }, 748 | { 749 | created_at: "2013-10-25T10:42:13-07:00", 750 | payload: { issue_id: 21531959, comment_id: 27111710 }, 751 | public: true, 752 | type: "IssueCommentEvent", 753 | url: "https://github.com/peers/peerjs/issues/96#issuecomment-27111710", 754 | actor: "ericz", 755 | actor_attributes: { 756 | login: "ericz", 757 | type: "User", 758 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 759 | name: "Eric Zhang", 760 | company: "Lever", 761 | blog: "https://twitter.com/reallyez", 762 | location: "Berkeley", 763 | email: "eric@ericzhang.com", 764 | }, 765 | repository: { 766 | id: 7292898, 767 | name: "peerjs", 768 | url: "https://github.com/peers/peerjs", 769 | description: "Peer-to-peer data in the browser.", 770 | homepage: "https://peerjs.com", 771 | watchers: 1647, 772 | stargazers: 1647, 773 | forks: 145, 774 | fork: false, 775 | size: 2188, 776 | owner: "peers", 777 | private: false, 778 | open_issues: 20, 779 | has_issues: true, 780 | has_downloads: true, 781 | has_wiki: true, 782 | language: "JavaScript", 783 | created_at: "2012-12-22T23:28:47-08:00", 784 | pushed_at: "2013-11-09T22:58:31-08:00", 785 | master_branch: "master", 786 | organization: "peers", 787 | }, 788 | }, 789 | { 790 | created_at: "2013-10-25T10:36:48-07:00", 791 | payload: { issue_id: 21568882, comment_id: 27111312 }, 792 | public: true, 793 | type: "IssueCommentEvent", 794 | url: "https://github.com/peers/peerjs/issues/97#issuecomment-27111312", 795 | actor: "ericz", 796 | actor_attributes: { 797 | login: "ericz", 798 | type: "User", 799 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 800 | name: "Eric Zhang", 801 | company: "Lever", 802 | blog: "https://twitter.com/reallyez", 803 | location: "Berkeley", 804 | email: "eric@ericzhang.com", 805 | }, 806 | repository: { 807 | id: 7292898, 808 | name: "peerjs", 809 | url: "https://github.com/peers/peerjs", 810 | description: "Peer-to-peer data in the browser.", 811 | homepage: "https://peerjs.com", 812 | watchers: 1647, 813 | stargazers: 1647, 814 | forks: 145, 815 | fork: false, 816 | size: 2188, 817 | owner: "peers", 818 | private: false, 819 | open_issues: 20, 820 | has_issues: true, 821 | has_downloads: true, 822 | has_wiki: true, 823 | language: "JavaScript", 824 | created_at: "2012-12-22T23:28:47-08:00", 825 | pushed_at: "2013-11-09T22:58:31-08:00", 826 | master_branch: "master", 827 | organization: "peers", 828 | }, 829 | }, 830 | { 831 | created_at: "2013-10-23T15:52:46-07:00", 832 | payload: { issue_id: 21487688, comment_id: 26953214 }, 833 | public: true, 834 | type: "IssueCommentEvent", 835 | url: "https://github.com/peers/peerjs/issues/95#issuecomment-26953214", 836 | actor: "ericz", 837 | actor_attributes: { 838 | login: "ericz", 839 | type: "User", 840 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 841 | name: "Eric Zhang", 842 | company: "Lever", 843 | blog: "https://twitter.com/reallyez", 844 | location: "Berkeley", 845 | email: "eric@ericzhang.com", 846 | }, 847 | repository: { 848 | id: 7292898, 849 | name: "peerjs", 850 | url: "https://github.com/peers/peerjs", 851 | description: "Peer-to-peer data in the browser.", 852 | homepage: "https://peerjs.com", 853 | watchers: 1647, 854 | stargazers: 1647, 855 | forks: 145, 856 | fork: false, 857 | size: 2188, 858 | owner: "peers", 859 | private: false, 860 | open_issues: 20, 861 | has_issues: true, 862 | has_downloads: true, 863 | has_wiki: true, 864 | language: "JavaScript", 865 | created_at: "2012-12-22T23:28:47-08:00", 866 | pushed_at: "2013-11-09T22:58:31-08:00", 867 | master_branch: "master", 868 | organization: "peers", 869 | }, 870 | }, 871 | { 872 | created_at: "2013-10-22T20:07:24-07:00", 873 | payload: { action: "opened", issue: 21431237, number: 93 }, 874 | public: true, 875 | type: "IssuesEvent", 876 | url: "https://github.com/peers/peerjs/issues/93", 877 | actor: "ericz", 878 | actor_attributes: { 879 | login: "ericz", 880 | type: "User", 881 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 882 | name: "Eric Zhang", 883 | company: "Lever", 884 | blog: "https://twitter.com/reallyez", 885 | location: "Berkeley", 886 | email: "eric@ericzhang.com", 887 | }, 888 | repository: { 889 | id: 7292898, 890 | name: "peerjs", 891 | url: "https://github.com/peers/peerjs", 892 | description: "Peer-to-peer data in the browser.", 893 | homepage: "https://peerjs.com", 894 | watchers: 1647, 895 | stargazers: 1647, 896 | forks: 145, 897 | fork: false, 898 | size: 2188, 899 | owner: "peers", 900 | private: false, 901 | open_issues: 20, 902 | has_issues: true, 903 | has_downloads: true, 904 | has_wiki: true, 905 | language: "JavaScript", 906 | created_at: "2012-12-22T23:28:47-08:00", 907 | pushed_at: "2013-11-09T22:58:31-08:00", 908 | master_branch: "master", 909 | organization: "peers", 910 | }, 911 | }, 912 | { 913 | created_at: "2013-10-22T19:15:46-07:00", 914 | payload: { issue_id: 21259595, comment_id: 26875922 }, 915 | public: true, 916 | type: "IssueCommentEvent", 917 | url: "https://github.com/peers/peerjs/issues/89#issuecomment-26875922", 918 | actor: "ericz", 919 | actor_attributes: { 920 | login: "ericz", 921 | type: "User", 922 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 923 | name: "Eric Zhang", 924 | company: "Lever", 925 | blog: "https://twitter.com/reallyez", 926 | location: "Berkeley", 927 | email: "eric@ericzhang.com", 928 | }, 929 | repository: { 930 | id: 7292898, 931 | name: "peerjs", 932 | url: "https://github.com/peers/peerjs", 933 | description: "Peer-to-peer data in the browser.", 934 | homepage: "https://peerjs.com", 935 | watchers: 1647, 936 | stargazers: 1647, 937 | forks: 145, 938 | fork: false, 939 | size: 2188, 940 | owner: "peers", 941 | private: false, 942 | open_issues: 20, 943 | has_issues: true, 944 | has_downloads: true, 945 | has_wiki: true, 946 | language: "JavaScript", 947 | created_at: "2012-12-22T23:28:47-08:00", 948 | pushed_at: "2013-11-09T22:58:31-08:00", 949 | master_branch: "master", 950 | organization: "peers", 951 | }, 952 | }, 953 | { 954 | created_at: "2013-10-22T19:06:59-07:00", 955 | payload: { issue_id: 20917093, comment_id: 26875655 }, 956 | public: true, 957 | type: "IssueCommentEvent", 958 | url: "https://github.com/peers/peerjs/issues/86#issuecomment-26875655", 959 | actor: "ericz", 960 | actor_attributes: { 961 | login: "ericz", 962 | type: "User", 963 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 964 | name: "Eric Zhang", 965 | company: "Lever", 966 | blog: "https://twitter.com/reallyez", 967 | location: "Berkeley", 968 | email: "eric@ericzhang.com", 969 | }, 970 | repository: { 971 | id: 7292898, 972 | name: "peerjs", 973 | url: "https://github.com/peers/peerjs", 974 | description: "Peer-to-peer data in the browser.", 975 | homepage: "https://peerjs.com", 976 | watchers: 1647, 977 | stargazers: 1647, 978 | forks: 145, 979 | fork: false, 980 | size: 2188, 981 | owner: "peers", 982 | private: false, 983 | open_issues: 20, 984 | has_issues: true, 985 | has_downloads: true, 986 | has_wiki: true, 987 | language: "JavaScript", 988 | created_at: "2012-12-22T23:28:47-08:00", 989 | pushed_at: "2013-11-09T22:58:31-08:00", 990 | master_branch: "master", 991 | organization: "peers", 992 | }, 993 | }, 994 | { 995 | created_at: "2013-10-22T10:42:17-07:00", 996 | payload: { issue_id: 21337614, comment_id: 26824752 }, 997 | public: true, 998 | type: "IssueCommentEvent", 999 | url: "https://github.com/peers/peerjs/issues/90#issuecomment-26824752", 1000 | actor: "ericz", 1001 | actor_attributes: { 1002 | login: "ericz", 1003 | type: "User", 1004 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1005 | name: "Eric Zhang", 1006 | company: "Lever", 1007 | blog: "https://twitter.com/reallyez", 1008 | location: "Berkeley", 1009 | email: "eric@ericzhang.com", 1010 | }, 1011 | repository: { 1012 | id: 7292898, 1013 | name: "peerjs", 1014 | url: "https://github.com/peers/peerjs", 1015 | description: "Peer-to-peer data in the browser.", 1016 | homepage: "https://peerjs.com", 1017 | watchers: 1647, 1018 | stargazers: 1647, 1019 | forks: 145, 1020 | fork: false, 1021 | size: 2188, 1022 | owner: "peers", 1023 | private: false, 1024 | open_issues: 20, 1025 | has_issues: true, 1026 | has_downloads: true, 1027 | has_wiki: true, 1028 | language: "JavaScript", 1029 | created_at: "2012-12-22T23:28:47-08:00", 1030 | pushed_at: "2013-11-09T22:58:31-08:00", 1031 | master_branch: "master", 1032 | organization: "peers", 1033 | }, 1034 | }, 1035 | { 1036 | created_at: "2013-10-21T21:00:18-07:00", 1037 | payload: { issue_id: 21337614, comment_id: 26775684 }, 1038 | public: true, 1039 | type: "IssueCommentEvent", 1040 | url: "https://github.com/peers/peerjs/issues/90#issuecomment-26775684", 1041 | actor: "ericz", 1042 | actor_attributes: { 1043 | login: "ericz", 1044 | type: "User", 1045 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1046 | name: "Eric Zhang", 1047 | company: "Lever", 1048 | blog: "https://twitter.com/reallyez", 1049 | location: "Berkeley", 1050 | email: "eric@ericzhang.com", 1051 | }, 1052 | repository: { 1053 | id: 7292898, 1054 | name: "peerjs", 1055 | url: "https://github.com/peers/peerjs", 1056 | description: "Peer-to-peer data in the browser.", 1057 | homepage: "https://peerjs.com", 1058 | watchers: 1647, 1059 | stargazers: 1647, 1060 | forks: 145, 1061 | fork: false, 1062 | size: 2188, 1063 | owner: "peers", 1064 | private: false, 1065 | open_issues: 20, 1066 | has_issues: true, 1067 | has_downloads: true, 1068 | has_wiki: true, 1069 | language: "JavaScript", 1070 | created_at: "2012-12-22T23:28:47-08:00", 1071 | pushed_at: "2013-11-09T22:58:31-08:00", 1072 | master_branch: "master", 1073 | organization: "peers", 1074 | }, 1075 | }, 1076 | { 1077 | created_at: "2013-10-19T02:06:52-07:00", 1078 | payload: { 1079 | ref: "0.3.1", 1080 | ref_type: "tag", 1081 | master_branch: "master", 1082 | description: "Peer-to-peer data in the browser.", 1083 | }, 1084 | public: true, 1085 | type: "CreateEvent", 1086 | url: "https://github.com/peers/peerjs/tree/0.3.1", 1087 | actor: "ericz", 1088 | actor_attributes: { 1089 | login: "ericz", 1090 | type: "User", 1091 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1092 | name: "Eric Zhang", 1093 | company: "Lever", 1094 | blog: "https://twitter.com/reallyez", 1095 | location: "Berkeley", 1096 | email: "eric@ericzhang.com", 1097 | }, 1098 | repository: { 1099 | id: 7292898, 1100 | name: "peerjs", 1101 | url: "https://github.com/peers/peerjs", 1102 | description: "Peer-to-peer data in the browser.", 1103 | homepage: "https://peerjs.com", 1104 | watchers: 1647, 1105 | stargazers: 1647, 1106 | forks: 145, 1107 | fork: false, 1108 | size: 2188, 1109 | owner: "peers", 1110 | private: false, 1111 | open_issues: 20, 1112 | has_issues: true, 1113 | has_downloads: true, 1114 | has_wiki: true, 1115 | language: "JavaScript", 1116 | created_at: "2012-12-22T23:28:47-08:00", 1117 | pushed_at: "2013-11-09T22:58:31-08:00", 1118 | master_branch: "master", 1119 | organization: "peers", 1120 | }, 1121 | }, 1122 | { 1123 | created_at: "2013-10-19T02:06:43-07:00", 1124 | payload: { ref: "0.3.1", ref_type: "tag" }, 1125 | public: true, 1126 | type: "DeleteEvent", 1127 | url: "https://github.com/", 1128 | actor: "ericz", 1129 | actor_attributes: { 1130 | login: "ericz", 1131 | type: "User", 1132 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1133 | name: "Eric Zhang", 1134 | company: "Lever", 1135 | blog: "https://twitter.com/reallyez", 1136 | location: "Berkeley", 1137 | email: "eric@ericzhang.com", 1138 | }, 1139 | repository: { 1140 | id: 7292898, 1141 | name: "peerjs", 1142 | url: "https://github.com/peers/peerjs", 1143 | description: "Peer-to-peer data in the browser.", 1144 | homepage: "https://peerjs.com", 1145 | watchers: 1647, 1146 | stargazers: 1647, 1147 | forks: 145, 1148 | fork: false, 1149 | size: 2188, 1150 | owner: "peers", 1151 | private: false, 1152 | open_issues: 20, 1153 | has_issues: true, 1154 | has_downloads: true, 1155 | has_wiki: true, 1156 | language: "JavaScript", 1157 | created_at: "2012-12-22T23:28:47-08:00", 1158 | pushed_at: "2013-11-09T22:58:31-08:00", 1159 | master_branch: "master", 1160 | organization: "peers", 1161 | }, 1162 | }, 1163 | { 1164 | created_at: "2013-10-19T02:05:42-07:00", 1165 | payload: { 1166 | shas: [ 1167 | [ 1168 | "720eb3e881220f78eaca3d715ce7afe9324d1a3e", 1169 | "really.ez@gmail.com", 1170 | "0.3.1", 1171 | "ericz", 1172 | true, 1173 | ], 1174 | [ 1175 | "79e10688c56524479f3b2c0cb069c4ac7e065b57", 1176 | "really.ez@gmail.com", 1177 | "Set maxRetransmits to 0 when reliable false", 1178 | "ericz", 1179 | true, 1180 | ], 1181 | ], 1182 | size: 2, 1183 | ref: "refs/heads/master", 1184 | head: "79e10688c56524479f3b2c0cb069c4ac7e065b57", 1185 | }, 1186 | public: true, 1187 | type: "PushEvent", 1188 | url: "https://github.com/peers/peerjs/compare/b474a4cba6...79e10688c5", 1189 | actor: "ericz", 1190 | actor_attributes: { 1191 | login: "ericz", 1192 | type: "User", 1193 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1194 | name: "Eric Zhang", 1195 | company: "Lever", 1196 | blog: "https://twitter.com/reallyez", 1197 | location: "Berkeley", 1198 | email: "eric@ericzhang.com", 1199 | }, 1200 | repository: { 1201 | id: 7292898, 1202 | name: "peerjs", 1203 | url: "https://github.com/peers/peerjs", 1204 | description: "Peer-to-peer data in the browser.", 1205 | homepage: "https://peerjs.com", 1206 | watchers: 1647, 1207 | stargazers: 1647, 1208 | forks: 145, 1209 | fork: false, 1210 | size: 2188, 1211 | owner: "peers", 1212 | private: false, 1213 | open_issues: 20, 1214 | has_issues: true, 1215 | has_downloads: true, 1216 | has_wiki: true, 1217 | language: "JavaScript", 1218 | created_at: "2012-12-22T23:28:47-08:00", 1219 | pushed_at: "2013-11-09T22:58:31-08:00", 1220 | master_branch: "master", 1221 | organization: "peers", 1222 | }, 1223 | }, 1224 | { 1225 | created_at: "2013-10-19T02:02:15-07:00", 1226 | payload: { 1227 | ref: "0.3.1", 1228 | ref_type: "tag", 1229 | master_branch: "master", 1230 | description: "Peer-to-peer data in the browser.", 1231 | }, 1232 | public: true, 1233 | type: "CreateEvent", 1234 | url: "https://github.com/peers/peerjs/tree/0.3.1", 1235 | actor: "ericz", 1236 | actor_attributes: { 1237 | login: "ericz", 1238 | type: "User", 1239 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1240 | name: "Eric Zhang", 1241 | company: "Lever", 1242 | blog: "https://twitter.com/reallyez", 1243 | location: "Berkeley", 1244 | email: "eric@ericzhang.com", 1245 | }, 1246 | repository: { 1247 | id: 7292898, 1248 | name: "peerjs", 1249 | url: "https://github.com/peers/peerjs", 1250 | description: "Peer-to-peer data in the browser.", 1251 | homepage: "https://peerjs.com", 1252 | watchers: 1647, 1253 | stargazers: 1647, 1254 | forks: 145, 1255 | fork: false, 1256 | size: 2188, 1257 | owner: "peers", 1258 | private: false, 1259 | open_issues: 20, 1260 | has_issues: true, 1261 | has_downloads: true, 1262 | has_wiki: true, 1263 | language: "JavaScript", 1264 | created_at: "2012-12-22T23:28:47-08:00", 1265 | pushed_at: "2013-11-09T22:58:31-08:00", 1266 | master_branch: "master", 1267 | organization: "peers", 1268 | }, 1269 | }, 1270 | { 1271 | created_at: "2013-10-19T02:02:13-07:00", 1272 | payload: { 1273 | shas: [ 1274 | [ 1275 | "b474a4cba6156dabd1312cd25a520b4286e362f6", 1276 | "really.ez@gmail.com", 1277 | "Setting reliable to false by default", 1278 | "ericz", 1279 | true, 1280 | ], 1281 | ], 1282 | size: 1, 1283 | ref: "refs/heads/master", 1284 | head: "b474a4cba6156dabd1312cd25a520b4286e362f6", 1285 | }, 1286 | public: true, 1287 | type: "PushEvent", 1288 | url: "https://github.com/peers/peerjs/compare/93fc4931b2...b474a4cba6", 1289 | actor: "ericz", 1290 | actor_attributes: { 1291 | login: "ericz", 1292 | type: "User", 1293 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1294 | name: "Eric Zhang", 1295 | company: "Lever", 1296 | blog: "https://twitter.com/reallyez", 1297 | location: "Berkeley", 1298 | email: "eric@ericzhang.com", 1299 | }, 1300 | repository: { 1301 | id: 7292898, 1302 | name: "peerjs", 1303 | url: "https://github.com/peers/peerjs", 1304 | description: "Peer-to-peer data in the browser.", 1305 | homepage: "https://peerjs.com", 1306 | watchers: 1647, 1307 | stargazers: 1647, 1308 | forks: 145, 1309 | fork: false, 1310 | size: 2188, 1311 | owner: "peers", 1312 | private: false, 1313 | open_issues: 20, 1314 | has_issues: true, 1315 | has_downloads: true, 1316 | has_wiki: true, 1317 | language: "JavaScript", 1318 | created_at: "2012-12-22T23:28:47-08:00", 1319 | pushed_at: "2013-11-09T22:58:31-08:00", 1320 | master_branch: "master", 1321 | organization: "peers", 1322 | }, 1323 | }, 1324 | { 1325 | created_at: "2013-10-19T01:53:16-07:00", 1326 | payload: { 1327 | shas: [ 1328 | [ 1329 | "3949c236345171987b9291059fbaf9024eeca680", 1330 | "really.ez@gmail.com", 1331 | "0.3.1", 1332 | "ericz", 1333 | true, 1334 | ], 1335 | [ 1336 | "93fc4931b24c0261c5fda71e0441a5ee8bda70b2", 1337 | "really.ez@gmail.com", 1338 | "Update reliable doc", 1339 | "ericz", 1340 | true, 1341 | ], 1342 | ], 1343 | size: 2, 1344 | ref: "refs/heads/master", 1345 | head: "93fc4931b24c0261c5fda71e0441a5ee8bda70b2", 1346 | }, 1347 | public: true, 1348 | type: "PushEvent", 1349 | url: "https://github.com/peers/peerjs/compare/cd287e2fae...93fc4931b2", 1350 | actor: "ericz", 1351 | actor_attributes: { 1352 | login: "ericz", 1353 | type: "User", 1354 | gravatar_id: "c584ef7fe434331f7068ea49cacd88b9", 1355 | name: "Eric Zhang", 1356 | company: "Lever", 1357 | blog: "https://twitter.com/reallyez", 1358 | location: "Berkeley", 1359 | email: "eric@ericzhang.com", 1360 | }, 1361 | repository: { 1362 | id: 7292898, 1363 | name: "peerjs", 1364 | url: "https://github.com/peers/peerjs", 1365 | description: "Peer-to-peer data in the browser.", 1366 | homepage: "https://peerjs.com", 1367 | watchers: 1647, 1368 | stargazers: 1647, 1369 | forks: 145, 1370 | fork: false, 1371 | size: 2188, 1372 | owner: "peers", 1373 | private: false, 1374 | open_issues: 20, 1375 | has_issues: true, 1376 | has_downloads: true, 1377 | has_wiki: true, 1378 | language: "JavaScript", 1379 | created_at: "2012-12-22T23:28:47-08:00", 1380 | pushed_at: "2013-11-09T22:58:31-08:00", 1381 | master_branch: "master", 1382 | organization: "peers", 1383 | }, 1384 | }, 1385 | ]; 1386 | export default data; 1387 | 1388 | export const blob = new Blob( 1389 | data.map((x) => JSON.stringify(x)), 1390 | { type: "application/json" }, 1391 | ); 1392 | export const objWithBlob = { ...data[0], blob }; 1393 | -------------------------------------------------------------------------------- /__test__/numbers.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "@jest/globals"; 2 | 3 | import { packAndUnpack } from "./util"; 4 | 5 | describe("Binarypack", () => { 6 | it("should keep valid UTF-8", async () => { 7 | const values = [ 8 | 0, 9 | 1, 10 | -1, 11 | // 12 | Math.PI, 13 | -Math.PI, 14 | //8 bit 15 | 0x7f, 16 | 0x0f, 17 | //16 bit 18 | 0x7fff, 19 | 0x0fff, 20 | //32 bit 21 | 0x7fffffff, 22 | 0x0fffffff, 23 | //64 bit 24 | // 0x7FFFFFFFFFFFFFFF, 25 | 0x0fffffffffffffff, 26 | ]; 27 | // expect.assertions(values.length); 28 | for (const v of values) { 29 | expect(packAndUnpack(v)).toEqual(v); 30 | } 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /__test__/objects.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "@jest/globals"; 2 | 3 | import commit_data from "./data"; 4 | import { packAndUnpack } from "./util"; 5 | 6 | describe("Binarypack", () => { 7 | it("should keep objects intact", async () => { 8 | const values = commit_data; 9 | // expect.assertions(values.length); 10 | for (const v of values) { 11 | expect(packAndUnpack(v)).toEqual(v); 12 | } 13 | }); 14 | it("should keep very large object intact", async () => { 15 | const v: { [key: number]: number } = {}; 16 | for (let i = 0; i < 0xffff; i++) { 17 | v[i] = i; 18 | } 19 | expect(packAndUnpack(v)).toEqual(v); 20 | }); 21 | it("should keep arrays of objects intact", async () => { 22 | expect(packAndUnpack(commit_data)).toEqual(commit_data); 23 | }); 24 | it("should keep empty and very large arrays intact", async () => { 25 | const values = [[], Array(0xffff).fill(0)]; 26 | // expect.assertions(values.length); 27 | for (const v of values) { 28 | expect(packAndUnpack(v)).toEqual(v); 29 | } 30 | }); 31 | it("should keep null", async () => { 32 | expect(packAndUnpack(null)).toEqual(null); 33 | }); 34 | 35 | it("should transfer Uint8Array views correctly", async () => { 36 | const arr = new Uint8Array(8); 37 | for (let i = 0; i < 8; i++) arr[i] = i; 38 | const v = new Uint8Array(arr.buffer, 4); // Half the array 39 | const result = packAndUnpack(v); 40 | 41 | expect(result).toBeInstanceOf(ArrayBuffer); 42 | if (result instanceof ArrayBuffer) 43 | expect(new Uint8Array(result)).toEqual(v); 44 | }); 45 | 46 | it("should transfer Uint8Array as ArrayBuffer", async () => { 47 | const values = [ 48 | new Uint8Array(), 49 | new Uint8Array([0]), 50 | new Uint8Array([0, 1, 2, 3, 4, 6, 7]), 51 | new Uint8Array([0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15]), 52 | new Uint8Array([ 53 | 0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 54 | 23, 24, 25, 26, 27, 28, 30, 31, 55 | ]), 56 | ]; 57 | // expect.assertions(values.length); 58 | for (const v of values) { 59 | const result = packAndUnpack(v); 60 | expect(result).toBeInstanceOf(ArrayBuffer); 61 | if (result instanceof ArrayBuffer) 62 | expect(new Uint8Array(result)).toEqual(v); 63 | } 64 | }); 65 | 66 | it("should transfer Int32Array as ArrayBuffer", async () => { 67 | const values = [ 68 | new Int32Array([0].map((x) => -x)), 69 | new Int32Array([0, 1, 2, 3, 4, 6, 7].map((x) => -x)), 70 | new Int32Array( 71 | [0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15].map((x) => -x), 72 | ), 73 | new Int32Array( 74 | [ 75 | 0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 76 | 22, 23, 24, 25, 26, 27, 28, 30, 31, 77 | ].map((x) => -x), 78 | ), 79 | ]; 80 | // expect.assertions(values.length); 81 | for (const v of values) { 82 | const result = packAndUnpack(v); 83 | expect(result).toBeInstanceOf(ArrayBuffer); 84 | if (result instanceof ArrayBuffer) 85 | expect(new Int32Array(result)).toEqual(v); 86 | } 87 | }); 88 | 89 | it("should keep ArrayBuffers", async () => { 90 | const values = [ 91 | new Uint8Array([]).buffer, 92 | new Uint8Array([0]).buffer, 93 | new Uint8Array([0, 1, 2, 3, 4, 6, 7]).buffer, 94 | new Uint8Array([0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15]).buffer, 95 | new Uint8Array([ 96 | 0, 1, 2, 3, 4, 6, 78, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 97 | 23, 24, 25, 26, 27, 28, 30, 31, 98 | ]).buffer, 99 | ]; 100 | // expect.assertions(values.length); 101 | for (const v of values) { 102 | expect(packAndUnpack(v)).toEqual(v); 103 | } 104 | }); 105 | 106 | it("should transfer Dates as String", async () => { 107 | const values = [new Date(), new Date(Date.UTC(1, 1, 1, 1, 1, 1, 1))]; 108 | // expect.assertions(values.length); 109 | for (const v of values) { 110 | expect(packAndUnpack(v)).toEqual(v.toString()); 111 | } 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /__test__/strings.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "@jest/globals"; 2 | 3 | import { packAndUnpack } from "./util"; 4 | 5 | describe("Binarypack", () => { 6 | it("should keep valid UTF-8", async () => { 7 | const values = [ 8 | "", 9 | "hello", 10 | "café", 11 | "中文", 12 | "broccoli🥦līp𨋢grin😃ok", 13 | "\u{10ffff}", 14 | ]; 15 | // expect.assertions(values.length); 16 | for (const v of values) { 17 | expect(packAndUnpack(v)).toEqual(v); 18 | } 19 | }); 20 | 21 | /** 22 | * A Javascript string with unpaired surrogates is not actually valid 23 | * UTF-16, and so it cannot be round-tripped to UTF-8 and back. 24 | * The recommended way to handle this is to replace each unpaired surrogate 25 | * with \uFFFD (the "replacement character"). 26 | * 27 | * Note a surrogate pair means two adjacent Javascript characters where the 28 | * first is in the range \uD800 - \uDBFF and the second is in the 29 | * range \uDC00 - \uDFFF. 30 | * To be valid UTF-16, Javascript characters from these ranges must *only* 31 | * appear in surrogate pairs. An *unpaired* surrogate means any such 32 | * Javascript character that is not paired up properly. 33 | * 34 | * https://github.com/peers/js-binarypack/issues/11#issuecomment-1445129237 35 | */ 36 | it("should replace unpaired surrogates", async () => { 37 | const v = "un\ud800paired\udfffsurrogates"; 38 | const expected = v.replace( 39 | /[\ud800-\udbff](?![\udc00-\udfff])|(? { 47 | const chunk = "ThisIsÁTèstString"; 48 | const v = chunk.repeat(1000); 49 | 50 | expect(await packAndUnpack(v)).toEqual(v); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /__test__/util.ts: -------------------------------------------------------------------------------- 1 | import { pack, Packable, unpack, Unpackable } from "../lib/binarypack"; 2 | 3 | export const packAndUnpack = (data: Packable) => { 4 | const encoded = pack(data); 5 | if (encoded instanceof Promise) { 6 | return encoded.then(unpack); 7 | } 8 | return unpack(encoded); 9 | }; 10 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", 3 | "files": { 4 | "ignore": ["dist", "node_modules", "package-lock.json"] 5 | }, 6 | "organizeImports": { 7 | "enabled": false 8 | }, 9 | "linter": { 10 | "enabled": false, 11 | "rules": { 12 | "recommended": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peers/js-binarypack/042242d7bded385a4fa310a14f48cffae7e0ddcc/bun.lockb -------------------------------------------------------------------------------- /lib/binarypack.ts: -------------------------------------------------------------------------------- 1 | import { BufferBuilder } from "./bufferbuilder"; 2 | 3 | export type Packable = 4 | | null 5 | | undefined 6 | | string 7 | | number 8 | | boolean 9 | | Date 10 | | ArrayBuffer 11 | | Blob 12 | | Array 13 | | { [key: string]: Packable } 14 | | ({ BYTES_PER_ELEMENT: number } & ArrayBufferView); 15 | export type Unpackable = 16 | | null 17 | | undefined 18 | | string 19 | | number 20 | | boolean 21 | | ArrayBuffer 22 | | Array 23 | | { [key: string]: Unpackable }; 24 | 25 | export function unpack(data: ArrayBuffer) { 26 | const unpacker = new Unpacker(data); 27 | return unpacker.unpack() as T; 28 | } 29 | 30 | export function pack(data: Packable) { 31 | const packer = new Packer(); 32 | const res = packer.pack(data); 33 | if (res instanceof Promise) { 34 | return res.then(() => packer.getBuffer()); 35 | } 36 | return packer.getBuffer(); 37 | } 38 | 39 | class Unpacker { 40 | private index: number; 41 | private readonly dataBuffer: ArrayBuffer; 42 | private readonly dataView: Uint8Array; 43 | private readonly length: number; 44 | 45 | constructor(data: ArrayBuffer) { 46 | this.index = 0; 47 | this.dataBuffer = data; 48 | this.dataView = new Uint8Array(this.dataBuffer); 49 | this.length = this.dataBuffer.byteLength; 50 | } 51 | 52 | unpack(): Unpackable { 53 | const type = this.unpack_uint8(); 54 | if (type < 0x80) { 55 | return type; 56 | } else if ((type ^ 0xe0) < 0x20) { 57 | return (type ^ 0xe0) - 0x20; 58 | } 59 | 60 | let size; 61 | if ((size = type ^ 0xa0) <= 0x0f) { 62 | return this.unpack_raw(size); 63 | } else if ((size = type ^ 0xb0) <= 0x0f) { 64 | return this.unpack_string(size); 65 | } else if ((size = type ^ 0x90) <= 0x0f) { 66 | return this.unpack_array(size); 67 | } else if ((size = type ^ 0x80) <= 0x0f) { 68 | return this.unpack_map(size); 69 | } 70 | 71 | switch (type) { 72 | case 0xc0: 73 | return null; 74 | case 0xc1: 75 | return undefined; 76 | case 0xc2: 77 | return false; 78 | case 0xc3: 79 | return true; 80 | case 0xca: 81 | return this.unpack_float(); 82 | case 0xcb: 83 | return this.unpack_double(); 84 | case 0xcc: 85 | return this.unpack_uint8(); 86 | case 0xcd: 87 | return this.unpack_uint16(); 88 | case 0xce: 89 | return this.unpack_uint32(); 90 | case 0xcf: 91 | return this.unpack_uint64(); 92 | case 0xd0: 93 | return this.unpack_int8(); 94 | case 0xd1: 95 | return this.unpack_int16(); 96 | case 0xd2: 97 | return this.unpack_int32(); 98 | case 0xd3: 99 | return this.unpack_int64(); 100 | case 0xd4: 101 | return undefined; 102 | case 0xd5: 103 | return undefined; 104 | case 0xd6: 105 | return undefined; 106 | case 0xd7: 107 | return undefined; 108 | case 0xd8: 109 | size = this.unpack_uint16(); 110 | return this.unpack_string(size); 111 | case 0xd9: 112 | size = this.unpack_uint32(); 113 | return this.unpack_string(size); 114 | case 0xda: 115 | size = this.unpack_uint16(); 116 | return this.unpack_raw(size); 117 | case 0xdb: 118 | size = this.unpack_uint32(); 119 | return this.unpack_raw(size); 120 | case 0xdc: 121 | size = this.unpack_uint16(); 122 | return this.unpack_array(size); 123 | case 0xdd: 124 | size = this.unpack_uint32(); 125 | return this.unpack_array(size); 126 | case 0xde: 127 | size = this.unpack_uint16(); 128 | return this.unpack_map(size); 129 | case 0xdf: 130 | size = this.unpack_uint32(); 131 | return this.unpack_map(size); 132 | } 133 | } 134 | 135 | unpack_uint8() { 136 | const byte = this.dataView[this.index] & 0xff; 137 | this.index++; 138 | return byte; 139 | } 140 | 141 | unpack_uint16() { 142 | const bytes = this.read(2); 143 | const uint16 = (bytes[0] & 0xff) * 256 + (bytes[1] & 0xff); 144 | this.index += 2; 145 | return uint16; 146 | } 147 | 148 | unpack_uint32() { 149 | const bytes = this.read(4); 150 | const uint32 = 151 | ((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]; 152 | this.index += 4; 153 | return uint32; 154 | } 155 | 156 | unpack_uint64() { 157 | const bytes = this.read(8); 158 | const uint64 = 159 | ((((((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]) * 160 | 256 + 161 | bytes[4]) * 162 | 256 + 163 | bytes[5]) * 164 | 256 + 165 | bytes[6]) * 166 | 256 + 167 | bytes[7]; 168 | this.index += 8; 169 | return uint64; 170 | } 171 | 172 | unpack_int8() { 173 | const uint8 = this.unpack_uint8(); 174 | return uint8 < 0x80 ? uint8 : uint8 - (1 << 8); 175 | } 176 | 177 | unpack_int16() { 178 | const uint16 = this.unpack_uint16(); 179 | return uint16 < 0x8000 ? uint16 : uint16 - (1 << 16); 180 | } 181 | 182 | unpack_int32() { 183 | const uint32 = this.unpack_uint32(); 184 | return uint32 < 2 ** 31 ? uint32 : uint32 - 2 ** 32; 185 | } 186 | 187 | unpack_int64() { 188 | const uint64 = this.unpack_uint64(); 189 | return uint64 < 2 ** 63 ? uint64 : uint64 - 2 ** 64; 190 | } 191 | 192 | unpack_raw(size: number) { 193 | if (this.length < this.index + size) { 194 | throw new Error( 195 | `BinaryPackFailure: index is out of range ${this.index} ${size} ${this.length}`, 196 | ); 197 | } 198 | const buf = this.dataBuffer.slice(this.index, this.index + size); 199 | this.index += size; 200 | 201 | return buf; 202 | } 203 | 204 | unpack_string(size: number) { 205 | const bytes = this.read(size); 206 | let i = 0; 207 | let str = ""; 208 | let c; 209 | let code; 210 | 211 | while (i < size) { 212 | c = bytes[i]; 213 | // The length of a UTF-8 sequence is specified in the first byte: 214 | // 0xxxxxxx means length 1, 215 | // 110xxxxx means length 2, 216 | // 1110xxxx means length 3, 217 | // 11110xxx means length 4. 218 | // 10xxxxxx is for non-initial bytes. 219 | if (c < 0xa0) { 220 | // One-byte sequence: bits 0xxxxxxx 221 | code = c; 222 | i++; 223 | } else if ((c ^ 0xc0) < 0x20) { 224 | // Two-byte sequence: bits 110xxxxx 10xxxxxx 225 | code = ((c & 0x1f) << 6) | (bytes[i + 1] & 0x3f); 226 | i += 2; 227 | } else if ((c ^ 0xe0) < 0x10) { 228 | // Three-byte sequence: bits 1110xxxx 10xxxxxx 10xxxxxx 229 | code = 230 | ((c & 0x0f) << 12) | 231 | ((bytes[i + 1] & 0x3f) << 6) | 232 | (bytes[i + 2] & 0x3f); 233 | i += 3; 234 | } else { 235 | // Four-byte sequence: bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 236 | code = 237 | ((c & 0x07) << 18) | 238 | ((bytes[i + 1] & 0x3f) << 12) | 239 | ((bytes[i + 2] & 0x3f) << 6) | 240 | (bytes[i + 3] & 0x3f); 241 | i += 4; 242 | } 243 | str += String.fromCodePoint(code); 244 | } 245 | 246 | this.index += size; 247 | return str; 248 | } 249 | 250 | unpack_array(size: number) { 251 | const objects = new Array(size); 252 | for (let i = 0; i < size; i++) { 253 | objects[i] = this.unpack(); 254 | } 255 | return objects; 256 | } 257 | 258 | unpack_map(size: number) { 259 | const map: { [key: string]: Unpackable } = {}; 260 | for (let i = 0; i < size; i++) { 261 | const key = this.unpack() as string; 262 | map[key] = this.unpack(); 263 | } 264 | return map; 265 | } 266 | 267 | unpack_float() { 268 | const uint32 = this.unpack_uint32(); 269 | const sign = uint32 >> 31; 270 | const exp = ((uint32 >> 23) & 0xff) - 127; 271 | const fraction = (uint32 & 0x7fffff) | 0x800000; 272 | return (sign === 0 ? 1 : -1) * fraction * 2 ** (exp - 23); 273 | } 274 | 275 | unpack_double() { 276 | const h32 = this.unpack_uint32(); 277 | const l32 = this.unpack_uint32(); 278 | const sign = h32 >> 31; 279 | const exp = ((h32 >> 20) & 0x7ff) - 1023; 280 | const hfrac = (h32 & 0xfffff) | 0x100000; 281 | const frac = hfrac * 2 ** (exp - 20) + l32 * 2 ** (exp - 52); 282 | return (sign === 0 ? 1 : -1) * frac; 283 | } 284 | 285 | read(length: number) { 286 | const j = this.index; 287 | if (j + length <= this.length) { 288 | return this.dataView.subarray(j, j + length); 289 | } else { 290 | throw new Error("BinaryPackFailure: read index out of range"); 291 | } 292 | } 293 | } 294 | 295 | export class Packer { 296 | private _bufferBuilder = new BufferBuilder(); 297 | private _textEncoder = new TextEncoder(); 298 | 299 | getBuffer() { 300 | return this._bufferBuilder.toArrayBuffer(); 301 | } 302 | 303 | pack(value: Packable) { 304 | if (typeof value === "string") { 305 | this.pack_string(value); 306 | } else if (typeof value === "number") { 307 | if (Math.floor(value) === value) { 308 | this.pack_integer(value); 309 | } else { 310 | this.pack_double(value); 311 | } 312 | } else if (typeof value === "boolean") { 313 | if (value === true) { 314 | this._bufferBuilder.append(0xc3); 315 | } else if (value === false) { 316 | this._bufferBuilder.append(0xc2); 317 | } 318 | } else if (value === undefined) { 319 | this._bufferBuilder.append(0xc0); 320 | } else if (typeof value === "object") { 321 | if (value === null) { 322 | this._bufferBuilder.append(0xc0); 323 | } else { 324 | const constructor = value.constructor; 325 | if (value instanceof Array) { 326 | const res = this.pack_array(value); 327 | if (res instanceof Promise) { 328 | return res.then(() => this._bufferBuilder.flush()); 329 | } 330 | } else if (value instanceof ArrayBuffer) { 331 | this.pack_bin(new Uint8Array(value)); 332 | } else if ("BYTES_PER_ELEMENT" in value) { 333 | const v = value as unknown as DataView; 334 | this.pack_bin(new Uint8Array(v.buffer, v.byteOffset, v.byteLength)); 335 | } else if (value instanceof Date) { 336 | this.pack_string(value.toString()); 337 | } else if (value instanceof Blob) { 338 | return value.arrayBuffer().then((buffer) => { 339 | this.pack_bin(new Uint8Array(buffer)); 340 | this._bufferBuilder.flush(); 341 | }); 342 | // this.pack_bin(new Uint8Array(await value.arrayBuffer())); 343 | } else if ( 344 | constructor == Object || 345 | constructor.toString().startsWith("class") 346 | ) { 347 | const res = this.pack_object(value); 348 | if (res instanceof Promise) { 349 | return res.then(() => this._bufferBuilder.flush()); 350 | } 351 | } else { 352 | throw new Error(`Type "${constructor.toString()}" not yet supported`); 353 | } 354 | } 355 | } else { 356 | throw new Error(`Type "${typeof value}" not yet supported`); 357 | } 358 | this._bufferBuilder.flush(); 359 | } 360 | 361 | pack_bin(blob: Uint8Array) { 362 | const length = blob.length; 363 | 364 | if (length <= 0x0f) { 365 | this.pack_uint8(0xa0 + length); 366 | } else if (length <= 0xffff) { 367 | this._bufferBuilder.append(0xda); 368 | this.pack_uint16(length); 369 | } else if (length <= 0xffffffff) { 370 | this._bufferBuilder.append(0xdb); 371 | this.pack_uint32(length); 372 | } else { 373 | throw new Error("Invalid length"); 374 | } 375 | this._bufferBuilder.append_buffer(blob); 376 | } 377 | 378 | pack_string(str: string) { 379 | const encoded = this._textEncoder.encode(str); 380 | const length = encoded.length; 381 | 382 | if (length <= 0x0f) { 383 | this.pack_uint8(0xb0 + length); 384 | } else if (length <= 0xffff) { 385 | this._bufferBuilder.append(0xd8); 386 | this.pack_uint16(length); 387 | } else if (length <= 0xffffffff) { 388 | this._bufferBuilder.append(0xd9); 389 | this.pack_uint32(length); 390 | } else { 391 | throw new Error("Invalid length"); 392 | } 393 | this._bufferBuilder.append_buffer(encoded); 394 | } 395 | 396 | pack_array(ary: Packable[]) { 397 | const length = ary.length; 398 | if (length <= 0x0f) { 399 | this.pack_uint8(0x90 + length); 400 | } else if (length <= 0xffff) { 401 | this._bufferBuilder.append(0xdc); 402 | this.pack_uint16(length); 403 | } else if (length <= 0xffffffff) { 404 | this._bufferBuilder.append(0xdd); 405 | this.pack_uint32(length); 406 | } else { 407 | throw new Error("Invalid length"); 408 | } 409 | 410 | const packNext = (index: number): Promise | void => { 411 | if (index < length) { 412 | const res = this.pack(ary[index]); 413 | if (res instanceof Promise) { 414 | return res.then(() => packNext(index + 1)); 415 | } 416 | return packNext(index + 1); 417 | } 418 | }; 419 | 420 | return packNext(0); 421 | } 422 | 423 | pack_integer(num: number) { 424 | if (num >= -0x20 && num <= 0x7f) { 425 | this._bufferBuilder.append(num & 0xff); 426 | } else if (num >= 0x00 && num <= 0xff) { 427 | this._bufferBuilder.append(0xcc); 428 | this.pack_uint8(num); 429 | } else if (num >= -0x80 && num <= 0x7f) { 430 | this._bufferBuilder.append(0xd0); 431 | this.pack_int8(num); 432 | } else if (num >= 0x0000 && num <= 0xffff) { 433 | this._bufferBuilder.append(0xcd); 434 | this.pack_uint16(num); 435 | } else if (num >= -0x8000 && num <= 0x7fff) { 436 | this._bufferBuilder.append(0xd1); 437 | this.pack_int16(num); 438 | } else if (num >= 0x00000000 && num <= 0xffffffff) { 439 | this._bufferBuilder.append(0xce); 440 | this.pack_uint32(num); 441 | } else if (num >= -0x80000000 && num <= 0x7fffffff) { 442 | this._bufferBuilder.append(0xd2); 443 | this.pack_int32(num); 444 | } else if (num >= -0x8000000000000000 && num <= 0x7fffffffffffffff) { 445 | this._bufferBuilder.append(0xd3); 446 | this.pack_int64(num); 447 | } else if (num >= 0x0000000000000000 && num <= 0xffffffffffffffff) { 448 | this._bufferBuilder.append(0xcf); 449 | this.pack_uint64(num); 450 | } else { 451 | throw new Error("Invalid integer"); 452 | } 453 | } 454 | 455 | pack_double(num: number) { 456 | let sign = 0; 457 | if (num < 0) { 458 | sign = 1; 459 | num = -num; 460 | } 461 | const exp = Math.floor(Math.log(num) / Math.LN2); 462 | const frac0 = num / 2 ** exp - 1; 463 | const frac1 = Math.floor(frac0 * 2 ** 52); 464 | const b32 = 2 ** 32; 465 | const h32 = 466 | (sign << 31) | ((exp + 1023) << 20) | ((frac1 / b32) & 0x0fffff); 467 | const l32 = frac1 % b32; 468 | this._bufferBuilder.append(0xcb); 469 | this.pack_int32(h32); 470 | this.pack_int32(l32); 471 | } 472 | 473 | pack_object(obj: { [key: string]: Packable }) { 474 | const keys = Object.keys(obj); 475 | const length = keys.length; 476 | if (length <= 0x0f) { 477 | this.pack_uint8(0x80 + length); 478 | } else if (length <= 0xffff) { 479 | this._bufferBuilder.append(0xde); 480 | this.pack_uint16(length); 481 | } else if (length <= 0xffffffff) { 482 | this._bufferBuilder.append(0xdf); 483 | this.pack_uint32(length); 484 | } else { 485 | throw new Error("Invalid length"); 486 | } 487 | 488 | const packNext = (index: number): Promise | void => { 489 | if (index < keys.length) { 490 | const prop = keys[index]; 491 | // eslint-disable-next-line no-prototype-builtins 492 | if (obj.hasOwnProperty(prop)) { 493 | this.pack(prop); 494 | const res = this.pack(obj[prop]); 495 | if (res instanceof Promise) { 496 | return res.then(() => packNext(index + 1)); 497 | } 498 | } 499 | return packNext(index + 1); 500 | } 501 | }; 502 | 503 | return packNext(0); 504 | } 505 | 506 | pack_uint8(num: number) { 507 | this._bufferBuilder.append(num); 508 | } 509 | 510 | pack_uint16(num: number) { 511 | this._bufferBuilder.append(num >> 8); 512 | this._bufferBuilder.append(num & 0xff); 513 | } 514 | 515 | pack_uint32(num: number) { 516 | const n = num & 0xffffffff; 517 | this._bufferBuilder.append((n & 0xff000000) >>> 24); 518 | this._bufferBuilder.append((n & 0x00ff0000) >>> 16); 519 | this._bufferBuilder.append((n & 0x0000ff00) >>> 8); 520 | this._bufferBuilder.append(n & 0x000000ff); 521 | } 522 | 523 | pack_uint64(num: number) { 524 | const high = num / 2 ** 32; 525 | const low = num % 2 ** 32; 526 | this._bufferBuilder.append((high & 0xff000000) >>> 24); 527 | this._bufferBuilder.append((high & 0x00ff0000) >>> 16); 528 | this._bufferBuilder.append((high & 0x0000ff00) >>> 8); 529 | this._bufferBuilder.append(high & 0x000000ff); 530 | this._bufferBuilder.append((low & 0xff000000) >>> 24); 531 | this._bufferBuilder.append((low & 0x00ff0000) >>> 16); 532 | this._bufferBuilder.append((low & 0x0000ff00) >>> 8); 533 | this._bufferBuilder.append(low & 0x000000ff); 534 | } 535 | 536 | pack_int8(num: number) { 537 | this._bufferBuilder.append(num & 0xff); 538 | } 539 | 540 | pack_int16(num: number) { 541 | this._bufferBuilder.append((num & 0xff00) >> 8); 542 | this._bufferBuilder.append(num & 0xff); 543 | } 544 | 545 | pack_int32(num: number) { 546 | this._bufferBuilder.append((num >>> 24) & 0xff); 547 | this._bufferBuilder.append((num & 0x00ff0000) >>> 16); 548 | this._bufferBuilder.append((num & 0x0000ff00) >>> 8); 549 | this._bufferBuilder.append(num & 0x000000ff); 550 | } 551 | 552 | pack_int64(num: number) { 553 | const high = Math.floor(num / 2 ** 32); 554 | const low = num % 2 ** 32; 555 | this._bufferBuilder.append((high & 0xff000000) >>> 24); 556 | this._bufferBuilder.append((high & 0x00ff0000) >>> 16); 557 | this._bufferBuilder.append((high & 0x0000ff00) >>> 8); 558 | this._bufferBuilder.append(high & 0x000000ff); 559 | this._bufferBuilder.append((low & 0xff000000) >>> 24); 560 | this._bufferBuilder.append((low & 0x00ff0000) >>> 16); 561 | this._bufferBuilder.append((low & 0x0000ff00) >>> 8); 562 | this._bufferBuilder.append(low & 0x000000ff); 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /lib/bufferbuilder.ts: -------------------------------------------------------------------------------- 1 | class BufferBuilder { 2 | private _pieces: number[]; 3 | private readonly _parts: ArrayBufferView[]; 4 | 5 | constructor() { 6 | this._pieces = []; 7 | this._parts = []; 8 | } 9 | 10 | append_buffer(data: ArrayBufferView) { 11 | this.flush(); 12 | this._parts.push(data); 13 | } 14 | 15 | append(data: number) { 16 | this._pieces.push(data); 17 | } 18 | 19 | flush() { 20 | if (this._pieces.length > 0) { 21 | const buf = new Uint8Array(this._pieces); 22 | this._parts.push(buf); 23 | this._pieces = []; 24 | } 25 | } 26 | 27 | private encoder = new TextEncoder(); 28 | 29 | public toArrayBuffer() { 30 | const buffer = []; 31 | for (const part of this._parts) { 32 | buffer.push(part); 33 | } 34 | return concatArrayBuffers(buffer).buffer; 35 | } 36 | } 37 | 38 | export { BufferBuilder }; 39 | 40 | function concatArrayBuffers(bufs: ArrayBufferView[]) { 41 | let size = 0; 42 | for (const buf of bufs) { 43 | size += buf.byteLength; 44 | } 45 | const result = new Uint8Array(size); 46 | let offset = 0; 47 | for (const buf of bufs) { 48 | const view = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); 49 | result.set(view, offset); 50 | offset += buf.byteLength; 51 | } 52 | return result; 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peerjs-js-binarypack", 3 | "version": "2.0.0", 4 | "description": "BinaryPack serialization", 5 | "homepage": "https://github.com/peers/js-binarypack", 6 | "main": "dist/binarypack.cjs", 7 | "module": "dist/binarypack.mjs", 8 | "source": "lib/binarypack.ts", 9 | "types": "dist/binarypack.d.ts", 10 | "type": "module", 11 | "exports": { 12 | ".": { 13 | "import": { 14 | "types": "./dist/binarypack.d.ts", 15 | "default": "./dist/binarypack.mjs" 16 | }, 17 | "require": { 18 | "types": "./dist/binarypack.d.ts", 19 | "default": "./dist/binarypack.cjs" 20 | } 21 | } 22 | }, 23 | "scripts": { 24 | "watch": "parcel watch", 25 | "build": "parcel build", 26 | "format": "biome format --write .", 27 | "lint": "eslint --ext .js,.ts .", 28 | "check": "tsc --noEmit" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/peers/js-binarypack" 33 | }, 34 | "funding": { 35 | "type": "opencollective", 36 | "url": "https://opencollective.com/peer" 37 | }, 38 | "collective": { 39 | "type": "opencollective", 40 | "url": "https://opencollective.com/peer" 41 | }, 42 | "author": "Eric Zhang", 43 | "contributors": [ 44 | { 45 | "name": "Eric Zhang", 46 | "email": "really.ez@gmail.com" 47 | }, 48 | { 49 | "name": "Jonas Gloning", 50 | "email": "34194370+jonasgloning@users.noreply.github.com" 51 | }, 52 | { 53 | "name": "afrokick", 54 | "email": "devbyru@gmail.com" 55 | }, 56 | { 57 | "name": "manvalls", 58 | "email": "manolo@chatiku.com" 59 | }, 60 | { 61 | "name": "Michelle Bu", 62 | "email": "michelle@michellebu.com" 63 | }, 64 | { 65 | "name": "Liu Cong", 66 | "email": "leehom2001@gmail.com" 67 | }, 68 | { 69 | "name": "Michelle Bu", 70 | "email": "michelle@stripe.com" 71 | }, 72 | { 73 | "name": "lmb", 74 | "email": "i@lmb.io" 75 | }, 76 | { 77 | "name": "orcaman", 78 | "email": "orhiltch@gmail.com" 79 | }, 80 | { 81 | "name": "Godfrey Chan", 82 | "email": "godfreykfc@gmail.com" 83 | }, 84 | { 85 | "name": "Jarrett Cruger", 86 | "email": "jcrugzz@gmail.com" 87 | }, 88 | { 89 | "name": "Rossi Lorenzo", 90 | "email": "snowycoder@gmail.com" 91 | }, 92 | { 93 | "name": "divec", 94 | "email": "david@troi.org" 95 | }, 96 | { 97 | "name": "renovate[bot]", 98 | "email": "29139614+renovate[bot]@users.noreply.github.com" 99 | } 100 | ], 101 | "devDependencies": { 102 | "@biomejs/biome": "1.5.3", 103 | "@jest/globals": "^29.7.0", 104 | "@parcel/packager-ts": "^2.8.3", 105 | "@parcel/transformer-typescript-types": "^2.8.3", 106 | "@semantic-release/changelog": "^6.0.2", 107 | "@semantic-release/git": "^10.0.1", 108 | "@typescript-eslint/eslint-plugin": "^6.0.0", 109 | "eslint": "^8.34.0", 110 | "parcel": "^2.8.3", 111 | "semantic-release": "^23.0.0", 112 | "typescript": "^5.0.0" 113 | }, 114 | "license": "MIT", 115 | "engines": { 116 | "node": ">= 14.0.0" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", ":assignAndReview(jonasgloning)"], 4 | "labels": ["dependencies"], 5 | "assignees": ["jonasgloning"], 6 | "major": { 7 | "dependencyDashboardApproval": true 8 | }, 9 | "packageRules": [ 10 | { 11 | "matchDepTypes": ["devDependencies"], 12 | "addLabels": ["dev-dependencies"], 13 | "automerge": true, 14 | "automergeType": "branch" 15 | }, 16 | { 17 | "matchUpdateTypes": ["minor", "patch"], 18 | "matchCurrentVersion": "!/^0/", 19 | "automerge": true, 20 | "automergeType": "branch" 21 | } 22 | ], 23 | "lockFileMaintenance": { 24 | "enabled": true, 25 | "automerge": true, 26 | "automergeType": "branch" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es2021", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true 8 | } 9 | } 10 | --------------------------------------------------------------------------------