├── .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 |
--------------------------------------------------------------------------------