- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
368 lines
10 KiB
TypeScript
368 lines
10 KiB
TypeScript
import * as Bytes from './Bytes.js'
|
|
import * as Errors from './Errors.js'
|
|
import * as Hex from './Hex.js'
|
|
import * as Cursor from './internal/cursor.js'
|
|
import type { ExactPartial, RecursiveArray } from './internal/types.js'
|
|
|
|
/**
|
|
* Decodes a Recursive-Length Prefix (RLP) value into a {@link ox#Bytes.Bytes} value.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { Rlp } from 'ox'
|
|
* Rlp.toBytes('0x8b68656c6c6f20776f726c64')
|
|
* // Uint8Array([139, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])
|
|
* ```
|
|
*
|
|
* @param value - The value to decode.
|
|
* @returns The decoded {@link ox#Bytes.Bytes} value.
|
|
*/
|
|
export function toBytes(
|
|
value: Bytes.Bytes | Hex.Hex,
|
|
): RecursiveArray<Bytes.Bytes> {
|
|
return to(value, 'Bytes')
|
|
}
|
|
|
|
export declare namespace toBytes {
|
|
type ErrorType = to.ErrorType
|
|
}
|
|
|
|
/**
|
|
* Decodes a Recursive-Length Prefix (RLP) value into a {@link ox#Hex.Hex} value.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { Rlp } from 'ox'
|
|
* Rlp.toHex('0x8b68656c6c6f20776f726c64')
|
|
* // 0x68656c6c6f20776f726c64
|
|
* ```
|
|
*
|
|
* @param value - The value to decode.
|
|
* @returns The decoded {@link ox#Hex.Hex} value.
|
|
*/
|
|
export function toHex(value: Bytes.Bytes | Hex.Hex): RecursiveArray<Hex.Hex> {
|
|
return to(value, 'Hex')
|
|
}
|
|
|
|
export declare namespace toHex {
|
|
type ErrorType = to.ErrorType
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Internal
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/** @internal */
|
|
export function to<
|
|
value extends Bytes.Bytes | Hex.Hex,
|
|
to extends 'Hex' | 'Bytes',
|
|
>(value: value, to: to | 'Hex' | 'Bytes'): to.ReturnType<to> {
|
|
const to_ = to ?? (typeof value === 'string' ? 'Hex' : 'Bytes')
|
|
|
|
const bytes = (() => {
|
|
if (typeof value === 'string') {
|
|
if (value.length > 3 && value.length % 2 !== 0)
|
|
throw new Hex.InvalidLengthError(value)
|
|
return Bytes.fromHex(value)
|
|
}
|
|
return value as Bytes.Bytes
|
|
})()
|
|
|
|
const cursor = Cursor.create(bytes, {
|
|
recursiveReadLimit: Number.POSITIVE_INFINITY,
|
|
})
|
|
const result = decodeRlpCursor(cursor, to_)
|
|
|
|
return result as to.ReturnType<to>
|
|
}
|
|
|
|
/** @internal */
|
|
export declare namespace to {
|
|
type ReturnType<to extends 'Hex' | 'Bytes' = 'Hex' | 'Bytes'> =
|
|
| (to extends 'Bytes' ? RecursiveArray<Bytes.Bytes> : never)
|
|
| (to extends 'Hex' ? RecursiveArray<Hex.Hex> : never)
|
|
|
|
type ErrorType =
|
|
| Bytes.fromHex.ErrorType
|
|
| decodeRlpCursor.ErrorType
|
|
| Cursor.create.ErrorType
|
|
| Hex.InvalidLengthError
|
|
| Errors.GlobalErrorType
|
|
}
|
|
|
|
/** @internal */
|
|
|
|
/** @internal */
|
|
export function decodeRlpCursor<to extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
cursor: Cursor.Cursor,
|
|
to: to | 'Hex' | 'Bytes' | undefined = 'Hex',
|
|
): decodeRlpCursor.ReturnType<to> {
|
|
if (cursor.bytes.length === 0)
|
|
return (
|
|
to === 'Hex' ? Hex.fromBytes(cursor.bytes) : cursor.bytes
|
|
) as decodeRlpCursor.ReturnType<to>
|
|
|
|
const prefix = cursor.readByte()
|
|
if (prefix < 0x80) cursor.decrementPosition(1)
|
|
|
|
// bytes
|
|
if (prefix < 0xc0) {
|
|
const length = readLength(cursor, prefix, 0x80)
|
|
const bytes = cursor.readBytes(length)
|
|
return (
|
|
to === 'Hex' ? Hex.fromBytes(bytes) : bytes
|
|
) as decodeRlpCursor.ReturnType<to>
|
|
}
|
|
|
|
// list
|
|
const length = readLength(cursor, prefix, 0xc0)
|
|
return readList(cursor, length, to) as {} as decodeRlpCursor.ReturnType<to>
|
|
}
|
|
|
|
/** @internal */
|
|
export declare namespace decodeRlpCursor {
|
|
type ReturnType<to extends 'Hex' | 'Bytes' = 'Hex'> = to.ReturnType<to>
|
|
type ErrorType =
|
|
| Hex.fromBytes.ErrorType
|
|
| readLength.ErrorType
|
|
| readList.ErrorType
|
|
| Errors.GlobalErrorType
|
|
}
|
|
|
|
/** @internal */
|
|
export function readLength(
|
|
cursor: Cursor.Cursor,
|
|
prefix: number,
|
|
offset: number,
|
|
) {
|
|
if (offset === 0x80 && prefix < 0x80) return 1
|
|
if (prefix <= offset + 55) return prefix - offset
|
|
if (prefix === offset + 55 + 1) return cursor.readUint8()
|
|
if (prefix === offset + 55 + 2) return cursor.readUint16()
|
|
if (prefix === offset + 55 + 3) return cursor.readUint24()
|
|
if (prefix === offset + 55 + 4) return cursor.readUint32()
|
|
throw new Errors.BaseError('Invalid RLP prefix')
|
|
}
|
|
|
|
/** @internal */
|
|
export declare namespace readLength {
|
|
type ErrorType = Errors.BaseError | Errors.GlobalErrorType
|
|
}
|
|
|
|
/** @internal */
|
|
export function readList<to extends 'Hex' | 'Bytes'>(
|
|
cursor: Cursor.Cursor,
|
|
length: number,
|
|
to: to | 'Hex' | 'Bytes',
|
|
) {
|
|
const position = cursor.position
|
|
const value: decodeRlpCursor.ReturnType<to>[] = []
|
|
while (cursor.position - position < length)
|
|
value.push(decodeRlpCursor(cursor, to))
|
|
return value
|
|
}
|
|
|
|
/** @internal */
|
|
export declare namespace readList {
|
|
type ErrorType = Errors.GlobalErrorType
|
|
}
|
|
|
|
type Encodable = {
|
|
length: number
|
|
encode(cursor: Cursor.Cursor): void
|
|
}
|
|
|
|
/**
|
|
* Encodes a {@link ox#Bytes.Bytes} or {@link ox#Hex.Hex} value into a Recursive-Length Prefix (RLP) value.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { Bytes, Rlp } from 'ox'
|
|
*
|
|
* Rlp.from('0x68656c6c6f20776f726c64', { as: 'Hex' })
|
|
* // @log: 0x8b68656c6c6f20776f726c64
|
|
*
|
|
* Rlp.from(Bytes.from([139, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), { as: 'Bytes' })
|
|
* // @log: Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])
|
|
* ```
|
|
*
|
|
* @param value - The {@link ox#Bytes.Bytes} or {@link ox#Hex.Hex} value to encode.
|
|
* @param options - Options.
|
|
* @returns The RLP value.
|
|
*/
|
|
export function from<as extends 'Hex' | 'Bytes'>(
|
|
value: RecursiveArray<Bytes.Bytes> | RecursiveArray<Hex.Hex>,
|
|
options: from.Options<as>,
|
|
): from.ReturnType<as> {
|
|
const { as } = options
|
|
|
|
const encodable = getEncodable(value)
|
|
const cursor = Cursor.create(new Uint8Array(encodable.length))
|
|
encodable.encode(cursor)
|
|
|
|
if (as === 'Hex') return Hex.fromBytes(cursor.bytes) as from.ReturnType<as>
|
|
return cursor.bytes as from.ReturnType<as>
|
|
}
|
|
|
|
export declare namespace from {
|
|
type Options<as extends 'Hex' | 'Bytes'> = {
|
|
/** The type to convert the RLP value to. */
|
|
as: as | 'Hex' | 'Bytes'
|
|
}
|
|
|
|
type ReturnType<as extends 'Hex' | 'Bytes'> =
|
|
| (as extends 'Bytes' ? Bytes.Bytes : never)
|
|
| (as extends 'Hex' ? Hex.Hex : never)
|
|
|
|
type ErrorType =
|
|
| Cursor.create.ErrorType
|
|
| Hex.fromBytes.ErrorType
|
|
| Bytes.fromHex.ErrorType
|
|
| Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* Encodes a {@link ox#Bytes.Bytes} value into a Recursive-Length Prefix (RLP) value.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { Bytes, Rlp } from 'ox'
|
|
*
|
|
* Rlp.fromBytes(Bytes.from([139, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]))
|
|
* // @log: Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])
|
|
* ```
|
|
*
|
|
* @param bytes - The {@link ox#Bytes.Bytes} value to encode.
|
|
* @param options - Options.
|
|
* @returns The RLP value.
|
|
*/
|
|
export function fromBytes<as extends 'Hex' | 'Bytes' = 'Bytes'>(
|
|
bytes: RecursiveArray<Bytes.Bytes>,
|
|
options: fromBytes.Options<as> = {},
|
|
): fromBytes.ReturnType<as> {
|
|
const { as = 'Bytes' } = options
|
|
return from(bytes, { as }) as never
|
|
}
|
|
|
|
export declare namespace fromBytes {
|
|
type Options<as extends 'Hex' | 'Bytes' = 'Bytes'> = ExactPartial<
|
|
from.Options<as>
|
|
>
|
|
|
|
type ReturnType<as extends 'Hex' | 'Bytes' = 'Bytes'> = from.ReturnType<as>
|
|
|
|
type ErrorType = from.ErrorType | Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* Encodes a {@link ox#Hex.Hex} value into a Recursive-Length Prefix (RLP) value.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { Rlp } from 'ox'
|
|
*
|
|
* Rlp.fromHex('0x68656c6c6f20776f726c64')
|
|
* // @log: 0x8b68656c6c6f20776f726c64
|
|
* ```
|
|
*
|
|
* @param hex - The {@link ox#Hex.Hex} value to encode.
|
|
* @param options - Options.
|
|
* @returns The RLP value.
|
|
*/
|
|
export function fromHex<as extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
hex: RecursiveArray<Hex.Hex>,
|
|
options: fromHex.Options<as> = {},
|
|
): fromHex.ReturnType<as> {
|
|
const { as = 'Hex' } = options
|
|
return from(hex, { as }) as never
|
|
}
|
|
|
|
export declare namespace fromHex {
|
|
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = ExactPartial<
|
|
from.Options<as>
|
|
>
|
|
|
|
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex'> = from.ReturnType<as>
|
|
|
|
type ErrorType = from.ErrorType | Errors.GlobalErrorType
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Internal
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function getEncodable(
|
|
bytes: RecursiveArray<Bytes.Bytes> | RecursiveArray<Hex.Hex>,
|
|
): Encodable {
|
|
if (Array.isArray(bytes))
|
|
return getEncodableList(bytes.map((x) => getEncodable(x)))
|
|
return getEncodableBytes(bytes as any)
|
|
}
|
|
|
|
function getEncodableList(list: Encodable[]): Encodable {
|
|
const bodyLength = list.reduce((acc, x) => acc + x.length, 0)
|
|
|
|
const sizeOfBodyLength = getSizeOfLength(bodyLength)
|
|
const length = (() => {
|
|
if (bodyLength <= 55) return 1 + bodyLength
|
|
return 1 + sizeOfBodyLength + bodyLength
|
|
})()
|
|
|
|
return {
|
|
length,
|
|
encode(cursor: Cursor.Cursor) {
|
|
if (bodyLength <= 55) {
|
|
cursor.pushByte(0xc0 + bodyLength)
|
|
} else {
|
|
cursor.pushByte(0xc0 + 55 + sizeOfBodyLength)
|
|
if (sizeOfBodyLength === 1) cursor.pushUint8(bodyLength)
|
|
else if (sizeOfBodyLength === 2) cursor.pushUint16(bodyLength)
|
|
else if (sizeOfBodyLength === 3) cursor.pushUint24(bodyLength)
|
|
else cursor.pushUint32(bodyLength)
|
|
}
|
|
for (const { encode } of list) {
|
|
encode(cursor)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
function getEncodableBytes(bytesOrHex: Bytes.Bytes | Hex.Hex): Encodable {
|
|
const bytes =
|
|
typeof bytesOrHex === 'string' ? Bytes.fromHex(bytesOrHex) : bytesOrHex
|
|
|
|
const sizeOfBytesLength = getSizeOfLength(bytes.length)
|
|
const length = (() => {
|
|
if (bytes.length === 1 && bytes[0]! < 0x80) return 1
|
|
if (bytes.length <= 55) return 1 + bytes.length
|
|
return 1 + sizeOfBytesLength + bytes.length
|
|
})()
|
|
|
|
return {
|
|
length,
|
|
encode(cursor: Cursor.Cursor) {
|
|
if (bytes.length === 1 && bytes[0]! < 0x80) {
|
|
cursor.pushBytes(bytes)
|
|
} else if (bytes.length <= 55) {
|
|
cursor.pushByte(0x80 + bytes.length)
|
|
cursor.pushBytes(bytes)
|
|
} else {
|
|
cursor.pushByte(0x80 + 55 + sizeOfBytesLength)
|
|
if (sizeOfBytesLength === 1) cursor.pushUint8(bytes.length)
|
|
else if (sizeOfBytesLength === 2) cursor.pushUint16(bytes.length)
|
|
else if (sizeOfBytesLength === 3) cursor.pushUint24(bytes.length)
|
|
else cursor.pushUint32(bytes.length)
|
|
cursor.pushBytes(bytes)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
function getSizeOfLength(length: number) {
|
|
if (length < 2 ** 8) return 1
|
|
if (length < 2 ** 16) return 2
|
|
if (length < 2 ** 24) return 3
|
|
if (length < 2 ** 32) return 4
|
|
throw new Errors.BaseError('Length is too large.')
|
|
}
|