├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── config.js ├── dist ├── hive-tx.min.js ├── hive-tx.min.js.LICENSE.txt └── hive-tx.min.js.map ├── examples └── example1.html ├── helpers ├── Asset.js ├── ByteBuffer.js ├── HexBuffer.js ├── PrivateKey.d.ts ├── PrivateKey.js ├── PublicKey.d.ts ├── PublicKey.js ├── Signature.d.ts ├── Signature.js ├── aes.js ├── call.js ├── crypto.js ├── deserializer.js ├── globalProps.js ├── memo.d.ts ├── memo.js ├── serializer.js ├── uint8Array.js ├── utils.d.ts └── utils.js ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json ├── transactions ├── broadcastTransaction.js ├── createTransaction.js └── signTransaction.js └── webpack.config.cjs /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | # dist 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | examples 65 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test.js -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "prettier.semi": false, 4 | "javascript.format.semicolons": "remove", 5 | "javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": true, 6 | "javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": true, 7 | "javascript.format.insertSpaceAfterCommaDelimiter": true, 8 | "editor.comments.insertSpace": true, 9 | "javascript.format.insertSpaceAfterSemicolonInForStatements": true, 10 | "javascript.format.insertSpaceBeforeAndAfterBinaryOperators": true, 11 | "javascript.format.insertSpaceBeforeFunctionParenthesis": true, 12 | "editor.useTabStops": true, 13 | "standard.autoFixOnSave": true, 14 | "standard.validate": [ 15 | "javascript" 16 | ] 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mahdi Yari 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 | # hive-tx 2 | 3 | Lightweight and complete JavaScript library for Hive blockchain - Web and NodeJS. 4 | 5 | #### Why this? 6 | 7 | The most lightweight while being a complete library. 8 | 9 | Some libraries are not easy to integrate and in some cases are incompatible with some frameworks like [Nativescript](https://www.nativescript.org/) 10 | 11 | Hive-tx is a solution to such cases when the other libraries are not working. 12 | 13 | ## Installation 14 | 15 | ```bash 16 | npm install hive-tx --save 17 | ``` 18 | 19 | ## Usage 20 | 21 | **Browser:** 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | `hiveTx` is available after including /dist/hive-tx.min.js file in your html file. 28 | 29 | **NodeJS:** 30 | 31 | ```js 32 | // ES Module 33 | import * as hiveTx from 'hive-tx' 34 | 35 | // OR import what you use 36 | import { call, Transaction, PrivateKey } from 'hive-tx' 37 | 38 | // OR in CommonJS environments using dynamic import() 39 | const hiveTx = await import('hive-tx') 40 | 41 | // OR import what you use 42 | const { call, Transaction, PrivateKey } = await import('hive-tx') 43 | ``` 44 | 45 | ## Usage examples 46 | 47 | **Configuration** 48 | 49 | Set or get configs: 50 | 51 | ```js 52 | // default values that are already defined in config.js 53 | hiveTx.config.node: [ 54 | 'https://api.hive.blog', 55 | 'https://api.deathwing.me', 56 | 'https://rpc.mahdiyari.info', 57 | 'https://techcoderx.com', 58 | 'https://hiveapi.actifit.io', 59 | 'https://hive-api.dlux.io', 60 | 'https://hive-api.arcange.eu', 61 | 'https://api.c0ff33a.uk' 62 | ] 63 | // OR hiveTx.config.node = "https://api.hive.blog" 64 | hiveTx.config.chain_id = "beeab0de00000000000000000000000000000000000000000000000000000000" 65 | hiveTx.config.address_prefix = "STM" 66 | hiveTx.config.axiosAdapter = null 67 | hiveTx.config.timeout: 5 // 5 seconds 68 | hiveTx.config.retry: 5 // consecutive retries on one call 69 | hiveTx.config.healthcheckInterval: 30_000 // in ms 70 | ``` 71 | 72 | You can define a different adapter if your environment doesn't support 'xhr' or 'http' 73 | See https://github.com/haverstack/axios-fetch-adapter 74 | Example: 75 | ```js 76 | import fetchAdapter from '@haverstack/axios-fetch-adapter' 77 | hiveTx.config.axiosAdapter = fetchAdapter 78 | ``` 79 | 80 | **Create transaction:** 81 | 82 | ```js 83 | const tx = new hiveTx.Transaction(trx?) 84 | ``` 85 | 86 | or 87 | 88 | ```js 89 | const tx = new hiveTx.Transaction() 90 | tx.create(operations, expiration = 60) 91 | ``` 92 | 93 | Example: 94 | 95 | ```js 96 | const operations = [ 97 | [ 98 | 'vote', 99 | { 100 | voter: 'guest123', 101 | author: 'guest123', 102 | permlink: '20191107t125713486z-post', 103 | weight: 9900 104 | } 105 | ] 106 | ] 107 | 108 | const tx = new hiveTx.Transaction() 109 | tx.create(operations).then(() => console.log(tx.transaction)) 110 | ``` 111 | 112 | **Sign transaction:** 113 | 114 | ```js 115 | const myKey = '5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg' 116 | const privateKey = hiveTx.PrivateKey.from(myKey) 117 | 118 | tx.sign(privateKey) 119 | console.log(tx.signedTransaction) 120 | ``` 121 | 122 | **Broadcast transaction:** 123 | 124 | ```js 125 | tx.broadcast().then(res => console.log(res)) 126 | ``` 127 | 128 | **Get transaction digest and id** 129 | 130 | Will return the hash and transaction id without broadcasting the transaction. 131 | ```js 132 | const digest = tx.digest() 133 | // { digest: Uint8Array, txId: string } 134 | ``` 135 | 136 | **Make node call:** 137 | 138 | ```js 139 | hiveTx.call(method, params = [], timeout = 10): Promise 140 | ``` 141 | 142 | Example: 143 | 144 | ```js 145 | hiveTx.call('condenser_api.get_accounts', [['mahdiyari']]).then(res => console.log(res)) 146 | ``` 147 | 148 | **Sign message and verify sginature:** 149 | ```js 150 | hiveTx.PrivateKey.sign(message: Uint8Array) 151 | hiveTx.PublicKey.verify(message: Uint8Array, signature: Signature) 152 | ``` 153 | 154 | Example: 155 | ```js 156 | const { sha256 } = require( 'hive-tx/helpers/crypto' ) 157 | 158 | const privateKey = hiveTx.PrivateKey.from('5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg') 159 | const publicKey = hiveTx.PublicKey.from('STM6aGPtxMUGnTPfKLSxdwCHbximSJxzrRjeQmwRW9BRCdrFotKLs') 160 | const message = sha256('testing') 161 | const signature = privateKey.sign(message) 162 | const verify = publicKey.verify(message, signature) // true 163 | ``` 164 | Or create Sginature from string: 165 | ```js 166 | const signature = hiveTx.Signature.from('1f019dc13a308cef138162cc16ab7c3aa1891941fddec66d83ff29b01b649a86600802d301f13505abc8c9ccbbeb86852fc71134fe209a6e717c6fd7b4cd1505a2') 167 | ``` 168 | 169 | **Generate random key** 170 | ```js 171 | hiveTx.PrivateKey.randomKey() 172 | ``` 173 | 174 | **Retrieve public key from Signature** 175 | ```js 176 | const signature = hiveTx.Signature.from(string) 177 | signature.getPublicKey(message) 178 | ``` 179 | 180 | For example we find the public key used for signing this transaction: 181 | https://hiveblocks.com/tx/207c06a5448e18b501d15891aed6f3ecbeb96b83 182 | 183 | ```js 184 | const signature = hiveTx.Signature.from('203dfa2f2620f94a033c424710bbf22c518e1d9aec4170b342789acdc714bf0b483ff1e2ec1fcd5607e5df767ba09751792484a7ac1cf31c94cf55b1e81df6be30') 185 | const trx = new hiveTx.Transaction({ 186 | ref_block_num: 30883, 187 | ref_block_prefix: 3663302639, 188 | expiration: '2023-05-26 07:49:44', 189 | operations: [[ 190 | 'vote', 191 | { 192 | voter: 'mahdiyari', 193 | author: 'afa.hb03', 194 | permlink: 'esp-engcoastal-sentry-splinterlands-art-contest-week-242-by-afahb03', 195 | weight: 2000 196 | } 197 | ]], 198 | extensions: [] 199 | }) 200 | const { digest } = trx.digest() 201 | const publicKey = signature.getPublicKey(digest).toString() 202 | // STM8WWUYHMdHLgEHidYCztswzfZCViA16EqGkAxt7RG4dWwDpFtCF 203 | // To find which account has this public key 204 | const account = await hiveTx.call('condenser_api.get_key_references', [["STM8WWUYHMdHLgEHidYCztswzfZCViA16EqGkAxt7RG4dWwDpFtCF"]]) 205 | // steemauto 206 | ``` 207 | 208 | **Encode/Decode Memo** 209 | ```js 210 | import { Memo, PrivateKey, PublicKey } from 'hive-tx' 211 | 212 | // sender private key 213 | const privateKey = PrivateKey.from('...') 214 | // receiver public memo key 215 | const publicKey = PublicKey.from('...') 216 | 217 | // must have # 218 | const memo = '#testing' 219 | const encryptedMemo = await Memo.encode(privateKey, publicKey, memo) 220 | 221 | // Decode using receiver's private memo key 222 | const decryptedMemo = await Memo.decode(privateKey, encryptedMemo) 223 | ``` 224 | 225 | ### Utils 226 | 227 | In browser build `utils` is exported. For example: 228 | ``` 229 | hiveTx.utils.validateUsername('test') 230 | ``` 231 | 232 | **Validate Username** 233 | Example: 234 | ```js 235 | import { validateUsername } from 'hive-tx/helpers/utils.js' 236 | console.log(validateUsername('test')) 237 | // null 238 | console.log(validateUsername('Big')) 239 | // Account name should start with a lowercase letter. 240 | ``` 241 | 242 | **makeBitMaskFilter - get_account_history** 243 | Example: 244 | ```js 245 | import { call } from 'hive-tx' 246 | import { makeBitMaskFilter, operations as op } from 'hive-tx/helpers/utils.js' 247 | const filter = makeBitMaskFilter( 248 | [ 249 | op.transfer, 250 | op.transfer_to_vesting 251 | ] 252 | ) 253 | call('condenser_api.get_account_history', ['mahdiyari', -1, 1, ...filter]) 254 | .then(res => console.log(res)) 255 | ``` 256 | 257 | **buildWitnessSetProperties** 258 | Needed for `witness_set_properties` operation. 259 | 260 | Example: 261 | ```js 262 | import { buildWitnessSetProperties } from 'hive-tx/helpers/utils.js' 263 | const owner = 'mahdiyari' 264 | const props = { 265 | key: 'STM1111111111111111111111111111111114T1Anm', // Required - signing key 266 | account_creation_fee: '0.000 HIVE', // optional 267 | account_subsidy_budget: 10000, // optional 268 | account_subsidy_decay: 330782, // optional 269 | maximum_block_size: 65536, // optional 270 | hbd_interest_rate: 0, // optional 271 | hbd_exchange_rate: { base: '0.250 HBD', quote: '1.000 HIVE' }, // optional 272 | url: 'https://testurl', // optional 273 | new_signing_key: "STM1111111111111111111111111111111114T1Anm" // optional 274 | } 275 | const witnessOps = buildWitnessSetProperties(owner, props) 276 | const trx = new Transaction().create(witnessOps) 277 | ``` 278 | 279 | **Retrying and failover** 280 | Hive-tx will retry and change the node to the next in the array IF `hiveTx.config.node` provided as array which by default is. 281 | By default hive-tx will use the timeout and retry values from the global config but you can supply timeout and retry values per request as well. 282 | ```js 283 | // timeout: 5, retry: 3 284 | hiveTx.call('condenser_api.get_accounts', [['mahdiyari']], 5, 3).then(res => console.log(res)) 285 | // retrying applies to broadcasting as well 286 | tx.broadcast(5, 3) 287 | ``` 288 | 289 | Note: Retrying a transaction is safe in this implementation as the chain won't accept a duplicate transaction. 290 | Note: retry = 5 will actually make 6 calls. 291 | Note: Failover will happen on any error and switch to the next node. 292 | 293 | 294 | ## License 295 | 296 | MIT 297 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | node: [ 3 | 'https://api.hive.blog', 4 | 'https://api.deathwing.me', 5 | 'https://rpc.mahdiyari.info', 6 | 'https://techcoderx.com', 7 | 'https://hiveapi.actifit.io', 8 | 'https://hive-api.dlux.io', 9 | 'https://hive-api.arcange.eu', 10 | 'https://api.c0ff33a.uk' 11 | ], 12 | chain_id: 'beeab0de00000000000000000000000000000000000000000000000000000000', 13 | address_prefix: 'STM', 14 | axiosAdapter: null, 15 | timeout: 5, // 5 seconds 16 | retry: 5, // consecutive retries on one call 17 | healthcheckInterval: 30_000 18 | } 19 | -------------------------------------------------------------------------------- /dist/hive-tx.min.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */ 2 | 3 | /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ 4 | 5 | /** 6 | 7 | * @license bytebuffer.js (c) 2015 Daniel Wirtz 8 | 9 | * Backing buffer: ArrayBuffer, Accessor: DataView 10 | 11 | * Released under the Apache License, Version 2.0 12 | 13 | * see: https://github.com/dcodeIO/bytebuffer.js for details 14 | 15 | * modified by @xmcl/bytebuffer 16 | 17 | * And customized for hive-tx 18 | 19 | */ 20 | -------------------------------------------------------------------------------- /examples/example1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Test steem-tx 9 | 10 | 11 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /helpers/Asset.js: -------------------------------------------------------------------------------- 1 | /** Class representing a hive asset, 2 | * e.g. `1.000 HIVE` or `12.112233 VESTS`. */ 3 | export class Asset { 4 | /** Create a new Asset instance from a string, e.g. `42.000 HIVE`. */ 5 | static fromString (string, expectedSymbol = null) { 6 | const [amountString, symbol] = string.split(' ') 7 | if ( 8 | ['STEEM', 'VESTS', 'SBD', 'TESTS', 'TBD', 'HIVE', 'HBD'].indexOf( 9 | symbol 10 | ) === -1 11 | ) { 12 | throw new Error(`Invalid asset symbol: ${symbol}`) 13 | } 14 | if (expectedSymbol && symbol !== expectedSymbol) { 15 | throw new Error( 16 | `Invalid asset, expected symbol: ${expectedSymbol} got: ${symbol}` 17 | ) 18 | } 19 | const amount = Number.parseFloat(amountString) 20 | if (!Number.isFinite(amount)) { 21 | throw new Error(`Invalid asset amount: ${amountString}`) 22 | } 23 | return new Asset(amount, symbol) 24 | } 25 | 26 | /** 27 | * Convenience to create new Asset. 28 | * @param symbol Symbol to use when created from number. Will also be used to validate 29 | * the asset, throws if the passed value has a different symbol than this. 30 | */ 31 | static from (value, symbol = null) { 32 | if (value instanceof Asset) { 33 | if (symbol && value.symbol !== symbol) { 34 | throw new Error( 35 | `Invalid asset, expected symbol: ${symbol} got: ${value.symbol}` 36 | ) 37 | } 38 | return value 39 | } else if (typeof value === 'number' && Number.isFinite(value)) { 40 | return new Asset(value, symbol || 'STEEM') 41 | } else if (typeof value === 'string') { 42 | return Asset.fromString(value, symbol) 43 | } else { 44 | throw new Error(`Invalid asset '${String(value)}'`) 45 | } 46 | } 47 | 48 | // We convert HIVE & HBD strings to STEEM & SBD because the serialization should be based on STEEM & SBD 49 | constructor (amount, symbol) { 50 | this.amount = amount 51 | this.symbol = 52 | symbol === 'HIVE' ? 'STEEM' : symbol === 'HBD' ? 'SBD' : symbol 53 | } 54 | 55 | /** Return asset precision. */ 56 | getPrecision () { 57 | switch (this.symbol) { 58 | case 'TESTS': 59 | case 'TBD': 60 | case 'STEEM': 61 | case 'SBD': 62 | case 'HBD': 63 | case 'HIVE': 64 | return 3 65 | case 'VESTS': 66 | return 6 67 | } 68 | } 69 | 70 | /** Return a string representation of this asset, e.g. `42.000 HIVE`. */ 71 | toString () { 72 | return `${this.amount.toFixed(this.getPrecision())} ${this.symbol}` 73 | } 74 | 75 | toJSON () { 76 | return this.toString() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /helpers/ByteBuffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | * @license bytebuffer.js (c) 2015 Daniel Wirtz 4 | 5 | * Backing buffer: ArrayBuffer, Accessor: DataView 6 | 7 | * Released under the Apache License, Version 2.0 8 | 9 | * see: https://github.com/dcodeIO/bytebuffer.js for details 10 | 11 | * modified by @xmcl/bytebuffer 12 | 13 | * And customized for hive-tx 14 | 15 | */ 16 | 17 | export class ByteBuffer { 18 | /** 19 | 20 | * ByteBuffer version. 21 | 22 | * @type {string} 23 | 24 | * @const 25 | 26 | * @expose 27 | 28 | */ 29 | 30 | static VERSION = '0.0.1' 31 | 32 | /** 33 | 34 | * Little endian constant that can be used instead of its boolean value. Evaluates to `true`. 35 | 36 | * @type {boolean} 37 | 38 | * @const 39 | 40 | * @expose 41 | 42 | */ 43 | 44 | static LITTLE_ENDIAN = true 45 | 46 | /** 47 | 48 | * Big endian constant that can be used instead of its boolean value. Evaluates to `false`. 49 | 50 | * @type {boolean} 51 | 52 | * @const 53 | 54 | * @expose 55 | 56 | */ 57 | 58 | static BIG_ENDIAN = false 59 | 60 | /** 61 | 62 | * Default initial capacity of `16`. 63 | 64 | * @type {number} 65 | 66 | * @expose 67 | 68 | */ 69 | 70 | static DEFAULT_CAPACITY = 16 71 | 72 | /** 73 | 74 | * Default endianess of `false` for big endian. 75 | 76 | * @type {boolean} 77 | 78 | * @expose 79 | 80 | */ 81 | 82 | static DEFAULT_ENDIAN = ByteBuffer.BIG_ENDIAN 83 | 84 | /** 85 | 86 | * Default no assertions flag of `false`. 87 | 88 | * @type {boolean} 89 | 90 | * @expose 91 | 92 | */ 93 | 94 | static DEFAULT_NOASSERT = false 95 | 96 | /** 97 | 98 | * Backing ArrayBuffer. 99 | 100 | * @type {!ArrayBuffer} 101 | 102 | * @expose 103 | 104 | */ 105 | 106 | /** 107 | * Metrics representing number of bytes. Evaluates to `b`. 108 | * @type {string} 109 | * @const 110 | * @expose 111 | */ 112 | static METRICS_BYTES = 'b' 113 | 114 | buffer 115 | 116 | /** 117 | 118 | * DataView utilized to manipulate the backing buffer. Becomes `null` if the backing buffer has a capacity of `0`. 119 | 120 | * @type {?DataView} 121 | 122 | * @expose 123 | 124 | */ 125 | 126 | view 127 | 128 | /** 129 | 130 | * Absolute read/write offset. 131 | 132 | * @type {number} 133 | 134 | * @expose 135 | 136 | * @see ByteBuffer#flip 137 | 138 | * @see ByteBuffer#clear 139 | 140 | */ 141 | 142 | offset 143 | 144 | /** 145 | 146 | * Marked offset. 147 | 148 | * @type {number} 149 | 150 | * @expose 151 | 152 | * @see ByteBuffer#mark 153 | 154 | * @see ByteBuffer#reset 155 | 156 | */ 157 | 158 | markedOffset 159 | 160 | /** 161 | 162 | * Absolute limit of the contained data. Set to the backing buffer's capacity upon allocation. 163 | 164 | * @type {number} 165 | 166 | * @expose 167 | 168 | * @see ByteBuffer#flip 169 | 170 | * @see ByteBuffer#clear 171 | 172 | */ 173 | 174 | limit 175 | 176 | /** 177 | 178 | * Whether to use little endian byte order, defaults to `false` for big endian. 179 | 180 | * @type {boolean} 181 | 182 | * @expose 183 | 184 | */ 185 | 186 | littleEndian 187 | 188 | /** 189 | 190 | * Whether to skip assertions of offsets and values, defaults to `false`. 191 | 192 | * @type {boolean} 193 | 194 | * @expose 195 | 196 | */ 197 | 198 | noAssert 199 | 200 | /** 201 | 202 | * Constructs a new ByteBuffer. 203 | 204 | * @class The swiss army knife for binary data in JavaScript. 205 | 206 | * @exports ByteBuffer 207 | 208 | * @constructor 209 | 210 | * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}. 211 | 212 | * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to 213 | 214 | * {@link ByteBuffer.DEFAULT_ENDIAN}. 215 | 216 | * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to 217 | 218 | * {@link ByteBuffer.DEFAULT_NOASSERT}. 219 | 220 | * @expose 221 | 222 | */ 223 | 224 | constructor (capacity, littleEndian, noAssert) { 225 | if (typeof capacity === 'undefined') { 226 | capacity = ByteBuffer.DEFAULT_CAPACITY 227 | } 228 | 229 | if (typeof littleEndian === 'undefined') { 230 | littleEndian = ByteBuffer.DEFAULT_ENDIAN 231 | } 232 | 233 | if (typeof noAssert === 'undefined') { 234 | noAssert = ByteBuffer.DEFAULT_NOASSERT 235 | } 236 | 237 | if (!noAssert) { 238 | capacity = capacity | 0 239 | 240 | if (capacity < 0) { 241 | throw RangeError('Illegal capacity') 242 | } 243 | 244 | littleEndian = !!littleEndian 245 | 246 | noAssert = !!noAssert 247 | } 248 | 249 | this.buffer = capacity === 0 ? EMPTY_BUFFER : new ArrayBuffer(capacity) 250 | 251 | this.view = capacity === 0 ? new DataView(EMPTY_BUFFER) : new DataView(this.buffer) 252 | 253 | this.offset = 0 254 | 255 | this.markedOffset = -1 256 | 257 | this.limit = capacity 258 | 259 | this.littleEndian = littleEndian 260 | 261 | this.noAssert = noAssert 262 | } 263 | 264 | /** 265 | 266 | * Gets the accessor type. 267 | 268 | * @returns {Function} `Buffer` under node.js, `Uint8Array` respectively `DataView` in the browser (classes) 269 | 270 | * @expose 271 | 272 | */ 273 | 274 | static accessor = function () { 275 | return DataView 276 | } 277 | 278 | /** 279 | 280 | * Allocates a new ByteBuffer backed by a buffer of the specified capacity. 281 | 282 | * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}. 283 | 284 | * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to 285 | 286 | * {@link ByteBuffer.DEFAULT_ENDIAN}. 287 | 288 | * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to 289 | 290 | * {@link ByteBuffer.DEFAULT_NOASSERT}. 291 | 292 | * @returns {!ByteBuffer} 293 | 294 | * @expose 295 | 296 | */ 297 | 298 | static allocate = function (capacity, littleEndian, noAssert) { 299 | return new ByteBuffer(capacity, littleEndian, noAssert) 300 | } 301 | 302 | /** 303 | 304 | * Concatenates multiple ByteBuffers into one. 305 | 306 | * @param {!Array.} buffers Buffers to concatenate 307 | 308 | * @param {(string|boolean)=} encoding String encoding if `buffers` contains a string ("base64", "hex", "binary", 309 | 310 | * defaults to "utf8") 311 | 312 | * @param {boolean=} littleEndian Whether to use little or big endian byte order for the resulting ByteBuffer. Defaults 313 | 314 | * to {@link ByteBuffer.DEFAULT_ENDIAN}. 315 | 316 | * @param {boolean=} noAssert Whether to skip assertions of offsets and values for the resulting ByteBuffer. Defaults to 317 | 318 | * {@link ByteBuffer.DEFAULT_NOASSERT}. 319 | 320 | * @returns {!ByteBuffer} Concatenated ByteBuffer 321 | 322 | * @expose 323 | 324 | */ 325 | 326 | static concat = function (buffers, littleEndian, noAssert) { 327 | let capacity = 0 328 | 329 | const k = buffers.length 330 | 331 | let length 332 | 333 | for (let i2 = 0, length2; i2 < k; ++i2) { 334 | const buf = buffers[i2] 335 | 336 | if (!(buf instanceof ByteBuffer)) { 337 | buffers[i2] = ByteBuffer.wrap(buf) 338 | } 339 | 340 | length2 = buffers[i2].limit - buffers[i2].offset 341 | 342 | if (length2 > 0) { 343 | capacity += length2 344 | } 345 | } 346 | 347 | if (capacity === 0) { 348 | return new ByteBuffer(0, littleEndian, noAssert) 349 | } 350 | 351 | const bb = new ByteBuffer(capacity, littleEndian, noAssert) 352 | 353 | let bi 354 | 355 | const view = new Uint8Array(bb.buffer) 356 | 357 | let i = 0 358 | 359 | while (i < k) { 360 | bi = buffers[i++] 361 | 362 | length = bi.limit - bi.offset 363 | 364 | if (length <= 0) { 365 | continue 366 | } 367 | 368 | view.set(new Uint8Array(bi.buffer).subarray(bi.offset, bi.limit), bb.offset) 369 | 370 | bb.offset += length 371 | } 372 | 373 | bb.limit = bb.offset 374 | 375 | bb.offset = 0 376 | 377 | return bb 378 | } 379 | 380 | /** 381 | 382 | * Gets the backing buffer type. 383 | 384 | * @returns {Function} `Buffer` under node.js, `ArrayBuffer` in the browser (classes) 385 | 386 | * @expose 387 | 388 | */ 389 | 390 | static type = function () { 391 | return ArrayBuffer 392 | } 393 | 394 | /** 395 | 396 | * Wraps a buffer or a string. Sets the allocated ByteBuffer's {@link ByteBuffer#offset} to `0` and its 397 | 398 | * {@link ByteBuffer#limit} to the length of the wrapped data. 399 | 400 | * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string|!Array.} buffer Anything that can be wrapped 401 | 402 | * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to 403 | 404 | * {@link ByteBuffer.DEFAULT_ENDIAN}. 405 | 406 | * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to 407 | 408 | * {@link ByteBuffer.DEFAULT_NOASSERT}. 409 | 410 | * @returns {!ByteBuffer} A ByteBuffer wrapping `buffer` 411 | 412 | * @expose 413 | 414 | */ 415 | 416 | static wrap = function (buffer, littleEndian, noAssert) { 417 | if (buffer === null || typeof buffer !== 'object') { 418 | throw TypeError('Illegal buffer') 419 | } 420 | 421 | let bb 422 | 423 | if (buffer instanceof ByteBuffer) { 424 | bb = buffer.clone() 425 | 426 | bb.markedOffset = -1 427 | 428 | return bb 429 | } 430 | 431 | if (buffer instanceof Uint8Array) { 432 | bb = new ByteBuffer(0, littleEndian, noAssert) 433 | 434 | if (buffer.length > 0) { 435 | bb.buffer = buffer.buffer 436 | 437 | bb.offset = buffer.byteOffset 438 | 439 | bb.limit = buffer.byteOffset + buffer.byteLength 440 | 441 | bb.view = new DataView(buffer.buffer) 442 | } 443 | } else if (buffer instanceof ArrayBuffer) { 444 | bb = new ByteBuffer(0, littleEndian, noAssert) 445 | 446 | if (buffer.byteLength > 0) { 447 | bb.buffer = buffer 448 | 449 | bb.offset = 0 450 | 451 | bb.limit = buffer.byteLength 452 | 453 | bb.view = buffer.byteLength > 0 ? new DataView(buffer) : new DataView(EMPTY_BUFFER) 454 | } 455 | } else if (Object.prototype.toString.call(buffer) === '[object Array]') { 456 | bb = new ByteBuffer(buffer.length, littleEndian, noAssert) 457 | 458 | bb.limit = buffer.length 459 | 460 | for (let i = 0; i < buffer.length; ++i) { 461 | bb.view.setUint8(i, buffer[i]) 462 | } 463 | } else { 464 | throw TypeError('Illegal buffer') 465 | } 466 | 467 | return bb 468 | } 469 | 470 | /** 471 | 472 | * Reads the specified number of bytes. 473 | 474 | * @param {number} length Number of bytes to read 475 | 476 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `length` if omitted. 477 | 478 | * @returns {!ByteBuffer} 479 | 480 | * @expose 481 | 482 | */ 483 | 484 | readBytes (length, offset) { 485 | const relative = typeof offset === 'undefined' 486 | 487 | if (relative) { 488 | offset = this.offset 489 | } 490 | 491 | if (!this.noAssert) { 492 | if (typeof offset !== 'number' || offset % 1 !== 0) { 493 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 494 | } 495 | 496 | offset >>>= 0 497 | 498 | if (offset < 0 || offset + length > this.buffer.byteLength) { 499 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+' + length + ') <= ' + this.buffer.byteLength) 500 | } 501 | } 502 | 503 | const slice = this.slice(offset, offset + length) 504 | 505 | if (relative) { 506 | this.offset += length 507 | } 508 | 509 | return slice 510 | } 511 | 512 | /** 513 | 514 | * Writes a payload of bytes. This is an alias of {@link ByteBuffer#append}. 515 | 516 | * @function 517 | 518 | * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string} source Data to write. If `source` is a ByteBuffer, its offsets 519 | 520 | * will be modified according to the performed read operation. 521 | 522 | * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8") 523 | 524 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes 525 | 526 | * written if omitted. 527 | 528 | * @returns {!ByteBuffer} this 529 | 530 | * @expose 531 | 532 | */ 533 | 534 | writeBytes = this.append 535 | 536 | // types/ints/int8 537 | 538 | /** 539 | 540 | * Writes an 8bit signed integer. 541 | 542 | * @param {number} value Value to write 543 | 544 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 545 | 546 | * @returns {!ByteBuffer} this 547 | 548 | * @expose 549 | 550 | */ 551 | 552 | writeInt8 (value, offset) { 553 | const relative = typeof offset === 'undefined' 554 | 555 | if (relative) { 556 | offset = this.offset 557 | } 558 | 559 | if (!this.noAssert) { 560 | if (typeof value !== 'number' || value % 1 !== 0) { 561 | throw TypeError('Illegal value: ' + value + ' (not an integer)') 562 | } 563 | 564 | value |= 0 565 | 566 | if (typeof offset !== 'number' || offset % 1 !== 0) { 567 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 568 | } 569 | 570 | offset >>>= 0 571 | 572 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 573 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 574 | } 575 | } 576 | 577 | offset += 1 578 | 579 | let capacity0 = this.buffer.byteLength 580 | 581 | if (offset > capacity0) { 582 | this.resize((capacity0 *= 2) > offset ? capacity0 : offset) 583 | } 584 | 585 | offset -= 1 586 | 587 | this.view.setInt8(offset, value) 588 | 589 | if (relative) { 590 | this.offset += 1 591 | } 592 | 593 | return this 594 | } 595 | 596 | /** 597 | 598 | * Writes an 8bit signed integer. This is an alias of {@link ByteBuffer#writeInt8}. 599 | 600 | * @function 601 | 602 | * @param {number} value Value to write 603 | 604 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 605 | 606 | * @returns {!ByteBuffer} this 607 | 608 | * @expose 609 | 610 | */ 611 | 612 | writeByte = this.writeInt8 613 | 614 | /** 615 | 616 | * Reads an 8bit signed integer. 617 | 618 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 619 | 620 | * @returns {number} Value read 621 | 622 | * @expose 623 | 624 | */ 625 | 626 | readInt8 (offset) { 627 | const relative = typeof offset === 'undefined' 628 | 629 | if (relative) { 630 | offset = this.offset 631 | } 632 | 633 | if (!this.noAssert) { 634 | if (typeof offset !== 'number' || offset % 1 !== 0) { 635 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 636 | } 637 | 638 | offset >>>= 0 639 | 640 | if (offset < 0 || offset + 1 > this.buffer.byteLength) { 641 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+1) <= ' + this.buffer.byteLength) 642 | } 643 | } 644 | 645 | const value = this.view.getInt8(offset) 646 | 647 | if (relative) { 648 | this.offset += 1 649 | } 650 | 651 | return value 652 | } 653 | 654 | /** 655 | 656 | * Reads an 8bit signed integer. This is an alias of {@link ByteBuffer#readInt8}. 657 | 658 | * @function 659 | 660 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 661 | 662 | * @returns {number} Value read 663 | 664 | * @expose 665 | 666 | */ 667 | 668 | readByte = this.readInt8 669 | 670 | /** 671 | 672 | * Writes an 8bit unsigned integer. 673 | 674 | * @param {number} value Value to write 675 | 676 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 677 | 678 | * @returns {!ByteBuffer} this 679 | 680 | * @expose 681 | 682 | */ 683 | 684 | writeUint8 (value, offset) { 685 | const relative = typeof offset === 'undefined' 686 | 687 | if (relative) { 688 | offset = this.offset 689 | } 690 | 691 | if (!this.noAssert) { 692 | if (typeof value !== 'number' || value % 1 !== 0) { 693 | throw TypeError('Illegal value: ' + value + ' (not an integer)') 694 | } 695 | 696 | value >>>= 0 697 | 698 | if (typeof offset !== 'number' || offset % 1 !== 0) { 699 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 700 | } 701 | 702 | offset >>>= 0 703 | 704 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 705 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 706 | } 707 | } 708 | 709 | offset += 1 710 | 711 | let capacity1 = this.buffer.byteLength 712 | 713 | if (offset > capacity1) { 714 | this.resize((capacity1 *= 2) > offset ? capacity1 : offset) 715 | } 716 | 717 | offset -= 1 718 | 719 | this.view.setUint8(offset, value) 720 | 721 | if (relative) { 722 | this.offset += 1 723 | } 724 | 725 | return this 726 | } 727 | 728 | /** 729 | 730 | * Writes an 8bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint8}. 731 | 732 | * @function 733 | 734 | * @param {number} value Value to write 735 | 736 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 737 | 738 | * @returns {!ByteBuffer} this 739 | 740 | * @expose 741 | 742 | */ 743 | 744 | writeUInt8 = this.writeUint8 745 | 746 | /** 747 | 748 | * Reads an 8bit unsigned integer. 749 | 750 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 751 | 752 | * @returns {number} Value read 753 | 754 | * @expose 755 | 756 | */ 757 | 758 | readUint8 (offset) { 759 | const relative = typeof offset === 'undefined' 760 | 761 | if (relative) { 762 | offset = this.offset 763 | } 764 | 765 | if (!this.noAssert) { 766 | if (typeof offset !== 'number' || offset % 1 !== 0) { 767 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 768 | } 769 | 770 | offset >>>= 0 771 | 772 | if (offset < 0 || offset + 1 > this.buffer.byteLength) { 773 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+1) <= ' + this.buffer.byteLength) 774 | } 775 | } 776 | 777 | const value = this.view.getUint8(offset) 778 | 779 | if (relative) { 780 | this.offset += 1 781 | } 782 | 783 | return value 784 | } 785 | 786 | /** 787 | 788 | * Reads an 8bit unsigned integer. This is an alias of {@link ByteBuffer#readUint8}. 789 | 790 | * @function 791 | 792 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. 793 | 794 | * @returns {number} Value read 795 | 796 | * @expose 797 | 798 | */ 799 | 800 | readUInt8 = this.readUint8 801 | 802 | // types/ints/int16 803 | 804 | /** 805 | 806 | * Writes a 16bit signed integer. 807 | 808 | * @param {number} value Value to write 809 | 810 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 811 | 812 | * @throws {TypeError} If `offset` or `value` is not a valid number 813 | 814 | * @throws {RangeError} If `offset` is out of bounds 815 | 816 | * @expose 817 | 818 | */ 819 | 820 | writeInt16 (value, offset) { 821 | const relative = typeof offset === 'undefined' 822 | 823 | if (relative) { 824 | offset = this.offset 825 | } 826 | 827 | if (!this.noAssert) { 828 | if (typeof value !== 'number' || value % 1 !== 0) { 829 | throw TypeError('Illegal value: ' + value + ' (not an integer)') 830 | } 831 | 832 | value |= 0 833 | 834 | if (typeof offset !== 'number' || offset % 1 !== 0) { 835 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 836 | } 837 | 838 | offset >>>= 0 839 | 840 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 841 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 842 | } 843 | } 844 | 845 | offset += 2 846 | 847 | let capacity2 = this.buffer.byteLength 848 | 849 | if (offset > capacity2) { 850 | this.resize((capacity2 *= 2) > offset ? capacity2 : offset) 851 | } 852 | 853 | offset -= 2 854 | 855 | this.view.setInt16(offset, value, this.littleEndian) 856 | 857 | if (relative) { 858 | this.offset += 2 859 | } 860 | 861 | return this 862 | } 863 | 864 | /** 865 | 866 | * Writes a 16bit signed integer. This is an alias of {@link ByteBuffer#writeInt16}. 867 | 868 | * @function 869 | 870 | * @param {number} value Value to write 871 | 872 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 873 | 874 | * @throws {TypeError} If `offset` or `value` is not a valid number 875 | 876 | * @throws {RangeError} If `offset` is out of bounds 877 | 878 | * @expose 879 | 880 | */ 881 | 882 | writeShort = this.writeInt16 883 | 884 | /** 885 | 886 | * Reads a 16bit signed integer. 887 | 888 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 889 | 890 | * @returns {number} Value read 891 | 892 | * @throws {TypeError} If `offset` is not a valid number 893 | 894 | * @throws {RangeError} If `offset` is out of bounds 895 | 896 | * @expose 897 | 898 | */ 899 | 900 | readInt16 (offset) { 901 | const relative = typeof offset === 'undefined' 902 | 903 | if (typeof offset === 'undefined') { 904 | offset = this.offset 905 | } 906 | 907 | if (!this.noAssert) { 908 | if (typeof offset !== 'number' || offset % 1 !== 0) { 909 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 910 | } 911 | 912 | offset >>>= 0 913 | 914 | if (offset < 0 || offset + 2 > this.buffer.byteLength) { 915 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+2) <= ' + this.buffer.byteLength) 916 | } 917 | } 918 | 919 | const value = this.view.getInt16(offset, this.littleEndian) 920 | 921 | if (relative) { 922 | this.offset += 2 923 | } 924 | 925 | return value 926 | } 927 | 928 | /** 929 | 930 | * Reads a 16bit signed integer. This is an alias of {@link ByteBuffer#readInt16}. 931 | 932 | * @function 933 | 934 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 935 | 936 | * @returns {number} Value read 937 | 938 | * @throws {TypeError} If `offset` is not a valid number 939 | 940 | * @throws {RangeError} If `offset` is out of bounds 941 | 942 | * @expose 943 | 944 | */ 945 | 946 | readShort = this.readInt16 947 | 948 | /** 949 | 950 | * Writes a 16bit unsigned integer. 951 | 952 | * @param {number} value Value to write 953 | 954 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 955 | 956 | * @throws {TypeError} If `offset` or `value` is not a valid number 957 | 958 | * @throws {RangeError} If `offset` is out of bounds 959 | 960 | * @expose 961 | 962 | */ 963 | 964 | writeUint16 (value, offset) { 965 | const relative = typeof offset === 'undefined' 966 | 967 | if (relative) { 968 | offset = this.offset 969 | } 970 | 971 | if (!this.noAssert) { 972 | if (typeof value !== 'number' || value % 1 !== 0) { 973 | throw TypeError('Illegal value: ' + value + ' (not an integer)') 974 | } 975 | 976 | value >>>= 0 977 | 978 | if (typeof offset !== 'number' || offset % 1 !== 0) { 979 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 980 | } 981 | 982 | offset >>>= 0 983 | 984 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 985 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 986 | } 987 | } 988 | 989 | offset += 2 990 | 991 | let capacity3 = this.buffer.byteLength 992 | 993 | if (offset > capacity3) { 994 | this.resize((capacity3 *= 2) > offset ? capacity3 : offset) 995 | } 996 | 997 | offset -= 2 998 | 999 | this.view.setUint16(offset, value, this.littleEndian) 1000 | 1001 | if (relative) { 1002 | this.offset += 2 1003 | } 1004 | 1005 | return this 1006 | } 1007 | 1008 | /** 1009 | 1010 | * Writes a 16bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint16}. 1011 | 1012 | * @function 1013 | 1014 | * @param {number} value Value to write 1015 | 1016 | * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 1017 | 1018 | * @throws {TypeError} If `offset` or `value` is not a valid number 1019 | 1020 | * @throws {RangeError} If `offset` is out of bounds 1021 | 1022 | * @expose 1023 | 1024 | */ 1025 | 1026 | writeUInt16 = this.writeUint16 1027 | 1028 | /** 1029 | 1030 | * Reads a 16bit unsigned integer. 1031 | 1032 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 1033 | 1034 | * @returns {number} Value read 1035 | 1036 | * @throws {TypeError} If `offset` is not a valid number 1037 | 1038 | * @throws {RangeError} If `offset` is out of bounds 1039 | 1040 | * @expose 1041 | 1042 | */ 1043 | 1044 | readUint16 (offset) { 1045 | const relative = typeof offset === 'undefined' 1046 | 1047 | if (relative) { 1048 | offset = this.offset 1049 | } 1050 | 1051 | if (!this.noAssert) { 1052 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1053 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1054 | } 1055 | 1056 | offset >>>= 0 1057 | 1058 | if (offset < 0 || offset + 2 > this.buffer.byteLength) { 1059 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+2) <= ' + this.buffer.byteLength) 1060 | } 1061 | } 1062 | 1063 | const value = this.view.getUint16(offset, this.littleEndian) 1064 | 1065 | if (relative) { 1066 | this.offset += 2 1067 | } 1068 | 1069 | return value 1070 | } 1071 | 1072 | /** 1073 | 1074 | * Reads a 16bit unsigned integer. This is an alias of {@link ByteBuffer#readUint16}. 1075 | 1076 | * @function 1077 | 1078 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. 1079 | 1080 | * @returns {number} Value read 1081 | 1082 | * @throws {TypeError} If `offset` is not a valid number 1083 | 1084 | * @throws {RangeError} If `offset` is out of bounds 1085 | 1086 | * @expose 1087 | 1088 | */ 1089 | 1090 | readUInt16 = this.readUint16 1091 | 1092 | // types/ints/int32 1093 | 1094 | /** 1095 | 1096 | * Writes a 32bit signed integer. 1097 | 1098 | * @param {number} value Value to write 1099 | 1100 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1101 | 1102 | * @expose 1103 | 1104 | */ 1105 | 1106 | writeInt32 (value, offset) { 1107 | const relative = typeof offset === 'undefined' 1108 | 1109 | if (relative) { 1110 | offset = this.offset 1111 | } 1112 | 1113 | if (!this.noAssert) { 1114 | if (typeof value !== 'number' || value % 1 !== 0) { 1115 | throw TypeError('Illegal value: ' + value + ' (not an integer)') 1116 | } 1117 | 1118 | value |= 0 1119 | 1120 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1121 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1122 | } 1123 | 1124 | offset >>>= 0 1125 | 1126 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 1127 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 1128 | } 1129 | } 1130 | 1131 | offset += 4 1132 | 1133 | let capacity4 = this.buffer.byteLength 1134 | 1135 | if (offset > capacity4) { 1136 | this.resize((capacity4 *= 2) > offset ? capacity4 : offset) 1137 | } 1138 | 1139 | offset -= 4 1140 | 1141 | this.view.setInt32(offset, value, this.littleEndian) 1142 | 1143 | if (relative) { 1144 | this.offset += 4 1145 | } 1146 | 1147 | return this 1148 | } 1149 | 1150 | /** 1151 | 1152 | * Writes a 32bit signed integer. This is an alias of {@link ByteBuffer#writeInt32}. 1153 | 1154 | * @param {number} value Value to write 1155 | 1156 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1157 | 1158 | * @expose 1159 | 1160 | */ 1161 | 1162 | writeInt = this.writeInt32 1163 | 1164 | /** 1165 | 1166 | * Reads a 32bit signed integer. 1167 | 1168 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1169 | 1170 | * @returns {number} Value read 1171 | 1172 | * @expose 1173 | 1174 | */ 1175 | 1176 | readInt32 (offset) { 1177 | const relative = typeof offset === 'undefined' 1178 | 1179 | if (relative) { 1180 | offset = this.offset 1181 | } 1182 | 1183 | if (!this.noAssert) { 1184 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1185 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1186 | } 1187 | 1188 | offset >>>= 0 1189 | 1190 | if (offset < 0 || offset + 4 > this.buffer.byteLength) { 1191 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+4) <= ' + this.buffer.byteLength) 1192 | } 1193 | } 1194 | 1195 | const value = this.view.getInt32(offset, this.littleEndian) 1196 | 1197 | if (relative) { 1198 | this.offset += 4 1199 | } 1200 | 1201 | return value 1202 | } 1203 | 1204 | /** 1205 | 1206 | * Reads a 32bit signed integer. This is an alias of {@link ByteBuffer#readInt32}. 1207 | 1208 | * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `4` if omitted. 1209 | 1210 | * @returns {number} Value read 1211 | 1212 | * @expose 1213 | 1214 | */ 1215 | 1216 | readInt = this.readInt32 1217 | 1218 | /** 1219 | 1220 | * Writes a 32bit unsigned integer. 1221 | 1222 | * @param {number} value Value to write 1223 | 1224 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1225 | 1226 | * @expose 1227 | 1228 | */ 1229 | 1230 | writeUint32 (value, offset) { 1231 | const relative = typeof offset === 'undefined' 1232 | 1233 | if (relative) { 1234 | offset = this.offset 1235 | } 1236 | 1237 | if (!this.noAssert) { 1238 | if (typeof value !== 'number' || value % 1 !== 0) { 1239 | throw TypeError('Illegal value: ' + value + ' (not an integer)') 1240 | } 1241 | 1242 | value >>>= 0 1243 | 1244 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1245 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1246 | } 1247 | 1248 | offset >>>= 0 1249 | 1250 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 1251 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 1252 | } 1253 | } 1254 | 1255 | offset += 4 1256 | 1257 | let capacity5 = this.buffer.byteLength 1258 | 1259 | if (offset > capacity5) { 1260 | this.resize((capacity5 *= 2) > offset ? capacity5 : offset) 1261 | } 1262 | 1263 | offset -= 4 1264 | 1265 | this.view.setUint32(offset, value, this.littleEndian) 1266 | 1267 | if (relative) { 1268 | this.offset += 4 1269 | } 1270 | 1271 | return this 1272 | } 1273 | 1274 | /** 1275 | 1276 | * Writes a 32bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint32}. 1277 | 1278 | * @function 1279 | 1280 | * @param {number} value Value to write 1281 | 1282 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1283 | 1284 | * @expose 1285 | 1286 | */ 1287 | 1288 | writeUInt32 = this.writeUint32 1289 | 1290 | /** 1291 | 1292 | * Reads a 32bit unsigned integer. 1293 | 1294 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1295 | 1296 | * @returns {number} Value read 1297 | 1298 | * @expose 1299 | 1300 | */ 1301 | 1302 | readUint32 (offset) { 1303 | const relative = typeof offset === 'undefined' 1304 | 1305 | if (relative) { 1306 | offset = this.offset 1307 | } 1308 | 1309 | if (!this.noAssert) { 1310 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1311 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1312 | } 1313 | 1314 | offset >>>= 0 1315 | 1316 | if (offset < 0 || offset + 4 > this.buffer.byteLength) { 1317 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+4) <= ' + this.buffer.byteLength) 1318 | } 1319 | } 1320 | 1321 | const value = this.view.getUint32(offset, this.littleEndian) 1322 | 1323 | if (relative) { 1324 | this.offset += 4 1325 | } 1326 | 1327 | return value 1328 | } 1329 | 1330 | /** 1331 | 1332 | * Reads a 32bit unsigned integer. This is an alias of {@link ByteBuffer#readUint32}. 1333 | 1334 | * @function 1335 | 1336 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1337 | 1338 | * @returns {number} Value read 1339 | 1340 | * @expose 1341 | 1342 | */ 1343 | 1344 | readUInt32 = this.readUint32 1345 | 1346 | /** 1347 | 1348 | * Writes a 32bit float. 1349 | 1350 | * @param {number} value Value to write 1351 | 1352 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1353 | 1354 | * @returns {!ByteBuffer} this 1355 | 1356 | * @expose 1357 | 1358 | */ 1359 | 1360 | writeFloat32 (value, offset) { 1361 | const relative = typeof offset === 'undefined' 1362 | 1363 | if (relative) { 1364 | offset = this.offset 1365 | } 1366 | 1367 | if (!this.noAssert) { 1368 | if (typeof value !== 'number') { 1369 | throw TypeError('Illegal value: ' + value + ' (not a number)') 1370 | } 1371 | 1372 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1373 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1374 | } 1375 | 1376 | offset >>>= 0 1377 | 1378 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 1379 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 1380 | } 1381 | } 1382 | 1383 | offset += 4 1384 | 1385 | let capacity8 = this.buffer.byteLength 1386 | 1387 | if (offset > capacity8) { 1388 | this.resize((capacity8 *= 2) > offset ? capacity8 : offset) 1389 | } 1390 | 1391 | offset -= 4 1392 | 1393 | this.view.setFloat32(offset, value, this.littleEndian) 1394 | 1395 | if (relative) { 1396 | this.offset += 4 1397 | } 1398 | 1399 | return this 1400 | } 1401 | 1402 | /** 1403 | 1404 | * Writes a 32bit float. This is an alias of {@link ByteBuffer#writeFloat32}. 1405 | 1406 | * @function 1407 | 1408 | * @param {number} value Value to write 1409 | 1410 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1411 | 1412 | * @returns {!ByteBuffer} this 1413 | 1414 | * @expose 1415 | 1416 | */ 1417 | 1418 | writeFloat = this.writeFloat32 1419 | 1420 | /** 1421 | 1422 | * Reads a 32bit float. 1423 | 1424 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1425 | 1426 | * @returns {number} 1427 | 1428 | * @expose 1429 | 1430 | */ 1431 | 1432 | readFloat32 (offset) { 1433 | const relative = typeof offset === 'undefined' 1434 | 1435 | if (relative) { 1436 | offset = this.offset 1437 | } 1438 | 1439 | if (!this.noAssert) { 1440 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1441 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1442 | } 1443 | 1444 | offset >>>= 0 1445 | 1446 | if (offset < 0 || offset + 4 > this.buffer.byteLength) { 1447 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+4) <= ' + this.buffer.byteLength) 1448 | } 1449 | } 1450 | 1451 | const value = this.view.getFloat32(offset, this.littleEndian) 1452 | 1453 | if (relative) { 1454 | this.offset += 4 1455 | } 1456 | 1457 | return value 1458 | } 1459 | 1460 | /** 1461 | 1462 | * Reads a 32bit float. This is an alias of {@link ByteBuffer#readFloat32}. 1463 | 1464 | * @function 1465 | 1466 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. 1467 | 1468 | * @returns {number} 1469 | 1470 | * @expose 1471 | 1472 | */ 1473 | 1474 | readFloat = this.readFloat32 1475 | 1476 | // types/floats/float64 1477 | 1478 | /** 1479 | 1480 | * Writes a 64bit float. 1481 | 1482 | * @param {number} value Value to write 1483 | 1484 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 1485 | 1486 | * @returns {!ByteBuffer} this 1487 | 1488 | * @expose 1489 | 1490 | */ 1491 | 1492 | writeFloat64 (value, offset) { 1493 | const relative = typeof offset === 'undefined' 1494 | 1495 | if (relative) { 1496 | offset = this.offset 1497 | } 1498 | 1499 | if (!this.noAssert) { 1500 | if (typeof value !== 'number') { 1501 | throw TypeError('Illegal value: ' + value + ' (not a number)') 1502 | } 1503 | 1504 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1505 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1506 | } 1507 | 1508 | offset >>>= 0 1509 | 1510 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 1511 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 1512 | } 1513 | } 1514 | 1515 | offset += 8 1516 | 1517 | let capacity9 = this.buffer.byteLength 1518 | 1519 | if (offset > capacity9) { 1520 | this.resize((capacity9 *= 2) > offset ? capacity9 : offset) 1521 | } 1522 | 1523 | offset -= 8 1524 | 1525 | this.view.setFloat64(offset, value, this.littleEndian) 1526 | 1527 | if (relative) { 1528 | this.offset += 8 1529 | } 1530 | 1531 | return this 1532 | } 1533 | 1534 | /** 1535 | 1536 | * Writes a 64bit float. This is an alias of {@link ByteBuffer#writeFloat64}. 1537 | 1538 | * @function 1539 | 1540 | * @param {number} value Value to write 1541 | 1542 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 1543 | 1544 | * @returns {!ByteBuffer} this 1545 | 1546 | * @expose 1547 | 1548 | */ 1549 | 1550 | writeDouble = this.writeFloat64 1551 | 1552 | /** 1553 | 1554 | * Reads a 64bit float. 1555 | 1556 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 1557 | 1558 | * @returns {number} 1559 | 1560 | * @expose 1561 | 1562 | */ 1563 | 1564 | readFloat64 (offset) { 1565 | const relative = typeof offset === 'undefined' 1566 | 1567 | if (relative) { 1568 | offset = this.offset 1569 | } 1570 | 1571 | if (!this.noAssert) { 1572 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1573 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1574 | } 1575 | 1576 | offset >>>= 0 1577 | 1578 | if (offset < 0 || offset + 8 > this.buffer.byteLength) { 1579 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+8) <= ' + this.buffer.byteLength) 1580 | } 1581 | } 1582 | 1583 | const value = this.view.getFloat64(offset, this.littleEndian) 1584 | 1585 | if (relative) { 1586 | this.offset += 8 1587 | } 1588 | 1589 | return value 1590 | } 1591 | 1592 | /** 1593 | 1594 | * Reads a 64bit float. This is an alias of {@link ByteBuffer#readFloat64}. 1595 | 1596 | * @function 1597 | 1598 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 1599 | 1600 | * @returns {number} 1601 | 1602 | * @expose 1603 | 1604 | */ 1605 | 1606 | readDouble = this.readFloat64 1607 | 1608 | /** 1609 | 1610 | * Appends some data to this ByteBuffer. This will overwrite any contents behind the specified offset up to the appended 1611 | 1612 | * data's length. 1613 | 1614 | * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array} source Data to append. If `source` is a ByteBuffer, its offsets 1615 | 1616 | * will be modified according to the performed read operation. 1617 | 1618 | * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8") 1619 | 1620 | * @param {number=} offset Offset to append at. Will use and increase {@link ByteBuffer#offset} by the number of bytes 1621 | 1622 | * written if omitted. 1623 | 1624 | * @returns {!ByteBuffer} this 1625 | 1626 | * @expose 1627 | 1628 | * @example A relative `<01 02>03.append(<04 05>)` will result in `<01 02 04 05>, 04 05|` 1629 | 1630 | * @example An absolute `<01 02>03.append(04 05>, 1)` will result in `<01 04>05, 04 05|` 1631 | 1632 | */ 1633 | 1634 | append (source, offset) { 1635 | const relative = typeof offset === 'undefined' 1636 | 1637 | if (relative) { 1638 | offset = this.offset 1639 | } 1640 | 1641 | if (!this.noAssert) { 1642 | if (typeof offset !== 'number' || offset % 1 !== 0) { 1643 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 1644 | } 1645 | 1646 | offset >>>= 0 1647 | 1648 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 1649 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 1650 | } 1651 | } 1652 | 1653 | if (!(source instanceof ByteBuffer)) { 1654 | source = ByteBuffer.wrap(source) 1655 | } 1656 | 1657 | const length = source.limit - source.offset 1658 | 1659 | if (length <= 0) { 1660 | return this 1661 | } 1662 | 1663 | offset += length 1664 | 1665 | let capacity16 = this.buffer.byteLength 1666 | 1667 | if (offset > capacity16) { 1668 | this.resize((capacity16 *= 2) > offset ? capacity16 : offset) 1669 | } 1670 | 1671 | offset -= length 1672 | 1673 | new Uint8Array(this.buffer, offset).set(new Uint8Array(source.buffer).subarray(source.offset, source.limit)) 1674 | 1675 | source.offset += length 1676 | 1677 | if (relative) { 1678 | this.offset += length 1679 | } 1680 | 1681 | return this 1682 | } 1683 | 1684 | /** 1685 | 1686 | * Appends this ByteBuffer's contents to another ByteBuffer. This will overwrite any contents at and after the 1687 | 1688 | specified offset up to the length of this ByteBuffer's data. 1689 | 1690 | * @param {!ByteBuffer} target Target ByteBuffer 1691 | 1692 | * @param {number=} offset Offset to append to. Will use and increase {@link ByteBuffer#offset} by the number of bytes 1693 | 1694 | * read if omitted. 1695 | 1696 | * @returns {!ByteBuffer} this 1697 | 1698 | * @expose 1699 | 1700 | * @see ByteBuffer#append 1701 | 1702 | */ 1703 | 1704 | appendTo (target, offset) { 1705 | target.append(this, offset) 1706 | 1707 | return this 1708 | } 1709 | 1710 | /** 1711 | 1712 | * Enables or disables assertions of argument types and offsets. Assertions are enabled by default but you can opt to 1713 | 1714 | * disable them if your code already makes sure that everything is valid. 1715 | 1716 | * @param {boolean} assert `true` to enable assertions, otherwise `false` 1717 | 1718 | * @returns {!ByteBuffer} this 1719 | 1720 | * @expose 1721 | 1722 | */ 1723 | 1724 | assert (assert) { 1725 | this.noAssert = !assert 1726 | 1727 | return this 1728 | } 1729 | 1730 | /** 1731 | 1732 | * Gets the capacity of this ByteBuffer's backing buffer. 1733 | 1734 | * @returns {number} Capacity of the backing buffer 1735 | 1736 | * @expose 1737 | 1738 | */ 1739 | 1740 | capacity () { 1741 | return this.buffer.byteLength 1742 | } 1743 | 1744 | /** 1745 | 1746 | * Clears this ByteBuffer's offsets by setting {@link ByteBuffer#offset} to `0` and {@link ByteBuffer#limit} to the 1747 | 1748 | * backing buffer's capacity. Discards {@link ByteBuffer#markedOffset}. 1749 | 1750 | * @returns {!ByteBuffer} this 1751 | 1752 | * @expose 1753 | 1754 | */ 1755 | 1756 | clear () { 1757 | this.offset = 0 1758 | 1759 | this.limit = this.buffer.byteLength 1760 | 1761 | this.markedOffset = -1 1762 | 1763 | return this 1764 | } 1765 | 1766 | /** 1767 | 1768 | * Creates a cloned instance of this ByteBuffer, preset with this ByteBuffer's values for {@link ByteBuffer#offset}, 1769 | 1770 | * {@link ByteBuffer#markedOffset} and {@link ByteBuffer#limit}. 1771 | 1772 | * @param {boolean=} copy Whether to copy the backing buffer or to return another view on the same, defaults to `false` 1773 | 1774 | * @returns {!ByteBuffer} Cloned instance 1775 | 1776 | * @expose 1777 | 1778 | */ 1779 | 1780 | clone (copy) { 1781 | const bb = new ByteBuffer(0, this.littleEndian, this.noAssert) 1782 | 1783 | if (copy) { 1784 | bb.buffer = new ArrayBuffer(this.buffer.byteLength) 1785 | 1786 | new Uint8Array(bb.buffer).set(this.buffer) 1787 | 1788 | bb.view = new DataView(bb.buffer) 1789 | } else { 1790 | bb.buffer = this.buffer 1791 | 1792 | bb.view = this.view 1793 | } 1794 | 1795 | bb.offset = this.offset 1796 | 1797 | bb.markedOffset = this.markedOffset 1798 | 1799 | bb.limit = this.limit 1800 | 1801 | return bb 1802 | } 1803 | 1804 | /** 1805 | 1806 | * Compacts this ByteBuffer to be backed by a {@link ByteBuffer#buffer} of its contents' length. Contents are the bytes 1807 | 1808 | * between {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. Will set `offset = 0` and `limit = capacity` and 1809 | 1810 | * adapt {@link ByteBuffer#markedOffset} to the same relative position if set. 1811 | 1812 | * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset} 1813 | 1814 | * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit} 1815 | 1816 | * @returns {!ByteBuffer} this 1817 | 1818 | * @expose 1819 | 1820 | */ 1821 | 1822 | compact (begin, end) { 1823 | if (typeof begin === 'undefined') { 1824 | begin = this.offset 1825 | } 1826 | 1827 | if (typeof end === 'undefined') { 1828 | end = this.limit 1829 | } 1830 | 1831 | if (!this.noAssert) { 1832 | if (typeof begin !== 'number' || begin % 1 !== 0) { 1833 | throw TypeError('Illegal begin: Not an integer') 1834 | } 1835 | 1836 | begin >>>= 0 1837 | 1838 | if (typeof end !== 'number' || end % 1 !== 0) { 1839 | throw TypeError('Illegal end: Not an integer') 1840 | } 1841 | 1842 | end >>>= 0 1843 | 1844 | if (begin < 0 || begin > end || end > this.buffer.byteLength) { 1845 | throw RangeError('Illegal range: 0 <= ' + begin + ' <= ' + end + ' <= ' + this.buffer.byteLength) 1846 | } 1847 | } 1848 | 1849 | if (begin === 0 && end === this.buffer.byteLength) { 1850 | return this 1851 | } 1852 | 1853 | const len = end - begin 1854 | 1855 | if (len === 0) { 1856 | this.buffer = EMPTY_BUFFER 1857 | 1858 | this.view = new DataView(EMPTY_BUFFER) 1859 | 1860 | if (this.markedOffset >= 0) { 1861 | this.markedOffset -= begin 1862 | } 1863 | 1864 | this.offset = 0 1865 | 1866 | this.limit = 0 1867 | 1868 | return this 1869 | } 1870 | 1871 | const buffer = new ArrayBuffer(len) 1872 | 1873 | new Uint8Array(buffer).set(new Uint8Array(this.buffer).subarray(begin, end)) 1874 | 1875 | this.buffer = buffer 1876 | 1877 | this.view = new DataView(buffer) 1878 | 1879 | if (this.markedOffset >= 0) { 1880 | this.markedOffset -= begin 1881 | } 1882 | 1883 | this.offset = 0 1884 | 1885 | this.limit = len 1886 | 1887 | return this 1888 | } 1889 | 1890 | /** 1891 | 1892 | * Creates a copy of this ByteBuffer's contents. Contents are the bytes between {@link ByteBuffer#offset} and 1893 | 1894 | * {@link ByteBuffer#limit}. 1895 | 1896 | * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}. 1897 | 1898 | * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}. 1899 | 1900 | * @returns {!ByteBuffer} Copy 1901 | 1902 | * @expose 1903 | 1904 | */ 1905 | 1906 | copy (begin, end) { 1907 | if (typeof begin === 'undefined') { 1908 | begin = this.offset 1909 | } 1910 | 1911 | if (typeof end === 'undefined') { 1912 | end = this.limit 1913 | } 1914 | 1915 | if (!this.noAssert) { 1916 | if (typeof begin !== 'number' || begin % 1 !== 0) { 1917 | throw TypeError('Illegal begin: Not an integer') 1918 | } 1919 | 1920 | begin >>>= 0 1921 | 1922 | if (typeof end !== 'number' || end % 1 !== 0) { 1923 | throw TypeError('Illegal end: Not an integer') 1924 | } 1925 | 1926 | end >>>= 0 1927 | 1928 | if (begin < 0 || begin > end || end > this.buffer.byteLength) { 1929 | throw RangeError('Illegal range: 0 <= ' + begin + ' <= ' + end + ' <= ' + this.buffer.byteLength) 1930 | } 1931 | } 1932 | 1933 | if (begin === end) { 1934 | return new ByteBuffer(0, this.littleEndian, this.noAssert) 1935 | } 1936 | 1937 | const capacity = end - begin 1938 | 1939 | const bb = new ByteBuffer(capacity, this.littleEndian, this.noAssert) 1940 | 1941 | bb.offset = 0 1942 | 1943 | bb.limit = capacity 1944 | 1945 | if (bb.markedOffset >= 0) { 1946 | bb.markedOffset -= begin 1947 | } 1948 | 1949 | this.copyTo(bb, 0, begin, end) 1950 | 1951 | return bb 1952 | } 1953 | 1954 | /** 1955 | 1956 | * Copies this ByteBuffer's contents to another ByteBuffer. Contents are the bytes between {@link ByteBuffer#offset} and 1957 | 1958 | * {@link ByteBuffer#limit}. 1959 | 1960 | * @param {!ByteBuffer} target Target ByteBuffer 1961 | 1962 | * @param {number=} targetOffset Offset to copy to. Will use and increase the target's {@link ByteBuffer#offset} 1963 | 1964 | * by the number of bytes copied if omitted. 1965 | 1966 | * @param {number=} sourceOffset Offset to start copying from. Will use and increase {@link ByteBuffer#offset} by the 1967 | 1968 | * number of bytes copied if omitted. 1969 | 1970 | * @param {number=} sourceLimit Offset to end copying from, defaults to {@link ByteBuffer#limit} 1971 | 1972 | * @returns {!ByteBuffer} this 1973 | 1974 | * @expose 1975 | 1976 | */ 1977 | 1978 | copyTo (target, targetOffset, sourceOffset, sourceLimit) { 1979 | const targetRelative = typeof targetOffset === 'undefined' 1980 | const relative = typeof sourceOffset === 'undefined' 1981 | 1982 | if (!this.noAssert) { 1983 | if (!(target instanceof ByteBuffer)) { 1984 | throw TypeError('Illegal target: Not a ByteBuffer') 1985 | } 1986 | } 1987 | 1988 | targetOffset = (targetRelative) ? target.offset : targetOffset | 0 1989 | 1990 | sourceOffset = (relative) ? this.offset : sourceOffset | 0 1991 | 1992 | sourceLimit = typeof sourceLimit === 'undefined' ? this.limit : sourceLimit | 0 1993 | 1994 | if (targetOffset < 0 || targetOffset > target.buffer.byteLength) { 1995 | throw RangeError('Illegal target range: 0 <= ' + targetOffset + ' <= ' + target.buffer.byteLength) 1996 | } 1997 | 1998 | if (sourceOffset < 0 || sourceLimit > this.buffer.byteLength) { 1999 | throw RangeError('Illegal source range: 0 <= ' + sourceOffset + ' <= ' + this.buffer.byteLength) 2000 | } 2001 | 2002 | const len = sourceLimit - sourceOffset 2003 | 2004 | if (len === 0) { 2005 | return target 2006 | } 2007 | 2008 | target.ensureCapacity(targetOffset + len) 2009 | 2010 | new Uint8Array(target.buffer).set(new Uint8Array(this.buffer).subarray(sourceOffset, sourceLimit), targetOffset) 2011 | 2012 | if (relative) { 2013 | this.offset += len 2014 | } 2015 | 2016 | if (targetRelative) { 2017 | target.offset += len 2018 | } 2019 | 2020 | return this 2021 | } 2022 | 2023 | /** 2024 | 2025 | * Makes sure that this ByteBuffer is backed by a {@link ByteBuffer#buffer} of at least the specified capacity. If the 2026 | 2027 | * current capacity is exceeded, it will be doubled. If double the current capacity is less than the required capacity, 2028 | 2029 | * the required capacity will be used instead. 2030 | 2031 | * @param {number} capacity Required capacity 2032 | 2033 | * @returns {!ByteBuffer} this 2034 | 2035 | * @expose 2036 | 2037 | */ 2038 | 2039 | ensureCapacity (capacity) { 2040 | let current = this.buffer.byteLength 2041 | 2042 | if (current < capacity) { 2043 | return this.resize((current *= 2) > capacity ? current : capacity) 2044 | } 2045 | 2046 | return this 2047 | } 2048 | 2049 | /** 2050 | 2051 | * Overwrites this ByteBuffer's contents with the specified value. Contents are the bytes between 2052 | 2053 | * {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. 2054 | 2055 | * @param {number|string} value Byte value to fill with. If given as a string, the first character is used. 2056 | 2057 | * @param {number=} begin Begin offset. Will use and increase {@link ByteBuffer#offset} by the number of bytes 2058 | 2059 | * written if omitted. defaults to {@link ByteBuffer#offset}. 2060 | 2061 | * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}. 2062 | 2063 | * @returns {!ByteBuffer} this 2064 | 2065 | * @expose 2066 | 2067 | * @example `someByteBuffer.clear().fill(0)` fills the entire backing buffer with zeroes 2068 | 2069 | */ 2070 | 2071 | fill (value, begin, end) { 2072 | const relative = typeof begin === 'undefined' 2073 | 2074 | if (relative) { 2075 | begin = this.offset 2076 | } 2077 | 2078 | if (typeof value === 'string' && value.length > 0) { 2079 | value = value.charCodeAt(0) 2080 | } 2081 | 2082 | if (typeof begin === 'undefined') { 2083 | begin = this.offset 2084 | } 2085 | 2086 | if (typeof end === 'undefined') { 2087 | end = this.limit 2088 | } 2089 | 2090 | if (!this.noAssert) { 2091 | if (typeof value !== 'number' || value % 1 !== 0) { 2092 | throw TypeError('Illegal value: ' + value + ' (not an integer)') 2093 | } 2094 | 2095 | value |= 0 2096 | 2097 | if (typeof begin !== 'number' || begin % 1 !== 0) { 2098 | throw TypeError('Illegal begin: Not an integer') 2099 | } 2100 | 2101 | begin >>>= 0 2102 | 2103 | if (typeof end !== 'number' || end % 1 !== 0) { 2104 | throw TypeError('Illegal end: Not an integer') 2105 | } 2106 | 2107 | end >>>= 0 2108 | 2109 | if (begin < 0 || begin > end || end > this.buffer.byteLength) { 2110 | throw RangeError('Illegal range: 0 <= ' + begin + ' <= ' + end + ' <= ' + this.buffer.byteLength) 2111 | } 2112 | } 2113 | 2114 | if (begin >= end) { 2115 | return this 2116 | } 2117 | 2118 | while (begin < end) { 2119 | this.view.setUint8(begin++, value) 2120 | } 2121 | 2122 | if (relative) { 2123 | this.offset = begin 2124 | } 2125 | 2126 | return this 2127 | } 2128 | 2129 | /** 2130 | 2131 | * Makes this ByteBuffer ready for a new sequence of write or relative read operations. Sets `limit = offset` and 2132 | 2133 | * `offset = 0`. Make sure always to flip a ByteBuffer when all relative read or write operations are complete. 2134 | 2135 | * @returns {!ByteBuffer} this 2136 | 2137 | * @expose 2138 | 2139 | */ 2140 | 2141 | flip () { 2142 | this.limit = this.offset 2143 | 2144 | this.offset = 0 2145 | 2146 | return this 2147 | } 2148 | 2149 | /** 2150 | 2151 | * Marks an offset on this ByteBuffer to be used later. 2152 | 2153 | * @param {number=} offset Offset to mark. Defaults to {@link ByteBuffer#offset}. 2154 | 2155 | * @returns {!ByteBuffer} this 2156 | 2157 | * @throws {TypeError} If `offset` is not a valid number 2158 | 2159 | * @throws {RangeError} If `offset` is out of bounds 2160 | 2161 | * @see ByteBuffer#reset 2162 | 2163 | * @expose 2164 | 2165 | */ 2166 | 2167 | mark (offset) { 2168 | offset = typeof offset === 'undefined' ? this.offset : offset 2169 | 2170 | if (!this.noAssert) { 2171 | if (typeof offset !== 'number' || offset % 1 !== 0) { 2172 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 2173 | } 2174 | 2175 | offset >>>= 0 2176 | 2177 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 2178 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 2179 | } 2180 | } 2181 | 2182 | this.markedOffset = offset 2183 | 2184 | return this 2185 | } 2186 | 2187 | /** 2188 | 2189 | * Sets the byte order. 2190 | 2191 | * @param {boolean} littleEndian `true` for little endian byte order, `false` for big endian 2192 | 2193 | * @returns {!ByteBuffer} this 2194 | 2195 | * @expose 2196 | 2197 | */ 2198 | 2199 | order (littleEndian) { 2200 | if (!this.noAssert) { 2201 | if (typeof littleEndian !== 'boolean') { 2202 | throw TypeError('Illegal littleEndian: Not a boolean') 2203 | } 2204 | } 2205 | 2206 | this.littleEndian = !!littleEndian 2207 | 2208 | return this 2209 | } 2210 | 2211 | /** 2212 | 2213 | * Switches (to) little endian byte order. 2214 | 2215 | * @param {boolean=} littleEndian Defaults to `true`, otherwise uses big endian 2216 | 2217 | * @returns {!ByteBuffer} this 2218 | 2219 | * @expose 2220 | 2221 | */ 2222 | 2223 | LE (littleEndian) { 2224 | this.littleEndian = typeof littleEndian !== 'undefined' ? !!littleEndian : true 2225 | 2226 | return this 2227 | } 2228 | 2229 | /** 2230 | 2231 | * Switches (to) big endian byte order. 2232 | 2233 | * @param {boolean=} bigEndian Defaults to `true`, otherwise uses little endian 2234 | 2235 | * @returns {!ByteBuffer} this 2236 | 2237 | * @expose 2238 | 2239 | */ 2240 | 2241 | BE (bigEndian) { 2242 | this.littleEndian = typeof bigEndian !== 'undefined' ? !bigEndian : false 2243 | 2244 | return this 2245 | } 2246 | 2247 | /** 2248 | 2249 | * Prepends some data to this ByteBuffer. This will overwrite any contents before the specified offset up to the 2250 | 2251 | * prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer 2252 | 2253 | * will be resized and its contents moved accordingly. 2254 | 2255 | * @param {!ByteBuffer|!ArrayBuffer} source Data to prepend. If `source` is a ByteBuffer, its offset will be 2256 | 2257 | * modified according to the performed read operation. 2258 | 2259 | * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8") 2260 | 2261 | * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes 2262 | 2263 | * prepended if omitted. 2264 | 2265 | * @returns {!ByteBuffer} this 2266 | 2267 | * @expose 2268 | 2269 | * @example A relative `00<01 02 03>.prepend(<04 05>)` results in `<04 05 01 02 03>, 04 05|` 2270 | 2271 | * @example An absolute `00<01 02 03>.prepend(<04 05>, 2)` results in `04<05 02 03>, 04 05|` 2272 | 2273 | */ 2274 | 2275 | prepend (source, offset) { 2276 | const relative = typeof offset === 'undefined' 2277 | 2278 | if (relative) { 2279 | offset = this.offset 2280 | } 2281 | 2282 | if (!this.noAssert) { 2283 | if (typeof offset !== 'number' || offset % 1 !== 0) { 2284 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 2285 | } 2286 | 2287 | offset >>>= 0 2288 | 2289 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 2290 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 2291 | } 2292 | } 2293 | 2294 | if (!(source instanceof ByteBuffer)) { 2295 | source = ByteBuffer.wrap(source) 2296 | } 2297 | 2298 | const len = source.limit - source.offset 2299 | 2300 | if (len <= 0) { 2301 | return this 2302 | } 2303 | 2304 | const diff = len - offset 2305 | 2306 | if (diff > 0) { 2307 | const buffer = new ArrayBuffer(this.buffer.byteLength + diff) 2308 | 2309 | const arrayView = new Uint8Array(buffer) 2310 | 2311 | arrayView.set(new Uint8Array(this.buffer).subarray(offset, this.buffer.byteLength), len) 2312 | 2313 | this.buffer = buffer 2314 | 2315 | this.view = new DataView(buffer) 2316 | 2317 | this.offset += diff 2318 | 2319 | if (this.markedOffset >= 0) { 2320 | this.markedOffset += diff 2321 | } 2322 | 2323 | this.limit += diff 2324 | 2325 | offset += diff 2326 | } else { 2327 | const arrayView = new Uint8Array(this.buffer) 2328 | 2329 | arrayView.set(new Uint8Array(source.buffer).subarray(source.offset, source.limit), offset - len) 2330 | } 2331 | 2332 | source.offset = source.limit 2333 | 2334 | if (relative) { 2335 | this.offset -= len 2336 | } 2337 | 2338 | return this 2339 | } 2340 | 2341 | /** 2342 | 2343 | * Prepends this ByteBuffer to another ByteBuffer. This will overwrite any contents before the specified offset up to the 2344 | 2345 | * prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer 2346 | 2347 | * will be resized and its contents moved accordingly. 2348 | 2349 | * @param {!ByteBuffer} target Target ByteBuffer 2350 | 2351 | * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes 2352 | 2353 | * prepended if omitted. 2354 | 2355 | * @returns {!ByteBuffer} this 2356 | 2357 | * @expose 2358 | 2359 | * @see ByteBuffer#prepend 2360 | 2361 | */ 2362 | 2363 | prependTo (target, offset) { 2364 | target.prepend(this, offset) 2365 | 2366 | return this 2367 | } 2368 | 2369 | /** 2370 | 2371 | * Gets the number of remaining readable bytes. Contents are the bytes between {@link ByteBuffer#offset} and 2372 | 2373 | * {@link ByteBuffer#limit}, so this returns `limit - offset`. 2374 | 2375 | * @returns {number} Remaining readable bytes. May be negative if `offset > limit`. 2376 | 2377 | * @expose 2378 | 2379 | */ 2380 | 2381 | remaining () { 2382 | return this.limit - this.offset 2383 | } 2384 | 2385 | /** 2386 | 2387 | * Resets this ByteBuffer's {@link ByteBuffer#offset}. If an offset has been marked through {@link ByteBuffer#mark} 2388 | 2389 | * before, `offset` will be set to {@link ByteBuffer#markedOffset}, which will then be discarded. If no offset has been 2390 | 2391 | * marked, sets `offset = 0`. 2392 | 2393 | * @returns {!ByteBuffer} this 2394 | 2395 | * @see ByteBuffer#mark 2396 | 2397 | * @expose 2398 | 2399 | */ 2400 | 2401 | reset () { 2402 | if (this.markedOffset >= 0) { 2403 | this.offset = this.markedOffset 2404 | 2405 | this.markedOffset = -1 2406 | } else { 2407 | this.offset = 0 2408 | } 2409 | 2410 | return this 2411 | } 2412 | 2413 | /** 2414 | 2415 | * Resizes this ByteBuffer to be backed by a buffer of at least the given capacity. Will do nothing if already that 2416 | 2417 | * large or larger. 2418 | 2419 | * @param {number} capacity Capacity required 2420 | 2421 | * @returns {!ByteBuffer} this 2422 | 2423 | * @throws {TypeError} If `capacity` is not a number 2424 | 2425 | * @throws {RangeError} If `capacity < 0` 2426 | 2427 | * @expose 2428 | 2429 | */ 2430 | 2431 | resize (capacity) { 2432 | if (!this.noAssert) { 2433 | if (typeof capacity !== 'number' || capacity % 1 !== 0) { 2434 | throw TypeError('Illegal capacity: ' + capacity + ' (not an integer)') 2435 | } 2436 | 2437 | capacity |= 0 2438 | 2439 | if (capacity < 0) { 2440 | throw RangeError('Illegal capacity: 0 <= ' + capacity) 2441 | } 2442 | } 2443 | 2444 | if (this.buffer.byteLength < capacity) { 2445 | const buffer = new ArrayBuffer(capacity) 2446 | 2447 | new Uint8Array(buffer).set(new Uint8Array(this.buffer)) 2448 | 2449 | this.buffer = buffer 2450 | 2451 | this.view = new DataView(buffer) 2452 | } 2453 | 2454 | return this 2455 | } 2456 | 2457 | /** 2458 | 2459 | * Reverses this ByteBuffer's contents. 2460 | 2461 | * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset} 2462 | 2463 | * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit} 2464 | 2465 | * @returns {!ByteBuffer} this 2466 | 2467 | * @expose 2468 | 2469 | */ 2470 | 2471 | reverse (begin, end) { 2472 | if (typeof begin === 'undefined') { 2473 | begin = this.offset 2474 | } 2475 | 2476 | if (typeof end === 'undefined') { 2477 | end = this.limit 2478 | } 2479 | 2480 | if (!this.noAssert) { 2481 | if (typeof begin !== 'number' || begin % 1 !== 0) { 2482 | throw TypeError('Illegal begin: Not an integer') 2483 | } 2484 | 2485 | begin >>>= 0 2486 | 2487 | if (typeof end !== 'number' || end % 1 !== 0) { 2488 | throw TypeError('Illegal end: Not an integer') 2489 | } 2490 | 2491 | end >>>= 0 2492 | 2493 | if (begin < 0 || begin > end || end > this.buffer.byteLength) { 2494 | throw RangeError('Illegal range: 0 <= ' + begin + ' <= ' + end + ' <= ' + this.buffer.byteLength) 2495 | } 2496 | } 2497 | 2498 | if (begin === end) { 2499 | return this 2500 | } 2501 | 2502 | Array.prototype.reverse.call(new Uint8Array(this.buffer).subarray(begin, end)) 2503 | 2504 | this.view = new DataView(this.buffer) 2505 | 2506 | return this 2507 | } 2508 | 2509 | /** 2510 | 2511 | * Skips the next `length` bytes. This will just advance 2512 | 2513 | * @param {number} length Number of bytes to skip. May also be negative to move the offset back. 2514 | 2515 | * @returns {!ByteBuffer} this 2516 | 2517 | * @expose 2518 | 2519 | */ 2520 | 2521 | skip (length) { 2522 | if (!this.noAssert) { 2523 | if (typeof length !== 'number' || length % 1 !== 0) { 2524 | throw TypeError('Illegal length: ' + length + ' (not an integer)') 2525 | } 2526 | 2527 | length |= 0 2528 | } 2529 | 2530 | const offset = this.offset + length 2531 | 2532 | if (!this.noAssert) { 2533 | if (offset < 0 || offset > this.buffer.byteLength) { 2534 | throw RangeError('Illegal length: 0 <= ' + this.offset + ' + ' + length + ' <= ' + this.buffer.byteLength) 2535 | } 2536 | } 2537 | 2538 | this.offset = offset 2539 | 2540 | return this 2541 | } 2542 | 2543 | /** 2544 | 2545 | * Slices this ByteBuffer by creating a cloned instance with `offset = begin` and `limit = end`. 2546 | 2547 | * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}. 2548 | 2549 | * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}. 2550 | 2551 | * @returns {!ByteBuffer} Clone of this ByteBuffer with slicing applied, backed by the same {@link ByteBuffer#buffer} 2552 | 2553 | * @expose 2554 | 2555 | */ 2556 | 2557 | slice (begin, end) { 2558 | if (typeof begin === 'undefined') { 2559 | begin = this.offset 2560 | } 2561 | 2562 | if (typeof end === 'undefined') { 2563 | end = this.limit 2564 | } 2565 | 2566 | if (!this.noAssert) { 2567 | if (typeof begin !== 'number' || begin % 1 !== 0) { 2568 | throw TypeError('Illegal begin: Not an integer') 2569 | } 2570 | 2571 | begin >>>= 0 2572 | 2573 | if (typeof end !== 'number' || end % 1 !== 0) { 2574 | throw TypeError('Illegal end: Not an integer') 2575 | } 2576 | 2577 | end >>>= 0 2578 | 2579 | if (begin < 0 || begin > end || end > this.buffer.byteLength) { 2580 | throw RangeError('Illegal range: 0 <= ' + begin + ' <= ' + end + ' <= ' + this.buffer.byteLength) 2581 | } 2582 | } 2583 | 2584 | const bb = this.clone() 2585 | 2586 | bb.offset = begin 2587 | 2588 | bb.limit = end 2589 | 2590 | return bb 2591 | } 2592 | 2593 | /** 2594 | 2595 | * Writes a 64bit signed integer. 2596 | 2597 | * @param {number|bigint} value Value to write 2598 | 2599 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2600 | 2601 | * @returns {!ByteBuffer} this 2602 | 2603 | * @expose 2604 | 2605 | */ 2606 | 2607 | writeInt64 (value, offset) { 2608 | const relative = typeof offset === 'undefined' 2609 | 2610 | if (typeof offset === 'undefined') { 2611 | offset = this.offset 2612 | } 2613 | 2614 | if (!this.noAssert) { 2615 | if (typeof value === 'number') { 2616 | value = BigInt(value) 2617 | } 2618 | 2619 | if (typeof offset !== 'number' || offset % 1 !== 0) { 2620 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 2621 | } 2622 | 2623 | offset >>>= 0 2624 | 2625 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 2626 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 2627 | } 2628 | } 2629 | 2630 | if (typeof value === 'number') { 2631 | value = BigInt(value) 2632 | } 2633 | 2634 | offset += 8 2635 | 2636 | let capacity6 = this.buffer.byteLength 2637 | 2638 | if (offset > capacity6) { 2639 | this.resize((capacity6 *= 2) > offset ? capacity6 : offset) 2640 | } 2641 | 2642 | offset -= 8 2643 | 2644 | this.view.setBigInt64(offset, value, this.littleEndian) 2645 | 2646 | if (relative) { 2647 | this.offset += 8 2648 | } 2649 | 2650 | return this 2651 | } 2652 | 2653 | /** 2654 | 2655 | * Writes a 64bit signed integer. This is an alias of {@link ByteBuffer#writeInt64}. 2656 | 2657 | * @param {number|!bigint} value Value to write 2658 | 2659 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2660 | 2661 | * @returns {!ByteBuffer} this 2662 | 2663 | * @expose 2664 | 2665 | */ 2666 | 2667 | writeLong = this.writeInt64 2668 | 2669 | /** 2670 | 2671 | * Reads a 64bit signed integer. 2672 | 2673 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2674 | 2675 | * @returns {!bigint} 2676 | 2677 | * @expose 2678 | 2679 | */ 2680 | 2681 | readInt64 (offset) { 2682 | const relative = typeof offset === 'undefined' 2683 | 2684 | if (relative) { 2685 | offset = this.offset 2686 | } 2687 | 2688 | if (!this.noAssert) { 2689 | if (typeof offset !== 'number' || offset % 1 !== 0) { 2690 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 2691 | } 2692 | 2693 | offset >>>= 0 2694 | 2695 | if (offset < 0 || offset + 8 > this.buffer.byteLength) { 2696 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+8) <= ' + this.buffer.byteLength) 2697 | } 2698 | } 2699 | 2700 | const value = this.view.getBigInt64(offset, this.littleEndian) 2701 | 2702 | if (relative) { 2703 | this.offset += 8 2704 | } 2705 | 2706 | return value 2707 | } 2708 | 2709 | /** 2710 | 2711 | * Reads a 64bit signed integer. This is an alias of {@link ByteBuffer#readInt64}. 2712 | 2713 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2714 | 2715 | * @returns {!bigint} 2716 | 2717 | * @expose 2718 | 2719 | */ 2720 | 2721 | readLong = this.readInt64 2722 | 2723 | /** 2724 | 2725 | * Writes a 64bit unsigned integer. 2726 | 2727 | * @param {number|!bigint} value Value to write 2728 | 2729 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2730 | 2731 | * @returns {!ByteBuffer} this 2732 | 2733 | * @expose 2734 | 2735 | */ 2736 | 2737 | writeUint64 (value, offset) { 2738 | const relative = typeof offset === 'undefined' 2739 | 2740 | if (typeof offset === 'undefined') { 2741 | offset = this.offset 2742 | } 2743 | 2744 | if (!this.noAssert) { 2745 | if (typeof value === 'number') { 2746 | value = BigInt(value) 2747 | } 2748 | 2749 | if (typeof offset !== 'number' || offset % 1 !== 0) { 2750 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 2751 | } 2752 | 2753 | offset >>>= 0 2754 | 2755 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { 2756 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+0) <= ' + this.buffer.byteLength) 2757 | } 2758 | } 2759 | 2760 | if (typeof value === 'number') { 2761 | value = BigInt(value) 2762 | } 2763 | 2764 | offset += 8 2765 | 2766 | let capacity7 = this.buffer.byteLength 2767 | 2768 | if (offset > capacity7) { 2769 | this.resize((capacity7 *= 2) > offset ? capacity7 : offset) 2770 | } 2771 | 2772 | offset -= 8 2773 | 2774 | this.view.setBigUint64(offset, value, this.littleEndian) 2775 | 2776 | if (relative) { 2777 | this.offset += 8 2778 | } 2779 | 2780 | return this 2781 | } 2782 | 2783 | /** 2784 | 2785 | * Writes a 64bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint64}. 2786 | 2787 | * @function 2788 | 2789 | * @param {number|!bigint} value Value to write 2790 | 2791 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2792 | 2793 | * @returns {!ByteBuffer} this 2794 | 2795 | * @expose 2796 | 2797 | */ 2798 | 2799 | writeUInt64 = this.writeUint64 2800 | 2801 | /** 2802 | 2803 | * Reads a 64bit unsigned integer. 2804 | 2805 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2806 | 2807 | * @returns {!bigint} 2808 | 2809 | * @expose 2810 | 2811 | */ 2812 | 2813 | readUint64 (offset) { 2814 | const relative = typeof offset === 'undefined' 2815 | 2816 | if (relative) { 2817 | offset = this.offset 2818 | } 2819 | 2820 | if (!this.noAssert) { 2821 | if (typeof offset !== 'number' || offset % 1 !== 0) { 2822 | throw TypeError('Illegal offset: ' + offset + ' (not an integer)') 2823 | } 2824 | 2825 | offset >>>= 0 2826 | 2827 | if (offset < 0 || offset + 8 > this.buffer.byteLength) { 2828 | throw RangeError('Illegal offset: 0 <= ' + offset + ' (+8) <= ' + this.buffer.byteLength) 2829 | } 2830 | } 2831 | 2832 | const value = this.view.getBigUint64(offset, this.littleEndian) 2833 | 2834 | if (relative) { 2835 | this.offset += 8 2836 | } 2837 | 2838 | return value 2839 | } 2840 | 2841 | /** 2842 | 2843 | * Reads a 64bit unsigned integer. This is an alias of {@link ByteBuffer#readUint64}. 2844 | 2845 | * @function 2846 | 2847 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. 2848 | 2849 | * @returns {!Long} 2850 | 2851 | * @expose 2852 | 2853 | */ 2854 | 2855 | readUInt64 = this.readUint64 2856 | 2857 | /** 2858 | 2859 | * Returns a copy of the backing buffer that contains this ByteBuffer's contents. Contents are the bytes between 2860 | 2861 | * {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. 2862 | 2863 | * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory if 2864 | 2865 | * possible. Defaults to `false` 2866 | 2867 | * @returns {!ArrayBuffer} Contents as an ArrayBuffer 2868 | 2869 | * @expose 2870 | 2871 | */ 2872 | 2873 | toBuffer (forceCopy) { 2874 | let offset = this.offset 2875 | 2876 | let limit = this.limit 2877 | 2878 | if (!this.noAssert) { 2879 | if (typeof offset !== 'number' || offset % 1 !== 0) { 2880 | throw TypeError('Illegal offset: Not an integer') 2881 | } 2882 | 2883 | offset >>>= 0 2884 | 2885 | if (typeof limit !== 'number' || limit % 1 !== 0) { 2886 | throw TypeError('Illegal limit: Not an integer') 2887 | } 2888 | 2889 | limit >>>= 0 2890 | 2891 | if (offset < 0 || offset > limit || limit > this.buffer.byteLength) { 2892 | throw RangeError('Illegal range: 0 <= ' + offset + ' <= ' + limit + ' <= ' + this.buffer.byteLength) 2893 | } 2894 | } 2895 | 2896 | if (!forceCopy) { 2897 | if (offset === 0 && limit === this.buffer.byteLength) { 2898 | return this.buffer 2899 | } 2900 | 2901 | return this.buffer.slice(offset, limit) 2902 | } 2903 | 2904 | if (offset === limit) { 2905 | return EMPTY_BUFFER 2906 | } 2907 | 2908 | const buffer = new ArrayBuffer(limit - offset) 2909 | 2910 | new Uint8Array(buffer).set(new Uint8Array(this.buffer).subarray(offset, limit), 0) 2911 | 2912 | return buffer 2913 | } 2914 | 2915 | /** 2916 | 2917 | * Returns a raw buffer compacted to contain this ByteBuffer's contents. Contents are the bytes between 2918 | 2919 | * {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. This is an alias of {@link ByteBuffer#toBuffer}. 2920 | 2921 | * @function 2922 | 2923 | * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory. 2924 | 2925 | * Defaults to `false` 2926 | 2927 | * @returns {!ArrayBuffer} Contents as an ArrayBuffer 2928 | 2929 | * @expose 2930 | 2931 | */ 2932 | 2933 | toArrayBuffer = this.toBuffer 2934 | 2935 | writeVarint32 (value, offset) { 2936 | const relative = typeof offset === 'undefined' 2937 | if (relative) offset = this.offset 2938 | if (!this.noAssert) { 2939 | if (typeof value !== 'number' || value % 1 !== 0) { throw TypeError('Illegal value: ' + value + ' (not an integer)') } 2940 | value |= 0 2941 | if (typeof offset !== 'number' || offset % 1 !== 0) { throw TypeError('Illegal offset: ' + offset + ' (not an integer)') } 2942 | offset >>>= 0 2943 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { throw RangeError('Illegal offset: 0 <= ' + offset + ' (+' + 0 + ') <= ' + this.buffer.byteLength) } 2944 | } 2945 | const size = this.calculateVarint32(value) 2946 | let b 2947 | offset += size 2948 | let capacity10 = this.buffer.byteLength 2949 | if (offset > capacity10) { this.resize((capacity10 *= 2) > offset ? capacity10 : offset) } 2950 | offset -= size 2951 | value >>>= 0 2952 | while (value >= 0x80) { 2953 | b = (value & 0x7f) | 0x80 2954 | this.view.setUint8(offset++, b) 2955 | value >>>= 7 2956 | } 2957 | this.view.setUint8(offset++, value) 2958 | if (relative) { 2959 | this.offset = offset 2960 | return this 2961 | } 2962 | return size 2963 | } 2964 | 2965 | readVarint32 = function (offset) { 2966 | const relative = typeof offset === 'undefined' 2967 | if (relative) offset = this.offset 2968 | if (!this.noAssert) { 2969 | if (typeof offset !== 'number' || offset % 1 !== 0) { throw TypeError('Illegal offset: ' + offset + ' (not an integer)') } 2970 | offset >>>= 0 2971 | if (offset < 0 || offset + 1 > this.buffer.byteLength) { throw RangeError('Illegal offset: 0 <= ' + offset + ' (+' + 1 + ') <= ' + this.buffer.byteLength) } 2972 | } 2973 | let c = 0 2974 | let value = 0 >>> 0 2975 | let b 2976 | do { 2977 | if (!this.noAssert && offset > this.limit) { 2978 | const err = Error('Truncated') 2979 | err.truncated = true 2980 | throw err 2981 | } 2982 | b = this.view.getUint8(offset++) 2983 | if (c < 5) { value |= (b & 0x7f) << (7 * c) } 2984 | ++c 2985 | } while ((b & 0x80) !== 0) 2986 | value |= 0 2987 | if (relative) { 2988 | this.offset = offset 2989 | return value 2990 | } 2991 | return { 2992 | value, 2993 | length: c 2994 | } 2995 | } 2996 | 2997 | calculateVarint32 (value) { 2998 | // ref: src/google/protobuf/io/coded_stream.cc 2999 | value = value >>> 0 3000 | if (value < 1 << 7) return 1 3001 | else if (value < 1 << 14) return 2 3002 | else if (value < 1 << 21) return 3 3003 | else if (value < 1 << 28) return 4 3004 | else return 5 3005 | } 3006 | 3007 | writeVString (str, offset) { 3008 | const relative = typeof offset === 'undefined' 3009 | if (relative) offset = this.offset 3010 | if (!this.noAssert) { 3011 | if (typeof str !== 'string') { throw TypeError('Illegal str: Not a string') } 3012 | if (typeof offset !== 'number' || offset % 1 !== 0) { throw TypeError('Illegal offset: ' + offset + ' (not an integer)') } 3013 | offset >>>= 0 3014 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { throw RangeError('Illegal offset: 0 <= ' + offset + ' (+' + 0 + ') <= ' + this.buffer.byteLength) } 3015 | } 3016 | const start = offset 3017 | const k = utfx.calculateUTF16asUTF8(stringSource(str), this.noAssert)[1] 3018 | const l = this.calculateVarint32(k) 3019 | offset += l + k 3020 | let capacity15 = this.buffer.byteLength 3021 | if (offset > capacity15) { this.resize((capacity15 *= 2) > offset ? capacity15 : offset) } 3022 | offset -= l + k 3023 | offset += this.writeVarint32(k, offset) 3024 | utfx.encodeUTF16toUTF8(stringSource(str), function (b) { 3025 | this.view.setUint8(offset++, b) 3026 | }.bind(this)) 3027 | if (offset !== start + k + l) { throw RangeError('Illegal range: Truncated data, ' + offset + ' == ' + (offset + k + l)) } 3028 | if (relative) { 3029 | this.offset = offset 3030 | return this 3031 | } 3032 | return offset - start 3033 | } 3034 | 3035 | readVString (offset) { 3036 | const relative = typeof offset === 'undefined' 3037 | if (relative) offset = this.offset 3038 | if (!this.noAssert) { 3039 | if (typeof offset !== 'number' || offset % 1 !== 0) { throw TypeError('Illegal offset: ' + offset + ' (not an integer)') } 3040 | offset >>>= 0 3041 | if (offset < 0 || offset + 1 > this.buffer.byteLength) { throw RangeError('Illegal offset: 0 <= ' + offset + ' (+' + 1 + ') <= ' + this.buffer.byteLength) } 3042 | } 3043 | const start = offset 3044 | const len = this.readVarint32(offset) 3045 | const str = this.readUTF8String(len.value, ByteBuffer.METRICS_BYTES, offset += len.length) 3046 | offset += str.length 3047 | if (relative) { 3048 | this.offset = offset 3049 | return str.string 3050 | } else { 3051 | return { 3052 | string: str.string, 3053 | length: offset - start 3054 | } 3055 | } 3056 | } 3057 | 3058 | readUTF8String (length, metrics, offset) { 3059 | if (typeof metrics === 'number') { 3060 | offset = metrics 3061 | metrics = undefined 3062 | } 3063 | const relative = typeof offset === 'undefined' 3064 | if (relative) offset = this.offset 3065 | if (typeof metrics === 'undefined') metrics = ByteBuffer.METRICS_CHARS 3066 | if (!this.noAssert) { 3067 | if (typeof length !== 'number' || length % 1 !== 0) { throw TypeError('Illegal length: ' + length + ' (not an integer)') } 3068 | length |= 0 3069 | if (typeof offset !== 'number' || offset % 1 !== 0) { throw TypeError('Illegal offset: ' + offset + ' (not an integer)') } 3070 | offset >>>= 0 3071 | if (offset < 0 || offset + 0 > this.buffer.byteLength) { throw RangeError('Illegal offset: 0 <= ' + offset + ' (+' + 0 + ') <= ' + this.buffer.byteLength) } 3072 | } 3073 | let i = 0 3074 | const start = offset 3075 | let sd 3076 | if (metrics === ByteBuffer.METRICS_CHARS) { // The same for node and the browser 3077 | sd = stringDestination() 3078 | utfx.decodeUTF8(function () { 3079 | return i < length && offset < this.limit ? this.view.getUint8(offset++) : null 3080 | }.bind(this), function (cp) { 3081 | ++i; utfx.UTF8toUTF16(cp, sd) 3082 | }) 3083 | if (i !== length) { throw RangeError('Illegal range: Truncated data, ' + i + ' == ' + length) } 3084 | if (relative) { 3085 | this.offset = offset 3086 | return sd() 3087 | } else { 3088 | return { 3089 | string: sd(), 3090 | length: offset - start 3091 | } 3092 | } 3093 | } else if (metrics === ByteBuffer.METRICS_BYTES) { 3094 | if (!this.noAssert) { 3095 | if (typeof offset !== 'number' || offset % 1 !== 0) { throw TypeError('Illegal offset: ' + offset + ' (not an integer)') } 3096 | offset >>>= 0 3097 | if (offset < 0 || offset + length > this.buffer.byteLength) { throw RangeError('Illegal offset: 0 <= ' + offset + ' (+' + length + ') <= ' + this.buffer.byteLength) } 3098 | } 3099 | const k = offset + length 3100 | utfx.decodeUTF8toUTF16(function () { 3101 | return offset < k ? this.view.getUint8(offset++) : null 3102 | }.bind(this), sd = stringDestination(), this.noAssert) 3103 | if (offset !== k) { throw RangeError('Illegal range: Truncated data, ' + offset + ' == ' + k) } 3104 | if (relative) { 3105 | this.offset = offset 3106 | return sd() 3107 | } else { 3108 | return { 3109 | string: sd(), 3110 | length: offset - start 3111 | } 3112 | } 3113 | } else { throw TypeError('Unsupported metrics: ' + metrics) } 3114 | } 3115 | } 3116 | function stringDestination () { 3117 | const cs = []; const ps = []; return function () { 3118 | if (arguments.length === 0) { return ps.join('') + stringFromCharCode.apply(String, cs) } 3119 | if (cs.length + arguments.length > 1024) { 3120 | ps.push(stringFromCharCode.apply(String, cs)) 3121 | cs.length = 0 3122 | } 3123 | Array.prototype.push.apply(cs, arguments) 3124 | } 3125 | } 3126 | const stringFromCharCode = String.fromCharCode 3127 | 3128 | function stringSource (s) { 3129 | let i = 0; return function () { 3130 | return i < s.length ? s.charCodeAt(i++) : null 3131 | } 3132 | } 3133 | 3134 | const EMPTY_BUFFER = new ArrayBuffer(0) 3135 | 3136 | /** 3137 | * utfx namespace. 3138 | * @inner 3139 | * @type {!Object.} 3140 | */ 3141 | const utfx = {} 3142 | 3143 | /** 3144 | * Maximum valid code point. 3145 | * @type {number} 3146 | * @const 3147 | */ 3148 | utfx.MAX_CODEPOINT = 0x10FFFF 3149 | 3150 | /** 3151 | * Encodes UTF8 code points to UTF8 bytes. 3152 | * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point 3153 | * respectively `null` if there are no more code points left or a single numeric code point. 3154 | * @param {!function(number)} dst Bytes destination as a function successively called with the next byte 3155 | */ 3156 | utfx.encodeUTF8 = function (src, dst) { 3157 | let cp = null 3158 | if (typeof src === 'number') { 3159 | cp = src 3160 | src = function () { return null } 3161 | } 3162 | while (cp !== null || (cp = src()) !== null) { 3163 | if (cp < 0x80) { dst(cp & 0x7F) } else if (cp < 0x800) { 3164 | dst(((cp >> 6) & 0x1F) | 0xC0) 3165 | dst((cp & 0x3F) | 0x80) 3166 | } else if (cp < 0x10000) { 3167 | dst(((cp >> 12) & 0x0F) | 0xE0) 3168 | dst(((cp >> 6) & 0x3F) | 0x80) 3169 | dst((cp & 0x3F) | 0x80) 3170 | } else { 3171 | dst(((cp >> 18) & 0x07) | 0xF0) 3172 | dst(((cp >> 12) & 0x3F) | 0x80) 3173 | dst(((cp >> 6) & 0x3F) | 0x80) 3174 | dst((cp & 0x3F) | 0x80) 3175 | } 3176 | cp = null 3177 | } 3178 | } 3179 | 3180 | /** 3181 | * Decodes UTF8 bytes to UTF8 code points. 3182 | * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there 3183 | * are no more bytes left. 3184 | * @param {!function(number)} dst Code points destination as a function successively called with each decoded code point. 3185 | * @throws {RangeError} If a starting byte is invalid in UTF8 3186 | * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the 3187 | * remaining bytes. 3188 | */ 3189 | utfx.decodeUTF8 = function (src, dst) { 3190 | let a; let b; let c; let d; const fail = function (b) { 3191 | b = b.slice(0, b.indexOf(null)) 3192 | const err = Error(b.toString()) 3193 | err.name = 'TruncatedError' 3194 | err.bytes = b 3195 | throw err 3196 | } 3197 | while ((a = src()) !== null) { 3198 | if ((a & 0x80) === 0) { dst(a) } else if ((a & 0xE0) === 0xC0) { 3199 | ((b = src()) === null) && fail([a, b]) 3200 | dst(((a & 0x1F) << 6) | (b & 0x3F)) 3201 | } else if ((a & 0xF0) === 0xE0) { 3202 | ((b = src()) === null || (c = src()) === null) && fail([a, b, c]) 3203 | dst(((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)) 3204 | } else if ((a & 0xF8) === 0xF0) { 3205 | ((b = src()) === null || (c = src()) === null || (d = src()) === null) && fail([a, b, c, d]) 3206 | dst(((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F)) 3207 | } else throw RangeError('Illegal starting byte: ' + a) 3208 | } 3209 | } 3210 | 3211 | /** 3212 | * Converts UTF16 characters to UTF8 code points. 3213 | * @param {!function():number|null} src Characters source as a function returning the next char code respectively 3214 | * `null` if there are no more characters left. 3215 | * @param {!function(number)} dst Code points destination as a function successively called with each converted code 3216 | * point. 3217 | */ 3218 | utfx.UTF16toUTF8 = function (src, dst) { 3219 | let c1; let c2 = null 3220 | while (true) { 3221 | if ((c1 = c2 !== null ? c2 : src()) === null) { break } 3222 | if (c1 >= 0xD800 && c1 <= 0xDFFF) { 3223 | if ((c2 = src()) !== null) { 3224 | if (c2 >= 0xDC00 && c2 <= 0xDFFF) { 3225 | dst((c1 - 0xD800) * 0x400 + c2 - 0xDC00 + 0x10000) 3226 | c2 = null; continue 3227 | } 3228 | } 3229 | } 3230 | dst(c1) 3231 | } 3232 | if (c2 !== null) dst(c2) 3233 | } 3234 | 3235 | /** 3236 | * Converts UTF8 code points to UTF16 characters. 3237 | * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point 3238 | * respectively `null` if there are no more code points left or a single numeric code point. 3239 | * @param {!function(number)} dst Characters destination as a function successively called with each converted char code. 3240 | * @throws {RangeError} If a code point is out of range 3241 | */ 3242 | utfx.UTF8toUTF16 = function (src, dst) { 3243 | let cp = null 3244 | if (typeof src === 'number') { 3245 | cp = src 3246 | src = function () { return null } 3247 | } 3248 | while (cp !== null || (cp = src()) !== null) { 3249 | if (cp <= 0xFFFF) { dst(cp) } else { 3250 | cp -= 0x10000 3251 | dst((cp >> 10) + 0xD800) 3252 | dst((cp % 0x400) + 0xDC00) 3253 | } 3254 | cp = null 3255 | } 3256 | } 3257 | 3258 | /** 3259 | * Converts and encodes UTF16 characters to UTF8 bytes. 3260 | * @param {!function():number|null} src Characters source as a function returning the next char code respectively `null` 3261 | * if there are no more characters left. 3262 | * @param {!function(number)} dst Bytes destination as a function successively called with the next byte. 3263 | */ 3264 | utfx.encodeUTF16toUTF8 = function (src, dst) { 3265 | utfx.UTF16toUTF8(src, function (cp) { 3266 | utfx.encodeUTF8(cp, dst) 3267 | }) 3268 | } 3269 | 3270 | /** 3271 | * Decodes and converts UTF8 bytes to UTF16 characters. 3272 | * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there 3273 | * are no more bytes left. 3274 | * @param {!function(number)} dst Characters destination as a function successively called with each converted char code. 3275 | * @throws {RangeError} If a starting byte is invalid in UTF8 3276 | * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the remaining bytes. 3277 | */ 3278 | utfx.decodeUTF8toUTF16 = function (src, dst) { 3279 | utfx.decodeUTF8(src, function (cp) { 3280 | utfx.UTF8toUTF16(cp, dst) 3281 | }) 3282 | } 3283 | 3284 | /** 3285 | * Calculates the byte length of an UTF8 code point. 3286 | * @param {number} cp UTF8 code point 3287 | * @returns {number} Byte length 3288 | */ 3289 | utfx.calculateCodePoint = function (cp) { 3290 | return (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4 3291 | } 3292 | 3293 | /** 3294 | * Calculates the number of UTF8 bytes required to store UTF8 code points. 3295 | * @param {(!function():number|null)} src Code points source as a function returning the next code point respectively 3296 | * `null` if there are no more code points left. 3297 | * @returns {number} The number of UTF8 bytes required 3298 | */ 3299 | utfx.calculateUTF8 = function (src) { 3300 | let cp; let l = 0 3301 | while ((cp = src()) !== null) { l += (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4 } 3302 | return l 3303 | } 3304 | 3305 | /** 3306 | * Calculates the number of UTF8 code points respectively UTF8 bytes required to store UTF16 char codes. 3307 | * @param {(!function():number|null)} src Characters source as a function returning the next char code respectively 3308 | * `null` if there are no more characters left. 3309 | * @returns {!Array.} The number of UTF8 code points at index 0 and the number of UTF8 bytes required at index 1. 3310 | */ 3311 | utfx.calculateUTF16asUTF8 = function (src) { 3312 | let n = 0; let l = 0 3313 | utfx.UTF16toUTF8(src, function (cp) { 3314 | ++n; l += (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4 3315 | }) 3316 | return [n, l] 3317 | } 3318 | -------------------------------------------------------------------------------- /helpers/HexBuffer.js: -------------------------------------------------------------------------------- 1 | import { hexToUint8Array, uint8ArrayToHex } from './uint8Array.js' 2 | 3 | /** Buffer wrapper that serializes to a hex-encoded string. */ 4 | export class HexBuffer { 5 | /** Convenience to create a new HexBuffer, does not copy data if value passed is already a buffer. */ 6 | static from (value) { 7 | if (value instanceof HexBuffer) { 8 | return value 9 | } else if (value instanceof Uint8Array) { 10 | return new HexBuffer(value) 11 | } else if (typeof value === 'string') { 12 | return new HexBuffer(hexToUint8Array(value)) 13 | } else { 14 | return new HexBuffer(new Uint8Array(value)) 15 | } 16 | } 17 | 18 | constructor (buffer) { 19 | this.buffer = buffer 20 | } 21 | 22 | toString (encoding = 'hex') { 23 | return uint8ArrayToHex(this.buffer) 24 | } 25 | 26 | toJSON () { 27 | return this.toString() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /helpers/PrivateKey.d.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from './PublicKey' 2 | import { Signature } from './Signature' 3 | 4 | export type KeyRole = 'owner' | 'active' | 'posting' | 'memo' 5 | 6 | /** ECDSA (secp256k1) private key. */ 7 | export class PrivateKey { 8 | key: Uint8Array 9 | 10 | constructor(key?: Uint8Array) 11 | 12 | /** Derive the public key for this private key. */ 13 | createPublic(prefix?: string): PublicKey 14 | 15 | inspect(): string 16 | 17 | /** 18 | * Sign message. 19 | * @param message 32-byte message. 20 | */ 21 | sign(message: Uint8Array): Signature 22 | 23 | /** Return a WIF-encoded representation of the key. */ 24 | toString(): string 25 | 26 | /** Convenience to create a new instance from WIF string or Uint8Array */ 27 | static from(value: string | Uint8Array): PrivateKey 28 | 29 | /** Create key from username and password. */ 30 | static fromLogin( 31 | username: string, 32 | password: string, 33 | role?: KeyRole 34 | ): PrivateKey 35 | 36 | /** Create a new instance from a seed. */ 37 | static fromSeed(seed: string): PrivateKey 38 | 39 | /** Create a new instance from a WIF-encoded key. */ 40 | static fromString(wif: string): PrivateKey 41 | 42 | /** 43 | * Returns a randomly generated instance of PrivateKey 44 | * Might take up to 250ms 45 | */ 46 | static randomKey(): PrivateKey 47 | 48 | /** 49 | * Get shared secret for memo cryptography 50 | */ 51 | getSharedSecret (publicKey: PublicKey): Uint8Array 52 | } 53 | -------------------------------------------------------------------------------- /helpers/PrivateKey.js: -------------------------------------------------------------------------------- 1 | import bs58 from 'bs58' 2 | import { secp256k1 } from '@noble/curves/secp256k1' 3 | import { config } from '../config.js' 4 | import { sha256, sha512 } from './crypto.js' 5 | import { PublicKey } from './PublicKey.js' 6 | import { Signature } from './Signature.js' 7 | 8 | const NETWORK_ID = new Uint8Array([0x80]) 9 | const DEFAULT_ADDRESS_PREFIX = config.address_prefix 10 | 11 | /** ECDSA (secp256k1) private key. */ 12 | export class PrivateKey { 13 | constructor (key) { 14 | this.key = key 15 | try { 16 | secp256k1.getPublicKey(key) 17 | } catch (e) { 18 | throw new Error('invalid private key') 19 | } 20 | } 21 | 22 | /** Convenience to create a new instance from WIF string or Uint8Array */ 23 | static from (value) { 24 | if (typeof value === 'string') { 25 | return PrivateKey.fromString(value) 26 | } else { 27 | return new PrivateKey(value) 28 | } 29 | } 30 | 31 | /** Create a new instance from a WIF-encoded key. */ 32 | static fromString (wif) { 33 | return new PrivateKey(decodePrivate(wif).subarray(1)) 34 | } 35 | 36 | /** Create a new instance from a seed. */ 37 | static fromSeed (seed) { 38 | return new PrivateKey(sha256(seed)) 39 | } 40 | 41 | /** Create key from username and password. */ 42 | static fromLogin (username, password, role = 'active') { 43 | const seed = username + role + password 44 | return PrivateKey.fromSeed(seed) 45 | } 46 | 47 | /** 48 | * Sign message. 49 | * @param message 32-byte message. 50 | */ 51 | sign (message) { 52 | const options = { 53 | extraEntropy: true, 54 | lowS: true 55 | } 56 | const rv = secp256k1.sign(message, this.key, options) 57 | return Signature.from((rv.recovery + 31).toString(16) + rv.toCompactHex()) 58 | } 59 | 60 | /** Derive the public key for this private key. */ 61 | createPublic (prefix = DEFAULT_ADDRESS_PREFIX) { 62 | return new PublicKey(secp256k1.getPublicKey(this.key), prefix) 63 | } 64 | 65 | /** Return a WIF-encoded representation of the key. */ 66 | toString () { 67 | return encodePrivate(new Uint8Array([...NETWORK_ID, ...this.key])) 68 | } 69 | 70 | /** 71 | * Used by `utils.inspect` and `console.log` in node.js. Does not show the full key 72 | * to get the full encoded key you need to explicitly call {@link toString}. 73 | */ 74 | inspect () { 75 | const key = this.toString() 76 | return `PrivateKey: ${key.slice(0, 6)}...${key.slice(-6)}` 77 | } 78 | 79 | /** 80 | * Get shared secret for memo cryptography 81 | */ 82 | getSharedSecret (publicKey) { 83 | const s = secp256k1.getSharedSecret(this.key, publicKey.key) 84 | // strip the parity byte 85 | return sha512(s.subarray(1)) 86 | } 87 | 88 | /** 89 | * Returns a randomly generated instance of PrivateKey 90 | * Might take up to 250ms 91 | */ 92 | static randomKey () { 93 | return new PrivateKey(secp256k1.utils.randomPrivateKey()) 94 | } 95 | } 96 | 97 | const doubleSha256 = input => { 98 | const dbl = sha256(sha256(input)) 99 | return dbl 100 | } 101 | 102 | /** Encode bs58+doubleSha256-checksum private key. */ 103 | const encodePrivate = key => { 104 | // assert.equal(key.readUInt8(0), 0x80, 'private key network id mismatch') 105 | const checksum = doubleSha256(key) 106 | return bs58.encode(new Uint8Array([...key, ...checksum.slice(0, 4)])) 107 | } 108 | 109 | /** Decode bs58+doubleSha256-checksum encoded private key. */ 110 | const decodePrivate = encodedKey => { 111 | const buffer = bs58.decode(encodedKey) 112 | // assert.deepEqual(buffer.slice(0, 1), NETWORK_ID, 'private key network id mismatch') 113 | // const checksum = buffer.slice(-4) 114 | const key = buffer.slice(0, -4) 115 | // const checksumVerify = doubleSha256(key).slice(0, 4) 116 | // assert.deepEqual(checksumVerify, checksum, 'private key checksum mismatch') 117 | return key 118 | } 119 | -------------------------------------------------------------------------------- /helpers/PublicKey.d.ts: -------------------------------------------------------------------------------- 1 | import { Signature } from './Signature' 2 | 3 | /** ECDSA (secp256k1) public key. */ 4 | export class PublicKey { 5 | key: Uint8Array 6 | prefix: string 7 | 8 | /** Create a new instance from a WIF-encoded key. */ 9 | static fromString(wif: string): PublicKey 10 | 11 | /** Create a new instance. */ 12 | static from(value: string | PublicKey): PublicKey 13 | 14 | constructor(key: Uint8Array, prefix?: string) 15 | 16 | /** 17 | * Verify a 32-byte signature. 18 | * @param message 32-byte message to verify. 19 | * @param signature Instance of Signature to verify. 20 | */ 21 | verify(message: Uint8Array, signature: Signature): boolean 22 | 23 | /** Return a WIF-encoded representation of the key. */ 24 | toString(): string 25 | 26 | /** Return JSON representation of this key, same as toString(). */ 27 | toJSON(): string 28 | 29 | inspect(): string 30 | } 31 | -------------------------------------------------------------------------------- /helpers/PublicKey.js: -------------------------------------------------------------------------------- 1 | // const crypto = require('crypto') 2 | 3 | import { ripemd160 } from './crypto.js' 4 | import bs58 from 'bs58' 5 | import { config } from '../config.js' 6 | import { secp256k1 } from '@noble/curves/secp256k1' 7 | 8 | const DEFAULT_ADDRESS_PREFIX = config.address_prefix 9 | 10 | /** ECDSA (secp256k1) public key. */ 11 | export class PublicKey { 12 | constructor (key, prefix = DEFAULT_ADDRESS_PREFIX) { 13 | this.key = key 14 | this.prefix = prefix 15 | // assert(secp256k1.publicKeyVerify(key), 'invalid public key') 16 | } 17 | 18 | /** Create a new instance from a WIF-encoded key. */ 19 | static fromString (wif) { 20 | const { key, prefix } = decodePublic(wif) 21 | return new PublicKey(key, prefix) 22 | } 23 | 24 | /** Create a new instance. */ 25 | static from (value) { 26 | if (value instanceof PublicKey) { 27 | return value 28 | } else { 29 | return PublicKey.fromString(value) 30 | } 31 | } 32 | 33 | /** 34 | * Verify a 32-byte signature. 35 | * @param message 32-byte message to verify. 36 | * @param signature Signature to verify. 37 | */ 38 | verify (message, signature) { 39 | return secp256k1.verify(signature.data, message, this.key) 40 | } 41 | 42 | /** Return a WIF-encoded representation of the key. */ 43 | toString () { 44 | return encodePublic(this.key, this.prefix) 45 | } 46 | 47 | /** Return JSON representation of this key, same as toString(). */ 48 | toJSON () { 49 | return this.toString() 50 | } 51 | 52 | /** Used by `utils.inspect` and `console.log` in node.js. */ 53 | inspect () { 54 | return `PublicKey: ${this.toString()}` 55 | } 56 | } 57 | 58 | const encodePublic = (key, prefix) => { 59 | const checksum = ripemd160(key) 60 | return prefix + bs58.encode(new Uint8Array([...key, ...checksum.subarray(0, 4)])) 61 | } 62 | 63 | /** Decode bs58+ripemd160-checksum encoded public key. */ 64 | const decodePublic = encodedKey => { 65 | const prefix = encodedKey.slice(0, 3) 66 | encodedKey = encodedKey.slice(3) 67 | const buffer = bs58.decode(encodedKey) 68 | const key = buffer.subarray(0, buffer.length - 4) 69 | return { key, prefix } 70 | } 71 | -------------------------------------------------------------------------------- /helpers/Signature.d.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from './PublicKey' 2 | 3 | /** ECDSA (secp256k1) signature. */ 4 | export class Signature { 5 | data: Uint8Array 6 | recovery: number 7 | constructor(data: Uint8Array, recovery: number, compressed?: boolean) 8 | toBuffer(): Uint8Array 9 | /** String representation of the Signature */ 10 | customToString(): string 11 | /** Create a Signature from string */ 12 | static from(data: string): Signature 13 | /** Retrieve public key from the Signature by provided Hash message */ 14 | getPublicKey (message: Uint8Array | string): PublicKey 15 | } 16 | -------------------------------------------------------------------------------- /helpers/Signature.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from './PublicKey.js' 2 | import { secp256k1 } from '@noble/curves/secp256k1' 3 | import { hexToUint8Array, uint8ArrayToHex } from './uint8Array.js' 4 | 5 | /** ECDSA (secp256k1) signature. */ 6 | export class Signature { 7 | constructor (data, recovery, compressed = true) { 8 | this.data = data 9 | this.recovery = recovery 10 | this.compressed = compressed 11 | } 12 | 13 | static from (string) { 14 | if (typeof string === 'string') { 15 | const temp = hexToUint8Array(string) 16 | let recovery = parseInt(uint8ArrayToHex(temp.subarray(0, 1)), 16) - 31 17 | let compressed = true 18 | // non-compressed signatures have -4 19 | // https://github.com/bitcoin/bitcoin/blob/95ea54ba089610019a74c1176a2c7c0dba144b1c/src/key.cpp#L257 20 | if (recovery < 0) { 21 | compressed = false 22 | recovery = recovery + 4 23 | } 24 | const data = temp.subarray(1) 25 | return new Signature(data, recovery, compressed) 26 | } else { 27 | return new Error('Expected string for data') 28 | } 29 | } 30 | 31 | toBuffer () { 32 | const buffer = new Uint8Array(65).fill(0) 33 | if (this.compressed) { 34 | buffer[0] = (this.recovery + 31) & 0xFF 35 | } else { 36 | buffer[0] = (this.recovery + 27) & 0xFF 37 | } 38 | buffer.set(this.data, 1) 39 | return buffer 40 | } 41 | 42 | customToString () { 43 | return uint8ArrayToHex(this.toBuffer()) 44 | } 45 | 46 | getPublicKey (message) { 47 | if (message instanceof Uint8Array && message.length !== 32) { 48 | return new Error('Expected a valid sha256 hash as message') 49 | } 50 | if (typeof message === 'string' && message.length !== 64) { 51 | return new Error('Expected a valid sha256 hash as message') 52 | } 53 | const sig = secp256k1.Signature.fromCompact(this.data) 54 | const temp = new secp256k1.Signature(sig.r, sig.s, this.recovery) 55 | return new PublicKey(temp.recoverPublicKey(message).toRawBytes()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /helpers/aes.js: -------------------------------------------------------------------------------- 1 | import { ByteBuffer } from './ByteBuffer.js' 2 | import { sha256, sha512 } from './crypto.js' 3 | import { cbc as AESCBC } from '@noble/ciphers/aes' 4 | import { secp256k1 } from '@noble/curves/secp256k1' 5 | 6 | export const encrypt = ( 7 | privateKey, 8 | publicKey, 9 | message, 10 | nonce = uniqueNonce() 11 | ) => crypt(privateKey, publicKey, nonce, message) 12 | 13 | export const decrypt = async (privateKey, publicKey, nonce, message, checksum) => { 14 | const d = await crypt(privateKey, publicKey, nonce, message, checksum) 15 | return d.message 16 | } 17 | 18 | /** 19 | * @arg {Uint8Array} message - Encrypted or plain text message (see checksum) 20 | * @arg {number} checksum - shared secret checksum (null to encrypt, non-null to decrypt) 21 | */ 22 | const crypt = async (privateKey, publicKey, nonce, message, checksum) => { 23 | const nonceL = nonce instanceof BigInt ? nonce : BigInt(nonce) 24 | const S = privateKey.getSharedSecret(publicKey) 25 | let ebuf = new ByteBuffer( 26 | ByteBuffer.DEFAULT_CAPACITY, 27 | ByteBuffer.LITTLE_ENDIAN 28 | ) 29 | ebuf.writeUint64(nonceL) 30 | ebuf.append(S) 31 | ebuf.flip() 32 | 33 | ebuf = new Uint8Array(ebuf.toBuffer()) 34 | const encryptionKey = sha512(ebuf) 35 | const iv = encryptionKey.subarray(32, 48) 36 | const tag = encryptionKey.subarray(0, 32) 37 | 38 | // check if first 64 bit of sha256 hash treated as uint64_t truncated to 32 bits. 39 | const check = sha256(encryptionKey).subarray(0, 4) 40 | const cbuf = new ByteBuffer( 41 | ByteBuffer.DEFAULT_CAPACITY, 42 | ByteBuffer.LITTLE_ENDIAN 43 | ) 44 | cbuf.append(check) 45 | cbuf.flip() 46 | const check32 = cbuf.readUint32() 47 | if (checksum) { 48 | if (check32 !== checksum) { 49 | throw new Error('Invalid key') 50 | } 51 | message = await cryptoJsDecrypt(message, tag, iv) 52 | } else { 53 | message = await cryptoJsEncrypt(message, tag, iv) 54 | } 55 | return { nonce: nonceL, message, checksum: check32 } 56 | } 57 | 58 | /** 59 | * This method does not use a checksum, the returned data must be validated some other way. 60 | * @arg {string|Uint8Array} ciphertext - binary format 61 | * @return {Uint8Array} the decrypted message 62 | */ 63 | const cryptoJsDecrypt = async (message, tag, iv) => { 64 | let messageBuffer = message 65 | const decipher = AESCBC(tag, iv) 66 | messageBuffer = await decipher.decrypt(messageBuffer) 67 | // return Uint8Array.from(messageBuffer) 68 | return messageBuffer 69 | } 70 | 71 | /** 72 | * This method does not use a checksum, the returned data must be validated some other way. 73 | * @arg {string|Uint8Array} plaintext - binary format 74 | * @return {Uint8Array} binary 75 | */ 76 | export const cryptoJsEncrypt = async (message, tag, iv) => { 77 | let messageBuffer = message 78 | const cipher = AESCBC(tag, iv) 79 | messageBuffer = await cipher.encrypt(messageBuffer) 80 | // return Uint8Array.from(messageBuffer) 81 | return messageBuffer 82 | } 83 | 84 | /** @return {string} unique 64 bit unsigned number string. Being time based, 85 | * this is careful to never choose the same nonce twice. This value could 86 | * clsbe recorded in the blockchain for a long time. 87 | */ 88 | let uniqueNonceEntropy = null 89 | 90 | const uniqueNonce = () => { 91 | if (uniqueNonceEntropy === null) { 92 | const uint8randomArr = new Uint8Array(2) 93 | for (let i = 0; i < 2; ++i) { 94 | uint8randomArr[i] = secp256k1.utils.randomPrivateKey().at(i) 95 | } 96 | uniqueNonceEntropy = Math.round( 97 | (uint8randomArr[0] << 8) | uint8randomArr[1] 98 | ) 99 | } 100 | let long = BigInt(Date.now()) 101 | const entropy = ++uniqueNonceEntropy % 0xffff 102 | long = long << BigInt(16) | BigInt(entropy) 103 | return long 104 | } 105 | 106 | // const toLongObj = (o) => (o ? (Long.isLong(o) ? o : Long.fromString(o)) : o) 107 | -------------------------------------------------------------------------------- /helpers/call.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { config } from '../config.js' 3 | 4 | let nodeIndex = 0 5 | let numTries = 0 6 | 7 | /** 8 | * Make calls to hive node and retry - Only if provided config.node is an array 9 | * @param {string}method - e.g. condenser_api.get_dynamic_global_properties 10 | * @param {[any]|object}params - an array or object 11 | * @param {number}timeout - optional - default 5 seconds 12 | * @param {number}retry - optional - default 5 retries before throw 13 | */ 14 | export const call = async (method, params = [], timeout = config.timeout, retry = config.retry) => { 15 | let node = '' 16 | if (Array.isArray(config.node) && config.node.length > 0) { 17 | node = config.node[nodeIndex] 18 | } else { 19 | node = config.node 20 | } 21 | const body = JSON.stringify({ 22 | jsonrpc: '2.0', 23 | method, 24 | params, 25 | id: 1 26 | }) 27 | try { 28 | const res = await callWithTimeout(node, body, timeout) 29 | numTries = 0 30 | return res 31 | } catch (e) { 32 | numTries++ 33 | if (!Array.isArray(config.node) || numTries > retry) { 34 | throw e 35 | } 36 | await changeNode() 37 | return call(method, params, timeout, retry) 38 | } 39 | } 40 | 41 | const callWithTimeout = (url, body, timeout) => { 42 | const conf = {} 43 | if (config.axiosAdapter !== null) { 44 | conf.adapter = config.axiosAdapter 45 | } 46 | // Create a cancel token 47 | const source = axios.CancelToken.source() 48 | let resolved = 0 49 | let timerId 50 | return new Promise((resolve, reject) => { 51 | axios 52 | .post( 53 | url, 54 | body, 55 | { ...conf, cancelToken: source.token } 56 | ) 57 | .then(res => { 58 | if (res && res.status === 200) { 59 | resolved = 1 60 | resolve(res.data) 61 | } else { 62 | reject(new Error(`Unexpected response status: ${res.status}`)) 63 | } 64 | }).catch(e => { 65 | reject(e) 66 | }).finally(() => { 67 | if (timerId) { 68 | clearTimeout(timerId) 69 | } 70 | }) 71 | timerId = setTimeout(() => { 72 | if (!resolved) { 73 | source.cancel('Operation canceled by timeout') 74 | reject(new Error(`Network timeout: ${url}: ${body}`)) 75 | } 76 | }, timeout * 1000) 77 | }) 78 | } 79 | 80 | // The default axios adapter creates infinite sockets which leads to memory leak in Deno 81 | // Confirmed to NOT be a problem in nodejs 82 | // The following fixes the problem in Deno 83 | if ('Deno' in globalThis) { 84 | config.axiosAdapter = async (config) => { 85 | const { method, url, headers, data, ...rest } = config 86 | const response = await fetch(url, { 87 | method, 88 | headers: new Headers(headers), 89 | body: data, 90 | ...rest // Additional configurations if needed 91 | }) 92 | return { 93 | data: await response.json(), 94 | status: response.status, 95 | statusText: response.statusText, 96 | headers: Object.fromEntries(response.headers.entries()), 97 | config, 98 | request: response 99 | } 100 | } 101 | } 102 | 103 | /** Validate and use another RPC node */ 104 | const changeNode = async (newNodeIndex = nodeIndex + 1, tries = 0) => { 105 | if (!Array.isArray(config.node)) { 106 | return 107 | } 108 | if (tries > config.node.length) { 109 | throw new Error(`Can't find a working node! Current nodes are: ${config.node.join(', ')}`) 110 | } 111 | if (newNodeIndex > config.node.length - 1) { 112 | newNodeIndex = 0 113 | } 114 | const body = JSON.stringify({ 115 | jsonrpc: '2.0', 116 | method: 'condenser_api.get_accounts', 117 | params: [['mahdiyari']], 118 | id: 189 119 | }) 120 | try { 121 | const res = await callWithTimeout(config.node[newNodeIndex], body, config.timeout) 122 | if (res && res.id === 189 && res.result && res.result[0] && res.result[0].name === 'mahdiyari') { 123 | nodeIndex = newNodeIndex 124 | } else { 125 | return changeNode(newNodeIndex + 1, tries + 1) 126 | } 127 | } catch { 128 | return changeNode(newNodeIndex + 1, tries + 1) 129 | } 130 | } 131 | 132 | // Periodic healthcheck of the current node every 30s 133 | const healthcheck = setInterval(async () => { 134 | if (!Array.isArray(config.node)) { 135 | return 136 | } 137 | const body = JSON.stringify({ 138 | jsonrpc: '2.0', 139 | method: 'condenser_api.get_accounts', 140 | params: [['mahdiyari']], 141 | id: 88885 142 | }) 143 | try { 144 | const res = await callWithTimeout(config.node[nodeIndex], body, config.timeout) 145 | if (res && res.id === 88885 && res.result && res.result[0] && res.result[0].name === 'mahdiyari') { 146 | // do nothing 147 | } else { 148 | changeNode() 149 | } 150 | } catch { 151 | changeNode() 152 | } 153 | }, config.healthcheckInterval) 154 | 155 | // Don't keep the app active just because of this interval 156 | if (healthcheck && healthcheck.unref) { 157 | healthcheck.unref() 158 | } 159 | -------------------------------------------------------------------------------- /helpers/crypto.js: -------------------------------------------------------------------------------- 1 | import { sha256 as sh256 } from '@noble/hashes/sha256' 2 | import { sha512 as sh512 } from '@noble/hashes/sha512' 3 | import { ripemd160 as rp160 } from '@noble/hashes/ripemd160' 4 | 5 | export const sha256 = (input) => { 6 | return sh256(input) 7 | // return Uint8Array.from(sh256(input)) 8 | } 9 | 10 | export const sha512 = (input) => { 11 | return sh512(input) 12 | // return Uint8Array.from(sh512(input)) 13 | } 14 | 15 | export const ripemd160 = (input) => { 16 | return rp160(input) 17 | // return Uint8Array.from(rp160(input)) 18 | } 19 | -------------------------------------------------------------------------------- /helpers/deserializer.js: -------------------------------------------------------------------------------- 1 | import { ByteBuffer } from './ByteBuffer.js' 2 | import { PublicKey } from './PublicKey.js' 3 | 4 | const PublicKeyDeserializer = (buf) => { 5 | const c = fixedBuf(buf, 33) 6 | return new PublicKey(c) 7 | } 8 | 9 | const UInt64Deserializer = (b) => { 10 | b.flip() 11 | return b.readUint64() 12 | } 13 | 14 | const UInt32Deserializer = (b) => { 15 | b.flip() 16 | return b.readUint32() 17 | } 18 | 19 | const BinaryDeserializer = (b) => { 20 | b.flip() 21 | const len = b.readVarint32() 22 | const bCopy = b.copy(b.offset, b.offset + len) 23 | b.skip(len) 24 | return new Uint8Array(bCopy.toBuffer()) 25 | } 26 | 27 | const BufferDeserializer = 28 | (keyDeserializers) => 29 | (buf) => { 30 | const obj = {} 31 | for (const [key, deserializer] of keyDeserializers) { 32 | try { 33 | // Decodes a binary encoded string to a ByteBuffer. 34 | const temp = new ByteBuffer( 35 | ByteBuffer.DEFAULT_CAPACITY, 36 | ByteBuffer.LITTLE_ENDIAN 37 | ) 38 | buf = temp.append(buf) 39 | obj[key] = deserializer(buf) 40 | } catch (error) { 41 | error.message = `${key}: ${error.message}` 42 | throw error 43 | } 44 | } 45 | return obj 46 | } 47 | 48 | function fixedBuf (b, len) { 49 | if (!b) { 50 | throw Error('No buffer found on first parameter') 51 | } else { 52 | b.flip() 53 | const bCopy = b.copy(b.offset, b.offset + len) 54 | b.skip(len) 55 | return new Uint8Array(bCopy.toBuffer()) 56 | } 57 | } 58 | 59 | const EncryptedMemoDeserializer = BufferDeserializer([ 60 | ['from', PublicKeyDeserializer], 61 | ['to', PublicKeyDeserializer], 62 | ['nonce', UInt64Deserializer], 63 | ['check', UInt32Deserializer], 64 | ['encrypted', BinaryDeserializer] 65 | ]) 66 | 67 | export const Deserializer = { 68 | Memo: EncryptedMemoDeserializer 69 | } 70 | -------------------------------------------------------------------------------- /helpers/globalProps.js: -------------------------------------------------------------------------------- 1 | import { call } from './call.js' 2 | 3 | /** return global properties */ 4 | export const getGlobalProps = async () => { 5 | const res = await call('condenser_api.get_dynamic_global_properties') 6 | if (!res) { 7 | throw new Error("Couldn't resolve global properties") 8 | } 9 | if (res && (!res.id || !res.result)) { 10 | throw new Error('Bad response @ global props') 11 | } 12 | return res.result 13 | } 14 | -------------------------------------------------------------------------------- /helpers/memo.d.ts: -------------------------------------------------------------------------------- 1 | import { PrivateKey } from './PrivateKey.js' 2 | import { PublicKey } from './PublicKey.js' 3 | 4 | export type Memo = { 5 | encode (privateKey: PrivateKey, publicKey: PublicKey, memo: string, nonce?: any): Promise 6 | decode (privateKey: PrivateKey, memo: string): Promise 7 | } -------------------------------------------------------------------------------- /helpers/memo.js: -------------------------------------------------------------------------------- 1 | import bs58 from 'bs58' 2 | import { ByteBuffer } from './ByteBuffer.js' 3 | import { Serializer } from './serializer.js' 4 | import { PrivateKey } from './PrivateKey.js' 5 | import * as Aes from './aes.js' 6 | import { PublicKey } from './PublicKey.js' 7 | import { Deserializer } from './deserializer.js' 8 | 9 | /** 10 | * Memo/Any message encoding using AES (aes-cbc algorithm) 11 | * @param {Uint8Array|string} private_key Private memo key of sender 12 | * @param {Uint8Array|string} public_key public memo key of recipient 13 | * @param {string} memo message to be encrypted 14 | * @param {number} testNonce nonce with high entropy 15 | */ 16 | const encode = async (privateKey, publicKey, memo, testNonce) => { 17 | if (!memo.startsWith('#')) { 18 | return memo 19 | } 20 | memo = memo.substring(1) 21 | await checkEncryption() 22 | privateKey = toPrivateObj(privateKey) 23 | publicKey = toPublicObj(publicKey) 24 | const mbuf = new ByteBuffer( 25 | ByteBuffer.DEFAULT_CAPACITY, 26 | ByteBuffer.LITTLE_ENDIAN 27 | ) 28 | mbuf.writeVString(memo) 29 | const memoBuffer = new Uint8Array(mbuf.copy(0, mbuf.offset).toBuffer()) 30 | const { nonce, message, checksum } = await Aes.encrypt( 31 | privateKey, 32 | publicKey, 33 | memoBuffer, 34 | testNonce 35 | ) 36 | const mbuf2 = new ByteBuffer( 37 | ByteBuffer.DEFAULT_CAPACITY, 38 | ByteBuffer.LITTLE_ENDIAN 39 | ) 40 | Serializer.Memo(mbuf2, { 41 | check: checksum, 42 | encrypted: message, 43 | from: privateKey.createPublic(), 44 | nonce, 45 | to: publicKey 46 | }) 47 | mbuf2.flip() 48 | const data = new Uint8Array(mbuf2.toBuffer()) 49 | return '#' + bs58.encode(data) 50 | } 51 | 52 | /** 53 | * Encrypted memo/message decryption 54 | * @param {PrivateKey|string} private_key Private memo key of recipient 55 | * @param {string}memo Encrypted message/memo 56 | */ 57 | const decode = async (privateKey, memo) => { 58 | if (!memo.startsWith('#')) { 59 | return memo 60 | } 61 | memo = memo.substring(1) 62 | await checkEncryption() 63 | privateKey = toPrivateObj(privateKey) 64 | memo = bs58.decode(memo) 65 | let memoBuffer = Deserializer.Memo(memo) 66 | const { from, to, nonce, check, encrypted } = memoBuffer 67 | const pubkey = privateKey.createPublic().toString() 68 | const otherpub = 69 | pubkey === new PublicKey(from.key).toString() 70 | ? new PublicKey(to.key) 71 | : new PublicKey(from.key) 72 | memoBuffer = await Aes.decrypt(privateKey, otherpub, nonce, encrypted, check) 73 | const mbuf = new ByteBuffer( 74 | ByteBuffer.DEFAULT_CAPACITY, 75 | ByteBuffer.LITTLE_ENDIAN 76 | ) 77 | mbuf.append(memoBuffer) 78 | mbuf.flip() 79 | return '#' + mbuf.readVString() 80 | } 81 | 82 | let encodeTest 83 | const checkEncryption = async () => { 84 | if (encodeTest === undefined) { 85 | let plaintext 86 | encodeTest = true // prevent infinate looping 87 | try { 88 | const wif = '5JdeC9P7Pbd1uGdFVEsJ41EkEnADbbHGq6p1BwFxm6txNBsQnsw' 89 | const pubkey = 'STM8m5UgaFAAYQRuaNejYdS8FVLVp9Ss3K1qAVk5de6F8s3HnVbvA' 90 | const cyphertext = await encode(wif, pubkey, '#memo爱') 91 | plaintext = await decode(wif, cyphertext) 92 | } finally { 93 | encodeTest = plaintext === '#memo爱' 94 | } 95 | } 96 | if (encodeTest === false) { 97 | throw new Error('This environment does not support encryption.') 98 | } 99 | } 100 | 101 | const toPrivateObj = (o) => (o ? (o.key ? o : PrivateKey.fromString(o)) : o) 102 | const toPublicObj = (o) => (o ? (o.key ? o : PublicKey.fromString(o)) : o) 103 | 104 | export const Memo = { 105 | decode, 106 | encode 107 | } 108 | -------------------------------------------------------------------------------- /helpers/serializer.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from './PublicKey.js' 2 | import { Asset } from './Asset.js' 3 | import { HexBuffer } from './HexBuffer.js' 4 | 5 | const VoidSerializer = () => { 6 | throw new Error('Void can not be serialized') 7 | } 8 | const StringSerializer = (buffer, data) => { 9 | buffer.writeVString(data) 10 | } 11 | 12 | const Int8Serializer = (buffer, data) => { 13 | buffer.writeInt8(data) 14 | } 15 | 16 | const Int16Serializer = (buffer, data) => { 17 | buffer.writeInt16(data) 18 | } 19 | 20 | const Int32Serializer = (buffer, data) => { 21 | buffer.writeInt32(data) 22 | } 23 | 24 | const Int64Serializer = (buffer, data) => { 25 | buffer.writeInt64(data) 26 | } 27 | 28 | const UInt8Serializer = (buffer, data) => { 29 | buffer.writeUint8(data) 30 | } 31 | 32 | const UInt16Serializer = (buffer, data) => { 33 | buffer.writeUint16(data) 34 | } 35 | 36 | const UInt32Serializer = (buffer, data) => { 37 | buffer.writeUint32(data) 38 | } 39 | 40 | const UInt64Serializer = (buffer, data) => { 41 | buffer.writeUint64(data) 42 | } 43 | 44 | const BooleanSerializer = (buffer, data) => { 45 | buffer.writeByte(data ? 1 : 0) 46 | } 47 | 48 | const StaticVariantSerializer = (itemSerializers) => { 49 | return (buffer, data) => { 50 | let id = data[0] 51 | const item = data[1] 52 | // id was/is supposed to be 0 or integer here but seems to have been changed in e.g. comment_options 53 | // extensions: [ 54 | // [ 55 | // "comment_payout_beneficiaries", 56 | // { 57 | // "beneficiaries": [ 58 | // { 59 | // "account": "vimm", 60 | // "weight": 1000 61 | // } 62 | // ] 63 | // } 64 | // ] 65 | // ] 66 | // "comment_payout_beneficiaries" was 0 and at some point it got changed 67 | // It should still be serialized as a 0 or an integer 68 | // Now the question is, always 0? will need an example transaction to prove otherwise 69 | if (typeof id === 'string') { 70 | if (id === 'update_proposal_end_date') { 71 | id = 1 72 | } else { 73 | id = 0 74 | } 75 | } 76 | buffer.writeVarint32(id) 77 | itemSerializers[id](buffer, item) 78 | } 79 | } 80 | 81 | /** 82 | * Serialize asset. 83 | * @note This looses precision for amounts larger than 2^53-1/10^precision. 84 | * Should not be a problem in real-word usage. 85 | */ 86 | const AssetSerializer = (buffer, data) => { 87 | const asset = Asset.from(data) 88 | const precision = asset.getPrecision() 89 | buffer.writeInt64(Math.round(asset.amount * Math.pow(10, precision))) 90 | buffer.writeUint8(precision) 91 | for (let i = 0; i < 7; i++) { 92 | buffer.writeUint8(asset.symbol.charCodeAt(i) || 0) 93 | } 94 | } 95 | 96 | const DateSerializer = (buffer, data) => { 97 | buffer.writeUint32(Math.floor(new Date(data + 'Z').getTime() / 1000)) 98 | } 99 | 100 | const PublicKeySerializer = (buffer, data) => { 101 | if ( 102 | data === null || 103 | (typeof data === 'string' && 104 | data.slice(-39) === '1111111111111111111111111111111114T1Anm') 105 | ) { 106 | buffer.append(new Uint8Array(33).fill(0)) 107 | } else { 108 | buffer.append(PublicKey.from(data).key) 109 | } 110 | } 111 | 112 | const BinarySerializer = (size = null) => { 113 | return (buffer, data) => { 114 | data = HexBuffer.from(data) 115 | const len = data.buffer.length 116 | if (size) { 117 | if (len !== size) { 118 | throw new Error( 119 | `Unable to serialize binary. Expected ${size} bytes, got ${len}` 120 | ) 121 | } 122 | } else { 123 | buffer.writeVarint32(len) 124 | } 125 | buffer.append(data.buffer) 126 | } 127 | } 128 | 129 | const VariableBinarySerializer = BinarySerializer() 130 | 131 | const FlatMapSerializer = (keySerializer, valueSerializer) => { 132 | return (buffer, data) => { 133 | buffer.writeVarint32(data.length) 134 | for (const [key, value] of data) { 135 | keySerializer(buffer, key) 136 | valueSerializer(buffer, value) 137 | } 138 | } 139 | } 140 | 141 | const ArraySerializer = (itemSerializer) => { 142 | return (buffer, data) => { 143 | buffer.writeVarint32(data.length) 144 | for (const item of data) { 145 | itemSerializer(buffer, item) 146 | } 147 | } 148 | } 149 | 150 | const ObjectSerializer = (keySerializers) => { 151 | return (buffer, data) => { 152 | for (const [key, serializer] of keySerializers) { 153 | try { 154 | serializer(buffer, data[key]) 155 | } catch (error) { 156 | error.message = `${key}: ${error.message}` 157 | throw error 158 | } 159 | } 160 | } 161 | } 162 | 163 | const OptionalSerializer = (valueSerializer) => { 164 | return (buffer, data) => { 165 | if (data !== undefined) { 166 | buffer.writeByte(1) 167 | valueSerializer(buffer, data) 168 | } else { 169 | buffer.writeByte(0) 170 | } 171 | } 172 | } 173 | 174 | const AuthoritySerializer = ObjectSerializer([ 175 | ['weight_threshold', UInt32Serializer], 176 | ['account_auths', FlatMapSerializer(StringSerializer, UInt16Serializer)], 177 | ['key_auths', FlatMapSerializer(PublicKeySerializer, UInt16Serializer)] 178 | ]) 179 | 180 | const BeneficiarySerializer = ObjectSerializer([ 181 | ['account', StringSerializer], 182 | ['weight', UInt16Serializer] 183 | ]) 184 | 185 | const PriceSerializer = ObjectSerializer([ 186 | ['base', AssetSerializer], 187 | ['quote', AssetSerializer] 188 | ]) 189 | 190 | const SignedBlockHeaderSerializer = ObjectSerializer([ 191 | ['previous', BinarySerializer(20)], 192 | ['timestamp', DateSerializer], 193 | ['witness', StringSerializer], 194 | ['transaction_merkle_root', BinarySerializer(20)], 195 | ['extensions', ArraySerializer(VoidSerializer)], 196 | ['witness_signature', BinarySerializer(65)] 197 | ]) 198 | 199 | const ChainPropertiesSerializer = ObjectSerializer([ 200 | ['account_creation_fee', AssetSerializer], 201 | ['maximum_block_size', UInt32Serializer], 202 | ['hbd_interest_rate', UInt16Serializer] 203 | ]) 204 | 205 | const OperationDataSerializer = (operationId, definitions) => { 206 | const objectSerializer = ObjectSerializer(definitions) 207 | return (buffer, data) => { 208 | buffer.writeVarint32(operationId) 209 | objectSerializer(buffer, data) 210 | } 211 | } 212 | 213 | const OperationSerializers = {} 214 | 215 | OperationSerializers.account_create = OperationDataSerializer(9, [ 216 | ['fee', AssetSerializer], 217 | ['creator', StringSerializer], 218 | ['new_account_name', StringSerializer], 219 | ['owner', AuthoritySerializer], 220 | ['active', AuthoritySerializer], 221 | ['posting', AuthoritySerializer], 222 | ['memo_key', PublicKeySerializer], 223 | ['json_metadata', StringSerializer] 224 | ]) 225 | 226 | OperationSerializers.account_create_with_delegation = OperationDataSerializer( 227 | 41, 228 | [ 229 | ['fee', AssetSerializer], 230 | ['delegation', AssetSerializer], 231 | ['creator', StringSerializer], 232 | ['new_account_name', StringSerializer], 233 | ['owner', AuthoritySerializer], 234 | ['active', AuthoritySerializer], 235 | ['posting', AuthoritySerializer], 236 | ['memo_key', PublicKeySerializer], 237 | ['json_metadata', StringSerializer], 238 | ['extensions', ArraySerializer(VoidSerializer)] 239 | ] 240 | ) 241 | 242 | OperationSerializers.account_update = OperationDataSerializer(10, [ 243 | ['account', StringSerializer], 244 | ['owner', OptionalSerializer(AuthoritySerializer)], 245 | ['active', OptionalSerializer(AuthoritySerializer)], 246 | ['posting', OptionalSerializer(AuthoritySerializer)], 247 | ['memo_key', PublicKeySerializer], 248 | ['json_metadata', StringSerializer] 249 | ]) 250 | 251 | OperationSerializers.account_witness_proxy = OperationDataSerializer(13, [ 252 | ['account', StringSerializer], 253 | ['proxy', StringSerializer] 254 | ]) 255 | 256 | OperationSerializers.account_witness_vote = OperationDataSerializer(12, [ 257 | ['account', StringSerializer], 258 | ['witness', StringSerializer], 259 | ['approve', BooleanSerializer] 260 | ]) 261 | 262 | OperationSerializers.cancel_transfer_from_savings = OperationDataSerializer( 263 | 34, 264 | [ 265 | ['from', StringSerializer], 266 | ['request_id', UInt32Serializer] 267 | ] 268 | ) 269 | 270 | OperationSerializers.change_recovery_account = OperationDataSerializer(26, [ 271 | ['account_to_recover', StringSerializer], 272 | ['new_recovery_account', StringSerializer], 273 | ['extensions', ArraySerializer(VoidSerializer)] 274 | ]) 275 | 276 | OperationSerializers.claim_account = OperationDataSerializer(22, [ 277 | ['creator', StringSerializer], 278 | ['fee', AssetSerializer], 279 | ['extensions', ArraySerializer(VoidSerializer)] 280 | ]) 281 | 282 | OperationSerializers.claim_reward_balance = OperationDataSerializer(39, [ 283 | ['account', StringSerializer], 284 | ['reward_hive', AssetSerializer], 285 | ['reward_hbd', AssetSerializer], 286 | ['reward_vests', AssetSerializer] 287 | ]) 288 | 289 | OperationSerializers.comment = OperationDataSerializer(1, [ 290 | ['parent_author', StringSerializer], 291 | ['parent_permlink', StringSerializer], 292 | ['author', StringSerializer], 293 | ['permlink', StringSerializer], 294 | ['title', StringSerializer], 295 | ['body', StringSerializer], 296 | ['json_metadata', StringSerializer] 297 | ]) 298 | 299 | OperationSerializers.comment_options = OperationDataSerializer(19, [ 300 | ['author', StringSerializer], 301 | ['permlink', StringSerializer], 302 | ['max_accepted_payout', AssetSerializer], 303 | ['percent_hbd', UInt16Serializer], 304 | ['allow_votes', BooleanSerializer], 305 | ['allow_curation_rewards', BooleanSerializer], 306 | [ 307 | 'extensions', 308 | ArraySerializer( 309 | StaticVariantSerializer([ 310 | ObjectSerializer([ 311 | ['beneficiaries', ArraySerializer(BeneficiarySerializer)] 312 | ]) 313 | ]) 314 | ) 315 | ] 316 | ]) 317 | 318 | OperationSerializers.convert = OperationDataSerializer(8, [ 319 | ['owner', StringSerializer], 320 | ['requestid', UInt32Serializer], 321 | ['amount', AssetSerializer] 322 | ]) 323 | 324 | OperationSerializers.create_claimed_account = OperationDataSerializer(23, [ 325 | ['creator', StringSerializer], 326 | ['new_account_name', StringSerializer], 327 | ['owner', AuthoritySerializer], 328 | ['active', AuthoritySerializer], 329 | ['posting', AuthoritySerializer], 330 | ['memo_key', PublicKeySerializer], 331 | ['json_metadata', StringSerializer], 332 | ['extensions', ArraySerializer(VoidSerializer)] 333 | ]) 334 | 335 | OperationSerializers.custom = OperationDataSerializer(15, [ 336 | ['required_auths', ArraySerializer(StringSerializer)], 337 | ['id', UInt16Serializer], 338 | ['data', VariableBinarySerializer] 339 | ]) 340 | 341 | OperationSerializers.custom_binary = OperationDataSerializer(35, [ 342 | ['required_owner_auths', ArraySerializer(StringSerializer)], 343 | ['required_active_auths', ArraySerializer(StringSerializer)], 344 | ['required_posting_auths', ArraySerializer(StringSerializer)], 345 | ['required_auths', ArraySerializer(AuthoritySerializer)], 346 | ['id', StringSerializer], 347 | ['data', VariableBinarySerializer] 348 | ]) 349 | 350 | OperationSerializers.custom_json = OperationDataSerializer(18, [ 351 | ['required_auths', ArraySerializer(StringSerializer)], 352 | ['required_posting_auths', ArraySerializer(StringSerializer)], 353 | ['id', StringSerializer], 354 | ['json', StringSerializer] 355 | ]) 356 | 357 | OperationSerializers.decline_voting_rights = OperationDataSerializer(36, [ 358 | ['account', StringSerializer], 359 | ['decline', BooleanSerializer] 360 | ]) 361 | 362 | OperationSerializers.delegate_vesting_shares = OperationDataSerializer(40, [ 363 | ['delegator', StringSerializer], 364 | ['delegatee', StringSerializer], 365 | ['vesting_shares', AssetSerializer] 366 | ]) 367 | 368 | OperationSerializers.delete_comment = OperationDataSerializer(17, [ 369 | ['author', StringSerializer], 370 | ['permlink', StringSerializer] 371 | ]) 372 | 373 | OperationSerializers.escrow_approve = OperationDataSerializer(31, [ 374 | ['from', StringSerializer], 375 | ['to', StringSerializer], 376 | ['agent', StringSerializer], 377 | ['who', StringSerializer], 378 | ['escrow_id', UInt32Serializer], 379 | ['approve', BooleanSerializer] 380 | ]) 381 | 382 | OperationSerializers.escrow_dispute = OperationDataSerializer(28, [ 383 | ['from', StringSerializer], 384 | ['to', StringSerializer], 385 | ['agent', StringSerializer], 386 | ['who', StringSerializer], 387 | ['escrow_id', UInt32Serializer] 388 | ]) 389 | 390 | OperationSerializers.escrow_release = OperationDataSerializer(29, [ 391 | ['from', StringSerializer], 392 | ['to', StringSerializer], 393 | ['agent', StringSerializer], 394 | ['who', StringSerializer], 395 | ['receiver', StringSerializer], 396 | ['escrow_id', UInt32Serializer], 397 | ['hbd_amount', AssetSerializer], 398 | ['hive_amount', AssetSerializer] 399 | ]) 400 | 401 | OperationSerializers.escrow_transfer = OperationDataSerializer(27, [ 402 | ['from', StringSerializer], 403 | ['to', StringSerializer], 404 | ['agent', StringSerializer], 405 | ['escrow_id', UInt32Serializer], 406 | ['hbd_amount', AssetSerializer], 407 | ['hive_amount', AssetSerializer], 408 | ['fee', AssetSerializer], 409 | ['ratification_deadline', DateSerializer], 410 | ['escrow_expiration', DateSerializer], 411 | ['json_meta', StringSerializer] 412 | ]) 413 | 414 | OperationSerializers.feed_publish = OperationDataSerializer(7, [ 415 | ['publisher', StringSerializer], 416 | ['exchange_rate', PriceSerializer] 417 | ]) 418 | 419 | OperationSerializers.limit_order_cancel = OperationDataSerializer(6, [ 420 | ['owner', StringSerializer], 421 | ['orderid', UInt32Serializer] 422 | ]) 423 | 424 | OperationSerializers.limit_order_create = OperationDataSerializer(5, [ 425 | ['owner', StringSerializer], 426 | ['orderid', UInt32Serializer], 427 | ['amount_to_sell', AssetSerializer], 428 | ['min_to_receive', AssetSerializer], 429 | ['fill_or_kill', BooleanSerializer], 430 | ['expiration', DateSerializer] 431 | ]) 432 | 433 | OperationSerializers.limit_order_create2 = OperationDataSerializer(21, [ 434 | ['owner', StringSerializer], 435 | ['orderid', UInt32Serializer], 436 | ['amount_to_sell', AssetSerializer], 437 | ['fill_or_kill', BooleanSerializer], 438 | ['exchange_rate', PriceSerializer], 439 | ['expiration', DateSerializer] 440 | ]) 441 | 442 | OperationSerializers.recover_account = OperationDataSerializer(25, [ 443 | ['account_to_recover', StringSerializer], 444 | ['new_owner_authority', AuthoritySerializer], 445 | ['recent_owner_authority', AuthoritySerializer], 446 | ['extensions', ArraySerializer(VoidSerializer)] 447 | ]) 448 | 449 | OperationSerializers.report_over_production = OperationDataSerializer(16, [ 450 | ['reporter', StringSerializer], 451 | ['first_block', SignedBlockHeaderSerializer], 452 | ['second_block', SignedBlockHeaderSerializer] 453 | ]) 454 | 455 | OperationSerializers.request_account_recovery = OperationDataSerializer(24, [ 456 | ['recovery_account', StringSerializer], 457 | ['account_to_recover', StringSerializer], 458 | ['new_owner_authority', AuthoritySerializer], 459 | ['extensions', ArraySerializer(VoidSerializer)] 460 | ]) 461 | 462 | OperationSerializers.reset_account = OperationDataSerializer(37, [ 463 | ['reset_account', StringSerializer], 464 | ['account_to_reset', StringSerializer], 465 | ['new_owner_authority', AuthoritySerializer] 466 | ]) 467 | 468 | OperationSerializers.set_reset_account = OperationDataSerializer(38, [ 469 | ['account', StringSerializer], 470 | ['current_reset_account', StringSerializer], 471 | ['reset_account', StringSerializer] 472 | ]) 473 | 474 | OperationSerializers.set_withdraw_vesting_route = OperationDataSerializer(20, [ 475 | ['from_account', StringSerializer], 476 | ['to_account', StringSerializer], 477 | ['percent', UInt16Serializer], 478 | ['auto_vest', BooleanSerializer] 479 | ]) 480 | 481 | OperationSerializers.transfer = OperationDataSerializer(2, [ 482 | ['from', StringSerializer], 483 | ['to', StringSerializer], 484 | ['amount', AssetSerializer], 485 | ['memo', StringSerializer] 486 | ]) 487 | 488 | OperationSerializers.transfer_from_savings = OperationDataSerializer(33, [ 489 | ['from', StringSerializer], 490 | ['request_id', UInt32Serializer], 491 | ['to', StringSerializer], 492 | ['amount', AssetSerializer], 493 | ['memo', StringSerializer] 494 | ]) 495 | 496 | OperationSerializers.transfer_to_savings = OperationDataSerializer(32, [ 497 | ['from', StringSerializer], 498 | ['to', StringSerializer], 499 | ['amount', AssetSerializer], 500 | ['memo', StringSerializer] 501 | ]) 502 | 503 | OperationSerializers.transfer_to_vesting = OperationDataSerializer(3, [ 504 | ['from', StringSerializer], 505 | ['to', StringSerializer], 506 | ['amount', AssetSerializer] 507 | ]) 508 | 509 | OperationSerializers.vote = OperationDataSerializer(0, [ 510 | ['voter', StringSerializer], 511 | ['author', StringSerializer], 512 | ['permlink', StringSerializer], 513 | ['weight', Int16Serializer] 514 | ]) 515 | 516 | OperationSerializers.withdraw_vesting = OperationDataSerializer(4, [ 517 | ['account', StringSerializer], 518 | ['vesting_shares', AssetSerializer] 519 | ]) 520 | 521 | OperationSerializers.witness_update = OperationDataSerializer(11, [ 522 | ['owner', StringSerializer], 523 | ['url', StringSerializer], 524 | ['block_signing_key', PublicKeySerializer], 525 | ['props', ChainPropertiesSerializer], 526 | ['fee', AssetSerializer] 527 | ]) 528 | 529 | OperationSerializers.witness_set_properties = OperationDataSerializer(42, [ 530 | ['owner', StringSerializer], 531 | ['props', FlatMapSerializer(StringSerializer, VariableBinarySerializer)], 532 | ['extensions', ArraySerializer(VoidSerializer)] 533 | ]) 534 | 535 | OperationSerializers.account_update2 = OperationDataSerializer(43, [ 536 | ['account', StringSerializer], 537 | ['owner', OptionalSerializer(AuthoritySerializer)], 538 | ['active', OptionalSerializer(AuthoritySerializer)], 539 | ['posting', OptionalSerializer(AuthoritySerializer)], 540 | ['memo_key', OptionalSerializer(PublicKeySerializer)], 541 | ['json_metadata', StringSerializer], 542 | ['posting_json_metadata', StringSerializer], 543 | ['extensions', ArraySerializer(VoidSerializer)] 544 | ]) 545 | 546 | OperationSerializers.create_proposal = OperationDataSerializer(44, [ 547 | ['creator', StringSerializer], 548 | ['receiver', StringSerializer], 549 | ['start_date', DateSerializer], 550 | ['end_date', DateSerializer], 551 | ['daily_pay', AssetSerializer], 552 | ['subject', StringSerializer], 553 | ['permlink', StringSerializer], 554 | ['extensions', ArraySerializer(VoidSerializer)] 555 | ]) 556 | 557 | OperationSerializers.update_proposal_votes = OperationDataSerializer(45, [ 558 | ['voter', StringSerializer], 559 | ['proposal_ids', ArraySerializer(Int64Serializer)], 560 | ['approve', BooleanSerializer], 561 | ['extensions', ArraySerializer(VoidSerializer)] 562 | ]) 563 | 564 | OperationSerializers.remove_proposal = OperationDataSerializer(46, [ 565 | ['proposal_owner', StringSerializer], 566 | ['proposal_ids', ArraySerializer(Int64Serializer)], 567 | ['extensions', ArraySerializer(VoidSerializer)] 568 | ]) 569 | 570 | const ProposalUpdateSerializer = ObjectSerializer([ 571 | ['end_date', DateSerializer] 572 | ]) 573 | 574 | OperationSerializers.update_proposal = OperationDataSerializer(47, [ 575 | ['proposal_id', UInt64Serializer], 576 | ['creator', StringSerializer], 577 | ['daily_pay', AssetSerializer], 578 | ['subject', StringSerializer], 579 | ['permlink', StringSerializer], 580 | [ 581 | 'extensions', 582 | ArraySerializer( 583 | StaticVariantSerializer([VoidSerializer, ProposalUpdateSerializer]) 584 | ) 585 | ] 586 | ]) 587 | 588 | OperationSerializers.collateralized_convert = OperationDataSerializer(48, [ 589 | ['owner', StringSerializer], 590 | ['requestid', UInt32Serializer], 591 | ['amount', AssetSerializer] 592 | ]) 593 | 594 | OperationSerializers.recurrent_transfer = OperationDataSerializer(49, [ 595 | ['from', StringSerializer], 596 | ['to', StringSerializer], 597 | ['amount', AssetSerializer], 598 | ['memo', StringSerializer], 599 | ['recurrence', UInt16Serializer], 600 | ['executions', UInt16Serializer], 601 | ['extensions', ArraySerializer(VoidSerializer)] 602 | ]) 603 | 604 | const OperationSerializer = (buffer, operation) => { 605 | const serializer = OperationSerializers[operation[0]] 606 | if (!serializer) { 607 | throw new Error(`No serializer for operation: ${operation[0]}`) 608 | } 609 | try { 610 | serializer(buffer, operation[1]) 611 | } catch (error) { 612 | error.message = `${operation[0]}: ${error.message}` 613 | throw error 614 | } 615 | } 616 | 617 | const TransactionSerializer = ObjectSerializer([ 618 | ['ref_block_num', UInt16Serializer], 619 | ['ref_block_prefix', UInt32Serializer], 620 | ['expiration', DateSerializer], 621 | ['operations', ArraySerializer(OperationSerializer)], 622 | ['extensions', ArraySerializer(StringSerializer)] 623 | ]) 624 | 625 | const EncryptedMemoSerializer = ObjectSerializer([ 626 | ['from', PublicKeySerializer], 627 | ['to', PublicKeySerializer], 628 | ['nonce', UInt64Serializer], 629 | ['check', UInt32Serializer], 630 | ['encrypted', BinarySerializer()] 631 | ]) 632 | 633 | export const Serializer = { 634 | Array: ArraySerializer, 635 | Asset: AssetSerializer, 636 | Authority: AuthoritySerializer, 637 | Binary: BinarySerializer, 638 | Boolean: BooleanSerializer, 639 | Date: DateSerializer, 640 | FlatMap: FlatMapSerializer, 641 | Int16: Int16Serializer, 642 | Int32: Int32Serializer, 643 | Int64: Int64Serializer, 644 | Int8: Int8Serializer, 645 | Memo: EncryptedMemoSerializer, 646 | Object: ObjectSerializer, 647 | Operation: OperationSerializer, 648 | Optional: OptionalSerializer, 649 | Price: PriceSerializer, 650 | PublicKey: PublicKeySerializer, 651 | StaticVariant: StaticVariantSerializer, 652 | String: StringSerializer, 653 | Transaction: TransactionSerializer, 654 | UInt16: UInt16Serializer, 655 | UInt32: UInt32Serializer, 656 | UInt64: UInt64Serializer, 657 | UInt8: UInt8Serializer, 658 | Void: VoidSerializer 659 | } 660 | -------------------------------------------------------------------------------- /helpers/uint8Array.js: -------------------------------------------------------------------------------- 1 | export const hexToUint8Array = (hexString) => { 2 | const bytes = [] 3 | for (let i = 0; i < hexString.length; i += 2) { 4 | bytes.push(parseInt(hexString.substr(i, 2), 16)) 5 | } 6 | return new Uint8Array(bytes) 7 | } 8 | export const uint8ArrayToHex = (uint8Array) => { 9 | return Array.from(uint8Array) 10 | .map(byte => byte.toString(16).padStart(2, '0')) 11 | .join('') 12 | } 13 | -------------------------------------------------------------------------------- /helpers/utils.d.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "./PublicKey.js" 2 | 3 | /** Return null for a valid username */ 4 | export function validateUsername (username: string): null | string 5 | 6 | /** 7 | * Make bitmask filter to be used with get_account_history call 8 | * @param allowedOperations Array of operations index numbers 9 | * @example 10 | * import { call } from 'hive-tx' 11 | * import { makeBitMaskFilter, operations as op } from 'hive-tx/helpers/utils.js' 12 | * const filter = makeBitMaskFilter( 13 | * [ 14 | * op.transfer, 15 | * op.transfer_to_vesting 16 | * ] 17 | * ) 18 | * call('condenser_api.get_account_history', ['mahdiyari', -1, 1, ...filter]) 19 | * .then(res => console.log(res)) 20 | */ 21 | export function makeBitMaskFilter (allowedOperations: number[]): any[] 22 | 23 | /** List of operations and their id */ 24 | export const operations: { string: number } 25 | 26 | interface WitnessProps { 27 | account_creation_fee?: string 28 | account_subsidy_budget?: number 29 | account_subsidy_decay?: number 30 | key: PublicKey | string 31 | maximum_block_size?: number 32 | new_signing_key?: PublicKey | string | null 33 | hbd_exchange_rate?: { base: string, quote: string } 34 | hbd_interest_rate?: number 35 | url?: string 36 | } 37 | 38 | /** Needed for creating witness_set_properties operation 39 | * @example 40 | * import { buildWitnessSetProperties } from 'hive-tx/helpers/utils.js' 41 | * const owner = 'mahdiyari' 42 | * const props = { 43 | * key: 'STM1111111111111111111111111111111114T1Anm', // Required - signing key 44 | * account_creation_fee: '0.000 HIVE', // optional 45 | * account_subsidy_budget: 10000, // optional 46 | * account_subsidy_decay: 330782, // optional 47 | * maximum_block_size: 65536, // optional 48 | * hbd_interest_rate: 0, // optional 49 | * hbd_exchange_rate: { base: '0.250 HBD', quote: '1.000 HIVE' }, // optional 50 | * url: 'https://testurl', // optional 51 | * new_signing_key: "STM1111111111111111111111111111111114T1Anm" // optional 52 | * } 53 | * const witnessOps = buildWitnessSetProperties(owner, props) 54 | * const trx = new Transaction().create(witnessOps) 55 | */ 56 | export function buildWitnessSetProperties (owner: string, props: WitnessProps): string 57 | -------------------------------------------------------------------------------- /helpers/utils.js: -------------------------------------------------------------------------------- 1 | import { Serializer } from './serializer.js' 2 | import { ByteBuffer } from './ByteBuffer.js' 3 | 4 | /** Return null for a valid username */ 5 | export const validateUsername = (username) => { 6 | let suffix = 'Account name should ' 7 | if (!username) { 8 | return suffix + 'not be empty.' 9 | } 10 | const length = username.length 11 | if (length < 3) { 12 | return suffix + 'be longer.' 13 | } 14 | if (length > 16) { 15 | return suffix + 'be shorter.' 16 | } 17 | if (/\./.test(username)) { 18 | suffix = 'Each account segment should ' 19 | } 20 | const ref = username.split('.') 21 | const len = ref.length 22 | for (let i = 0; i < len; i++) { 23 | const label = ref[i] 24 | if (!/^[a-z]/.test(label)) { 25 | return suffix + 'start with a lowercase letter.' 26 | } 27 | if (!/^[a-z0-9-]*$/.test(label)) { 28 | return suffix + 'have only lowercase letters, digits, or dashes.' 29 | } 30 | if (!/[a-z0-9]$/.test(label)) { 31 | return suffix + 'end with a lowercase letter or digit.' 32 | } 33 | if (!(label.length >= 3)) { 34 | return suffix + 'be longer' 35 | } 36 | } 37 | return null 38 | } 39 | 40 | export const operations = { 41 | vote: 0, 42 | comment: 1, 43 | transfer: 2, 44 | transfer_to_vesting: 3, 45 | withdraw_vesting: 4, 46 | limit_order_create: 5, 47 | limit_order_cancel: 6, 48 | feed_publish: 7, 49 | convert: 8, 50 | account_create: 9, 51 | account_update: 10, 52 | witness_update: 11, 53 | account_witness_vote: 12, 54 | account_witness_proxy: 13, 55 | pow: 14, 56 | custom: 15, 57 | report_over_production: 16, 58 | delete_comment: 17, 59 | custom_json: 18, 60 | comment_options: 19, 61 | set_withdraw_vesting_route: 20, 62 | limit_order_create2: 21, 63 | claim_account: 22, 64 | create_claimed_account: 23, 65 | request_account_recovery: 24, 66 | recover_account: 25, 67 | change_recovery_account: 26, 68 | escrow_transfer: 27, 69 | escrow_dispute: 28, 70 | escrow_release: 29, 71 | pow2: 30, 72 | escrow_approve: 31, 73 | transfer_to_savings: 32, 74 | transfer_from_savings: 33, 75 | cancel_transfer_from_savings: 34, 76 | custom_binary: 35, 77 | decline_voting_rights: 36, 78 | reset_account: 37, 79 | set_reset_account: 38, 80 | claim_reward_balance: 39, 81 | delegate_vesting_shares: 40, 82 | account_create_with_delegation: 41, 83 | witness_set_properties: 42, 84 | account_update2: 43, 85 | create_proposal: 44, 86 | update_proposal_votes: 45, 87 | remove_proposal: 46, 88 | update_proposal: 47, 89 | collateralized_convert: 48, 90 | recurrent_transfer: 49, 91 | // virtual ops 92 | fill_convert_request: 50, 93 | author_reward: 51, 94 | curation_reward: 52, 95 | comment_reward: 53, 96 | liquidity_reward: 54, 97 | interest: 55, 98 | fill_vesting_withdraw: 56, 99 | fill_order: 57, 100 | shutdown_witness: 58, 101 | fill_transfer_from_savings: 59, 102 | hardfork: 60, 103 | comment_payout_update: 61, 104 | return_vesting_delegation: 62, 105 | comment_benefactor_reward: 63, 106 | producer_reward: 64, 107 | clear_null_account_balance: 65, 108 | proposal_pay: 66, 109 | sps_fund: 67, 110 | hardfork_hive: 68, 111 | hardfork_hive_restore: 69, 112 | delayed_voting: 70, 113 | consolidate_treasury_balance: 71, 114 | effective_comment_vote: 72, 115 | ineffective_delete_comment: 73, 116 | sps_convert: 74, 117 | expired_account_notification: 75, 118 | changed_recovery_account: 76, 119 | transfer_to_vesting_completed: 77, 120 | pow_reward: 78, 121 | vesting_shares_split: 79, 122 | account_created: 80, 123 | fill_collateralized_convert_request: 81, 124 | system_warning: 82, 125 | fill_recurrent_transfer: 83, 126 | failed_recurrent_transfer: 84, 127 | limit_order_cancelled: 85, 128 | producer_missed: 86, 129 | proposal_fee: 87, 130 | collateralized_convert_immediate_conversion: 88, 131 | escrow_approved: 89, 132 | escrow_rejected: 90, 133 | proxy_cleared: 91, 134 | declined_voting_rights: 92 135 | } 136 | 137 | /** 138 | * Make bitmask filter to be used with get_account_history call 139 | */ 140 | export const makeBitMaskFilter = (allowedOperations) => { 141 | return allowedOperations 142 | .reduce(reduceFunction, [BigInt(0), BigInt(0)]) 143 | .map((value) => 144 | (value !== BigInt(0)) ? value.toString() : null 145 | ) 146 | } 147 | const reduceFunction = ([low, high], allowedOperation) => { 148 | if (allowedOperation < 64) { 149 | return [low | (BigInt(1) << BigInt(allowedOperation)), high] 150 | } else { 151 | return [low, high | (BigInt(1) << BigInt(allowedOperation - 64))] 152 | } 153 | } 154 | 155 | /** 156 | * Needed for witness_set_properties operation 157 | * Example in utils.d.ts 158 | */ 159 | export const buildWitnessSetProperties = (owner, props) => { 160 | const data = { 161 | extensions: [], 162 | owner, 163 | props: [] 164 | } 165 | for (const key of Object.keys(props)) { 166 | let type 167 | switch (key) { 168 | case 'key': 169 | case 'new_signing_key': 170 | type = Serializer.PublicKey 171 | break 172 | case 'account_subsidy_budget': 173 | case 'account_subsidy_decay': 174 | case 'maximum_block_size': 175 | type = Serializer.UInt32 176 | break 177 | case 'hbd_interest_rate': 178 | type = Serializer.UInt16 179 | break 180 | case 'url': 181 | type = Serializer.String 182 | break 183 | case 'hbd_exchange_rate': 184 | type = Serializer.Price 185 | break 186 | case 'account_creation_fee': 187 | type = Serializer.Asset 188 | break 189 | default: 190 | throw new Error(`Unknown witness prop: ${key}`) 191 | } 192 | data.props.push([key, serialize(type, props[key])]) 193 | } 194 | data.props.sort((a, b) => a[0].localeCompare(b[0])) 195 | return ['witness_set_properties', data] 196 | } 197 | 198 | const serialize = (serializer, data) => { 199 | const buffer = new ByteBuffer( 200 | ByteBuffer.DEFAULT_CAPACITY, 201 | ByteBuffer.LITTLE_ENDIAN 202 | ) 203 | serializer(buffer, data) 204 | buffer.flip() 205 | // `props` values must be hex 206 | return buffer.toString('hex') 207 | } 208 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { PrivateKey as PK } from './helpers/PrivateKey' 2 | import { PublicKey as PubK } from './helpers/PublicKey' 3 | import { Signature as Sig } from './helpers/Signature' 4 | import { Memo as MemoType } from './helpers/memo' 5 | import {buildWitnessSetProperties, makeBitMaskFilter, validateUsername, operations } from './helpers/utils' 6 | 7 | declare module 'hive-tx' 8 | 9 | export class PrivateKey extends PK {} 10 | export class PublicKey extends PubK {} 11 | export class Signature extends Sig {} 12 | 13 | /** Transaction for Hive blockchain */ 14 | export class Transaction { 15 | transaction: { 16 | expiration: string, 17 | extensions: any[], 18 | operations: any[], 19 | ref_block_num: number, 20 | ref_block_prefix: number 21 | } 22 | 23 | signedTransaction: { 24 | expiration: string, 25 | extensions: any[], 26 | operations: any[], 27 | ref_block_num: number, 28 | ref_block_prefix: number 29 | signatures: string[] 30 | } | undefined 31 | 32 | constructor(trx?: object) 33 | 34 | /** Broadcast the signed transaction. 35 | * @param {number}timeout - optional - default 5 seconds 36 | * @param {number}retry - optional - default 5 times 37 | */ 38 | broadcast(timeout?: number, retry?: number): Promise<{ 39 | id: number 40 | jsonrpc: string 41 | result: { tx_id: string; status: string } 42 | } | {error: object}> 43 | 44 | /** Create the transaction by operations 45 | * @param {[Array]} operations 46 | * @param {Number} expiration Optional - Default 60 seconds 47 | */ 48 | create( 49 | operations: any[], 50 | expiration?: number 51 | ): Promise<{ 52 | expiration: string 53 | extensions: any[] 54 | operations: any[] 55 | ref_block_num: number 56 | ref_block_prefix: number 57 | }> 58 | 59 | /** Sign the transaction by key or keys[] (supports multi signature). 60 | * It is also possible to sign with one key at a time for multi signature. 61 | * @param {PrivateKey|[PrivateKey]} keys single key or multiple keys in array 62 | */ 63 | sign(keys: PrivateKey | PrivateKey[]): { 64 | expiration: string 65 | extensions: any[] 66 | operations: any[] 67 | ref_block_num: number 68 | ref_block_prefix: number 69 | signatures: string[] 70 | } 71 | 72 | /** Return the transaction hash which can be used to verify against a signature */ 73 | digest(): { 74 | digest: Uint8Array, 75 | txId: string 76 | } 77 | 78 | /** 79 | * Add a signature to already created transaction. You can add multiple signatures to one transaction but one at a time. 80 | * This method is used when you sign your transaction with other tools instead of built-in .sign() method. 81 | */ 82 | addSignature(signature: string): { 83 | expiration: string 84 | extensions: any[] 85 | operations: any[] 86 | ref_block_num: number 87 | ref_block_prefix: number 88 | signatures: string[] 89 | } 90 | } 91 | 92 | /** hive-tx configurations */ 93 | export const config: { 94 | address_prefix: string 95 | chain_id: string 96 | node: string[] | string, 97 | axiosAdapter: null | 'xhr' | 'http' | any, 98 | timeout: number 99 | retry: number 100 | healthcheckInterval: number 101 | } 102 | 103 | /** 104 | * Make calls to a hive node 105 | * @param {string}method - e.g. condenser_api.get_dynamic_global_properties 106 | * @param {[any]|object}params - array or object 107 | * @param {number}timeout - optional - default 5 seconds 108 | * @param {number}retry - optional - default 5 times 109 | */ 110 | export function call(method: string, params?: any[] | object, timeout?: number, retry?: number): Promise 111 | 112 | export const Memo: MemoType 113 | 114 | export const utils = { 115 | makeBitMaskFilter, 116 | validateUsername, 117 | operations, 118 | buildWitnessSetProperties 119 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { signTransaction, transactionDigest } from './transactions/signTransaction.js' 2 | import { createTransaction } from './transactions/createTransaction.js' 3 | import { broadcastTransaction } from './transactions/broadcastTransaction.js' 4 | import { PrivateKey } from './helpers/PrivateKey.js' 5 | import { PublicKey } from './helpers/PublicKey.js' 6 | import { Signature } from './helpers/Signature.js' 7 | import { call } from './helpers/call.js' 8 | import { config } from './config.js' 9 | import { Memo } from './helpers/memo.js' 10 | import * as utils from './helpers/utils.js' 11 | 12 | /** Transaction for Hive blockchain */ 13 | class Transaction { 14 | /** A transaction object could be passed or created later 15 | * @param {{}} trx Object of transaction - Optional 16 | */ 17 | constructor (trx = null) { 18 | this.created = true 19 | if (!trx) { 20 | this.created = false 21 | } 22 | this.transaction = trx 23 | } 24 | 25 | /** Create the transaction by operations 26 | * @param {[Array]} operations 27 | * @param {Number} expiration Optional - Default 60 seconds 28 | */ 29 | async create (operations, expiration = 60) { 30 | this.transaction = await createTransaction(operations, expiration) 31 | this.created = true 32 | return this.transaction 33 | } 34 | 35 | /** Sign the transaction by key or keys[] (supports multi signature). 36 | * It is also possible to sign with one key at a time for multi signature. 37 | * @param {PrivateKey|[PrivateKey]} keys single key or multiple keys in array 38 | */ 39 | sign (keys) { 40 | if (!this.created) { 41 | throw new Error('First create a transaction by .create(operations)') 42 | } 43 | if (this.signedTransaction) { 44 | const { signedTransaction, txId } = signTransaction(this.signedTransaction, keys) 45 | this.signedTransaction = signedTransaction 46 | this.txId = txId 47 | } else { 48 | const { signedTransaction, txId } = signTransaction(this.transaction, keys) 49 | this.signedTransaction = signedTransaction 50 | this.txId = txId 51 | } 52 | return this.signedTransaction 53 | } 54 | 55 | /** Broadcast the signed transaction. */ 56 | async broadcast (timeout = 5, retry = 5) { 57 | if (!this.created) { 58 | throw new Error('First create a transaction by .create(operations)') 59 | } 60 | if (!this.signedTransaction) { 61 | throw new Error('First sign the transaction by .sign(keys)') 62 | } 63 | const result = await broadcastTransaction(this.signedTransaction, timeout, retry) 64 | if (result.error) { 65 | // When we retry, we might have already broadcasted the transaction 66 | // So catch duplicate trx error and return trx id 67 | if (result.error.message.includes('Duplicate transaction check failed')) { 68 | return { 69 | id: 1, 70 | jsonrpc: '2.0', 71 | result: { tx_id: this.txId, status: 'unkown' } 72 | } 73 | } 74 | return result 75 | } 76 | if (!this.txId) { 77 | this.txId = this.digest().txId 78 | } 79 | return { 80 | id: 1, 81 | jsonrpc: '2.0', 82 | result: { tx_id: this.txId, status: 'unkown' } 83 | } 84 | } 85 | 86 | /** Return the transaction hash which can be used to verify against a signature */ 87 | digest () { 88 | if (!this.created) { 89 | throw new Error('First create a transaction by .create(operations)') 90 | } 91 | return transactionDigest(this.transaction) 92 | } 93 | 94 | /** 95 | * Add a signature to already created transaction. You can add multiple signatures to one transaction but one at a time. 96 | * This method is used when you sign your transaction with other tools instead of built-in .sign() method. 97 | */ 98 | addSignature (signature = '') { 99 | if (!this.created) { 100 | throw new Error('First create a transaction by .create(operations)') 101 | } 102 | if (typeof signature !== 'string') { 103 | throw new Error('Signature must be string') 104 | } 105 | if (signature.length !== 130) { 106 | throw new Error('Signature must be 130 characters long') 107 | } 108 | if (!this.signedTransaction) { 109 | this.signedTransaction = { ...this.transaction } 110 | } 111 | if (Array.isArray(this.signedTransaction.signature)) { 112 | this.signedTransaction.signatures.push(signature) 113 | } else { 114 | this.signedTransaction.signatures = [signature] 115 | } 116 | return this.signedTransaction 117 | } 118 | } 119 | 120 | export { Transaction, PrivateKey, call, config, PublicKey, Signature, Memo, utils } 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hive-tx", 3 | "version": "6.1.3", 4 | "description": "Create, sign, and broadcast transactions on Hive blockchain", 5 | "type": "module", 6 | "module": "./index.js", 7 | "exports": "./index.js", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "build:browser": "webpack --config webpack.config.cjs --mode=production" 11 | }, 12 | "keywords": [ 13 | "hive", 14 | "tx", 15 | "transaction", 16 | "sign", 17 | "broadcast", 18 | "blockchain", 19 | "hive-tx" 20 | ], 21 | "author": "Mahdi Yari", 22 | "license": "MIT", 23 | "dependencies": { 24 | "@noble/ciphers": "^1.2.1", 25 | "@noble/curves": "^1.8.1", 26 | "@noble/hashes": "^1.7.1", 27 | "axios": "^1.8.4", 28 | "bs58": "6.0.0" 29 | }, 30 | "homepage": "https://github.com/mahdiyari/hive-tx-js", 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/mahdiyari/hive-tx-js.git" 34 | }, 35 | "devDependencies": { 36 | "process": "0.11.10", 37 | "standard": "17.1.0", 38 | "webpack": "^5.97.1", 39 | "webpack-cli": "^6.0.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /transactions/broadcastTransaction.js: -------------------------------------------------------------------------------- 1 | import { config } from '../config.js' 2 | import { call } from '../helpers/call.js' 3 | 4 | /** Broadcast signed transaction */ 5 | export const broadcastTransaction = async (signedTransaction, timeout = config.timeout, retry = config.retry) => { 6 | const result = await call('condenser_api.broadcast_transaction', [ 7 | signedTransaction 8 | ], timeout, retry) 9 | return result 10 | } 11 | -------------------------------------------------------------------------------- /transactions/createTransaction.js: -------------------------------------------------------------------------------- 1 | import { getGlobalProps } from '../helpers/globalProps.js' 2 | import { hexToUint8Array } from '../helpers/uint8Array.js' 3 | 4 | /** Create transaction by operations */ 5 | export const createTransaction = async (operations, exp) => { 6 | const expireTime = exp ? 1000 * exp : 1000 * 60 7 | const props = await getGlobalProps() 8 | const refBlockNum = props.head_block_number & 0xffff 9 | const uintArray = hexToUint8Array(props.head_block_id) 10 | const dataView = new DataView(uintArray.buffer) 11 | const refBlockPrefix = dataView.getUint32(4, true) 12 | const expiration = new Date(Date.now() + expireTime) 13 | .toISOString() 14 | .slice(0, -5) 15 | const extensions = [] 16 | return { 17 | expiration, 18 | extensions, 19 | operations, 20 | ref_block_num: refBlockNum, 21 | ref_block_prefix: refBlockPrefix 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /transactions/signTransaction.js: -------------------------------------------------------------------------------- 1 | import { ByteBuffer } from '../helpers/ByteBuffer.js' 2 | // import ByteBuffer2 from 'bytebuffer-hex-custom' 3 | import { config } from '../config.js' 4 | import { sha256 } from '../helpers/crypto.js' 5 | import { Serializer } from '../helpers/serializer.js' 6 | import { hexToUint8Array, uint8ArrayToHex } from '../helpers/uint8Array.js' 7 | 8 | const CHAIN_ID = hexToUint8Array(config.chain_id) 9 | 10 | /** 11 | * Sign a transaction by keys (supports multi signature) 12 | * @param transaction - transaction to be signed 13 | * @param keys - Array of keys 14 | */ 15 | export const signTransaction = (transaction, keys) => { 16 | const { digest, txId } = transactionDigest(transaction, CHAIN_ID) 17 | const signedTransaction = { ...transaction } 18 | if (!signedTransaction.signatures) { 19 | signedTransaction.signatures = [] 20 | } 21 | if (!Array.isArray(keys)) { 22 | keys = [keys] 23 | } 24 | for (const key of keys) { 25 | const signature = key.sign(digest) 26 | signedTransaction.signatures.push(signature.customToString()) 27 | } 28 | 29 | return { signedTransaction, txId } 30 | } 31 | 32 | /** Serialize transaction */ 33 | export const transactionDigest = (transaction, chainId = CHAIN_ID) => { 34 | const buffer = new ByteBuffer( 35 | ByteBuffer.DEFAULT_CAPACITY, 36 | ByteBuffer.LITTLE_ENDIAN 37 | ) 38 | // const buffer2 = new ByteBuffer2( 39 | // ByteBuffer2.DEFAULT_CAPACITY, 40 | // ByteBuffer2.LITTLE_ENDIAN 41 | // ) 42 | const temp = { ...transaction } 43 | // const temp2 = { ...transaction } 44 | try { 45 | Serializer.Transaction(buffer, temp) 46 | // Serializer.Transaction(buffer2, temp2) 47 | } catch (cause) { 48 | throw new Error('Unable to serialize transaction: ' + cause) 49 | } 50 | buffer.flip() 51 | // console.log(buffer.toBuffer()) 52 | // console.log(buffer2.toBuffer()) 53 | const transactionData = new Uint8Array(buffer.toBuffer()) 54 | // console.log(uint8ArrayToHex(transactionData)) 55 | const txId = uint8ArrayToHex(sha256(transactionData)).slice(0, 40) 56 | const digest = sha256(new Uint8Array([...chainId, ...transactionData])) 57 | return { digest, txId } 58 | } 59 | -------------------------------------------------------------------------------- /webpack.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './index.js', 6 | output: { 7 | filename: 'hive-tx.min.js', 8 | path: path.resolve(__dirname, 'dist'), 9 | globalObject: 'this', 10 | library: { 11 | name: 'hiveTx', 12 | type: 'umd' 13 | } 14 | }, 15 | devtool: 'source-map', 16 | resolve: { 17 | fallback: { 18 | 'process/browser': require.resolve('process/browser') 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.ProvidePlugin({ 23 | process: 'process/browser' 24 | }), 25 | new webpack.NormalModuleReplacementPlugin(/node:/, (resource) => { 26 | resource.request = resource.request.replace(/^node:/, '') 27 | }) 28 | ] 29 | } 30 | --------------------------------------------------------------------------------