├── .gitattributes ├── .github └── workflows │ ├── build.yaml │ └── deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── base64.md ├── package-lock.json ├── package.json ├── playground ├── index-raw.html ├── polyfill-core.mjs ├── polyfill-install.mjs ├── prism.css ├── style.css └── water-light.css ├── scripts └── static-highlight.js ├── spec.html ├── stream.mjs └── test-polyfill.mjs /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html -diff merge=ours 2 | spec.js -diff merge=ours 3 | spec.css -diff merge=ours 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build spec 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 20 18 | - run: npm ci 19 | - run: npm run build 20 | - run: npm run check-format 21 | - run: npm run test 22 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 20 17 | - run: npm ci 18 | - run: npm run build 19 | - uses: JamesIves/github-pages-deploy-action@v4.4.1 20 | with: 21 | branch: gh-pages 22 | folder: dist 23 | clean: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ECMA TC39 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uint8Array to/from base64 and hex 2 | 3 | base64 is a common way to represent arbitrary binary data as ASCII. JavaScript has Uint8Arrays to work with binary data, but no built-in mechanism to encode that data as base64, nor to take base64'd data and produce a corresponding Uint8Arrays. This is a proposal to fix that. It also adds methods for converting between hex strings and Uint8Arrays. 4 | 5 | It is currently at stage 3 of [the TC39 process](https://tc39.es/process-document/): it is ready for implementations. See [this issue](https://github.com/tc39/proposal-arraybuffer-base64/issues/51) for current status. 6 | 7 | Try it out on [the playground](https://tc39.github.io/proposal-arraybuffer-base64/). 8 | 9 | Spec text is available [here](https://tc39.github.io/proposal-arraybuffer-base64/spec/), and test262 tests in [this PR](https://github.com/tc39/test262/pull/3994). 10 | 11 | Implementers may be interested in [the open-source simdutf library](https://github.com/simdutf/simdutf/?tab=readme-ov-file#base64), which provides a fast implementation of a base64 decoder which matches `Uint8Array.fromBase64(string)` (including handling of whitespace) when it is called without specifying any options. As of this writing it only works on latin1 strings, but a utf16 version [may be coming](https://github.com/simdutf/simdutf/pull/375#issuecomment-2016979707). 12 | 13 | ## Basic API 14 | 15 | ```js 16 | let arr = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]); 17 | console.log(arr.toBase64()); 18 | // 'SGVsbG8gV29ybGQ=' 19 | console.log(arr.toHex()); 20 | // '48656c6c6f20576f726c64' 21 | ``` 22 | 23 | ```js 24 | let string = 'SGVsbG8gV29ybGQ='; 25 | console.log(Uint8Array.fromBase64(string)); 26 | // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]) 27 | 28 | string = '48656c6c6f20576f726c64'; 29 | console.log(Uint8Array.fromHex(string)); 30 | // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]) 31 | ``` 32 | 33 | This would add `Uint8Array.prototype.toBase64`/`Uint8Array.prototype.toHex` and `Uint8Array.fromBase64`/`Uint8Array.fromHex` methods. The latter pair would throw if given a string which is not properly encoded. 34 | 35 | ## Base64 options 36 | 37 | Additional options are supplied in an options bag argument: 38 | 39 | - `alphabet`: Allows specifying the alphabet as either `base64` or `base64url`. 40 | 41 | - `lastChunkHandling`: Recall that base64 decoding operates on chunks of 4 characters at a time, but the input may have some characters which don't fit evenly into such a chunk of 4 characters. This option determines how the final chunk of characters should be handled. The three options are `"loose"` (the default), which treats the chunk as if it had any necessary `=` padding (but throws if this is not possible, i.e. there is exactly one extra character); `"strict"`, which enforces that the chunk has exactly 4 characters (counting `=` padding) and that [overflow bits](https://datatracker.ietf.org/doc/html/rfc4648#section-3.5) are 0; and `"stop-before-partial"`, which stops decoding before the final chunk unless the final chunk has exactly 4 characters. 42 | 43 | - `omitPadding`: When encoding, whether to include `=` padding. Defaults to `false`, i.e., padding is included. 44 | 45 | The hex methods do not take any options. 46 | 47 | ## Writing to an existing Uint8Array 48 | 49 | The `Uint8Array.prototype.setFromBase64` method allows writing to an existing Uint8Array. Like the [TextEncoder `encodeInto` method](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encodeInto), it returns a `{ read, written }` pair. 50 | 51 | ```js 52 | let target = new Uint8Array(8); 53 | let { read, written } = target.setFromBase64('Zm9vYmFy'); 54 | assert.deepStrictEqual([...target], [102, 111, 111, 98, 97, 114, 0, 0]); 55 | assert.deepStrictEqual({ read, written }, { read: 8, written: 6 }); 56 | ``` 57 | 58 | This method takes an optional final options bag with the same options as above. 59 | 60 | As with `encodeInto`, there is not explicit support for writing to specified offset of the target, but you can accomplish that by creating a subarray. 61 | 62 | `Uint8Array.prototype.setFromHex` is the same except for hex. 63 | 64 | ## Streaming 65 | 66 | There is no explicit support for streaming. However, it is [relatively straightforward to do effeciently in userland](./stream.mjs) on top of this API, with support for all the same options as the underlying functions. 67 | 68 | ## FAQ 69 | 70 | ### What variation exists among base64 implementations in standards, in other languages, and in existing JavaScript libraries? 71 | 72 | I have a [whole page on that](./base64.md), with tables and footnotes and everything. There is relatively little room for variation, but languages and libraries manage to explore almost all of the room there is. 73 | 74 | To summarize, base64 encoders can vary in the following ways: 75 | 76 | - Standard or URL-safe alphabet 77 | - Whether `=` is included in output 78 | - Whether to add linebreaks after a certain number of characters 79 | 80 | and decoders can vary in the following ways: 81 | 82 | - Standard or URL-safe alphabet 83 | - Whether `=` is required in input, and how to handle malformed padding (e.g. extra `=`) 84 | - Whether to fail on non-zero padding bits 85 | - Whether lines must be of a limited length 86 | - How non-base64-alphabet characters are handled (sometimes with special handling for only a subset, like whitespace) 87 | 88 | ### What alphabets are supported? 89 | 90 | For base64, you can specify either base64 or base64url for both the encoder and the decoder. 91 | 92 | For hex, both lowercase and uppercase characters (including mixed within the same string) will decode successfully. Output is always lowercase. 93 | 94 | ### How are the extra padding bits handled? 95 | 96 | If the length of your input data isn't exactly a multiple of 3 bytes, then encoding it will use either 2 or 3 base64 characters to encode the final 1 or 2 bytes. Since each base64 character is 6 bits, this means you'll be using either 12 or 18 bits to represent 8 or 16 bits, which means you have an extra 4 or 2 bits which don't encode anything. 97 | 98 | Per [the RFC](https://datatracker.ietf.org/doc/html/rfc4648#section-3.5), decoders MAY reject input strings where the padding bits are non-zero. Here, non-zero padding bits are silently ignored unless `lastChunkHandling: "strict"` is specified. 99 | 100 | ### How is whitespace handled? 101 | 102 | The encoders do not output whitespace. The hex decoder does not allow it as input. The base64 decoder allows [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace) anywhere in the string. 103 | 104 | ### How are other characters handled? 105 | 106 | The presence of any other characters causes an exception. 107 | 108 | ### Why are these synchronous? 109 | 110 | In practice most base64'd data I encounter is on the order of hundreds of bytes (e.g. SSH keys), which can be encoded and decoded extremely quickly. It would be a shame to require Promises to deal with such data, I think, especially given that the alternatives people currently use all appear to be synchronous. 111 | 112 | ### Why just these encodings? 113 | 114 | While other string encodings exist, none are nearly as commonly used as these two. 115 | 116 | See issues [#7](https://github.com/tc39/proposal-arraybuffer-base64/issues/7), [#8](https://github.com/tc39/proposal-arraybuffer-base64/issues/8), and [#11](https://github.com/tc39/proposal-arraybuffer-base64/issues/11). 117 | 118 | ### Why not just use `atob` and `btoa`? 119 | 120 | Those methods take and consume strings, rather than translating between a string and a Uint8Array. 121 | 122 | ### Why not TextEncoder? 123 | 124 | base64 is not a text encoding format; there's no [code points](https://unicode.org/glossary/#code_point) involved. So despite fitting with the type signature of TextEncoder/TextDecoder, base64 encoding and decoding is not a conceptually appropriate thing for those APIs to do. 125 | 126 | That's also been the consensus when it's come up [previously](https://discourse.wicg.io/t/base64-with-textencoder-textdecoder/1307/2). 127 | 128 | ### What if I just want to encode a portion of an ArrayBuffer? 129 | 130 | Uint8Arrays can be partial views of an underlying buffer, so you can create such a view and invoke `.toBase64` on it. 131 | -------------------------------------------------------------------------------- /base64.md: -------------------------------------------------------------------------------- 1 | # Notes on Base64 as it exists 2 | 3 | Towards an implementation in JavaScript. 4 | 5 | ## The RFCs 6 | 7 | There are two RFCs which are still generally relevant in modern times: [4648](https://datatracker.ietf.org/doc/html/rfc4648), which defines only the base64 and base64url encodings, and [2045](https://datatracker.ietf.org/doc/html/rfc2045#section-6.8), which defines [MIME](https://en.wikipedia.org/wiki/MIME) and includes a base64 encoding. 8 | 9 | RFC 4648 is "the base64 RFC". It obsoletes RFC [3548](https://datatracker.ietf.org/doc/html/rfc3548). 10 | 11 | - It defines both the standard (`+/`) and url-safe (`-_`) alphabets. 12 | - "Implementations MUST include appropriate pad characters at the end of encoded data unless the specification referring to this document explicitly states otherwise." Certain malformed padding MAY be ignored. 13 | - "Decoders MAY chose to reject an encoding if the pad bits have not been set to zero" 14 | - "Implementations MUST reject the encoded data if it contains characters outside the base alphabet when interpreting base-encoded data, unless the specification referring to this document explicitly states otherwise." 15 | 16 | RFC 2045 is not usually relevant, but it's worth summarizing its behavior anyway: 17 | 18 | - Only the standard (`+/`) alphabet is supported. 19 | - It defines only an encoding. The encoding is specified to include `=`. No direction is given for decoders which encounter data which is not padded with `=`, or which has non-zero padding bits. In practice, decoders seem to ignore both. 20 | - "Any characters outside of the base64 alphabet are to be ignored in base64-encoded data." 21 | - MIME requires lines of length at most 76 characters, seperated by CRLF. 22 | 23 | RFCs [1421](https://datatracker.ietf.org/doc/html/rfc1421) and [7468](https://datatracker.ietf.org/doc/html/rfc7468), which define "Privacy-Enhanced Mail" and related things (think `-----BEGIN PRIVATE KEY-----`), are basically identical to the above except that they mandate lines of exactly 64 characters, except that the last line may be shorter. 24 | 25 | RFC [4880](https://datatracker.ietf.org/doc/html/rfc4880#section-6) defines OpenPGP messages and is just the RFC 2045 format plus a checksum. In practice, only whitespace is ignored, not all non-base64 characters. 26 | 27 | No other variations are contemplated in any other RFC or implementation that I'm aware of. That is, we have the following ways that base64 encoders can vary: 28 | 29 | - Standard or URL-safe alphabet 30 | - Whether `=` is included in output 31 | - Whether to add linebreaks after a certain number of characters 32 | 33 | and the following ways that base64 decoders can vary: 34 | 35 | - Standard or URL-safe alphabet 36 | - Whether `=` is required in input, and how to handle malformed padding (e.g. extra `=`) 37 | - Whether to fail on non-zero padding bits 38 | - Whether lines must be of a limited length 39 | - How non-base64-alphabet characters are handled (sometimes with special handling for only a subset, like whitespace) 40 | 41 | ## Programming languages 42 | 43 | Note that neither C++ nor Rust have built-in base64 support. In C++ the Boost library is quite common in large projects and parts sometimes get pulled in to the standard library, and in Rust the [base64 crate](https://docs.rs/base64/latest/base64/) is the clear choice of everyone, so I'm mentioning those as well. 44 | 45 | "✅ / ⚙️" means the default is yes but it's configurable. A bare "⚙️" means it's configurable and there is no default. 46 | 47 | | | supports urlsafe | `=`s in output | whitespace in output | can omit `=`s in input | can have non-zero padding bits | can have arbitrary characters in input | can have whitespace in input | 48 | | ------------------- | ---------------- | -------------- | -------------------- | ---------------------- | ------------------------------ | -------------------------------------- | ---------------------------- | 49 | | C++ (Boost) | ❌ | ❌ | ❌ | ?[^cpp] | ? | ❌ | ❌ | 50 | | Ruby | ✅ | ✅ / ⚙️[^ruby] | ✅ / ⚙️[^ruby2] | ✅ / ⚙️ | ✅ / ⚙️ | ❌ | ✅ / ⚙️ | 51 | | Python | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ / ⚙️ | ✅ / ⚙️ | 52 | | Rust (base64 crate) | ✅ | ⚙️ | ❌ | ⚙️ | ⚙️ | ❌ | ❌ | 53 | | Java | ✅ | ✅ / ⚙️ | ❌ / ⚙️[^java] | ✅ | ✅ | ❌ | ❌ / ⚙️ | 54 | | Go | ✅ | ✅ | ❌ | ✅ / ⚙️ | ✅ / ⚙️ | ❌ | ✅[^go] | 55 | | C# | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | 56 | | PHP | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ / ⚙️ | ✅ / ⚙️ | 57 | | Swift | ❌ | ✅ | ❌ / ⚙️ | ❌ | ✅ | ❌ / ⚙️ | ❌ / ⚙️ | 58 | 59 | [^cpp]: Boost adds extra null bytes to the output when padding is present, and treats non-zero padding bits as meaningful (i.e. it produces more output when they are present) 60 | [^ruby]: Ruby only allows configuring padding with the urlsafe alphabet 61 | [^ruby2]: Ruby adds linebreaks every 60 characters 62 | [^java]: Java allows MIME-format output, with `\r\n` sequences after every 76 characters of output 63 | [^go]: Go only allows linebreaks specifically 64 | 65 | ## JS libraries 66 | 67 | Only including libraries with a least a million downloads per week and at least 100 distinct dependents. 68 | 69 | | | supports urlsafe | `=`s in output | whitespace in output | can omit `=`s in input | can have non-zero padding bits | can have arbitrary characters in input | can have whitespace in input | 70 | | --------------------------- | ----------------- | -------------- | -------------------- | ---------------------- | ------------------------------ | -------------------------------------- | ---------------------------- | 71 | | `atob`/`btoa` | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | 72 | | Node's Buffer | ✅[^node] | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | 73 | | base64-js (38m/wk) | ✅ (for decoding) | ✅ | ❌ | ❌ | ✅ | ❌[^base64-js] | ❌ | 74 | | @smithy/util-base64 (8m/wk) | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 75 | | crypto-js (6m/wk) | ✅ | ✅ | ❌ | ✅ | ✅ | ❌[^crypto-js] | ❌ | 76 | | js-base64 (5m/wk) | ✅ | ✅ / ⚙️ | ❌ | ✅ | ✅ | ✅ | ✅ | 77 | | base64-arraybuffer (4m/wk) | ❌ | ✅ | ❌ | ✅ | ✅ | ❌[^base64-arraybuffer] | ❌ | 78 | | base64url (2m/wk) | ✅ | ❌ / ⚙️ | ❌ | ✅ | ✅ | ✅ | ✅ | 79 | | base-64 (2m/wk) | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | 80 | 81 | [^node]: Node allows mixing alphabets within the same string in input 82 | [^base64-js]: Illegal characters are interpreted as `A` 83 | [^crypto-js]: Illegal characters are interpreted as `A` 84 | [^base64-arraybuffer]: Illegal characters are interpreted as `A` 85 | 86 | ## "Whitespace" 87 | 88 | In all of the above, "whitespace" means only _ASCII_ whitespace. I don't think anyone has special handling for Unicode but non-ASCII whitespace. 89 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proposal-arraybuffer-base64", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "proposal-arraybuffer-base64", 8 | "dependencies": { 9 | "@tc39/ecma262-biblio": "2.1.2672", 10 | "ecmarkup": "^18.0.0", 11 | "jsdom": "^21.1.1", 12 | "prismjs": "^1.29.0" 13 | } 14 | }, 15 | "node_modules/@babel/code-frame": { 16 | "version": "7.12.11", 17 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", 18 | "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", 19 | "dependencies": { 20 | "@babel/highlight": "^7.10.4" 21 | } 22 | }, 23 | "node_modules/@babel/helper-validator-identifier": { 24 | "version": "7.19.1", 25 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", 26 | "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", 27 | "engines": { 28 | "node": ">=6.9.0" 29 | } 30 | }, 31 | "node_modules/@babel/highlight": { 32 | "version": "7.18.6", 33 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 34 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 35 | "dependencies": { 36 | "@babel/helper-validator-identifier": "^7.18.6", 37 | "chalk": "^2.0.0", 38 | "js-tokens": "^4.0.0" 39 | }, 40 | "engines": { 41 | "node": ">=6.9.0" 42 | } 43 | }, 44 | "node_modules/@babel/highlight/node_modules/ansi-styles": { 45 | "version": "3.2.1", 46 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 47 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 48 | "dependencies": { 49 | "color-convert": "^1.9.0" 50 | }, 51 | "engines": { 52 | "node": ">=4" 53 | } 54 | }, 55 | "node_modules/@babel/highlight/node_modules/chalk": { 56 | "version": "2.4.2", 57 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 58 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 59 | "dependencies": { 60 | "ansi-styles": "^3.2.1", 61 | "escape-string-regexp": "^1.0.5", 62 | "supports-color": "^5.3.0" 63 | }, 64 | "engines": { 65 | "node": ">=4" 66 | } 67 | }, 68 | "node_modules/@babel/highlight/node_modules/color-convert": { 69 | "version": "1.9.3", 70 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 71 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 72 | "dependencies": { 73 | "color-name": "1.1.3" 74 | } 75 | }, 76 | "node_modules/@babel/highlight/node_modules/color-name": { 77 | "version": "1.1.3", 78 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 79 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 80 | }, 81 | "node_modules/@babel/highlight/node_modules/has-flag": { 82 | "version": "3.0.0", 83 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 84 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 85 | "engines": { 86 | "node": ">=4" 87 | } 88 | }, 89 | "node_modules/@babel/highlight/node_modules/supports-color": { 90 | "version": "5.5.0", 91 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 92 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 93 | "dependencies": { 94 | "has-flag": "^3.0.0" 95 | }, 96 | "engines": { 97 | "node": ">=4" 98 | } 99 | }, 100 | "node_modules/@esfx/async-canceltoken": { 101 | "version": "1.0.0", 102 | "resolved": "https://registry.npmjs.org/@esfx/async-canceltoken/-/async-canceltoken-1.0.0.tgz", 103 | "integrity": "sha512-3Ps/4NPd7qFltmHL+CYXCjZtNXcQGV9BZmpzu8Rt3/0SZMtbQve0gtX0uJDJGvAWa6w3IB4HrKVP12VPoFONmA==", 104 | "dependencies": { 105 | "@esfx/cancelable": "^1.0.0", 106 | "@esfx/canceltoken": "^1.0.0", 107 | "@esfx/disposable": "^1.0.0", 108 | "tslib": "^2.4.0" 109 | } 110 | }, 111 | "node_modules/@esfx/cancelable": { 112 | "version": "1.0.0", 113 | "resolved": "https://registry.npmjs.org/@esfx/cancelable/-/cancelable-1.0.0.tgz", 114 | "integrity": "sha512-2dry/TuOT9ydpw86f396v09cyi/gLeGPIZSH4Gx+V/qKQaS/OXCRurCY+Cn8zkBfTAgFsjk9NE15d+LPo2kt9A==", 115 | "dependencies": { 116 | "@esfx/disposable": "^1.0.0" 117 | } 118 | }, 119 | "node_modules/@esfx/canceltoken": { 120 | "version": "1.0.0", 121 | "resolved": "https://registry.npmjs.org/@esfx/canceltoken/-/canceltoken-1.0.0.tgz", 122 | "integrity": "sha512-/TgdzC5O89w5v0TgwE2wcdtampWNAFOxzurCtb4RxYVr3m72yk3Bg82vMdznx+H9nnf28zVDR0PtpZO9FxmOkw==", 123 | "dependencies": { 124 | "@esfx/cancelable": "^1.0.0", 125 | "@esfx/disposable": "^1.0.0", 126 | "tslib": "^2.4.0" 127 | } 128 | }, 129 | "node_modules/@esfx/disposable": { 130 | "version": "1.0.0", 131 | "resolved": "https://registry.npmjs.org/@esfx/disposable/-/disposable-1.0.0.tgz", 132 | "integrity": "sha512-hu7EI+YxlEWEKrb2himbS13HNaq5mlUePASf99KeQqkiNeqiAZbKqG4w59uDcLZs8JrV3qJqS/NYib5ZMhbfTQ==" 133 | }, 134 | "node_modules/@nodelib/fs.scandir": { 135 | "version": "2.1.5", 136 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 137 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 138 | "dependencies": { 139 | "@nodelib/fs.stat": "2.0.5", 140 | "run-parallel": "^1.1.9" 141 | }, 142 | "engines": { 143 | "node": ">= 8" 144 | } 145 | }, 146 | "node_modules/@nodelib/fs.stat": { 147 | "version": "2.0.5", 148 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 149 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 150 | "engines": { 151 | "node": ">= 8" 152 | } 153 | }, 154 | "node_modules/@nodelib/fs.walk": { 155 | "version": "1.2.8", 156 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 157 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 158 | "dependencies": { 159 | "@nodelib/fs.scandir": "2.1.5", 160 | "fastq": "^1.6.0" 161 | }, 162 | "engines": { 163 | "node": ">= 8" 164 | } 165 | }, 166 | "node_modules/@tc39/ecma262-biblio": { 167 | "version": "2.1.2672", 168 | "resolved": "https://registry.npmjs.org/@tc39/ecma262-biblio/-/ecma262-biblio-2.1.2672.tgz", 169 | "integrity": "sha512-S4AVovjzgm9cq1O2Odn7y+OqZ3LNKVGzhd5zVggueFogQyQPw8mpaIzdGxqrm7jmkRtnggO9i+NY5g23GCBXNg==" 170 | }, 171 | "node_modules/@tootallnate/once": { 172 | "version": "2.0.0", 173 | "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", 174 | "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", 175 | "engines": { 176 | "node": ">= 10" 177 | } 178 | }, 179 | "node_modules/abab": { 180 | "version": "2.0.6", 181 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", 182 | "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" 183 | }, 184 | "node_modules/acorn": { 185 | "version": "8.8.2", 186 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", 187 | "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", 188 | "bin": { 189 | "acorn": "bin/acorn" 190 | }, 191 | "engines": { 192 | "node": ">=0.4.0" 193 | } 194 | }, 195 | "node_modules/acorn-globals": { 196 | "version": "7.0.1", 197 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", 198 | "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", 199 | "dependencies": { 200 | "acorn": "^8.1.0", 201 | "acorn-walk": "^8.0.2" 202 | } 203 | }, 204 | "node_modules/acorn-walk": { 205 | "version": "8.2.0", 206 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 207 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 208 | "engines": { 209 | "node": ">=0.4.0" 210 | } 211 | }, 212 | "node_modules/agent-base": { 213 | "version": "6.0.2", 214 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 215 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 216 | "dependencies": { 217 | "debug": "4" 218 | }, 219 | "engines": { 220 | "node": ">= 6.0.0" 221 | } 222 | }, 223 | "node_modules/ansi-styles": { 224 | "version": "4.3.0", 225 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 226 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 227 | "dependencies": { 228 | "color-convert": "^2.0.1" 229 | }, 230 | "engines": { 231 | "node": ">=8" 232 | }, 233 | "funding": { 234 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 235 | } 236 | }, 237 | "node_modules/argparse": { 238 | "version": "1.0.10", 239 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 240 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 241 | "dependencies": { 242 | "sprintf-js": "~1.0.2" 243 | } 244 | }, 245 | "node_modules/array-back": { 246 | "version": "3.1.0", 247 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 248 | "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", 249 | "engines": { 250 | "node": ">=6" 251 | } 252 | }, 253 | "node_modules/asynckit": { 254 | "version": "0.4.0", 255 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 256 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 257 | }, 258 | "node_modules/braces": { 259 | "version": "3.0.2", 260 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 261 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 262 | "dependencies": { 263 | "fill-range": "^7.0.1" 264 | }, 265 | "engines": { 266 | "node": ">=8" 267 | } 268 | }, 269 | "node_modules/browser-process-hrtime": { 270 | "version": "1.0.0", 271 | "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", 272 | "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" 273 | }, 274 | "node_modules/chalk": { 275 | "version": "4.1.2", 276 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 277 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 278 | "dependencies": { 279 | "ansi-styles": "^4.1.0", 280 | "supports-color": "^7.1.0" 281 | }, 282 | "engines": { 283 | "node": ">=10" 284 | }, 285 | "funding": { 286 | "url": "https://github.com/chalk/chalk?sponsor=1" 287 | } 288 | }, 289 | "node_modules/color-convert": { 290 | "version": "2.0.1", 291 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 292 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 293 | "dependencies": { 294 | "color-name": "~1.1.4" 295 | }, 296 | "engines": { 297 | "node": ">=7.0.0" 298 | } 299 | }, 300 | "node_modules/color-name": { 301 | "version": "1.1.4", 302 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 303 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 304 | }, 305 | "node_modules/combined-stream": { 306 | "version": "1.0.8", 307 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 308 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 309 | "dependencies": { 310 | "delayed-stream": "~1.0.0" 311 | }, 312 | "engines": { 313 | "node": ">= 0.8" 314 | } 315 | }, 316 | "node_modules/command-line-args": { 317 | "version": "5.2.1", 318 | "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", 319 | "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", 320 | "dependencies": { 321 | "array-back": "^3.1.0", 322 | "find-replace": "^3.0.0", 323 | "lodash.camelcase": "^4.3.0", 324 | "typical": "^4.0.0" 325 | }, 326 | "engines": { 327 | "node": ">=4.0.0" 328 | } 329 | }, 330 | "node_modules/command-line-usage": { 331 | "version": "6.1.3", 332 | "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", 333 | "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", 334 | "dependencies": { 335 | "array-back": "^4.0.2", 336 | "chalk": "^2.4.2", 337 | "table-layout": "^1.0.2", 338 | "typical": "^5.2.0" 339 | }, 340 | "engines": { 341 | "node": ">=8.0.0" 342 | } 343 | }, 344 | "node_modules/command-line-usage/node_modules/ansi-styles": { 345 | "version": "3.2.1", 346 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 347 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 348 | "dependencies": { 349 | "color-convert": "^1.9.0" 350 | }, 351 | "engines": { 352 | "node": ">=4" 353 | } 354 | }, 355 | "node_modules/command-line-usage/node_modules/array-back": { 356 | "version": "4.0.2", 357 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", 358 | "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", 359 | "engines": { 360 | "node": ">=8" 361 | } 362 | }, 363 | "node_modules/command-line-usage/node_modules/chalk": { 364 | "version": "2.4.2", 365 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 366 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 367 | "dependencies": { 368 | "ansi-styles": "^3.2.1", 369 | "escape-string-regexp": "^1.0.5", 370 | "supports-color": "^5.3.0" 371 | }, 372 | "engines": { 373 | "node": ">=4" 374 | } 375 | }, 376 | "node_modules/command-line-usage/node_modules/color-convert": { 377 | "version": "1.9.3", 378 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 379 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 380 | "dependencies": { 381 | "color-name": "1.1.3" 382 | } 383 | }, 384 | "node_modules/command-line-usage/node_modules/color-name": { 385 | "version": "1.1.3", 386 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 387 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 388 | }, 389 | "node_modules/command-line-usage/node_modules/has-flag": { 390 | "version": "3.0.0", 391 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 392 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 393 | "engines": { 394 | "node": ">=4" 395 | } 396 | }, 397 | "node_modules/command-line-usage/node_modules/supports-color": { 398 | "version": "5.5.0", 399 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 400 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 401 | "dependencies": { 402 | "has-flag": "^3.0.0" 403 | }, 404 | "engines": { 405 | "node": ">=4" 406 | } 407 | }, 408 | "node_modules/command-line-usage/node_modules/typical": { 409 | "version": "5.2.0", 410 | "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", 411 | "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", 412 | "engines": { 413 | "node": ">=8" 414 | } 415 | }, 416 | "node_modules/cssom": { 417 | "version": "0.5.0", 418 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", 419 | "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" 420 | }, 421 | "node_modules/cssstyle": { 422 | "version": "3.0.0", 423 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", 424 | "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", 425 | "dependencies": { 426 | "rrweb-cssom": "^0.6.0" 427 | }, 428 | "engines": { 429 | "node": ">=14" 430 | } 431 | }, 432 | "node_modules/data-urls": { 433 | "version": "4.0.0", 434 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", 435 | "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", 436 | "dependencies": { 437 | "abab": "^2.0.6", 438 | "whatwg-mimetype": "^3.0.0", 439 | "whatwg-url": "^12.0.0" 440 | }, 441 | "engines": { 442 | "node": ">=14" 443 | } 444 | }, 445 | "node_modules/debug": { 446 | "version": "4.3.1", 447 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 448 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 449 | "dependencies": { 450 | "ms": "2.1.2" 451 | }, 452 | "engines": { 453 | "node": ">=6.0" 454 | }, 455 | "peerDependenciesMeta": { 456 | "supports-color": { 457 | "optional": true 458 | } 459 | } 460 | }, 461 | "node_modules/decimal.js": { 462 | "version": "10.4.3", 463 | "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", 464 | "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" 465 | }, 466 | "node_modules/dedent-js": { 467 | "version": "1.0.1", 468 | "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", 469 | "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==" 470 | }, 471 | "node_modules/deep-extend": { 472 | "version": "0.6.0", 473 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 474 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 475 | "engines": { 476 | "node": ">=4.0.0" 477 | } 478 | }, 479 | "node_modules/deep-is": { 480 | "version": "0.1.3", 481 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 482 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" 483 | }, 484 | "node_modules/delayed-stream": { 485 | "version": "1.0.0", 486 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 487 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 488 | "engines": { 489 | "node": ">=0.4.0" 490 | } 491 | }, 492 | "node_modules/domexception": { 493 | "version": "4.0.0", 494 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", 495 | "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", 496 | "dependencies": { 497 | "webidl-conversions": "^7.0.0" 498 | }, 499 | "engines": { 500 | "node": ">=12" 501 | } 502 | }, 503 | "node_modules/ecmarkdown": { 504 | "version": "8.1.0", 505 | "resolved": "https://registry.npmjs.org/ecmarkdown/-/ecmarkdown-8.1.0.tgz", 506 | "integrity": "sha512-dx6cM6RFjzAXkWr2KQRikED4gy70NFQ0vTI4XUQM/LWcjUYRJUbGdd7nd++trXi5az1JSe49TeeCIVMKDXOtcQ==", 507 | "dependencies": { 508 | "escape-html": "^1.0.1" 509 | } 510 | }, 511 | "node_modules/ecmarkup": { 512 | "version": "18.0.0", 513 | "resolved": "https://registry.npmjs.org/ecmarkup/-/ecmarkup-18.0.0.tgz", 514 | "integrity": "sha512-VSItKQ+39dv1FeR1YbGGlJ/rx17wsPSkS7morrOCwLGHh+7ehy89hao+rQ0/ptiBAN3nbytXzwUBUTC3XNmxaA==", 515 | "dependencies": { 516 | "chalk": "^4.1.2", 517 | "command-line-args": "^5.2.0", 518 | "command-line-usage": "^6.1.1", 519 | "dedent-js": "^1.0.1", 520 | "ecmarkdown": "^8.1.0", 521 | "eslint-formatter-codeframe": "^7.32.1", 522 | "fast-glob": "^3.2.7", 523 | "grammarkdown": "^3.2.0", 524 | "highlight.js": "11.0.1", 525 | "html-escape": "^1.0.2", 526 | "js-yaml": "^3.13.1", 527 | "jsdom": "^19.0.0", 528 | "nwsapi": "2.2.0", 529 | "parse5": "^6.0.1", 530 | "prex": "^0.4.7", 531 | "promise-debounce": "^1.0.1" 532 | }, 533 | "bin": { 534 | "ecmarkup": "bin/ecmarkup.js", 535 | "emu-format": "bin/emu-format.js" 536 | }, 537 | "engines": { 538 | "node": ">= 12 || ^11.10.1 || ^10.13 || ^8.10" 539 | } 540 | }, 541 | "node_modules/ecmarkup/node_modules/acorn-globals": { 542 | "version": "6.0.0", 543 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", 544 | "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", 545 | "dependencies": { 546 | "acorn": "^7.1.1", 547 | "acorn-walk": "^7.1.1" 548 | } 549 | }, 550 | "node_modules/ecmarkup/node_modules/acorn-globals/node_modules/acorn": { 551 | "version": "7.4.1", 552 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 553 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 554 | "bin": { 555 | "acorn": "bin/acorn" 556 | }, 557 | "engines": { 558 | "node": ">=0.4.0" 559 | } 560 | }, 561 | "node_modules/ecmarkup/node_modules/acorn-walk": { 562 | "version": "7.2.0", 563 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", 564 | "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", 565 | "engines": { 566 | "node": ">=0.4.0" 567 | } 568 | }, 569 | "node_modules/ecmarkup/node_modules/cssstyle": { 570 | "version": "2.3.0", 571 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", 572 | "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", 573 | "dependencies": { 574 | "cssom": "~0.3.6" 575 | }, 576 | "engines": { 577 | "node": ">=8" 578 | } 579 | }, 580 | "node_modules/ecmarkup/node_modules/cssstyle/node_modules/cssom": { 581 | "version": "0.3.8", 582 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", 583 | "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" 584 | }, 585 | "node_modules/ecmarkup/node_modules/data-urls": { 586 | "version": "3.0.2", 587 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", 588 | "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", 589 | "dependencies": { 590 | "abab": "^2.0.6", 591 | "whatwg-mimetype": "^3.0.0", 592 | "whatwg-url": "^11.0.0" 593 | }, 594 | "engines": { 595 | "node": ">=12" 596 | } 597 | }, 598 | "node_modules/ecmarkup/node_modules/data-urls/node_modules/whatwg-url": { 599 | "version": "11.0.0", 600 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", 601 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", 602 | "dependencies": { 603 | "tr46": "^3.0.0", 604 | "webidl-conversions": "^7.0.0" 605 | }, 606 | "engines": { 607 | "node": ">=12" 608 | } 609 | }, 610 | "node_modules/ecmarkup/node_modules/jsdom": { 611 | "version": "19.0.0", 612 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", 613 | "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", 614 | "dependencies": { 615 | "abab": "^2.0.5", 616 | "acorn": "^8.5.0", 617 | "acorn-globals": "^6.0.0", 618 | "cssom": "^0.5.0", 619 | "cssstyle": "^2.3.0", 620 | "data-urls": "^3.0.1", 621 | "decimal.js": "^10.3.1", 622 | "domexception": "^4.0.0", 623 | "escodegen": "^2.0.0", 624 | "form-data": "^4.0.0", 625 | "html-encoding-sniffer": "^3.0.0", 626 | "http-proxy-agent": "^5.0.0", 627 | "https-proxy-agent": "^5.0.0", 628 | "is-potential-custom-element-name": "^1.0.1", 629 | "nwsapi": "^2.2.0", 630 | "parse5": "6.0.1", 631 | "saxes": "^5.0.1", 632 | "symbol-tree": "^3.2.4", 633 | "tough-cookie": "^4.0.0", 634 | "w3c-hr-time": "^1.0.2", 635 | "w3c-xmlserializer": "^3.0.0", 636 | "webidl-conversions": "^7.0.0", 637 | "whatwg-encoding": "^2.0.0", 638 | "whatwg-mimetype": "^3.0.0", 639 | "whatwg-url": "^10.0.0", 640 | "ws": "^8.2.3", 641 | "xml-name-validator": "^4.0.0" 642 | }, 643 | "engines": { 644 | "node": ">=12" 645 | }, 646 | "peerDependencies": { 647 | "canvas": "^2.5.0" 648 | }, 649 | "peerDependenciesMeta": { 650 | "canvas": { 651 | "optional": true 652 | } 653 | } 654 | }, 655 | "node_modules/ecmarkup/node_modules/nwsapi": { 656 | "version": "2.2.0", 657 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", 658 | "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" 659 | }, 660 | "node_modules/ecmarkup/node_modules/parse5": { 661 | "version": "6.0.1", 662 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", 663 | "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" 664 | }, 665 | "node_modules/ecmarkup/node_modules/saxes": { 666 | "version": "5.0.1", 667 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", 668 | "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", 669 | "dependencies": { 670 | "xmlchars": "^2.2.0" 671 | }, 672 | "engines": { 673 | "node": ">=10" 674 | } 675 | }, 676 | "node_modules/ecmarkup/node_modules/tr46": { 677 | "version": "3.0.0", 678 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", 679 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", 680 | "dependencies": { 681 | "punycode": "^2.1.1" 682 | }, 683 | "engines": { 684 | "node": ">=12" 685 | } 686 | }, 687 | "node_modules/ecmarkup/node_modules/w3c-xmlserializer": { 688 | "version": "3.0.0", 689 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", 690 | "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", 691 | "dependencies": { 692 | "xml-name-validator": "^4.0.0" 693 | }, 694 | "engines": { 695 | "node": ">=12" 696 | } 697 | }, 698 | "node_modules/ecmarkup/node_modules/whatwg-url": { 699 | "version": "10.0.0", 700 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", 701 | "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", 702 | "dependencies": { 703 | "tr46": "^3.0.0", 704 | "webidl-conversions": "^7.0.0" 705 | }, 706 | "engines": { 707 | "node": ">=12" 708 | } 709 | }, 710 | "node_modules/entities": { 711 | "version": "4.4.0", 712 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", 713 | "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", 714 | "engines": { 715 | "node": ">=0.12" 716 | }, 717 | "funding": { 718 | "url": "https://github.com/fb55/entities?sponsor=1" 719 | } 720 | }, 721 | "node_modules/escape-html": { 722 | "version": "1.0.3", 723 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 724 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 725 | }, 726 | "node_modules/escape-string-regexp": { 727 | "version": "1.0.5", 728 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 729 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 730 | "engines": { 731 | "node": ">=0.8.0" 732 | } 733 | }, 734 | "node_modules/escodegen": { 735 | "version": "2.0.0", 736 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", 737 | "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", 738 | "dependencies": { 739 | "esprima": "^4.0.1", 740 | "estraverse": "^5.2.0", 741 | "esutils": "^2.0.2", 742 | "optionator": "^0.8.1" 743 | }, 744 | "bin": { 745 | "escodegen": "bin/escodegen.js", 746 | "esgenerate": "bin/esgenerate.js" 747 | }, 748 | "engines": { 749 | "node": ">=6.0" 750 | }, 751 | "optionalDependencies": { 752 | "source-map": "~0.6.1" 753 | } 754 | }, 755 | "node_modules/escodegen/node_modules/estraverse": { 756 | "version": "5.3.0", 757 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 758 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 759 | "engines": { 760 | "node": ">=4.0" 761 | } 762 | }, 763 | "node_modules/escodegen/node_modules/levn": { 764 | "version": "0.3.0", 765 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 766 | "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", 767 | "dependencies": { 768 | "prelude-ls": "~1.1.2", 769 | "type-check": "~0.3.2" 770 | }, 771 | "engines": { 772 | "node": ">= 0.8.0" 773 | } 774 | }, 775 | "node_modules/escodegen/node_modules/optionator": { 776 | "version": "0.8.3", 777 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", 778 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", 779 | "dependencies": { 780 | "deep-is": "~0.1.3", 781 | "fast-levenshtein": "~2.0.6", 782 | "levn": "~0.3.0", 783 | "prelude-ls": "~1.1.2", 784 | "type-check": "~0.3.2", 785 | "word-wrap": "~1.2.3" 786 | }, 787 | "engines": { 788 | "node": ">= 0.8.0" 789 | } 790 | }, 791 | "node_modules/escodegen/node_modules/prelude-ls": { 792 | "version": "1.1.2", 793 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 794 | "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", 795 | "engines": { 796 | "node": ">= 0.8.0" 797 | } 798 | }, 799 | "node_modules/escodegen/node_modules/type-check": { 800 | "version": "0.3.2", 801 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 802 | "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", 803 | "dependencies": { 804 | "prelude-ls": "~1.1.2" 805 | }, 806 | "engines": { 807 | "node": ">= 0.8.0" 808 | } 809 | }, 810 | "node_modules/eslint-formatter-codeframe": { 811 | "version": "7.32.1", 812 | "resolved": "https://registry.npmjs.org/eslint-formatter-codeframe/-/eslint-formatter-codeframe-7.32.1.tgz", 813 | "integrity": "sha512-DK/3Q3+zVKq/7PdSYiCxPrsDF8H/TRMK5n8Hziwr4IMkMy+XiKSwbpj25AdajS63I/B61Snetq4uVvX9fOLyAg==", 814 | "dependencies": { 815 | "@babel/code-frame": "7.12.11", 816 | "chalk": "^4.0.0" 817 | }, 818 | "engines": { 819 | "node": "^10.12.0 || >=12.0.0" 820 | } 821 | }, 822 | "node_modules/esprima": { 823 | "version": "4.0.1", 824 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 825 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 826 | "bin": { 827 | "esparse": "bin/esparse.js", 828 | "esvalidate": "bin/esvalidate.js" 829 | }, 830 | "engines": { 831 | "node": ">=4" 832 | } 833 | }, 834 | "node_modules/esutils": { 835 | "version": "2.0.3", 836 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 837 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 838 | "engines": { 839 | "node": ">=0.10.0" 840 | } 841 | }, 842 | "node_modules/fast-glob": { 843 | "version": "3.2.12", 844 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", 845 | "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", 846 | "dependencies": { 847 | "@nodelib/fs.stat": "^2.0.2", 848 | "@nodelib/fs.walk": "^1.2.3", 849 | "glob-parent": "^5.1.2", 850 | "merge2": "^1.3.0", 851 | "micromatch": "^4.0.4" 852 | }, 853 | "engines": { 854 | "node": ">=8.6.0" 855 | } 856 | }, 857 | "node_modules/fast-levenshtein": { 858 | "version": "2.0.6", 859 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 860 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" 861 | }, 862 | "node_modules/fastq": { 863 | "version": "1.15.0", 864 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 865 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 866 | "dependencies": { 867 | "reusify": "^1.0.4" 868 | } 869 | }, 870 | "node_modules/fill-range": { 871 | "version": "7.0.1", 872 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 873 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 874 | "dependencies": { 875 | "to-regex-range": "^5.0.1" 876 | }, 877 | "engines": { 878 | "node": ">=8" 879 | } 880 | }, 881 | "node_modules/find-replace": { 882 | "version": "3.0.0", 883 | "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 884 | "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 885 | "dependencies": { 886 | "array-back": "^3.0.1" 887 | }, 888 | "engines": { 889 | "node": ">=4.0.0" 890 | } 891 | }, 892 | "node_modules/form-data": { 893 | "version": "4.0.0", 894 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 895 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 896 | "dependencies": { 897 | "asynckit": "^0.4.0", 898 | "combined-stream": "^1.0.8", 899 | "mime-types": "^2.1.12" 900 | }, 901 | "engines": { 902 | "node": ">= 6" 903 | } 904 | }, 905 | "node_modules/glob-parent": { 906 | "version": "5.1.2", 907 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 908 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 909 | "dependencies": { 910 | "is-glob": "^4.0.1" 911 | }, 912 | "engines": { 913 | "node": ">= 6" 914 | } 915 | }, 916 | "node_modules/grammarkdown": { 917 | "version": "3.2.0", 918 | "resolved": "https://registry.npmjs.org/grammarkdown/-/grammarkdown-3.2.0.tgz", 919 | "integrity": "sha512-pEVUvG2Kxv/PwM3Dm3kFEU1/GHRkNcFWmk/zkqN/y0uoQtPaZ+5VaBacMQAaFOIL9WGYjHXtqpkT5YRvySsISQ==", 920 | "dependencies": { 921 | "@esfx/async-canceltoken": "^1.0.0-pre.13", 922 | "@esfx/cancelable": "^1.0.0-pre.13" 923 | }, 924 | "bin": { 925 | "grammarkdown": "bin/grammarkdown" 926 | } 927 | }, 928 | "node_modules/has-flag": { 929 | "version": "4.0.0", 930 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 931 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 932 | "engines": { 933 | "node": ">=8" 934 | } 935 | }, 936 | "node_modules/highlight.js": { 937 | "version": "11.0.1", 938 | "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.0.1.tgz", 939 | "integrity": "sha512-EqYpWyTF2s8nMfttfBA2yLKPNoZCO33pLS4MnbXQ4hECf1TKujCt1Kq7QAdrio7roL4+CqsfjqwYj4tYgq0pJQ==", 940 | "engines": { 941 | "node": ">=12.0.0" 942 | } 943 | }, 944 | "node_modules/html-encoding-sniffer": { 945 | "version": "3.0.0", 946 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", 947 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", 948 | "dependencies": { 949 | "whatwg-encoding": "^2.0.0" 950 | }, 951 | "engines": { 952 | "node": ">=12" 953 | } 954 | }, 955 | "node_modules/html-escape": { 956 | "version": "1.0.2", 957 | "resolved": "https://registry.npmjs.org/html-escape/-/html-escape-1.0.2.tgz", 958 | "integrity": "sha512-r4cqVc7QAX1/jpPsW9OJNsTTtFhcf+ZBqoA3rWOddMg/y+n6ElKfz+IGKbvV2RTeECDzyrQXa2rpo3IFFrANWg==" 959 | }, 960 | "node_modules/http-proxy-agent": { 961 | "version": "5.0.0", 962 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", 963 | "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", 964 | "dependencies": { 965 | "@tootallnate/once": "2", 966 | "agent-base": "6", 967 | "debug": "4" 968 | }, 969 | "engines": { 970 | "node": ">= 6" 971 | } 972 | }, 973 | "node_modules/https-proxy-agent": { 974 | "version": "5.0.1", 975 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 976 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 977 | "dependencies": { 978 | "agent-base": "6", 979 | "debug": "4" 980 | }, 981 | "engines": { 982 | "node": ">= 6" 983 | } 984 | }, 985 | "node_modules/iconv-lite": { 986 | "version": "0.6.3", 987 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 988 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 989 | "dependencies": { 990 | "safer-buffer": ">= 2.1.2 < 3.0.0" 991 | }, 992 | "engines": { 993 | "node": ">=0.10.0" 994 | } 995 | }, 996 | "node_modules/is-extglob": { 997 | "version": "2.1.1", 998 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 999 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1000 | "engines": { 1001 | "node": ">=0.10.0" 1002 | } 1003 | }, 1004 | "node_modules/is-glob": { 1005 | "version": "4.0.3", 1006 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1007 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1008 | "dependencies": { 1009 | "is-extglob": "^2.1.1" 1010 | }, 1011 | "engines": { 1012 | "node": ">=0.10.0" 1013 | } 1014 | }, 1015 | "node_modules/is-number": { 1016 | "version": "7.0.0", 1017 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1018 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1019 | "engines": { 1020 | "node": ">=0.12.0" 1021 | } 1022 | }, 1023 | "node_modules/is-potential-custom-element-name": { 1024 | "version": "1.0.1", 1025 | "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", 1026 | "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" 1027 | }, 1028 | "node_modules/js-tokens": { 1029 | "version": "4.0.0", 1030 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1031 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 1032 | }, 1033 | "node_modules/js-yaml": { 1034 | "version": "3.14.1", 1035 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 1036 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 1037 | "dependencies": { 1038 | "argparse": "^1.0.7", 1039 | "esprima": "^4.0.0" 1040 | }, 1041 | "bin": { 1042 | "js-yaml": "bin/js-yaml.js" 1043 | } 1044 | }, 1045 | "node_modules/jsdom": { 1046 | "version": "21.1.1", 1047 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", 1048 | "integrity": "sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==", 1049 | "dependencies": { 1050 | "abab": "^2.0.6", 1051 | "acorn": "^8.8.2", 1052 | "acorn-globals": "^7.0.0", 1053 | "cssstyle": "^3.0.0", 1054 | "data-urls": "^4.0.0", 1055 | "decimal.js": "^10.4.3", 1056 | "domexception": "^4.0.0", 1057 | "escodegen": "^2.0.0", 1058 | "form-data": "^4.0.0", 1059 | "html-encoding-sniffer": "^3.0.0", 1060 | "http-proxy-agent": "^5.0.0", 1061 | "https-proxy-agent": "^5.0.1", 1062 | "is-potential-custom-element-name": "^1.0.1", 1063 | "nwsapi": "^2.2.2", 1064 | "parse5": "^7.1.2", 1065 | "rrweb-cssom": "^0.6.0", 1066 | "saxes": "^6.0.0", 1067 | "symbol-tree": "^3.2.4", 1068 | "tough-cookie": "^4.1.2", 1069 | "w3c-xmlserializer": "^4.0.0", 1070 | "webidl-conversions": "^7.0.0", 1071 | "whatwg-encoding": "^2.0.0", 1072 | "whatwg-mimetype": "^3.0.0", 1073 | "whatwg-url": "^12.0.1", 1074 | "ws": "^8.13.0", 1075 | "xml-name-validator": "^4.0.0" 1076 | }, 1077 | "engines": { 1078 | "node": ">=14" 1079 | }, 1080 | "peerDependencies": { 1081 | "canvas": "^2.5.0" 1082 | }, 1083 | "peerDependenciesMeta": { 1084 | "canvas": { 1085 | "optional": true 1086 | } 1087 | } 1088 | }, 1089 | "node_modules/lodash.camelcase": { 1090 | "version": "4.3.0", 1091 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 1092 | "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" 1093 | }, 1094 | "node_modules/merge2": { 1095 | "version": "1.4.1", 1096 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1097 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1098 | "engines": { 1099 | "node": ">= 8" 1100 | } 1101 | }, 1102 | "node_modules/micromatch": { 1103 | "version": "4.0.5", 1104 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 1105 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 1106 | "dependencies": { 1107 | "braces": "^3.0.2", 1108 | "picomatch": "^2.3.1" 1109 | }, 1110 | "engines": { 1111 | "node": ">=8.6" 1112 | } 1113 | }, 1114 | "node_modules/mime-db": { 1115 | "version": "1.52.0", 1116 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1117 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1118 | "engines": { 1119 | "node": ">= 0.6" 1120 | } 1121 | }, 1122 | "node_modules/mime-types": { 1123 | "version": "2.1.35", 1124 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1125 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1126 | "dependencies": { 1127 | "mime-db": "1.52.0" 1128 | }, 1129 | "engines": { 1130 | "node": ">= 0.6" 1131 | } 1132 | }, 1133 | "node_modules/ms": { 1134 | "version": "2.1.2", 1135 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1136 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1137 | }, 1138 | "node_modules/nwsapi": { 1139 | "version": "2.2.2", 1140 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", 1141 | "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" 1142 | }, 1143 | "node_modules/parse5": { 1144 | "version": "7.1.2", 1145 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", 1146 | "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", 1147 | "dependencies": { 1148 | "entities": "^4.4.0" 1149 | }, 1150 | "funding": { 1151 | "url": "https://github.com/inikulin/parse5?sponsor=1" 1152 | } 1153 | }, 1154 | "node_modules/picomatch": { 1155 | "version": "2.3.1", 1156 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1157 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1158 | "engines": { 1159 | "node": ">=8.6" 1160 | }, 1161 | "funding": { 1162 | "url": "https://github.com/sponsors/jonschlinkert" 1163 | } 1164 | }, 1165 | "node_modules/prex": { 1166 | "version": "0.4.9", 1167 | "resolved": "https://registry.npmjs.org/prex/-/prex-0.4.9.tgz", 1168 | "integrity": "sha512-pQCB9AH8MXQRBaelDkhnTkqY6GRiXt1xWlx2hBReZYZwVA0m7EQcnF/K55zr87cCADDHmdD+qq7G6a8Pu+BRFA==", 1169 | "deprecated": "This package has been deprecated in favor of several '@esfx/*' packages that replace it. Please see the README for more information", 1170 | "dependencies": { 1171 | "@esfx/cancelable": "^1.0.0 || >=1.0.0-pre.13", 1172 | "@esfx/disposable": "^1.0.0 || >=1.0.0-pre.13" 1173 | } 1174 | }, 1175 | "node_modules/prismjs": { 1176 | "version": "1.29.0", 1177 | "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", 1178 | "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", 1179 | "engines": { 1180 | "node": ">=6" 1181 | } 1182 | }, 1183 | "node_modules/promise-debounce": { 1184 | "version": "1.0.1", 1185 | "resolved": "https://registry.npmjs.org/promise-debounce/-/promise-debounce-1.0.1.tgz", 1186 | "integrity": "sha512-jq3Crngf1DaaOXQIOUkPr7LsW4UsWyn0KW1MJ+yMn5njTJ+F1AuHmjjwJhod9HuoNSSMspSLS9PS3V7BrexwjQ==" 1187 | }, 1188 | "node_modules/psl": { 1189 | "version": "1.9.0", 1190 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", 1191 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" 1192 | }, 1193 | "node_modules/punycode": { 1194 | "version": "2.3.0", 1195 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 1196 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", 1197 | "engines": { 1198 | "node": ">=6" 1199 | } 1200 | }, 1201 | "node_modules/querystringify": { 1202 | "version": "2.2.0", 1203 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 1204 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" 1205 | }, 1206 | "node_modules/queue-microtask": { 1207 | "version": "1.2.3", 1208 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1209 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1210 | "funding": [ 1211 | { 1212 | "type": "github", 1213 | "url": "https://github.com/sponsors/feross" 1214 | }, 1215 | { 1216 | "type": "patreon", 1217 | "url": "https://www.patreon.com/feross" 1218 | }, 1219 | { 1220 | "type": "consulting", 1221 | "url": "https://feross.org/support" 1222 | } 1223 | ] 1224 | }, 1225 | "node_modules/reduce-flatten": { 1226 | "version": "2.0.0", 1227 | "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", 1228 | "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", 1229 | "engines": { 1230 | "node": ">=6" 1231 | } 1232 | }, 1233 | "node_modules/requires-port": { 1234 | "version": "1.0.0", 1235 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 1236 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 1237 | }, 1238 | "node_modules/reusify": { 1239 | "version": "1.0.4", 1240 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1241 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1242 | "engines": { 1243 | "iojs": ">=1.0.0", 1244 | "node": ">=0.10.0" 1245 | } 1246 | }, 1247 | "node_modules/rrweb-cssom": { 1248 | "version": "0.6.0", 1249 | "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", 1250 | "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" 1251 | }, 1252 | "node_modules/run-parallel": { 1253 | "version": "1.2.0", 1254 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1255 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1256 | "funding": [ 1257 | { 1258 | "type": "github", 1259 | "url": "https://github.com/sponsors/feross" 1260 | }, 1261 | { 1262 | "type": "patreon", 1263 | "url": "https://www.patreon.com/feross" 1264 | }, 1265 | { 1266 | "type": "consulting", 1267 | "url": "https://feross.org/support" 1268 | } 1269 | ], 1270 | "dependencies": { 1271 | "queue-microtask": "^1.2.2" 1272 | } 1273 | }, 1274 | "node_modules/safer-buffer": { 1275 | "version": "2.1.2", 1276 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1277 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1278 | }, 1279 | "node_modules/saxes": { 1280 | "version": "6.0.0", 1281 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", 1282 | "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", 1283 | "dependencies": { 1284 | "xmlchars": "^2.2.0" 1285 | }, 1286 | "engines": { 1287 | "node": ">=v12.22.7" 1288 | } 1289 | }, 1290 | "node_modules/source-map": { 1291 | "version": "0.6.1", 1292 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1293 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1294 | "optional": true, 1295 | "engines": { 1296 | "node": ">=0.10.0" 1297 | } 1298 | }, 1299 | "node_modules/sprintf-js": { 1300 | "version": "1.0.3", 1301 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1302 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" 1303 | }, 1304 | "node_modules/supports-color": { 1305 | "version": "7.2.0", 1306 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1307 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1308 | "dependencies": { 1309 | "has-flag": "^4.0.0" 1310 | }, 1311 | "engines": { 1312 | "node": ">=8" 1313 | } 1314 | }, 1315 | "node_modules/symbol-tree": { 1316 | "version": "3.2.4", 1317 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", 1318 | "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" 1319 | }, 1320 | "node_modules/table-layout": { 1321 | "version": "1.0.2", 1322 | "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", 1323 | "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", 1324 | "dependencies": { 1325 | "array-back": "^4.0.1", 1326 | "deep-extend": "~0.6.0", 1327 | "typical": "^5.2.0", 1328 | "wordwrapjs": "^4.0.0" 1329 | }, 1330 | "engines": { 1331 | "node": ">=8.0.0" 1332 | } 1333 | }, 1334 | "node_modules/table-layout/node_modules/array-back": { 1335 | "version": "4.0.2", 1336 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", 1337 | "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", 1338 | "engines": { 1339 | "node": ">=8" 1340 | } 1341 | }, 1342 | "node_modules/table-layout/node_modules/typical": { 1343 | "version": "5.2.0", 1344 | "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", 1345 | "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", 1346 | "engines": { 1347 | "node": ">=8" 1348 | } 1349 | }, 1350 | "node_modules/to-regex-range": { 1351 | "version": "5.0.1", 1352 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1353 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1354 | "dependencies": { 1355 | "is-number": "^7.0.0" 1356 | }, 1357 | "engines": { 1358 | "node": ">=8.0" 1359 | } 1360 | }, 1361 | "node_modules/tough-cookie": { 1362 | "version": "4.1.2", 1363 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", 1364 | "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", 1365 | "dependencies": { 1366 | "psl": "^1.1.33", 1367 | "punycode": "^2.1.1", 1368 | "universalify": "^0.2.0", 1369 | "url-parse": "^1.5.3" 1370 | }, 1371 | "engines": { 1372 | "node": ">=6" 1373 | } 1374 | }, 1375 | "node_modules/tr46": { 1376 | "version": "4.1.1", 1377 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", 1378 | "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", 1379 | "dependencies": { 1380 | "punycode": "^2.3.0" 1381 | }, 1382 | "engines": { 1383 | "node": ">=14" 1384 | } 1385 | }, 1386 | "node_modules/tslib": { 1387 | "version": "2.5.0", 1388 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", 1389 | "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" 1390 | }, 1391 | "node_modules/typical": { 1392 | "version": "4.0.0", 1393 | "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 1394 | "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", 1395 | "engines": { 1396 | "node": ">=8" 1397 | } 1398 | }, 1399 | "node_modules/universalify": { 1400 | "version": "0.2.0", 1401 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", 1402 | "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", 1403 | "engines": { 1404 | "node": ">= 4.0.0" 1405 | } 1406 | }, 1407 | "node_modules/url-parse": { 1408 | "version": "1.5.10", 1409 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 1410 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 1411 | "dependencies": { 1412 | "querystringify": "^2.1.1", 1413 | "requires-port": "^1.0.0" 1414 | } 1415 | }, 1416 | "node_modules/w3c-hr-time": { 1417 | "version": "1.0.2", 1418 | "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", 1419 | "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", 1420 | "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", 1421 | "dependencies": { 1422 | "browser-process-hrtime": "^1.0.0" 1423 | } 1424 | }, 1425 | "node_modules/w3c-xmlserializer": { 1426 | "version": "4.0.0", 1427 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", 1428 | "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", 1429 | "dependencies": { 1430 | "xml-name-validator": "^4.0.0" 1431 | }, 1432 | "engines": { 1433 | "node": ">=14" 1434 | } 1435 | }, 1436 | "node_modules/webidl-conversions": { 1437 | "version": "7.0.0", 1438 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 1439 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 1440 | "engines": { 1441 | "node": ">=12" 1442 | } 1443 | }, 1444 | "node_modules/whatwg-encoding": { 1445 | "version": "2.0.0", 1446 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", 1447 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", 1448 | "dependencies": { 1449 | "iconv-lite": "0.6.3" 1450 | }, 1451 | "engines": { 1452 | "node": ">=12" 1453 | } 1454 | }, 1455 | "node_modules/whatwg-mimetype": { 1456 | "version": "3.0.0", 1457 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", 1458 | "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", 1459 | "engines": { 1460 | "node": ">=12" 1461 | } 1462 | }, 1463 | "node_modules/whatwg-url": { 1464 | "version": "12.0.1", 1465 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", 1466 | "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", 1467 | "dependencies": { 1468 | "tr46": "^4.1.1", 1469 | "webidl-conversions": "^7.0.0" 1470 | }, 1471 | "engines": { 1472 | "node": ">=14" 1473 | } 1474 | }, 1475 | "node_modules/word-wrap": { 1476 | "version": "1.2.3", 1477 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1478 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1479 | "engines": { 1480 | "node": ">=0.10.0" 1481 | } 1482 | }, 1483 | "node_modules/wordwrapjs": { 1484 | "version": "4.0.1", 1485 | "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", 1486 | "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", 1487 | "dependencies": { 1488 | "reduce-flatten": "^2.0.0", 1489 | "typical": "^5.2.0" 1490 | }, 1491 | "engines": { 1492 | "node": ">=8.0.0" 1493 | } 1494 | }, 1495 | "node_modules/wordwrapjs/node_modules/typical": { 1496 | "version": "5.2.0", 1497 | "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", 1498 | "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", 1499 | "engines": { 1500 | "node": ">=8" 1501 | } 1502 | }, 1503 | "node_modules/ws": { 1504 | "version": "8.13.0", 1505 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", 1506 | "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", 1507 | "engines": { 1508 | "node": ">=10.0.0" 1509 | }, 1510 | "peerDependencies": { 1511 | "bufferutil": "^4.0.1", 1512 | "utf-8-validate": ">=5.0.2" 1513 | }, 1514 | "peerDependenciesMeta": { 1515 | "bufferutil": { 1516 | "optional": true 1517 | }, 1518 | "utf-8-validate": { 1519 | "optional": true 1520 | } 1521 | } 1522 | }, 1523 | "node_modules/xml-name-validator": { 1524 | "version": "4.0.0", 1525 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", 1526 | "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", 1527 | "engines": { 1528 | "node": ">=12" 1529 | } 1530 | }, 1531 | "node_modules/xmlchars": { 1532 | "version": "2.2.0", 1533 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", 1534 | "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" 1535 | } 1536 | } 1537 | } 1538 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "proposal-arraybuffer-base64", 4 | "scripts": { 5 | "build-playground": "mkdir -p dist && cp playground/* dist && node scripts/static-highlight.js playground/index-raw.html > dist/index.html && rm dist/index-raw.html", 6 | "build-spec": "mkdir -p dist/spec && ecmarkup --lint-spec --strict --load-biblio @tc39/ecma262-biblio --verbose --mark-effects spec.html --assets-dir dist/spec dist/spec/index.html", 7 | "build": "npm run build-playground && npm run build-spec", 8 | "format": "emu-format --write spec.html", 9 | "check-format": "emu-format --check spec.html", 10 | "test": "node --test" 11 | }, 12 | "dependencies": { 13 | "@tc39/ecma262-biblio": "2.1.2672", 14 | "ecmarkup": "^18.0.0", 15 | "jsdom": "^21.1.1", 16 | "prismjs": "^1.29.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /playground/index-raw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Base64 in JavaScript proposal 5 | 6 | 7 | 8 | 9 | 10 | 47 | 48 | 49 |

Proposed Support for Base64 in JavaScript

50 | 51 |

Introduction

52 |

This page documents a stage 3 proposal for native base64 and hex encoding and decoding for binary data in JavaScript, and includes a non-production polyfill you can experiment with in the browser's console.

53 |

The proposal would provide methods for encoding and decoding Uint8Arrays as base64 and hex strings.

54 |

Feedback on the proposal's repository is appreciated.

55 |

Specification text for the proposal is available here.

56 | 57 |

API

58 |

Basic usage

59 |

Encoding a Uint8Array to a base64 string:

60 |

 61 | let arr = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);
 62 | console.log(arr.toBase64());
 63 | // 'SGVsbG8gV29ybGQ='
 64 | 
