import { Err , ErrorCode } from "./error" ; import { toByteArray , fromByteArray , byteLength , isBase64 } from "./base64" ; import { upgrade , downgrade } from "./migrations" ; export { bytesToBase32 , base32ToBytes } from "./base32" ; export interface SerializationOptions { property : string ; toProperty : string ; exclude : boolean ; arrayDeserializeIndividually : boolean ; fromRaw : ( raw : any ) => any ; toRaw : ( val : any , version ? : string ) => any ; } function registerSerializationOptions ( proto , property , opts ) { if ( ! proto . hasOwnProperty ( "_propertySerializationOptions" ) ) { const parentOptions = proto . _propertySerializationOptions || [ ] ; proto . _propertySerializationOptions = parentOptions ? [ ... parentOptions ] : [ ] ; } proto . _propertySerializationOptions . unshift ( Object . assign ( { property , toProperty : property , exclude : false , arrayDeserializeIndividually : true , toRaw : ( ) => { } , fromRaw : ( ) => { } } , opts ) ) ; } /**
 * Decorator for defining request handler methods
 */ export function AsSerializable ( cls , toProperty ? ) { return ( proto , prop ) => registerSerializationOptions ( proto , prop , { toProperty : toProperty || prop , toRaw : ( val , version ? ) => val . toRaw ( version ) , fromRaw : ( raw ) => new cls ( ) . fromRaw ( raw ) } ) ; } export function AsBytes ( toProperty ? ) { return ( proto , prop ) => registerSerializationOptions ( proto , prop , { toProperty : toProperty || prop , toRaw : ( val ) => bytesToBase64 ( val ) , fromRaw : ( raw ) => base64ToBytes ( raw ) } ) ; } export function AsSet ( toProperty ? ) { return ( proto , prop ) => registerSerializationOptions ( proto , prop , { toProperty : toProperty || prop , arrayDeserializeIndividually : false , toRaw : ( val ) => [ ... val ] , fromRaw : ( raw ) => new Set ( raw ) } ) ; } export function AsDate ( toProperty ? ) { return ( proto , prop ) => registerSerializationOptions ( proto , prop , { toProperty : toProperty || prop , toRaw : ( val ) => { try { return val . toISOString ( ) ; } catch ( e ) { return null ; } } , fromRaw : ( raw ) => new Date ( raw ) } ) ; } export function Exclude ( ) { return ( proto , prop ) => registerSerializationOptions ( proto , prop , { exclude : true } ) ; } export function Serialize ( opts ) { return ( proto , prop ) => registerSerializationOptions ( proto , prop , opts ) ; } example export class Serializable { /**
     * A string representing the objects "type", useful for segmenting storage,
     * among other things. Defaults to the lowercase class name, but can be
     * overwritten by subclasses
     */ get kind ( ) : string { return this . constructor . name . toLowerCase ( ) ; } _propertySerializationOptions ! : SerializationOptions [ ] ; /**
     * This is called during deserialization and should verify that all
     * properties have been populated with values of the correct type.
     * Subclasses should implement this method based on their class structure.
     */ validate ( ) { return true ; } /**
     * Creates a raw javascript object representation of the class, which
     * can be used for storage or data transmission. Also handles "downgrading" to previous
     * versions. Use [[_toRaw]] for subclass-specific behavior.
     */ toRaw ( version ? ) { let raw = this . _toRaw ( version ) ; raw . kind = this . kind ; raw = downgrade ( this . kind , raw , version ) ; return raw ; } /**
     * Restores propertiers from a raw object of the same form generated by
     * [[toRaw]]. The base implementation blindly copies over values from the
     * raw object via `Object.assign` so subclasses should explictly process
     * any propertyies that need special treatment.
     *
     * Also takes are of validation and "upgrading" in case the raw object
     * has an old version. Use the protected [[_fromRaw]] method to implement
     * subclass-specific behavior.
     */ fromRaw ( raw ) { raw = upgrade ( this . kind , raw ) ; this . _fromRaw ( raw ) ; try { if ( ! this . validate ( ) ) { console . log ( "failed to validate" , this . kind , raw ) ; throw new Err ( ErrorCode . ENCODING_ERROR ) ; } } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR ) ; } return this ; } /**
     * Returns a JSON serialization of the object
     */ toJSON ( ) { return JSON . stringify ( this . toRaw ( ) ) ; } /**
     * Deserializes the object from a JSON string
     */ fromJSON ( json ) { return this . fromRaw ( JSON . parse ( json ) ) ; } /**
     * Returns a serialization of the object in form of a byte array
     */ toBytes ( ) { return stringToBytes ( this . toJSON ( ) ) ; } /**
     * Deserializes the object from a byte array
     */ fromBytes ( bytes ) { return this . fromJSON ( bytesToString ( bytes ) ) ; } /**
     * Creates a deep clone of the object
     */ clone ( ) { return new this . constructor ( ) . fromRaw ( this . toRaw ( ) ) ; } /**
     * Transform this object into a raw javascript object used for
     * serialization.  The default implementation simply copies all iterable
     * properties not included in the [[exlude]] array and calls [[toRaw]] on
     * any properties that are themselfes instances of [[Serializable]].  This
     * method should be overwritten by subclasses if certain properties require
     * special treatment.
     */ protected _toRaw ( version ) { let raw = { } as any ; for ( const [ prop , val ] of Object . entries ( this ) ) { const opts = this . _propertySerializationOptions && this . _propertySerializationOptions . find ( opts => opts . property === prop ) ; if ( prop . startsWith ( "_" ) || ( opts && opts . exclude ) ) { continue ; } if ( opts && typeof val !== "undefined" && val !== null ) { raw [ opts . property ] = Array . isArray ( val ) ? val . map ( v => opts . toRaw ( v , version ) ) : opts . toRaw ( val , version ) ; } else { raw [ prop ] = val ; } } return raw ; } /**
     * Restore values from a raw object. The default implementation simply copies over
     * all iterable properties from the base object. Overwrite this method for properties
     * that require special treatment
     */ protected _fromRaw ( raw ) { for ( const [ prop , val ] of Object . entries ( raw ) ) { if ( prop === "kind" ) { continue ; } const opts = this . _propertySerializationOptions && this . _propertySerializationOptions . find ( opts => opts . toProperty === prop ) ; if ( ! opts && ! this . hasOwnProperty ( prop ) ) { continue ; } if ( opts && typeof val !== "undefined" && val !== null ) { this [ opts . property ] = Array . isArray ( val ) && opts . arrayDeserializeIndividually ? val . map ( v => opts . fromRaw ( v ) ) : opts . fromRaw ( val ) ; } else { this [ prop ] = val ; } } } } /**
 * Generic type representing the constructor of a class extending [[Serializable]]
 */ export type SerializableConstructor = new ( ... args ) => Serializable ; /**
 * Creates a string from a raw javascript object
 */ export function marshal ( obj ) { try { return JSON . stringify ( obj ) ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } /**
 * Creates a raw javascript object from a string
 */ export function unmarshal ( str ) { try { return JSON . parse ( str ) ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } export { isBase64 } ; /**
 * Converts a byte array to a base64 string
 */ export function bytesToBase64 ( inp , urlSafe = true ) { try { return fromByteArray ( inp , urlSafe ) ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } /**
 * Converts a base64 string to a byte array
 */ export function base64ToBytes ( inp ) { try { return toByteArray ( inp ) ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } /**
 * Converts a utf-8 string to a byte array
 */ export function stringToBytes ( str ) { try { return new TextEncoder ( ) . encode ( str ) ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } /**
 * Converts a byte array to an utf-8 string
 */ export function bytesToString ( bytes , encoding = "utf-8" ) { try { return new TextDecoder ( encoding ) . decode ( bytes ) ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } /**
 * Converts a utf-8 string to its base64 representation
 */ export function stringToBase64 ( str , urlSafe = true ) { const bytes = stringToBytes ( str ) ; return bytesToBase64 ( bytes , urlSafe ) ; } /**
 * Converts the base64 representation of a utf-a string to it's original representation
 */ export function base64ToString ( inp ) { const bytes = base64ToBytes ( inp ) ; return bytesToString ( bytes ) ; } /**
 * Returns the byte length of a base64 string
 */ export function base64ByteLength ( inp ) { return byteLength ( inp ) ; } /**
 * Converts a hex string to a byte array
 */ export function hexToBytes ( str ) { try { const bytes = new Uint8Array ( str . length / 2 ) ; for ( let i = 0 ; i < bytes . length ; i ++ ) { bytes [ i ] = parseInt ( str . substring ( i * 2 , i * 2 + 2 ) , 16 ) ; } return bytes ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } /**
 * Converts a byte array to its hexadecimal representation
 */ export function bytesToHex ( bytes ) { try { let str = "" ; for ( const b of bytes ) { const s = b . toString ( 16 ) ; str += s . length == 1 ? "0" + s : s ; } return str ; } catch ( e ) { throw new Err ( ErrorCode . ENCODING_ERROR , e . toString ( ) ) ; } } /**
 * Converts a base64 string to its hexadecimal representation
 */ export function base64ToHex ( b64 ) { return bytesToHex ( base64ToBytes ( b64 ) ) ; } /**
 * Converts a hex string to its base64 representation
 */ export function hexToBase64 ( hex ) { return bytesToBase64 ( hexToBytes ( hex ) ) ; } export function numToBytes ( num ) { return hexToBytes ( num . toString ( 16 ) . padStart ( 16 , "0" ) ) ; } export function bytesToNum ( bytes ) { return parseInt ( bytesToHex ( bytes ) , 16 ) ; } /**
 * Concatenates a number of Uint8Arrays to a single array
 */ export function concatBytes ( arrs , delimiter ? ) { let length = arrs . reduce ( ( len , arr ) => len + arr . length , 0 ) ; if ( typeof delimiter !== "undefined" ) { length += arrs . length - 1 ; } const res = new Uint8Array ( length ) ; let offset = 0 ; for ( const arr of arrs ) { res . set ( arr , offset ) ; offset += arr . length ; if ( typeof delimiter !== "undefined" && offset < length ) { res . set ( [ delimiter ] , offset ) ; offset ++ ; } } return res ; } /** Checks two byte arrays for equality */ export function equalBytes ( a , b ) { if ( a . length !== b . length ) { return false ; } for ( let i = 0 ; i < a . length ; i ++ ) { if ( a [ i ] !== b [ i ] ) { return false ; } } return true ; } /**
 * Checks two array-like objects for equality in constant time
 * (given that the `===` operator performs in constant time over all elements)
 */ export function equalCT < T extends ArrayLike < any > > ( a , b ) { let match = true ; for ( let i = 0 ; i < a . length ; i ++ ) { match = match && a [ i ] === b [ i ] ; } return a . length === b . length && match ; }