@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
146 lines (130 loc) • 4.95 kB
text/typescript
import {IParamReader} from "./IParamReader";
import {Buffer} from "buffer";
function ensureBuffer(input: any): Buffer {
if(input instanceof Buffer) return input;
return Buffer.from(input);
}
export class ParamDecoder implements IParamReader {
frameHeader?: Buffer;
frameData: Buffer[] = [];
frameDataLength: number = 0;
closed: boolean = false;
params: {
[key: string]: {
promise: Promise<any>,
resolve?: (data: any) => void,
reject?: (err: any) => void
}
} = {};
/**
* Called when a frame is fully ready such that it can be parsed
*
* @param data Frame data
* @private
*/
private onFrameRead(data: Buffer) {
const obj = JSON.parse(data.toString());
for(let key in obj) {
if(this.params[key]==null) {
this.params[key] = {
promise: Promise.resolve(obj[key])
};
} else {
const resolveFn = this.params[key].resolve;
if(resolveFn!=null) {
resolveFn(obj[key]);
delete this.params[key].resolve;
delete this.params[key].reject;
}
}
}
}
/**
* Called when data is read from the underlying source
*
* @param data Data that has been read from the underlying source
* @protected
*/
protected onData(data: Buffer): void {
let leavesBuffer: Buffer | null = data;
while(leavesBuffer!=null && leavesBuffer.length>0) {
if(this.frameHeader==null) {
if(leavesBuffer.length<=4) {
this.frameHeader = leavesBuffer;
leavesBuffer = null;
} else {
this.frameHeader = ensureBuffer(leavesBuffer.subarray(0, 4));
leavesBuffer = ensureBuffer(leavesBuffer.subarray(4));
}
} else if(this.frameHeader.length<4) {
const requiredLen = 4-this.frameHeader.length;
if(leavesBuffer.length<=requiredLen) {
this.frameHeader = Buffer.concat([this.frameHeader, leavesBuffer]);
leavesBuffer = null;
} else {
this.frameHeader = Buffer.concat([this.frameHeader, leavesBuffer.subarray(0, requiredLen)]);
leavesBuffer = ensureBuffer(leavesBuffer.subarray(requiredLen));
}
}
if(leavesBuffer==null) continue;
if(this.frameHeader==null || this.frameHeader.length<4) continue;
const frameLength = this.frameHeader.readUint32LE!=null ? this.frameHeader.readUint32LE() : this.frameHeader.readUInt32LE();
const requiredLen = frameLength-this.frameDataLength;
if(leavesBuffer.length<=requiredLen) {
this.frameData.push(leavesBuffer);
this.frameDataLength += leavesBuffer.length;
leavesBuffer = null;
} else {
this.frameData.push(leavesBuffer.subarray(0, requiredLen));
this.frameDataLength += requiredLen;
leavesBuffer = leavesBuffer.subarray(requiredLen);
}
if(frameLength===this.frameDataLength) {
//Message read success
this.onFrameRead(Buffer.concat(this.frameData));
delete this.frameHeader;
this.frameData = [];
this.frameDataLength = 0;
}
}
}
/**
* Called when the underlying source ends/closes/cancels
* @protected
*/
protected onEnd(): void {
for(let key in this.params) {
this.params[key].reject?.(new Error("EOF before field seen!"));
}
this.closed = true;
}
/**
* Called when an error happens with the underlying stream
*
* @param e Error
* @protected
*/
protected onError(e: any): void {
for(let key in this.params) {
this.params[key].reject?.(e);
}
this.closed = true;
}
getParam<T>(key: string): Promise<T> {
if(this.params[key]==null) {
if(this.closed) return Promise.reject(new Error("Stream already closed without param received!"));
let resolve: (data: any) => void;
let reject: (err: any) => void;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject;
});
this.params[key] = {
resolve: resolve!,
reject: reject!,
promise
}
}
return this.params[key].promise;
}
}