65 | 66 |

Decoding a base64 string to a Uint8Array:

67 |

 68 | let string = 'SGVsbG8gV29ybGQ=';
 69 | console.log(Uint8Array.fromBase64(string));
 70 | // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])
 71 | 
72 | 73 |

Encoding a Uint8Array to a hex string:

74 |

 75 | let arr = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);
 76 | console.log(arr.toHex());
 77 | // '48656c6c6f20576f726c64'
 78 | 
79 | 80 |

Decoding a hex string to a Uint8Array:

81 |

 82 | let string = '48656c6c6f20576f726c64';
 83 | console.log(Uint8Array.fromHex(string));
 84 | // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])
 85 | 
86 | 87 |

Options

88 |

The base64 methods take an optional options bag which allows specifying the alphabet as either "base64" (the default) or "base64url" (the URL-safe variant).

89 |

The base64 decoder also allows specifying the behavior for the final chunk with lastChunkHandling. Recall that base64 decoding operates on chunks of 4 characters at a time, but the input may have some characters which don't fit evenly into such a chunk of 4 characters. This option determines how the final chunk of characters should be handled. The three options are "loose" (the default), which treats the chunk as if it had any necessary = padding (but throws if this is not possible, i.e. there is exactly one extra character); "strict", which enforces that the chunk has exactly 4 characters (counting = padding) and that overflow bits are 0; and "stop-before-partial", which stops decoding before the final chunk unless the final chunk has exactly 4 characters.

90 |

The base64 encoder allows omitting padding by specifying omitPadding: true. The default is to include padding.

91 |

The hex methods do not have any options.

92 | 93 |

 94 | let array = new Uint8Array([251, 255, 191]);
 95 | console.log(array.toBase64({ alphabet: 'base64' }));
 96 | // '+/+/'
 97 | console.log(array.toBase64({ alphabet: 'base64url' }));
 98 | // '-_-_'
 99 | 
100 | console.log(Uint8Array.fromBase64('SGVsbG8g\nV29ybG R'));
101 | // works, despite whitespace, missing padding, and non-zero overflow bits
102 | 
103 | try {
104 |   Uint8Array.fromBase64('SGVsbG8gV29ybGR=', { lastChunkHandling: 'strict' });
105 | } catch {
106 |   console.log('with lastChunkHandling: "strict", overflow bits are rejected');
107 | }
108 | try {
109 |   Uint8Array.fromBase64('SGVsbG8gV29ybGQ', { lastChunkHandling: 'strict' });
110 | } catch {
111 |   console.log('with lastChunkHandling: "strict", overflow bits are rejected');
112 | }
113 | 
114 | console.log((new Uint8Array([72])).toBase64()); // 'SA=='
115 | console.log((new Uint8Array([72])).toBase64({ omitPadding: true })); // 'SA'
116 | 
117 | 118 |

Writing to an existing Uint8Array

119 |

The Uint8Array.prototype.setFromBase64 method allows writing to an existing Uint8Array. Like the TextEncoder encodeInto method, it returns a { read, written } pair.

120 | 121 |

This method takes an optional final options bag with the same options as above.

122 | 123 |

124 | let target = new Uint8Array(7);
125 | let { read, written } = target.setFromBase64('Zm9vYmFy');
126 | console.log({ target, read, written });
127 | // { target: Uint8Array([102, 111, 111, 98, 97, 114, 0]), read: 8, written: 6 }
128 | 
129 | 130 |

Uint8Array.prototype.setFromHex is the same except for hex.

131 | 132 |

133 | let target = new Uint8Array(6);
134 | let { read, written } = target.setFromHex('deadbeef');
135 | console.log({ target, read, written });
136 | // { target: Uint8Array([222, 173, 190, 239, 0, 0]), read: 8, written: 4 }
137 | 
138 | 139 |

Streaming

140 |

There is no support for streaming. However, it can be implemented in userland.

141 | 142 | 145 | -------------------------------------------------------------------------------- /playground/polyfill-core.mjs: -------------------------------------------------------------------------------- 1 | let base64Characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 2 | let base64UrlCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; 3 | 4 | let tag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), Symbol.toStringTag).get; 5 | export function checkUint8Array(arg) { 6 | let kind; 7 | try { 8 | kind = tag.call(arg); 9 | } catch { 10 | throw new TypeError('not a Uint8Array'); 11 | } 12 | if (kind !== 'Uint8Array') { 13 | throw new TypeError('not a Uint8Array'); 14 | } 15 | } 16 | 17 | function assert(condition, message) { 18 | if (!condition) { 19 | throw new Error(`Assert failed: ${message}`); 20 | } 21 | } 22 | 23 | function getOptions(options) { 24 | if (typeof options === 'undefined') { 25 | return Object.create(null); 26 | } 27 | if (options && typeof options === 'object') { 28 | return options; 29 | } 30 | throw new TypeError('options is not object'); 31 | } 32 | 33 | export function uint8ArrayToBase64(arr, options) { 34 | checkUint8Array(arr); 35 | let opts = getOptions(options); 36 | let alphabet = opts.alphabet; 37 | if (typeof alphabet === 'undefined') { 38 | alphabet = 'base64'; 39 | } 40 | if (alphabet !== 'base64' && alphabet !== 'base64url') { 41 | throw new TypeError('expected alphabet to be either "base64" or "base64url"'); 42 | } 43 | let omitPadding = !!opts.omitPadding; 44 | 45 | if ('detached' in arr.buffer && arr.buffer.detached) { 46 | throw new TypeError('toBase64 called on array backed by detached buffer'); 47 | } 48 | 49 | let lookup = alphabet === 'base64' ? base64Characters : base64UrlCharacters; 50 | let result = ''; 51 | 52 | let i = 0; 53 | for (; i + 2 < arr.length; i += 3) { 54 | let triplet = (arr[i] << 16) + (arr[i + 1] << 8) + arr[i + 2]; 55 | result += 56 | lookup[(triplet >> 18) & 63] + 57 | lookup[(triplet >> 12) & 63] + 58 | lookup[(triplet >> 6) & 63] + 59 | lookup[triplet & 63]; 60 | } 61 | if (i + 2 === arr.length) { 62 | let triplet = (arr[i] << 16) + (arr[i + 1] << 8); 63 | result += 64 | lookup[(triplet >> 18) & 63] + 65 | lookup[(triplet >> 12) & 63] + 66 | lookup[(triplet >> 6) & 63] + 67 | (omitPadding ? '' : '='); 68 | } else if (i + 1 === arr.length) { 69 | let triplet = arr[i] << 16; 70 | result += 71 | lookup[(triplet >> 18) & 63] + 72 | lookup[(triplet >> 12) & 63] + 73 | (omitPadding ? '' : '=='); 74 | } 75 | return result; 76 | } 77 | 78 | function decodeBase64Chunk(chunk, throwOnExtraBits) { 79 | let actualChunkLength = chunk.length; 80 | if (actualChunkLength < 4) { 81 | chunk += actualChunkLength === 2 ? 'AA' : 'A'; 82 | } 83 | 84 | let map = new Map(base64Characters.split('').map((c, i) => [c, i])); 85 | 86 | let c1 = chunk[0]; 87 | let c2 = chunk[1]; 88 | let c3 = chunk[2]; 89 | let c4 = chunk[3]; 90 | 91 | let triplet = 92 | (map.get(c1) << 18) + 93 | (map.get(c2) << 12) + 94 | (map.get(c3) << 6) + 95 | map.get(c4); 96 | 97 | let chunkBytes = [ 98 | (triplet >> 16) & 255, 99 | (triplet >> 8) & 255, 100 | triplet & 255 101 | ]; 102 | 103 | if (actualChunkLength === 2) { 104 | if (throwOnExtraBits && chunkBytes[1] !== 0) { 105 | throw new SyntaxError('extra bits'); 106 | } 107 | return [chunkBytes[0]]; 108 | } else if (actualChunkLength === 3) { 109 | if (throwOnExtraBits && chunkBytes[2] !== 0) { 110 | throw new SyntaxError('extra bits'); 111 | } 112 | return [chunkBytes[0], chunkBytes[1]]; 113 | } 114 | return chunkBytes; 115 | } 116 | 117 | function skipAsciiWhitespace(string, index) { 118 | for (; index < string.length; ++index) { 119 | if (!/[\u0009\u000A\u000C\u000D\u0020]/.test(string[index])) { 120 | break; 121 | } 122 | } 123 | return index; 124 | } 125 | 126 | function fromBase64(string, alphabet, lastChunkHandling, maxLength) { 127 | if (maxLength === 0) { 128 | return { read: 0, bytes: [], error: null }; 129 | } 130 | 131 | let read = 0; 132 | let bytes = []; 133 | let chunk = ''; 134 | 135 | let index = 0 136 | while (true) { 137 | index = skipAsciiWhitespace(string, index); 138 | if (index === string.length) { 139 | if (chunk.length > 0) { 140 | if (lastChunkHandling === 'stop-before-partial') { 141 | return { bytes, read, error: null }; 142 | } else if (lastChunkHandling === 'loose') { 143 | if (chunk.length === 1) { 144 | let error = new SyntaxError('malformed padding: exactly one additional character'); 145 | return { bytes, read, error }; 146 | } 147 | bytes.push(...decodeBase64Chunk(chunk, false)); 148 | } else { 149 | assert(lastChunkHandling === 'strict'); 150 | let error = new SyntaxError('missing padding'); 151 | return { bytes, read, error }; 152 | } 153 | } 154 | return { bytes, read: string.length, error: null }; 155 | } 156 | let char = string[index]; 157 | ++index; 158 | if (char === '=') { 159 | if (chunk.length < 2) { 160 | let error = new SyntaxError('padding is too early'); 161 | return { bytes, read, error }; 162 | } 163 | index = skipAsciiWhitespace(string, index); 164 | if (chunk.length === 2) { 165 | if (index === string.length) { 166 | if (lastChunkHandling === 'stop-before-partial') { 167 | // two characters then `=` then EOS: this is, technically, a partial chunk 168 | return { bytes, read, error: null }; 169 | } 170 | let error = new SyntaxError('malformed padding - only one ='); 171 | return { bytes, read, error }; 172 | } 173 | if (string[index] === '=') { 174 | ++index; 175 | index = skipAsciiWhitespace(string, index); 176 | } 177 | } 178 | if (index < string.length) { 179 | let error = new SyntaxError('unexpected character after padding'); 180 | return { bytes, read, error }; 181 | } 182 | bytes.push(...decodeBase64Chunk(chunk, lastChunkHandling === 'strict')); 183 | assert(bytes.length <= maxLength); 184 | return { bytes, read: string.length, error: null }; 185 | } 186 | if (alphabet === 'base64url') { 187 | if (char === '+' || char === '/') { 188 | let error = new SyntaxError(`unexpected character ${JSON.stringify(char)}`); 189 | return { bytes, read, error }; 190 | } else if (char === '-') { 191 | char = '+'; 192 | } else if (char === '_') { 193 | char = '/'; 194 | } 195 | } 196 | if (!base64Characters.includes(char)) { 197 | let error = new SyntaxError(`unexpected character ${JSON.stringify(char)}`); 198 | return { bytes, read, error }; 199 | } 200 | let remainingBytes = maxLength - bytes.length; 201 | if (remainingBytes === 1 && chunk.length === 2 || remainingBytes === 2 && chunk.length === 3) { 202 | // special case: we can fit exactly the number of bytes currently represented by chunk, so we were just checking for `=` 203 | return { bytes, read, error: null }; 204 | } 205 | 206 | chunk += char; 207 | if (chunk.length === 4) { 208 | bytes.push(...decodeBase64Chunk(chunk, false)); 209 | chunk = ''; 210 | read = index; 211 | assert(bytes.length <= maxLength); 212 | if (bytes.length === maxLength) { 213 | return { bytes, read, error: null }; 214 | } 215 | } 216 | } 217 | } 218 | 219 | export function base64ToUint8Array(string, options, into) { 220 | let opts = getOptions(options); 221 | let alphabet = opts.alphabet; 222 | if (typeof alphabet === 'undefined') { 223 | alphabet = 'base64'; 224 | } 225 | if (alphabet !== 'base64' && alphabet !== 'base64url') { 226 | throw new TypeError('expected alphabet to be either "base64" or "base64url"'); 227 | } 228 | let lastChunkHandling = opts.lastChunkHandling; 229 | if (typeof lastChunkHandling === 'undefined') { 230 | lastChunkHandling = 'loose'; 231 | } 232 | if (!['loose', 'strict', 'stop-before-partial'].includes(lastChunkHandling)) { 233 | throw new TypeError('expected lastChunkHandling to be either "loose", "strict", or "stop-before-partial"'); 234 | } 235 | if (into && 'detached' in into.buffer && into.buffer.detached) { 236 | throw new TypeError('toBase64Into called on array backed by detached buffer'); 237 | } 238 | 239 | let maxLength = into ? into.length : 2 ** 53 - 1; 240 | 241 | let { bytes, read, error } = fromBase64(string, alphabet, lastChunkHandling, maxLength); 242 | if (error && !into) { 243 | throw error; 244 | } 245 | 246 | bytes = new Uint8Array(bytes); 247 | if (into && bytes.length > 0) { 248 | assert(bytes.length <= into.length); 249 | into.set(bytes); 250 | } 251 | 252 | if (error) { 253 | throw error; 254 | } 255 | 256 | return { read, bytes }; 257 | } 258 | 259 | export function uint8ArrayToHex(arr) { 260 | checkUint8Array(arr); 261 | if ('detached' in arr.buffer && arr.buffer.detached) { 262 | throw new TypeError('toHex called on array backed by detached buffer'); 263 | } 264 | let out = ''; 265 | for (let i = 0; i < arr.length; ++i) { 266 | out += arr[i].toString(16).padStart(2, '0'); 267 | } 268 | return out; 269 | } 270 | 271 | function fromHex(string, maxLength) { 272 | let bytes = []; 273 | let read = 0; 274 | if (maxLength > 0) { 275 | while (read < string.length) { 276 | let hexits = string.slice(read, read + 2); 277 | if (/[^0-9a-fA-F]/.test(hexits)) { 278 | let error = new SyntaxError('string should only contain hex characters'); 279 | return { read, bytes, error } 280 | } 281 | bytes.push(parseInt(hexits, 16)); 282 | read += 2; 283 | if (bytes.length === maxLength) { 284 | break; 285 | } 286 | } 287 | } 288 | return { read, bytes, error: null } 289 | } 290 | 291 | export function hexToUint8Array(string, into) { 292 | if (typeof string !== 'string') { 293 | throw new TypeError('expected string to be a string'); 294 | } 295 | if (into && 'detached' in into.buffer && into.buffer.detached) { 296 | throw new TypeError('fromHexInto called on array backed by detached buffer'); 297 | } 298 | if (string.length % 2 !== 0) { 299 | throw new SyntaxError('string should be an even number of characters'); 300 | } 301 | 302 | let maxLength = into ? into.length : 2 ** 53 - 1; 303 | let { read, bytes, error } = fromHex(string, maxLength); 304 | if (error && !into) { 305 | throw error; 306 | } 307 | 308 | bytes = new Uint8Array(bytes); 309 | if (into && bytes.length > 0) { 310 | assert(bytes.length <= into.length); 311 | into.set(bytes); 312 | } 313 | 314 | if (error) { 315 | throw error; 316 | } 317 | 318 | return { read, bytes }; 319 | } 320 | -------------------------------------------------------------------------------- /playground/polyfill-install.mjs: -------------------------------------------------------------------------------- 1 | import { uint8ArrayToBase64, base64ToUint8Array, uint8ArrayToHex, hexToUint8Array, checkUint8Array } from './polyfill-core.mjs'; 2 | 3 | // method shenanigans to make a non-constructor which can refer to "this" 4 | Uint8Array.prototype.toBase64 = { 5 | toBase64(options) { 6 | return uint8ArrayToBase64(this, options); 7 | } 8 | }.toBase64; 9 | Object.defineProperty(Uint8Array.prototype, 'toBase64', { enumerable: false }); 10 | Object.defineProperty(Uint8Array.prototype.toBase64, 'length', { value: 0 }); 11 | 12 | Uint8Array.fromBase64 = (string, options) => { 13 | if (typeof string !== 'string') { 14 | throw new TypeError('expected input to be a string'); 15 | } 16 | return base64ToUint8Array(string, options).bytes; 17 | }; 18 | Object.defineProperty(Uint8Array, 'fromBase64', { enumerable: false }); 19 | Object.defineProperty(Uint8Array.fromBase64, 'length', { value: 1 }); 20 | Object.defineProperty(Uint8Array.fromBase64, 'name', { value: 'fromBase64' }); 21 | 22 | // method shenanigans to make a non-constructor which can refer to "this" 23 | Uint8Array.prototype.setFromBase64 = { 24 | setFromBase64(string, options) { 25 | checkUint8Array(this); 26 | if (typeof string !== 'string') { 27 | throw new TypeError('expected input to be a string'); 28 | } 29 | let { read, bytes } = base64ToUint8Array(string, options, this); 30 | return { read, written: bytes.length }; 31 | } 32 | }.setFromBase64; 33 | Object.defineProperty(Uint8Array.prototype, 'setFromBase64', { enumerable: false }); 34 | Object.defineProperty(Uint8Array.prototype.setFromBase64, 'length', { value: 1 }); 35 | 36 | Uint8Array.prototype.toHex = { 37 | toHex() { 38 | return uint8ArrayToHex(this); 39 | } 40 | }.toHex; 41 | Object.defineProperty(Uint8Array.prototype, 'toHex', { enumerable: false }); 42 | 43 | Uint8Array.fromHex = (string) => { 44 | if (typeof string !== 'string') { 45 | throw new TypeError('expected input to be a string'); 46 | } 47 | return hexToUint8Array(string).bytes; 48 | }; 49 | Object.defineProperty(Uint8Array, 'fromHex', { enumerable: false }); 50 | Object.defineProperty(Uint8Array.fromHex, 'name', { value: 'fromHex' }); 51 | 52 | Uint8Array.prototype.setFromHex = { 53 | setFromHex(string) { 54 | checkUint8Array(this); 55 | if (typeof string !== 'string') { 56 | throw new TypeError('expected input to be a string'); 57 | } 58 | let { read, bytes } = hexToUint8Array(string, this); 59 | return { read, written: bytes.length }; 60 | } 61 | }.setFromHex; 62 | Object.defineProperty(Uint8Array.prototype, 'setFromHex', { enumerable: false }); 63 | -------------------------------------------------------------------------------- /playground/prism.css: -------------------------------------------------------------------------------- 1 | /* 2 | Prism.js stylesheet - https://prismjs.com/ 3 | 4 | Used under the MIT license 5 | 6 | Copyright (c) 2012 Lea Verou 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | */ 26 | 27 | /** 28 | * prism.js default theme for JavaScript, CSS and HTML 29 | * Based on dabblet (http://dabblet.com) 30 | * @author Lea Verou 31 | */ 32 | 33 | code[class*="language-"], 34 | pre[class*="language-"] { 35 | color: black; 36 | background: none; 37 | text-shadow: 0 1px white; 38 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 39 | font-size: 1em; 40 | text-align: left; 41 | white-space: pre; 42 | word-spacing: normal; 43 | word-break: normal; 44 | word-wrap: normal; 45 | line-height: 1.5; 46 | 47 | -moz-tab-size: 4; 48 | -o-tab-size: 4; 49 | tab-size: 4; 50 | 51 | -webkit-hyphens: none; 52 | -moz-hyphens: none; 53 | -ms-hyphens: none; 54 | hyphens: none; 55 | } 56 | 57 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 58 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 59 | text-shadow: none; 60 | background: #b3d4fc; 61 | } 62 | 63 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 64 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 65 | text-shadow: none; 66 | background: #b3d4fc; 67 | } 68 | 69 | @media print { 70 | code[class*="language-"], 71 | pre[class*="language-"] { 72 | text-shadow: none; 73 | } 74 | } 75 | 76 | /* Code blocks */ 77 | pre[class*="language-"] { 78 | padding: 1em; 79 | margin: .5em 0; 80 | overflow: auto; 81 | } 82 | 83 | :not(pre) > code[class*="language-"], 84 | pre[class*="language-"] { 85 | background: #f5f2f0; 86 | } 87 | 88 | /* Inline code */ 89 | :not(pre) > code[class*="language-"] { 90 | padding: .1em; 91 | border-radius: .3em; 92 | white-space: normal; 93 | } 94 | 95 | .token.comment, 96 | .token.prolog, 97 | .token.doctype, 98 | .token.cdata { 99 | color: slategray; 100 | } 101 | 102 | .token.punctuation { 103 | color: #999; 104 | } 105 | 106 | .token.namespace { 107 | opacity: .7; 108 | } 109 | 110 | .token.property, 111 | .token.tag, 112 | .token.boolean, 113 | .token.number, 114 | .token.constant, 115 | .token.symbol, 116 | .token.deleted { 117 | color: #905; 118 | } 119 | 120 | .token.selector, 121 | .token.attr-name, 122 | .token.string, 123 | .token.char, 124 | .token.builtin, 125 | .token.inserted { 126 | color: #690; 127 | } 128 | 129 | .token.operator, 130 | .token.entity, 131 | .token.url, 132 | .language-css .token.string, 133 | .style .token.string { 134 | color: #9a6e3a; 135 | /* This background color was intended by the author of this theme. */ 136 | background: hsla(0, 0%, 100%, .5); 137 | } 138 | 139 | .token.atrule, 140 | .token.attr-value, 141 | .token.keyword { 142 | color: #07a; 143 | } 144 | 145 | .token.function, 146 | .token.class-name { 147 | color: #DD4A68; 148 | } 149 | 150 | .token.regex, 151 | .token.important, 152 | .token.variable { 153 | color: #e90; 154 | } 155 | 156 | .token.important, 157 | .token.bold { 158 | font-weight: bold; 159 | } 160 | .token.italic { 161 | font-style: italic; 162 | } 163 | 164 | .token.entity { 165 | cursor: help; 166 | } 167 | -------------------------------------------------------------------------------- /playground/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 18px; 3 | line-height: 1.5; 4 | } 5 | h2 { 6 | border-bottom: 2px solid #999; 7 | margin-top: 2em; 8 | } 9 | pre { border-left: 4px solid #39a33c; } 10 | code { 11 | padding: 1px 5px 2px 5px; 12 | line-height: 1.1; 13 | display: inline-block; 14 | } 15 | a code { color: inherit; } 16 | a code:hover { text-decoration: underline; } 17 | pre code { line-height: 1.4; } 18 | pre code[class*="language-"] { font-size: 0.85em; } 19 | pre[class*="language-"] { 20 | position: relative; 21 | padding: 0.5em; 22 | } 23 | pre[class*="language-"] button { 24 | display: none; 25 | position: absolute; 26 | top: 5px; 27 | right: 5px; 28 | 29 | width: 1.7em; 30 | height: 1.7em; 31 | 32 | font-size: 1em; 33 | padding: 0.15rem; 34 | background-color: transparent; 35 | 36 | border: ridge 1px #7b7b7c; 37 | border-radius: 5px; 38 | } 39 | pre[class*="language-"]:hover button { 40 | display: initial; 41 | } 42 | pre[class*="language-"] button:hover { 43 | cursor: pointer; 44 | } 45 | footer { 46 | border-top: 1px solid var(--background); 47 | color: var(--text-muted); 48 | font-size: 0.8em; 49 | margin-top: 2em; 50 | padding-top: 10px; 51 | } -------------------------------------------------------------------------------- /playground/water-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | Water.css - https://watercss.kognise.dev/ 3 | 4 | Used under the MIT license: 5 | 6 | Copyright © 2019 Kognise 7 | 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: 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 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. 10 | */ 11 | 12 | :root { 13 | --background-body: #fff; 14 | --background: #efefef; 15 | --background-alt: #f7f7f7; 16 | --selection: #9e9e9e; 17 | --text-main: #363636; 18 | --text-bright: #000; 19 | --text-muted: #70777f; 20 | --links: #0076d1; 21 | --focus: #0096bfab; 22 | --border: #dbdbdb; 23 | --code: #000; 24 | --animation-duration: 0.1s; 25 | --button-base: #d0cfcf; 26 | --button-hover: #9b9b9b; 27 | --scrollbar-thumb: rgb(170, 170, 170); 28 | --scrollbar-thumb-hover: var(--button-hover); 29 | --form-placeholder: #949494; 30 | --form-text: #1d1d1d; 31 | --variable: #39a33c; 32 | --highlight: #ff0; 33 | --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); 34 | } 35 | 36 | html { 37 | scrollbar-color: rgb(170, 170, 170) #fff; 38 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 39 | scrollbar-width: thin; 40 | } 41 | 42 | body { 43 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif; 44 | line-height: 1.4; 45 | max-width: 800px; 46 | margin: 20px auto; 47 | padding: 0 10px; 48 | word-wrap: break-word; 49 | color: #363636; 50 | color: var(--text-main); 51 | background: #fff; 52 | background: var(--background-body); 53 | text-rendering: optimizeLegibility; 54 | } 55 | 56 | button { 57 | transition: 58 | background-color 0.1s linear, 59 | border-color 0.1s linear, 60 | color 0.1s linear, 61 | box-shadow 0.1s linear, 62 | transform 0.1s ease; 63 | transition: 64 | background-color var(--animation-duration) linear, 65 | border-color var(--animation-duration) linear, 66 | color var(--animation-duration) linear, 67 | box-shadow var(--animation-duration) linear, 68 | transform var(--animation-duration) ease; 69 | } 70 | 71 | input { 72 | transition: 73 | background-color 0.1s linear, 74 | border-color 0.1s linear, 75 | color 0.1s linear, 76 | box-shadow 0.1s linear, 77 | transform 0.1s ease; 78 | transition: 79 | background-color var(--animation-duration) linear, 80 | border-color var(--animation-duration) linear, 81 | color var(--animation-duration) linear, 82 | box-shadow var(--animation-duration) linear, 83 | transform var(--animation-duration) ease; 84 | } 85 | 86 | textarea { 87 | transition: 88 | background-color 0.1s linear, 89 | border-color 0.1s linear, 90 | color 0.1s linear, 91 | box-shadow 0.1s linear, 92 | transform 0.1s ease; 93 | transition: 94 | background-color var(--animation-duration) linear, 95 | border-color var(--animation-duration) linear, 96 | color var(--animation-duration) linear, 97 | box-shadow var(--animation-duration) linear, 98 | transform var(--animation-duration) ease; 99 | } 100 | 101 | h1 { 102 | font-size: 2.2em; 103 | margin-top: 0; 104 | } 105 | 106 | h1, 107 | h2, 108 | h3, 109 | h4, 110 | h5, 111 | h6 { 112 | margin-bottom: 12px; 113 | margin-top: 24px; 114 | } 115 | 116 | h1 { 117 | color: #000; 118 | color: var(--text-bright); 119 | } 120 | 121 | h2 { 122 | color: #000; 123 | color: var(--text-bright); 124 | } 125 | 126 | h3 { 127 | color: #000; 128 | color: var(--text-bright); 129 | } 130 | 131 | h4 { 132 | color: #000; 133 | color: var(--text-bright); 134 | } 135 | 136 | h5 { 137 | color: #000; 138 | color: var(--text-bright); 139 | } 140 | 141 | h6 { 142 | color: #000; 143 | color: var(--text-bright); 144 | } 145 | 146 | strong { 147 | color: #000; 148 | color: var(--text-bright); 149 | } 150 | 151 | h1, 152 | h2, 153 | h3, 154 | h4, 155 | h5, 156 | h6, 157 | b, 158 | strong, 159 | th { 160 | font-weight: 600; 161 | } 162 | 163 | q::before { 164 | content: none; 165 | } 166 | 167 | q::after { 168 | content: none; 169 | } 170 | 171 | blockquote { 172 | border-left: 4px solid #0096bfab; 173 | border-left: 4px solid var(--focus); 174 | margin: 1.5em 0; 175 | padding: 0.5em 1em; 176 | font-style: italic; 177 | } 178 | 179 | q { 180 | border-left: 4px solid #0096bfab; 181 | border-left: 4px solid var(--focus); 182 | margin: 1.5em 0; 183 | padding: 0.5em 1em; 184 | font-style: italic; 185 | } 186 | 187 | blockquote > footer { 188 | font-style: normal; 189 | border: 0; 190 | } 191 | 192 | blockquote cite { 193 | font-style: normal; 194 | } 195 | 196 | address { 197 | font-style: normal; 198 | } 199 | 200 | a[href^='mailto\:']::before { 201 | content: '📧 '; 202 | } 203 | 204 | a[href^='tel\:']::before { 205 | content: '📞 '; 206 | } 207 | 208 | a[href^='sms\:']::before { 209 | content: '💬 '; 210 | } 211 | 212 | mark { 213 | background-color: #ff0; 214 | background-color: var(--highlight); 215 | border-radius: 2px; 216 | padding: 0 2px 0 2px; 217 | color: #000; 218 | } 219 | 220 | a > code, 221 | a > strong { 222 | color: inherit; 223 | } 224 | 225 | button, 226 | select, 227 | input[type='submit'], 228 | input[type='reset'], 229 | input[type='button'], 230 | input[type='checkbox'], 231 | input[type='range'], 232 | input[type='radio'] { 233 | cursor: pointer; 234 | } 235 | 236 | input, 237 | select { 238 | display: block; 239 | } 240 | 241 | [type='checkbox'], 242 | [type='radio'] { 243 | display: initial; 244 | } 245 | 246 | input { 247 | color: #1d1d1d; 248 | color: var(--form-text); 249 | background-color: #efefef; 250 | background-color: var(--background); 251 | font-family: inherit; 252 | font-size: inherit; 253 | margin-right: 6px; 254 | margin-bottom: 6px; 255 | padding: 10px; 256 | border: none; 257 | border-radius: 6px; 258 | outline: none; 259 | } 260 | 261 | button { 262 | color: #1d1d1d; 263 | color: var(--form-text); 264 | background-color: #efefef; 265 | background-color: var(--background); 266 | font-family: inherit; 267 | font-size: inherit; 268 | margin-right: 6px; 269 | margin-bottom: 6px; 270 | padding: 10px; 271 | border: none; 272 | border-radius: 6px; 273 | outline: none; 274 | } 275 | 276 | textarea { 277 | color: #1d1d1d; 278 | color: var(--form-text); 279 | background-color: #efefef; 280 | background-color: var(--background); 281 | font-family: inherit; 282 | font-size: inherit; 283 | margin-right: 6px; 284 | margin-bottom: 6px; 285 | padding: 10px; 286 | border: none; 287 | border-radius: 6px; 288 | outline: none; 289 | } 290 | 291 | select { 292 | color: #1d1d1d; 293 | color: var(--form-text); 294 | background-color: #efefef; 295 | background-color: var(--background); 296 | font-family: inherit; 297 | font-size: inherit; 298 | margin-right: 6px; 299 | margin-bottom: 6px; 300 | padding: 10px; 301 | border: none; 302 | border-radius: 6px; 303 | outline: none; 304 | } 305 | 306 | button { 307 | background-color: #d0cfcf; 308 | background-color: var(--button-base); 309 | padding-right: 30px; 310 | padding-left: 30px; 311 | } 312 | 313 | input[type='submit'] { 314 | background-color: #d0cfcf; 315 | background-color: var(--button-base); 316 | padding-right: 30px; 317 | padding-left: 30px; 318 | } 319 | 320 | input[type='reset'] { 321 | background-color: #d0cfcf; 322 | background-color: var(--button-base); 323 | padding-right: 30px; 324 | padding-left: 30px; 325 | } 326 | 327 | input[type='button'] { 328 | background-color: #d0cfcf; 329 | background-color: var(--button-base); 330 | padding-right: 30px; 331 | padding-left: 30px; 332 | } 333 | 334 | button:hover { 335 | background: #9b9b9b; 336 | background: var(--button-hover); 337 | } 338 | 339 | input[type='submit']:hover { 340 | background: #9b9b9b; 341 | background: var(--button-hover); 342 | } 343 | 344 | input[type='reset']:hover { 345 | background: #9b9b9b; 346 | background: var(--button-hover); 347 | } 348 | 349 | input[type='button']:hover { 350 | background: #9b9b9b; 351 | background: var(--button-hover); 352 | } 353 | 354 | input[type='color'] { 355 | min-height: 2rem; 356 | padding: 8px; 357 | cursor: pointer; 358 | } 359 | 360 | input[type='checkbox'], 361 | input[type='radio'] { 362 | height: 1em; 363 | width: 1em; 364 | } 365 | 366 | input[type='radio'] { 367 | border-radius: 100%; 368 | } 369 | 370 | input { 371 | vertical-align: top; 372 | } 373 | 374 | label { 375 | vertical-align: middle; 376 | margin-bottom: 4px; 377 | display: inline-block; 378 | } 379 | 380 | input:not([type='checkbox']):not([type='radio']), 381 | input[type='range'], 382 | select, 383 | button, 384 | textarea { 385 | -webkit-appearance: none; 386 | } 387 | 388 | textarea { 389 | display: block; 390 | margin-right: 0; 391 | box-sizing: border-box; 392 | resize: vertical; 393 | } 394 | 395 | textarea:not([cols]) { 396 | width: 100%; 397 | } 398 | 399 | textarea:not([rows]) { 400 | min-height: 40px; 401 | height: 140px; 402 | } 403 | 404 | select { 405 | background: #efefef url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; 406 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; 407 | padding-right: 35px; 408 | } 409 | 410 | select::-ms-expand { 411 | display: none; 412 | } 413 | 414 | select[multiple] { 415 | padding-right: 10px; 416 | background-image: none; 417 | overflow-y: auto; 418 | } 419 | 420 | input:focus { 421 | box-shadow: 0 0 0 2px #0096bfab; 422 | box-shadow: 0 0 0 2px var(--focus); 423 | } 424 | 425 | select:focus { 426 | box-shadow: 0 0 0 2px #0096bfab; 427 | box-shadow: 0 0 0 2px var(--focus); 428 | } 429 | 430 | button:focus { 431 | box-shadow: 0 0 0 2px #0096bfab; 432 | box-shadow: 0 0 0 2px var(--focus); 433 | } 434 | 435 | textarea:focus { 436 | box-shadow: 0 0 0 2px #0096bfab; 437 | box-shadow: 0 0 0 2px var(--focus); 438 | } 439 | 440 | input[type='checkbox']:active, 441 | input[type='radio']:active, 442 | input[type='submit']:active, 443 | input[type='reset']:active, 444 | input[type='button']:active, 445 | input[type='range']:active, 446 | button:active { 447 | transform: translateY(2px); 448 | } 449 | 450 | input:disabled, 451 | select:disabled, 452 | button:disabled, 453 | textarea:disabled { 454 | cursor: not-allowed; 455 | opacity: 0.5; 456 | } 457 | 458 | ::-moz-placeholder { 459 | color: #949494; 460 | color: var(--form-placeholder); 461 | } 462 | 463 | :-ms-input-placeholder { 464 | color: #949494; 465 | color: var(--form-placeholder); 466 | } 467 | 468 | ::-ms-input-placeholder { 469 | color: #949494; 470 | color: var(--form-placeholder); 471 | } 472 | 473 | ::placeholder { 474 | color: #949494; 475 | color: var(--form-placeholder); 476 | } 477 | 478 | fieldset { 479 | border: 1px #0096bfab solid; 480 | border: 1px var(--focus) solid; 481 | border-radius: 6px; 482 | margin: 0; 483 | margin-bottom: 12px; 484 | padding: 10px; 485 | } 486 | 487 | legend { 488 | font-size: 0.9em; 489 | font-weight: 600; 490 | } 491 | 492 | input[type='range'] { 493 | margin: 10px 0; 494 | padding: 10px 0; 495 | background: transparent; 496 | } 497 | 498 | input[type='range']:focus { 499 | outline: none; 500 | } 501 | 502 | input[type='range']::-webkit-slider-runnable-track { 503 | width: 100%; 504 | height: 9.5px; 505 | -webkit-transition: 0.2s; 506 | transition: 0.2s; 507 | background: #efefef; 508 | background: var(--background); 509 | border-radius: 3px; 510 | } 511 | 512 | input[type='range']::-webkit-slider-thumb { 513 | box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d; 514 | height: 20px; 515 | width: 20px; 516 | border-radius: 50%; 517 | background: #dbdbdb; 518 | background: var(--border); 519 | -webkit-appearance: none; 520 | margin-top: -7px; 521 | } 522 | 523 | input[type='range']:focus::-webkit-slider-runnable-track { 524 | background: #efefef; 525 | background: var(--background); 526 | } 527 | 528 | input[type='range']::-moz-range-track { 529 | width: 100%; 530 | height: 9.5px; 531 | -moz-transition: 0.2s; 532 | transition: 0.2s; 533 | background: #efefef; 534 | background: var(--background); 535 | border-radius: 3px; 536 | } 537 | 538 | input[type='range']::-moz-range-thumb { 539 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 540 | height: 20px; 541 | width: 20px; 542 | border-radius: 50%; 543 | background: #dbdbdb; 544 | background: var(--border); 545 | } 546 | 547 | input[type='range']::-ms-track { 548 | width: 100%; 549 | height: 9.5px; 550 | background: transparent; 551 | border-color: transparent; 552 | border-width: 16px 0; 553 | color: transparent; 554 | } 555 | 556 | input[type='range']::-ms-fill-lower { 557 | background: #efefef; 558 | background: var(--background); 559 | border: 0.2px solid #010101; 560 | border-radius: 3px; 561 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 562 | } 563 | 564 | input[type='range']::-ms-fill-upper { 565 | background: #efefef; 566 | background: var(--background); 567 | border: 0.2px solid #010101; 568 | border-radius: 3px; 569 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 570 | } 571 | 572 | input[type='range']::-ms-thumb { 573 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 574 | border: 1px solid #000; 575 | height: 20px; 576 | width: 20px; 577 | border-radius: 50%; 578 | background: #dbdbdb; 579 | background: var(--border); 580 | } 581 | 582 | input[type='range']:focus::-ms-fill-lower { 583 | background: #efefef; 584 | background: var(--background); 585 | } 586 | 587 | input[type='range']:focus::-ms-fill-upper { 588 | background: #efefef; 589 | background: var(--background); 590 | } 591 | 592 | a { 593 | text-decoration: none; 594 | color: #0076d1; 595 | color: var(--links); 596 | } 597 | 598 | a:hover { 599 | text-decoration: underline; 600 | } 601 | 602 | code { 603 | background: #efefef; 604 | background: var(--background); 605 | color: #000; 606 | color: var(--code); 607 | padding: 2.5px 5px; 608 | border-radius: 6px; 609 | font-size: 1em; 610 | } 611 | 612 | samp { 613 | background: #efefef; 614 | background: var(--background); 615 | color: #000; 616 | color: var(--code); 617 | padding: 2.5px 5px; 618 | border-radius: 6px; 619 | font-size: 1em; 620 | } 621 | 622 | time { 623 | background: #efefef; 624 | background: var(--background); 625 | color: #000; 626 | color: var(--code); 627 | padding: 2.5px 5px; 628 | border-radius: 6px; 629 | font-size: 1em; 630 | } 631 | 632 | pre > code { 633 | padding: 10px; 634 | display: block; 635 | overflow-x: auto; 636 | } 637 | 638 | var { 639 | color: #39a33c; 640 | color: var(--variable); 641 | font-style: normal; 642 | font-family: monospace; 643 | } 644 | 645 | kbd { 646 | background: #efefef; 647 | background: var(--background); 648 | border: 1px solid #dbdbdb; 649 | border: 1px solid var(--border); 650 | border-radius: 2px; 651 | color: #363636; 652 | color: var(--text-main); 653 | padding: 2px 4px 2px 4px; 654 | } 655 | 656 | img, 657 | video { 658 | max-width: 100%; 659 | height: auto; 660 | } 661 | 662 | hr { 663 | border: none; 664 | border-top: 1px solid #dbdbdb; 665 | border-top: 1px solid var(--border); 666 | } 667 | 668 | table { 669 | border-collapse: collapse; 670 | margin-bottom: 10px; 671 | width: 100%; 672 | table-layout: fixed; 673 | } 674 | 675 | table caption { 676 | text-align: left; 677 | } 678 | 679 | td, 680 | th { 681 | padding: 6px; 682 | text-align: left; 683 | vertical-align: top; 684 | word-wrap: break-word; 685 | } 686 | 687 | thead { 688 | border-bottom: 1px solid #dbdbdb; 689 | border-bottom: 1px solid var(--border); 690 | } 691 | 692 | tfoot { 693 | border-top: 1px solid #dbdbdb; 694 | border-top: 1px solid var(--border); 695 | } 696 | 697 | tbody tr:nth-child(even) { 698 | background-color: #efefef; 699 | background-color: var(--background); 700 | } 701 | 702 | tbody tr:nth-child(even) button { 703 | background-color: #f7f7f7; 704 | background-color: var(--background-alt); 705 | } 706 | 707 | tbody tr:nth-child(even) button:hover { 708 | background-color: #fff; 709 | background-color: var(--background-body); 710 | } 711 | 712 | ::-webkit-scrollbar { 713 | height: 10px; 714 | width: 10px; 715 | } 716 | 717 | ::-webkit-scrollbar-track { 718 | background: #efefef; 719 | background: var(--background); 720 | border-radius: 6px; 721 | } 722 | 723 | ::-webkit-scrollbar-thumb { 724 | background: rgb(170, 170, 170); 725 | background: var(--scrollbar-thumb); 726 | border-radius: 6px; 727 | } 728 | 729 | ::-webkit-scrollbar-thumb:hover { 730 | background: #9b9b9b; 731 | background: var(--scrollbar-thumb-hover); 732 | } 733 | 734 | ::-moz-selection { 735 | background-color: #9e9e9e; 736 | background-color: var(--selection); 737 | color: #000; 738 | color: var(--text-bright); 739 | } 740 | 741 | ::selection { 742 | background-color: #9e9e9e; 743 | background-color: var(--selection); 744 | color: #000; 745 | color: var(--text-bright); 746 | } 747 | 748 | details { 749 | display: flex; 750 | flex-direction: column; 751 | align-items: flex-start; 752 | background-color: #f7f7f7; 753 | background-color: var(--background-alt); 754 | padding: 10px 10px 0; 755 | margin: 1em 0; 756 | border-radius: 6px; 757 | overflow: hidden; 758 | } 759 | 760 | details[open] { 761 | padding: 10px; 762 | } 763 | 764 | details > :last-child { 765 | margin-bottom: 0; 766 | } 767 | 768 | details[open] summary { 769 | margin-bottom: 10px; 770 | } 771 | 772 | summary { 773 | display: list-item; 774 | background-color: #efefef; 775 | background-color: var(--background); 776 | padding: 10px; 777 | margin: -10px -10px 0; 778 | cursor: pointer; 779 | outline: none; 780 | } 781 | 782 | summary:hover, 783 | summary:focus { 784 | text-decoration: underline; 785 | } 786 | 787 | details > :not(summary) { 788 | margin-top: 0; 789 | } 790 | 791 | summary::-webkit-details-marker { 792 | color: #363636; 793 | color: var(--text-main); 794 | } 795 | 796 | dialog { 797 | background-color: #f7f7f7; 798 | background-color: var(--background-alt); 799 | color: #363636; 800 | color: var(--text-main); 801 | border: none; 802 | border-radius: 6px; 803 | border-color: #dbdbdb; 804 | border-color: var(--border); 805 | padding: 10px 30px; 806 | } 807 | 808 | dialog > header:first-child { 809 | background-color: #efefef; 810 | background-color: var(--background); 811 | border-radius: 6px 6px 0 0; 812 | margin: -10px -30px 10px; 813 | padding: 10px; 814 | text-align: center; 815 | } 816 | 817 | dialog::-webkit-backdrop { 818 | background: #0000009c; 819 | -webkit-backdrop-filter: blur(4px); 820 | backdrop-filter: blur(4px); 821 | } 822 | 823 | dialog::backdrop { 824 | background: #0000009c; 825 | -webkit-backdrop-filter: blur(4px); 826 | backdrop-filter: blur(4px); 827 | } 828 | 829 | footer { 830 | border-top: 1px solid #dbdbdb; 831 | border-top: 1px solid var(--border); 832 | padding-top: 10px; 833 | color: #70777f; 834 | color: var(--text-muted); 835 | } 836 | 837 | body > footer { 838 | margin-top: 40px; 839 | } 840 | 841 | @media print { 842 | body, 843 | pre, 844 | code, 845 | summary, 846 | details, 847 | button, 848 | input, 849 | textarea { 850 | background-color: #fff; 851 | } 852 | 853 | button, 854 | input, 855 | textarea { 856 | border: 1px solid #000; 857 | } 858 | 859 | body, 860 | h1, 861 | h2, 862 | h3, 863 | h4, 864 | h5, 865 | h6, 866 | pre, 867 | code, 868 | button, 869 | input, 870 | textarea, 871 | footer, 872 | summary, 873 | strong { 874 | color: #000; 875 | } 876 | 877 | summary::marker { 878 | color: #000; 879 | } 880 | 881 | summary::-webkit-details-marker { 882 | color: #000; 883 | } 884 | 885 | tbody tr:nth-child(even) { 886 | background-color: #f2f2f2; 887 | } 888 | 889 | a { 890 | color: #00f; 891 | text-decoration: underline; 892 | } 893 | } 894 | -------------------------------------------------------------------------------- /scripts/static-highlight.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let fs = require('fs'); 4 | let path = require('path'); 5 | 6 | let jsdom = require('jsdom'); 7 | let Prism = require('prismjs'); 8 | 9 | // jsdom is the laziest possible way of doing this 10 | // but whatever it works fine and only takes ~1s 11 | function highlightCodeblocks(html) { 12 | let dom = new jsdom.JSDOM(html, { includeNodeLocations: true }); 13 | let nodes = dom.window.document.querySelectorAll('code[class="lang-js"],code[class="language-js"],code[class="lang-javascript"],code[class="language-javascript"]'); 14 | if (nodes.length === 0) { 15 | console.error('warning: no code blocks found'); 16 | return html; 17 | } 18 | // last-first 19 | let sorted = [...nodes].map(n => ({ node: n, loc: dom.nodeLocation(n) })).sort((a, b) => b.loc.startOffset - a.loc.startOffset); 20 | for (let { node, loc } of sorted) { 21 | let prefix = html.slice(0, loc.startTag.endOffset); 22 | let code = html.slice(loc.startTag.endOffset, loc.endTag.startOffset).trimStart(); // for the leading newline 23 | let suffix = html.slice(loc.endTag.startOffset); 24 | 25 | let formatted = Prism.highlight(code, Prism.languages.javascript, 'javascript'); 26 | html = `${prefix}${formatted}${suffix}`; 27 | } 28 | return html; 29 | } 30 | 31 | if (process.argv.length < 3) { 32 | console.log('Usage: node static-highlight.js path-to-input.html > path-to-output.html'); 33 | } 34 | let source = fs.readFileSync(process.argv[2], 'utf8'); 35 | console.log(highlightCodeblocks(source)); 36 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
  4 | title: Uint8Array to/from base64
  5 | status: proposal
  6 | stage: 3
  7 | location: https://github.com/tc39/proposal-arraybuffer-base64
  8 | copyright: false
  9 | contributors: Kevin Gibbons
 10 | 
11 | 12 |
13 |

Contributing to this Proposal

14 |

This proposal is developed on GitHub. A playground is available with examples and a non-production polyfill to allow direct experimentation.

15 |
16 | 17 | 18 |

Uint8Array.prototype.toBase64 ( [ _options_ ] )

19 | 20 | 1. Let _O_ be the *this* value. 21 | 1. Perform ? ValidateUint8Array(_O_). 22 | 1. Let _opts_ be ? GetOptionsObject(_options_). 23 | 1. Let _alphabet_ be ? Get(_opts_, *"alphabet"*). 24 | 1. If _alphabet_ is *undefined*, set _alphabet_ to *"base64"*. 25 | 1. If _alphabet_ is neither *"base64"* nor *"base64url"*, throw a *TypeError* exception. 26 | 1. Let _omitPadding_ be ToBoolean(? Get(_opts_, *"omitPadding"*)). 27 | 1. Let _toEncode_ be ? GetUint8ArrayBytes(_O_). 28 | 1. If _alphabet_ is *"base64"*, then 29 | 1. Let _outAscii_ be the sequence of code points which results from encoding _toEncode_ according to the base64 encoding specified in section 4 of RFC 4648. Padding is included if and only if _omitPadding_ is *false*. 30 | 1. Else, 31 | 1. Assert: _alphabet_ is *"base64url"*. 32 | 1. Let _outAscii_ be the sequence of code points which results from encoding _toEncode_ according to the base64url encoding specified in section 5 of RFC 4648. Padding is included if and only if _omitPadding_ is *false*. 33 | 1. Return CodePointsToString(_outAscii_). 34 | 35 |
36 | 37 | 38 |

Uint8Array.prototype.toHex ( )

39 | 40 | 1. Let _O_ be the *this* value. 41 | 1. Perform ? ValidateUint8Array(_O_). 42 | 1. Let _toEncode_ be ? GetUint8ArrayBytes(_O_). 43 | 1. Let _out_ be the empty String. 44 | 1. For each byte _byte_ of _toEncode_, do 45 | 1. Let _hex_ be Number::toString(𝔽(_byte_), 16). 46 | 1. Set _hex_ to StringPad(_hex_, 2, *"0"*, ~start~). 47 | 1. Set _out_ to the string-concatenation of _out_ and _hex_. 48 | 1. Return _out_. 49 | 50 |
51 | 52 | 53 |

Uint8Array.fromBase64 ( _string_ [ , _options_ ] )

54 | 55 | 1. If _string_ is not a String, throw a *TypeError* exception. 56 | 1. Let _opts_ be ? GetOptionsObject(_options_). 57 | 1. Let _alphabet_ be ? Get(_opts_, *"alphabet"*). 58 | 1. If _alphabet_ is *undefined*, set _alphabet_ to *"base64"*. 59 | 1. If _alphabet_ is neither *"base64"* nor *"base64url"*, throw a *TypeError* exception. 60 | 1. Let _lastChunkHandling_ be ? Get(_opts_, *"lastChunkHandling"*). 61 | 1. If _lastChunkHandling_ is *undefined*, set _lastChunkHandling_ to *"loose"*. 62 | 1. If _lastChunkHandling_ is not one of *"loose"*, *"strict"*, or *"stop-before-partial"*, throw a *TypeError* exception. 63 | 1. Let _result_ be FromBase64(_string_, _alphabet_, _lastChunkHandling_). 64 | 1. If _result_.[[Error]] is not ~none~, then 65 | 1. Throw _result_.[[Error]]. 66 | 1. Let _resultLength_ be the length of _result_.[[Bytes]]. 67 | 1. Let _ta_ be ? AllocateTypedArray(*"Uint8Array"*, %Uint8Array%, *"%Uint8Array.prototype%"*, _resultLength_). 68 | 1. Set the value at each index of _ta_.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of _result_.[[Bytes]]. 69 | 1. Return _ta_. 70 | 71 |
72 | 73 | 74 |

Uint8Array.prototype.setFromBase64 ( _string_ [ , _options_ ] )

75 | 76 | 1. Let _into_ be the *this* value. 77 | 1. Perform ? ValidateUint8Array(_into_). 78 | 1. If _string_ is not a String, throw a *TypeError* exception. 79 | 1. Let _opts_ be ? GetOptionsObject(_options_). 80 | 1. Let _alphabet_ be ? Get(_opts_, *"alphabet"*). 81 | 1. If _alphabet_ is *undefined*, set _alphabet_ to *"base64"*. 82 | 1. If _alphabet_ is neither *"base64"* nor *"base64url"*, throw a *TypeError* exception. 83 | 1. Let _lastChunkHandling_ be ? Get(_opts_, *"lastChunkHandling"*). 84 | 1. If _lastChunkHandling_ is *undefined*, set _lastChunkHandling_ to *"loose"*. 85 | 1. If _lastChunkHandling_ is not one of *"loose"*, *"strict"*, or *"stop-before-partial"*, throw a *TypeError* exception. 86 | 1. Let _taRecord_ be MakeTypedArrayWithBufferWitnessRecord(_into_, ~seq-cst~). 87 | 1. If IsTypedArrayOutOfBounds(_taRecord_) is *true*, throw a *TypeError* exception. 88 | 1. Let _byteLength_ be TypedArrayLength(_taRecord_). 89 | 1. Let _result_ be FromBase64(_string_, _alphabet_, _lastChunkHandling_, _byteLength_). 90 | 1. Let _bytes_ be _result_.[[Bytes]]. 91 | 1. Let _written_ be the length of _bytes_. 92 | 1. NOTE: FromBase64 does not invoke any user code, so the ArrayBuffer backing _into_ cannot have been detached or shrunk. 93 | 1. Assert: _written_ ≤ _byteLength_. 94 | 1. Perform SetUint8ArrayBytes(_into_, _bytes_). 95 | 1. If _result_.[[Error]] is not ~none~, then 96 | 1. Throw _result_.[[Error]]. 97 | 1. Let _resultObject_ be OrdinaryObjectCreate(%Object.prototype%). 98 | 1. Perform ! CreateDataPropertyOrThrow(_resultObject_, *"read"*, 𝔽(_result_.[[Read]])). 99 | 1. Perform ! CreateDataPropertyOrThrow(_resultObject_, *"written"*, 𝔽(_written_)). 100 | 1. Return _resultObject_. 101 | 102 |
103 | 104 | 105 |

Uint8Array.fromHex ( _string_ )

106 | 107 | 1. If _string_ is not a String, throw a *TypeError* exception. 108 | 1. Let _result_ be FromHex(_string_). 109 | 1. If _result_.[[Error]] is not ~none~, then 110 | 1. Throw _result_.[[Error]]. 111 | 1. Let _resultLength_ be the length of _result_.[[Bytes]]. 112 | 1. Let _ta_ be ? AllocateTypedArray(*"Uint8Array"*, %Uint8Array%, *"%Uint8Array.prototype%"*, _resultLength_). 113 | 1. Set the value at each index of _ta_.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of _result_.[[Bytes]]. 114 | 1. Return _ta_. 115 | 116 |
117 | 118 | 119 |

Uint8Array.prototype.setFromHex ( _string_ )

120 | 121 | 1. Let _into_ be the *this* value. 122 | 1. Perform ? ValidateUint8Array(_into_). 123 | 1. If _string_ is not a String, throw a *TypeError* exception. 124 | 1. Let _taRecord_ be MakeTypedArrayWithBufferWitnessRecord(_into_, ~seq-cst~). 125 | 1. If IsTypedArrayOutOfBounds(_taRecord_) is *true*, throw a *TypeError* exception. 126 | 1. Let _byteLength_ be TypedArrayLength(_taRecord_). 127 | 1. Let _result_ be FromHex(_string_, _byteLength_). 128 | 1. Let _bytes_ be _result_.[[Bytes]]. 129 | 1. Let _written_ be the length of _bytes_. 130 | 1. NOTE: FromHex does not invoke any user code, so the ArrayBuffer backing _into_ cannot have been detached or shrunk. 131 | 1. Assert: _written_ ≤ _byteLength_. 132 | 1. Perform SetUint8ArrayBytes(_into_, _bytes_). 133 | 1. If _result_.[[Error]] is not ~none~, then 134 | 1. Throw _result_.[[Error]]. 135 | 1. Let _resultObject_ be OrdinaryObjectCreate(%Object.prototype%). 136 | 1. Perform ! CreateDataPropertyOrThrow(_resultObject_, *"read"*, 𝔽(_result_.[[Read]])). 137 | 1. Perform ! CreateDataPropertyOrThrow(_resultObject_, *"written"*, 𝔽(_written_)). 138 | 1. Return _resultObject_. 139 | 140 |
141 | 142 | 143 |

144 | ValidateUint8Array ( 145 | _ta_: an ECMAScript language value, 146 | ): either a normal completion containing ~unused~ or a throw completion 147 |

148 |
149 | 150 | 1. Perform ? RequireInternalSlot(_ta_, [[TypedArrayName]]). 151 | 1. If _ta_.[[TypedArrayName]] is not *"Uint8Array"*, throw a *TypeError* exception. 152 | 1. Return ~unused~. 153 | 154 |
155 | 156 | 157 |

158 | GetUint8ArrayBytes ( 159 | _ta_: a Uint8Array, 160 | ): either a normal completion containing a List of byte values or a throw completion 161 |

162 |
163 | 164 | 1. Let _buffer_ be _ta_.[[ViewedArrayBuffer]]. 165 | 1. Let _taRecord_ be MakeTypedArrayWithBufferWitnessRecord(_ta_, ~seq-cst~). 166 | 1. If IsTypedArrayOutOfBounds(_taRecord_) is *true*, throw a *TypeError* exception. 167 | 1. Let _len_ be TypedArrayLength(_taRecord_). 168 | 1. Let _byteOffset_ be _ta_.[[ByteOffset]]. 169 | 1. Let _bytes_ be a new empty List. 170 | 1. Let _index_ be 0. 171 | 1. Repeat, while _index_ < _len_, 172 | 1. Let _byteIndex_ be _byteOffset_ + _index_. 173 | 1. Let _byte_ be ℝ(GetValueFromBuffer(_buffer_, _byteIndex_, ~uint8~, *true*, ~unordered~)). 174 | 1. Append _byte_ to _bytes_. 175 | 1. Set _index_ to _index_ + 1. 176 | 1. Return _bytes_. 177 | 178 |
179 | 180 | 181 |

182 | SetUint8ArrayBytes ( 183 | _into_: a Uint8Array, 184 | _bytes_: a List of byte values, 185 | ): ~unused~ 186 |

187 |
188 | 189 | 1. Let _offset_ be _into_.[[ByteOffset]]. 190 | 1. Let _len_ be the length of _bytes_. 191 | 1. Let _index_ be 0. 192 | 1. Repeat, while _index_ < _len_, 193 | 1. Let _byte_ be _bytes_[_index_]. 194 | 1. Let _byteIndexInBuffer_ be _index_ + _offset_. 195 | 1. Perform SetValueInBuffer(_into_.[[ViewedArrayBuffer]], _byteIndexInBuffer_, ~uint8~, 𝔽(_byte_), *true*, ~unordered~). 196 | 1. Set _index_ to _index_ + 1. 197 | 198 |
199 | 200 | 201 |

Helpers

202 | 203 | 204 |

205 | SkipAsciiWhitespace ( 206 | _string_: a string, 207 | _index_: a non-negative integer, 208 | ): a non-negative integer 209 |

210 |
211 |
212 | 213 | 1. Let _length_ be the length of _string_. 214 | 1. Repeat, while _index_ < _length_, 215 | 1. Let _char_ be the code unit at index _index_ of _string_. 216 | 1. If _char_ is neither 0x0009 (TAB), 0x000A (LF), 0x000C (FF), 0x000D (CR), nor 0x0020 (SPACE), then 217 | 1. Return _index_. 218 | 1. Set _index_ to _index_ + 1. 219 | 1. Return _index_. 220 | 221 |
222 | 223 | 224 |

225 | DecodeBase64Chunk ( 226 | _chunk_: a string, 227 | optional _throwOnExtraBits_: a boolean, 228 | ): either a normal completion containing a List of byte values, or a throw completion 229 |

230 |
231 |
232 |

The standard base64 alphabet is the String whose elements are the code units corresponding to every letter and number in the Unicode Basic Latin block along with *"+"* and *"/"*; that is, it is *"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"*.

233 | 234 | 1. Let _chunkLength_ be the length of _chunk_. 235 | 1. If _chunkLength_ is 2, then 236 | 1. Set _chunk_ to the string-concatenation of _chunk_ and *"AA"*. 237 | 1. Else if _chunkLength_ is 3, then 238 | 1. Set _chunk_ to the string-concatenation of _chunk_ and *"A"*. 239 | 1. Else, 240 | 1. Assert: _chunkLength_ is 4. 241 | 1. Let _byteSequence_ be the unique sequence of 3 bytes resulting from decoding _chunk_ as base64 (such that applying the base64 encoding specified in section 4 of RFC 4648 to _byteSequence_ would produce _chunk_). 242 | 1. Let _bytes_ be a List whose elements are the elements of _byteSequence_, in order. 243 | 1. If _chunkLength_ is 2, then 244 | 1. Assert: _throwOnExtraBits_ is present. 245 | 1. If _throwOnExtraBits_ is *true* and _bytes_[1] ≠ 0, then 246 | 1. Throw a *SyntaxError* exception. 247 | 1. Return « _bytes_[0] ». 248 | 1. Else if _chunkLength_ is 3, then 249 | 1. Assert: _throwOnExtraBits_ is present. 250 | 1. If _throwOnExtraBits_ is *true* and _bytes_[2] ≠ 0, then 251 | 1. Throw a *SyntaxError* exception. 252 | 1. Return « _bytes_[0], _bytes_[1] ». 253 | 1. Else, 254 | 1. Return _bytes_. 255 | 256 |
257 | 258 | 259 |

260 | FromBase64 ( 261 | _string_: a string, 262 | _alphabet_: *"base64"* or *"base64url"*, 263 | _lastChunkHandling_: *"loose"*, *"strict"*, or *"stop-before-partial"*, 264 | optional _maxLength_: a non-negative integer, 265 | ): a Record with fields [[Read]] (an integral Number), [[Bytes]] (a List of byte values), and [[Error]] (either ~none~ or a *SyntaxError* object) 266 |

267 |
268 |
269 | 270 | 1. If _maxLength_ is not present, then 271 | 1. Let _maxLength_ be 253 - 1. 272 | 1. NOTE: Because the input is a string, the length of strings is limited to 253 - 1 characters, and the output requires no more bytes than the input has characters, this limit can never be reached. However, it is editorially convenient to use a finite value here. 273 | 1. NOTE: The order of validation and decoding in the algorithm below is not observable. Implementations are encouraged to perform them in whatever order is most efficient, possibly interleaving validation with decoding, as long as the behaviour is observably equivalent. 274 | 1. If _maxLength_ is 0, then 275 | 1. Return the Record { [[Read]]: 0, [[Bytes]]: « », [[Error]]: ~none~ }. 276 | 1. Let _read_ be 0. 277 | 1. Let _bytes_ be « ». 278 | 1. Let _chunk_ be the empty String. 279 | 1. Let _chunkLength_ be 0. 280 | 1. Let _index_ be 0. 281 | 1. Let _length_ be the length of _string_. 282 | 1. Repeat, 283 | 1. Set _index_ to SkipAsciiWhitespace(_string_, _index_). 284 | 1. If _index_ = _length_, then 285 | 1. If _chunkLength_ > 0, then 286 | 1. If _lastChunkHandling_ is *"stop-before-partial"*, then 287 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: ~none~ }. 288 | 1. Else if _lastChunkHandling_ is *"loose"*, then 289 | 1. If _chunkLength_ is 1, then 290 | 1. Let _error_ be a new *SyntaxError* exception. 291 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 292 | 1. Set _bytes_ to the list-concatenation of _bytes_ and ! DecodeBase64Chunk(_chunk_, *false*). 293 | 1. Else, 294 | 1. Assert: _lastChunkHandling_ is *"strict"*. 295 | 1. Let _error_ be a new *SyntaxError* exception. 296 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 297 | 1. Return the Record { [[Read]]: _length_, [[Bytes]]: _bytes_, [[Error]]: ~none~ }. 298 | 1. Let _char_ be the substring of _string_ from _index_ to _index_ + 1. 299 | 1. Set _index_ to _index_ + 1. 300 | 1. If _char_ is *"="*, then 301 | 1. If _chunkLength_ < 2, then 302 | 1. Let _error_ be a new *SyntaxError* exception. 303 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 304 | 1. Set _index_ to SkipAsciiWhitespace(_string_, _index_). 305 | 1. If _chunkLength_ = 2, then 306 | 1. If _index_ = _length_, then 307 | 1. If _lastChunkHandling_ is *"stop-before-partial"*, then 308 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: ~none~ }. 309 | 1. Let _error_ be a new *SyntaxError* exception. 310 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 311 | 1. Set _char_ to the substring of _string_ from _index_ to _index_ + 1. 312 | 1. If _char_ is *"="*, then 313 | 1. Set _index_ to SkipAsciiWhitespace(_string_, _index_ + 1). 314 | 1. If _index_ < _length_, then 315 | 1. Let _error_ be a new *SyntaxError* exception. 316 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 317 | 1. If _lastChunkHandling_ is *"strict"*, let _throwOnExtraBits_ be *true*. 318 | 1. Else, let _throwOnExtraBits_ be *false*. 319 | 1. Let _decodeResult_ be Completion(DecodeBase64Chunk(_chunk_, _throwOnExtraBits_)). 320 | 1. If _decodeResult_ is an abrupt completion, then 321 | 1. Let _error_ be _decodeResult_.[[Value]]. 322 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 323 | 1. Set _bytes_ to the list-concatenation of _bytes_ and ! _decodeResult_. 324 | 1. Return the Record { [[Read]]: _length_, [[Bytes]]: _bytes_, [[Error]]: ~none~ }. 325 | 1. If _alphabet_ is *"base64url"*, then 326 | 1. If _char_ is either *"+"* or *"/"*, then 327 | 1. Let _error_ be a new *SyntaxError* exception. 328 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 329 | 1. Else if _char_ is *"-"*, then 330 | 1. Set _char_ to *"+"*. 331 | 1. Else if _char_ is *"_"*, then 332 | 1. Set _char_ to *"/"*. 333 | 1. If the sole code unit of _char_ is not an element of the standard base64 alphabet, then 334 | 1. Let _error_ be a new *SyntaxError* exception. 335 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 336 | 1. Let _remaining_ be _maxLength_ - the length of _bytes_. 337 | 1. If _remaining_ = 1 and _chunkLength_ = 2, or if _remaining_ = 2 and _chunkLength_ = 3, then 338 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: ~none~ }. 339 | 1. Set _chunk_ to the string-concatenation of _chunk_ and _char_. 340 | 1. Set _chunkLength_ to the length of _chunk_. 341 | 1. If _chunkLength_ = 4, then 342 | 1. Set _bytes_ to the list-concatenation of _bytes_ and ! DecodeBase64Chunk(_chunk_). 343 | 1. Set _chunk_ to the empty String. 344 | 1. Set _chunkLength_ to 0. 345 | 1. Set _read_ to _index_. 346 | 1. If the length of _bytes_ = _maxLength_, then 347 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: ~none~ }. 348 | 349 |
350 | 351 | 352 |

353 | FromHex ( 354 | _string_: a string, 355 | optional _maxLength_: a non-negative integer, 356 | ): a Record with fields [[Read]] (an integral Number), [[Bytes]] (a List of byte values), and [[Error]] (either ~none~ or a *SyntaxError* object) 357 |

358 |
359 |
360 | 361 | 1. If _maxLength_ is not present, let _maxLength_ be 253 - 1. 362 | 1. Let _length_ be the length of _string_. 363 | 1. Let _bytes_ be « ». 364 | 1. Let _read_ be 0. 365 | 1. If _length_ modulo 2 is not 0, then 366 | 1. Let _error_ be a new *SyntaxError* exception. 367 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 368 | 1. Repeat, while _read_ < _length_ and the length of _bytes_ < _maxLength_, 369 | 1. Let _hexits_ be the substring of _string_ from _read_ to _read_ + 2. 370 | 1. If _hexits_ contains any code units which are not in *"0123456789abcdefABCDEF"*, then 371 | 1. Let _error_ be a new *SyntaxError* exception. 372 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: _error_ }. 373 | 1. Set _read_ to _read_ + 2. 374 | 1. Let _byte_ be the integer value represented by _hexits_ in base-16 notation, using the letters A-F and a-f for digits with values 10 through 15. 375 | 1. Append _byte_ to _bytes_. 376 | 1. Return the Record { [[Read]]: _read_, [[Bytes]]: _bytes_, [[Error]]: ~none~ }. 377 | 378 |
379 | 380 | 381 | 382 | 383 |

GetOptionsObject ( _options_ )

384 | 385 | 1. If _options_ is *undefined*, then 386 | 1. Return OrdinaryObjectCreate(*null*). 387 | 1. If Type(_options_) is Object, then 388 | 1. Return _options_. 389 | 1. Throw a *TypeError* exception. 390 | 391 |
392 |
393 | 394 | 395 |

Bibliography

396 |

NOTE: We need to add RFC 4648 to the bibliography as part of landing this upstream.

397 |
    398 |
  1. 399 | RFC 4648 “The Base16, Base32, and Base64 Data Encodings”, available at <https://datatracker.ietf.org/doc/html/rfc4648> 400 |
  2. 401 |
402 |
403 | -------------------------------------------------------------------------------- /stream.mjs: -------------------------------------------------------------------------------- 1 | import './playground/polyfill-install.mjs'; 2 | 3 | // This mirrors the somewhat awkward TextDecoder API. 4 | // Better designs are of course possible. 5 | class Base64Decoder { 6 | #extra = ''; 7 | 8 | decode(chunk = '', options = {}) { 9 | let opts = { ...options }; 10 | // match TextEncoder API 11 | if (opts.stream) { 12 | opts.lastChunkHandling = 'stop-before-partial'; 13 | } 14 | chunk = this.#extra + chunk; 15 | this.#extra = ''; 16 | // for simplicity, allocate new memory every time 17 | // the calculation below is guaranteed to be enough, 18 | // but may be too much if there is whitespace 19 | // if you're really concerned about memory, a TextDecoder style API is a bad choice 20 | let buffer = new Uint8Array(Math.ceil(chunk.length * 3 / 4)); 21 | let { read, written } = buffer.setFromBase64(chunk, opts); 22 | buffer = buffer.subarray(0, written); 23 | this.#extra = chunk.slice(read); 24 | return buffer; 25 | } 26 | } 27 | 28 | 29 | class Base64Encoder { 30 | #extra; 31 | #extraLength; 32 | constructor() { 33 | this.#extra = new Uint8Array(3); 34 | this.#extraLength = 0; 35 | } 36 | 37 | // partly derived from https://github.com/lucacasonato/base64_streams/blob/main/src/iterator/encoder.ts 38 | encode(chunk = Uint8Array.of(), options = {}) { 39 | let stream = options.stream ?? false; 40 | 41 | if (this.#extraLength > 0) { 42 | let bytesNeeded = 3 - this.#extraLength; 43 | let bytesAvailable = Math.min(bytesNeeded, chunk.length); 44 | this.#extra.set(chunk.subarray(0, bytesAvailable), this.#extraLength); 45 | chunk = chunk.subarray(bytesAvailable); 46 | this.#extraLength += bytesAvailable; 47 | } 48 | 49 | if (!stream) { 50 | // assert: this.#extraLength.length === 0 || this.#extraLength === 3 || chunk.length === 0 51 | let prefix = this.#extra.subarray(0, this.#extraLength).toBase64(); 52 | this.#extraLength = 0; 53 | return prefix + chunk.toBase64(); 54 | } 55 | 56 | let extraReturn = ''; 57 | 58 | if (this.#extraLength === 3) { 59 | extraReturn = this.#extra.toBase64(); 60 | this.#extraLength = 0; 61 | } 62 | 63 | let remainder = chunk.length % 3; 64 | if (remainder > 0) { 65 | this.#extra.set(chunk.subarray(chunk.length - remainder)); 66 | this.#extraLength = remainder; 67 | chunk = chunk.subarray(0, chunk.length - remainder); 68 | } 69 | 70 | return extraReturn + chunk.toBase64(); 71 | } 72 | } 73 | 74 | let decoder = new Base64Decoder(); 75 | 76 | console.log(decoder.decode('SG Vsb ', { stream: true })); 77 | console.log(decoder.decode('G8gV29ybGR ', { stream: true })); 78 | console.log(decoder.decode('')); 79 | 80 | 81 | let encoder = new Base64Encoder(); 82 | 83 | console.log(encoder.encode(Uint8Array.of(72, 101, 108, 108, 111), { stream: true })); 84 | console.log(encoder.encode(Uint8Array.of(32, 87, 111, 114, 108, 100), { stream: true })); 85 | console.log(encoder.encode()); 86 | -------------------------------------------------------------------------------- /test-polyfill.mjs: -------------------------------------------------------------------------------- 1 | import test from 'node:test'; 2 | import assert from 'node:assert'; 3 | 4 | import './playground/polyfill-install.mjs'; 5 | 6 | let stringToBytes = str => new TextEncoder().encode(str); 7 | 8 | // https://datatracker.ietf.org/doc/html/rfc4648#section-10 9 | let standardBase64Vectors = [ 10 | ['', ''], 11 | ['f', 'Zg=='], 12 | ['fo', 'Zm8='], 13 | ['foo', 'Zm9v'], 14 | ['foob', 'Zm9vYg=='], 15 | ['fooba', 'Zm9vYmE='], 16 | ['foobar', 'Zm9vYmFy'], 17 | ]; 18 | test('standard vectors', async t => { 19 | for (let [string, result] of standardBase64Vectors) { 20 | await t.test(JSON.stringify(string), () => { 21 | assert.strictEqual(stringToBytes(string).toBase64(), result); 22 | 23 | assert.deepStrictEqual(Uint8Array.fromBase64(result), stringToBytes(string)); 24 | assert.deepStrictEqual(Uint8Array.fromBase64(result, { lastChunkHandling: 'strict' }), stringToBytes(string)); 25 | }); 26 | } 27 | }); 28 | 29 | test('omitPadding', async t => { 30 | for (let [string, result] of standardBase64Vectors) { 31 | await t.test(JSON.stringify(string), () => { 32 | assert.strictEqual(stringToBytes(string).toBase64({ omitPadding: true }), result.replace(/=/g, '')); 33 | }); 34 | } 35 | }); 36 | 37 | let malformedPadding = ['=', 'Zg=', 'Z===', 'Zm8==', 'Zm9v=']; 38 | test('malformed padding', async t => { 39 | for (let string of malformedPadding) { 40 | await t.test(JSON.stringify(string), () => { 41 | assert.throws(() => Uint8Array.fromBase64(string), SyntaxError); 42 | assert.throws(() => Uint8Array.fromBase64(string, { lastChunkHandling: 'strict' }), SyntaxError); 43 | }); 44 | } 45 | }); 46 | 47 | let illegal = [ 48 | 'Zm.9v', 49 | 'Zm9v^', 50 | 'Zg==&', 51 | 'Z−==', // U+2212 'Minus Sign' 52 | 'Z+==', // U+FF0B 'Fullwidth Plus Sign' 53 | 'Zg\u00A0==', // nbsp 54 | 'Zg\u2009==', // thin space 55 | 'Zg\u2028==', // line separator 56 | ]; 57 | test('illegal characters', async t => { 58 | for (let string of malformedPadding) { 59 | await t.test(JSON.stringify(string), () => { 60 | assert.throws(() => Uint8Array.fromBase64(string), SyntaxError); 61 | assert.throws(() => Uint8Array.fromBase64(string, { lastChunkHandling: 'strict' }), SyntaxError); 62 | }); 63 | } 64 | }); 65 | 66 | let onlyNonStrict = [ 67 | ['Zg', Uint8Array.of(0x66)], 68 | ['Zh', Uint8Array.of(0x66)], 69 | ['Zh==', Uint8Array.of(0x66)], 70 | ['Zm8', Uint8Array.of(0x66, 0x6f)], 71 | ['Zm9', Uint8Array.of(0x66, 0x6f)], 72 | ['Zm9=', Uint8Array.of(0x66, 0x6f)], 73 | ]; 74 | test('only valid in non-strict', async t => { 75 | for (let [encoded, decoded] of onlyNonStrict) { 76 | await t.test(JSON.stringify(encoded), () => { 77 | assert.deepStrictEqual(Uint8Array.fromBase64(encoded), decoded); 78 | assert.throws(() => Uint8Array.fromBase64(encoded, { lastChunkHandling: 'strict' }), SyntaxError); 79 | }); 80 | } 81 | }); 82 | 83 | test('alphabet-specific strings', async t => { 84 | let standardOnly = 'x+/y'; 85 | 86 | await t.test(JSON.stringify(standardOnly), () => { 87 | assert.deepStrictEqual(Uint8Array.fromBase64(standardOnly), Uint8Array.of(0xc7, 0xef, 0xf2)); 88 | assert.deepStrictEqual(Uint8Array.fromBase64(standardOnly, { alphabet: 'base64' }), Uint8Array.of(0xc7, 0xef, 0xf2)); 89 | assert.throws(() => Uint8Array.fromBase64(standardOnly, { alphabet: 'base64url' }), SyntaxError); 90 | }); 91 | 92 | let urlOnly = 'x-_y'; 93 | await t.test(JSON.stringify(urlOnly), () => { 94 | assert.deepStrictEqual(Uint8Array.fromBase64(urlOnly, { alphabet: 'base64url' }), Uint8Array.of(0xc7, 0xef, 0xf2)); 95 | assert.throws(() => Uint8Array.fromBase64(urlOnly), SyntaxError); 96 | assert.throws(() => Uint8Array.fromBase64(urlOnly, { alphabet: 'base64' }), SyntaxError); 97 | }); 98 | }); 99 | 100 | test('valid data before invalid data is written', async t => { 101 | let input = 'Zm9vYmFyxx!'; 102 | let target = new Uint8Array(9); 103 | 104 | assert.throws(() => target.setFromBase64(input), SyntaxError); 105 | assert.deepStrictEqual(target, Uint8Array.of(102, 111, 111, 98, 97, 114, 0, 0, 0)); 106 | }); 107 | 108 | test('writing to an existing buffer', async t => { 109 | let foobarInput = 'Zm9vYmFy'; 110 | let foobaInput = 'Zm9vYmE'; 111 | let foobarOutput = [102, 111, 111, 98, 97, 114]; 112 | 113 | await t.test('buffer exact', () => { 114 | let output = new Uint8Array(6); 115 | let { read, written } = output.setFromBase64(foobarInput); 116 | assert.deepStrictEqual([...output], foobarOutput); 117 | assert.deepStrictEqual({ read, written }, { read: 8, written: 6 }); 118 | }); 119 | 120 | await t.test('buffer too large', () => { 121 | let output = new Uint8Array(8); 122 | let { read, written } = output.setFromBase64(foobarInput); 123 | assert.deepStrictEqual([...output], [...foobarOutput, 0, 0]); 124 | assert.deepStrictEqual({ read, written }, { read: 8, written: 6 }); 125 | }); 126 | 127 | await t.test('buffer too small', () => { 128 | let output = new Uint8Array(5); 129 | let { read, written } = output.setFromBase64(foobarInput); 130 | assert.deepStrictEqual([...output], [...foobarOutput.slice(0, 3), 0, 0]); 131 | assert.deepStrictEqual({ read, written }, { read: 4, written: 3 }); 132 | }); 133 | 134 | await t.test('buffer exact, padded', () => { 135 | let output = new Uint8Array(5); 136 | let { read, written } = output.setFromBase64(foobaInput + '='); 137 | assert.deepStrictEqual([...output], foobarOutput.slice(0, 5)); 138 | assert.deepStrictEqual({ read, written }, { read: 8, written: 5 }); 139 | }); 140 | 141 | await t.test('buffer exact, not padded', () => { 142 | let output = new Uint8Array(5); 143 | let { read, written } = output.setFromBase64(foobaInput); 144 | assert.deepStrictEqual([...output], foobarOutput.slice(0, 5)); 145 | assert.deepStrictEqual({ read, written }, { read: 7, written: 5 }); 146 | }); 147 | 148 | await t.test('buffer exact, padded, stop-before-partial', () => { 149 | let output = new Uint8Array(5); 150 | let { read, written } = output.setFromBase64(foobaInput + '=', { lastChunkHandling: 'stop-before-partial' }); 151 | assert.deepStrictEqual([...output], foobarOutput.slice(0, 5)); 152 | assert.deepStrictEqual({ read, written }, { read: 8, written: 5 }); 153 | }); 154 | 155 | await t.test('buffer exact, not padded, stop-before-partial', () => { 156 | let output = new Uint8Array(5); 157 | let { read, written } = output.setFromBase64(foobaInput, { lastChunkHandling: 'stop-before-partial' }); 158 | assert.deepStrictEqual([...output], [...foobarOutput.slice(0, 3), 0, 0]); 159 | assert.deepStrictEqual({ read, written }, { read: 4, written: 3 }); 160 | }); 161 | 162 | await t.test('buffer too small, padded', () => { 163 | let output = new Uint8Array(4); 164 | let { read, written } = output.setFromBase64(foobaInput + '='); 165 | assert.deepStrictEqual([...output], [...foobarOutput.slice(0, 3), 0]); 166 | assert.deepStrictEqual({ read, written }, { read: 4, written: 3 }); 167 | }); 168 | 169 | await t.test('buffer too large, trailing whitespace', () => { 170 | let output = new Uint8Array(8); 171 | let { read, written } = output.setFromBase64(foobarInput + ' '.repeat(10)); 172 | assert.deepStrictEqual([...output], [...foobarOutput, 0, 0]); 173 | assert.deepStrictEqual({ read, written }, { read: 18, written: 6 }); 174 | }); 175 | 176 | await t.test('buffer too large, not padded, trailing whitespace', () => { 177 | let output = new Uint8Array(8); 178 | let { read, written } = output.setFromBase64(foobaInput + ' '.repeat(10)); 179 | assert.deepStrictEqual([...output], [...foobarOutput.slice(0, 5), 0, 0, 0]); 180 | assert.deepStrictEqual({ read, written }, { read: 17, written: 5 }); 181 | }); 182 | }); 183 | 184 | test('stop-before-partial', async t => { 185 | let foobaInput = 'Zm9vYmE'; 186 | let foobarOutput = [102, 111, 111, 98, 97, 114]; 187 | 188 | await t.test('no padding', () => { 189 | let output = Uint8Array.fromBase64(foobaInput, { lastChunkHandling: 'stop-before-partial' }); 190 | assert.deepStrictEqual([...output], foobarOutput.slice(0, 3)); 191 | }); 192 | 193 | await t.test('padding', () => { 194 | let output = Uint8Array.fromBase64(foobaInput + '=', { lastChunkHandling: 'stop-before-partial' }); 195 | assert.deepStrictEqual([...output], foobarOutput.slice(0, 5)); 196 | }); 197 | 198 | await t.test('no padding, trailing whitespace', () => { 199 | let output = new Uint8Array(8); 200 | let { read, written } = output.setFromBase64(foobaInput + ' '.repeat(10), { lastChunkHandling: 'stop-before-partial' }); 201 | assert.deepStrictEqual([...output], [...foobarOutput.slice(0, 3), 0, 0, 0, 0, 0]); 202 | assert.deepStrictEqual({ read, written }, { read: 4, written: 3 }); 203 | }); 204 | }); 205 | 206 | test('hex', async t => { 207 | let encoded = 'deadBEEF'; 208 | let decoded = [222, 173, 190, 239]; 209 | 210 | await t.test('basic decode', () => { 211 | assert.deepStrictEqual([...Uint8Array.fromHex(encoded)], decoded); 212 | }); 213 | 214 | await t.test('decode into, exact', () => { 215 | let output = new Uint8Array(4); 216 | let { read, written } = output.setFromHex(encoded); 217 | assert.deepStrictEqual([...output], decoded); 218 | assert.deepStrictEqual({ read, written }, { read: 8, written: 4 }); 219 | }); 220 | 221 | await t.test('decode into, buffer too large', () => { 222 | let output = new Uint8Array(6); 223 | let { read, written } = output.setFromHex(encoded); 224 | assert.deepStrictEqual([...output], [...decoded, 0, 0]); 225 | assert.deepStrictEqual({ read, written }, { read: 8, written: 4 }); 226 | }); 227 | 228 | await t.test('decode into, buffer too small', () => { 229 | let output = new Uint8Array(3); 230 | let { read, written } = output.setFromHex(encoded); 231 | assert.deepStrictEqual([...output], decoded.slice(0, 3)); 232 | assert.deepStrictEqual({ read, written }, { read: 6, written: 3 }); 233 | }); 234 | }); 235 | 236 | test('valid data before invalid data is written', async t => { 237 | let input = 'deadbeef!!'; 238 | let target = new Uint8Array(6); 239 | 240 | assert.throws(() => target.setFromHex(input), SyntaxError); 241 | assert.deepStrictEqual(target, Uint8Array.of(222, 173, 190, 239, 0, 0)); 242 | }); 243 | --------------------------------------------------------------------------------