@atomiqlabs/lp-lib 10.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/fees/IBtcFeeEstimator.d.ts +3 -0
- package/dist/fees/IBtcFeeEstimator.js +2 -0
- package/dist/fees/OneDollarFeeEstimator.d.ts +16 -0
- package/dist/fees/OneDollarFeeEstimator.js +71 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +52 -0
- package/dist/info/InfoHandler.d.ts +17 -0
- package/dist/info/InfoHandler.js +70 -0
- package/dist/plugins/IPlugin.d.ts +118 -0
- package/dist/plugins/IPlugin.js +33 -0
- package/dist/plugins/PluginManager.d.ts +89 -0
- package/dist/plugins/PluginManager.js +263 -0
- package/dist/prices/BinanceSwapPrice.d.ts +27 -0
- package/dist/prices/BinanceSwapPrice.js +106 -0
- package/dist/prices/CoinGeckoSwapPrice.d.ts +31 -0
- package/dist/prices/CoinGeckoSwapPrice.js +76 -0
- package/dist/storage/IIntermediaryStorage.d.ts +15 -0
- package/dist/storage/IIntermediaryStorage.js +2 -0
- package/dist/storagemanager/IntermediaryStorageManager.d.ts +15 -0
- package/dist/storagemanager/IntermediaryStorageManager.js +113 -0
- package/dist/storagemanager/StorageManager.d.ts +12 -0
- package/dist/storagemanager/StorageManager.js +74 -0
- package/dist/swaps/FromBtcBaseSwap.d.ts +12 -0
- package/dist/swaps/FromBtcBaseSwap.js +16 -0
- package/dist/swaps/FromBtcBaseSwapHandler.d.ts +118 -0
- package/dist/swaps/FromBtcBaseSwapHandler.js +294 -0
- package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +25 -0
- package/dist/swaps/FromBtcLnBaseSwapHandler.js +55 -0
- package/dist/swaps/ISwapPrice.d.ts +44 -0
- package/dist/swaps/ISwapPrice.js +73 -0
- package/dist/swaps/SwapHandler.d.ts +186 -0
- package/dist/swaps/SwapHandler.js +292 -0
- package/dist/swaps/SwapHandlerSwap.d.ts +75 -0
- package/dist/swaps/SwapHandlerSwap.js +72 -0
- package/dist/swaps/ToBtcBaseSwap.d.ts +35 -0
- package/dist/swaps/ToBtcBaseSwap.js +61 -0
- package/dist/swaps/ToBtcBaseSwapHandler.d.ts +94 -0
- package/dist/swaps/ToBtcBaseSwapHandler.js +233 -0
- package/dist/swaps/frombtc_abstract/FromBtcAbs.d.ts +92 -0
- package/dist/swaps/frombtc_abstract/FromBtcAbs.js +386 -0
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.d.ts +26 -0
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.js +63 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.d.ts +55 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.js +586 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.d.ts +43 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.js +99 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.d.ts +105 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.js +731 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +29 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.js +64 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.d.ts +79 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.js +514 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +28 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.js +66 -0
- package/dist/swaps/tobtc_abstract/ToBtcAbs.d.ts +290 -0
- package/dist/swaps/tobtc_abstract/ToBtcAbs.js +1056 -0
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.d.ts +29 -0
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.js +70 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.d.ts +246 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.js +1169 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +27 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.js +65 -0
- package/dist/utils/Utils.d.ts +32 -0
- package/dist/utils/Utils.js +109 -0
- package/dist/utils/coinselect2/accumulative.d.ts +6 -0
- package/dist/utils/coinselect2/accumulative.js +44 -0
- package/dist/utils/coinselect2/blackjack.d.ts +6 -0
- package/dist/utils/coinselect2/blackjack.js +41 -0
- package/dist/utils/coinselect2/index.d.ts +16 -0
- package/dist/utils/coinselect2/index.js +40 -0
- package/dist/utils/coinselect2/utils.d.ts +64 -0
- package/dist/utils/coinselect2/utils.js +121 -0
- package/dist/utils/paramcoders/IParamReader.d.ts +5 -0
- package/dist/utils/paramcoders/IParamReader.js +2 -0
- package/dist/utils/paramcoders/IParamWriter.d.ts +4 -0
- package/dist/utils/paramcoders/IParamWriter.js +2 -0
- package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -0
- package/dist/utils/paramcoders/LegacyParamEncoder.js +33 -0
- package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -0
- package/dist/utils/paramcoders/ParamDecoder.js +234 -0
- package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -0
- package/dist/utils/paramcoders/ParamEncoder.js +22 -0
- package/dist/utils/paramcoders/SchemaVerifier.d.ts +22 -0
- package/dist/utils/paramcoders/SchemaVerifier.js +85 -0
- package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -0
- package/dist/utils/paramcoders/server/ServerParamDecoder.js +105 -0
- package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -0
- package/dist/utils/paramcoders/server/ServerParamEncoder.js +76 -0
- package/package.json +43 -0
- package/src/fees/IBtcFeeEstimator.ts +7 -0
- package/src/fees/OneDollarFeeEstimator.ts +95 -0
- package/src/index.ts +46 -0
- package/src/info/InfoHandler.ts +106 -0
- package/src/plugins/IPlugin.ts +155 -0
- package/src/plugins/PluginManager.ts +310 -0
- package/src/prices/BinanceSwapPrice.ts +114 -0
- package/src/prices/CoinGeckoSwapPrice.ts +88 -0
- package/src/storage/IIntermediaryStorage.ts +21 -0
- package/src/storagemanager/IntermediaryStorageManager.ts +101 -0
- package/src/storagemanager/StorageManager.ts +68 -0
- package/src/swaps/FromBtcBaseSwap.ts +21 -0
- package/src/swaps/FromBtcBaseSwapHandler.ts +375 -0
- package/src/swaps/FromBtcLnBaseSwapHandler.ts +48 -0
- package/src/swaps/ISwapPrice.ts +94 -0
- package/src/swaps/SwapHandler.ts +404 -0
- package/src/swaps/SwapHandlerSwap.ts +133 -0
- package/src/swaps/ToBtcBaseSwap.ts +76 -0
- package/src/swaps/ToBtcBaseSwapHandler.ts +309 -0
- package/src/swaps/frombtc_abstract/FromBtcAbs.ts +484 -0
- package/src/swaps/frombtc_abstract/FromBtcSwapAbs.ts +77 -0
- package/src/swaps/frombtc_trusted/FromBtcTrusted.ts +661 -0
- package/src/swaps/frombtc_trusted/FromBtcTrustedSwap.ts +158 -0
- package/src/swaps/frombtcln_abstract/FromBtcLnAbs.ts +864 -0
- package/src/swaps/frombtcln_abstract/FromBtcLnSwapAbs.ts +82 -0
- package/src/swaps/frombtcln_trusted/FromBtcLnTrusted.ts +592 -0
- package/src/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.ts +90 -0
- package/src/swaps/tobtc_abstract/ToBtcAbs.ts +1249 -0
- package/src/swaps/tobtc_abstract/ToBtcSwapAbs.ts +112 -0
- package/src/swaps/tobtcln_abstract/ToBtcLnAbs.ts +1422 -0
- package/src/swaps/tobtcln_abstract/ToBtcLnSwapAbs.ts +87 -0
- package/src/utils/Utils.ts +108 -0
- package/src/utils/coinselect2/accumulative.js +32 -0
- package/src/utils/coinselect2/accumulative.ts +58 -0
- package/src/utils/coinselect2/blackjack.js +29 -0
- package/src/utils/coinselect2/blackjack.ts +54 -0
- package/src/utils/coinselect2/index.js +16 -0
- package/src/utils/coinselect2/index.ts +50 -0
- package/src/utils/coinselect2/utils.js +110 -0
- package/src/utils/coinselect2/utils.ts +183 -0
- package/src/utils/paramcoders/IParamReader.ts +8 -0
- package/src/utils/paramcoders/IParamWriter.ts +8 -0
- package/src/utils/paramcoders/LegacyParamEncoder.ts +28 -0
- package/src/utils/paramcoders/ParamDecoder.ts +219 -0
- package/src/utils/paramcoders/ParamEncoder.ts +30 -0
- package/src/utils/paramcoders/SchemaVerifier.ts +97 -0
- package/src/utils/paramcoders/server/ServerParamDecoder.ts +115 -0
- package/src/utils/paramcoders/server/ServerParamEncoder.ts +76 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// baseline estimates, used to improve performance
|
|
2
|
+
const TX_EMPTY_SIZE = 4 + 1 + 1 + 4;
|
|
3
|
+
const TX_INPUT_BASE = 32 + 4 + 1 + 4;
|
|
4
|
+
|
|
5
|
+
const WITNESS_OVERHEAD = 2/4;
|
|
6
|
+
|
|
7
|
+
const P2WPKH_WITNESS = (1+1+72+1+33)/4;
|
|
8
|
+
const P2TR_WITNESS = (1+1+65)/4;
|
|
9
|
+
|
|
10
|
+
const TX_INPUT_PUBKEYHASH = 107;
|
|
11
|
+
const TX_INPUT_P2SH_P2WPKH = 23 + P2WPKH_WITNESS + 1;
|
|
12
|
+
const TX_INPUT_P2WPKH = 0 + P2WPKH_WITNESS;
|
|
13
|
+
const TX_INPUT_P2WSH = 0 + (1+1+64)/4;
|
|
14
|
+
const TX_INPUT_P2TR = 0 + P2TR_WITNESS;
|
|
15
|
+
|
|
16
|
+
const TX_OUTPUT_BASE = 8 + 1;
|
|
17
|
+
|
|
18
|
+
const TX_OUTPUT_PUBKEYHASH = 25;
|
|
19
|
+
const TX_OUTPUT_P2SH_P2WPKH = 23;
|
|
20
|
+
const TX_OUTPUT_P2WPKH = 22;
|
|
21
|
+
const TX_OUTPUT_P2WSH = 34;
|
|
22
|
+
const TX_OUTPUT_P2TR = 34;
|
|
23
|
+
|
|
24
|
+
export type CoinselectAddressTypes = "p2sh-p2wpkh" | "p2wpkh" | "p2wsh" | "p2tr" | "p2pkh";
|
|
25
|
+
|
|
26
|
+
export type CoinselectTxInput = {
|
|
27
|
+
script?: Buffer,
|
|
28
|
+
witness?: Buffer,
|
|
29
|
+
txId: string,
|
|
30
|
+
vout: number,
|
|
31
|
+
type?: CoinselectAddressTypes,
|
|
32
|
+
value: number,
|
|
33
|
+
outputScript?: Buffer,
|
|
34
|
+
address?: string
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type CoinselectTxOutput = {
|
|
38
|
+
script?: Buffer,
|
|
39
|
+
address?: string,
|
|
40
|
+
type?: CoinselectAddressTypes,
|
|
41
|
+
value: number
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const INPUT_BYTES = {
|
|
46
|
+
"p2sh-p2wpkh": TX_INPUT_P2SH_P2WPKH,
|
|
47
|
+
"p2wpkh": TX_INPUT_P2WPKH,
|
|
48
|
+
"p2tr": TX_INPUT_P2TR,
|
|
49
|
+
"p2pkh": TX_INPUT_PUBKEYHASH,
|
|
50
|
+
"p2wsh": TX_INPUT_P2WSH
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function inputBytes (input: {
|
|
54
|
+
script?: Buffer,
|
|
55
|
+
witness?: Buffer,
|
|
56
|
+
type?: CoinselectAddressTypes
|
|
57
|
+
}) {
|
|
58
|
+
if(input.script==null && input.witness==null) {
|
|
59
|
+
return {length: TX_INPUT_BASE + INPUT_BYTES[input.type], isWitness: input.type!=="p2pkh"};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
length: TX_INPUT_BASE + (input.script?.length || 0) + ((input.witness?.length || 0)/4),
|
|
63
|
+
isWitness: input.witness!=null
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const OUTPUT_BYTES = {
|
|
68
|
+
"p2sh-p2wpkh": TX_OUTPUT_P2SH_P2WPKH,
|
|
69
|
+
"p2wpkh": TX_OUTPUT_P2WPKH,
|
|
70
|
+
"p2tr": TX_OUTPUT_P2TR,
|
|
71
|
+
"p2pkh": TX_OUTPUT_PUBKEYHASH,
|
|
72
|
+
"p2wsh": TX_OUTPUT_P2WSH
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function outputBytes (output: {
|
|
76
|
+
script?: Buffer,
|
|
77
|
+
type?: CoinselectAddressTypes
|
|
78
|
+
}): number {
|
|
79
|
+
return TX_OUTPUT_BASE + (output.script ? output.script.length : OUTPUT_BYTES[output.type]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const DUST_THRESHOLDS = {
|
|
83
|
+
"p2sh-p2wpkh": 540,
|
|
84
|
+
"p2wpkh": 294,
|
|
85
|
+
"p2tr": 330,
|
|
86
|
+
"p2pkh": 546,
|
|
87
|
+
"p2wsh": 330
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function dustThreshold (output: {
|
|
91
|
+
script?: Buffer,
|
|
92
|
+
type: CoinselectAddressTypes
|
|
93
|
+
}): number {
|
|
94
|
+
return DUST_THRESHOLDS[output.type];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function transactionBytes (
|
|
98
|
+
inputs: {
|
|
99
|
+
script?: Buffer,
|
|
100
|
+
type?: CoinselectAddressTypes
|
|
101
|
+
}[],
|
|
102
|
+
outputs: {
|
|
103
|
+
script?: Buffer,
|
|
104
|
+
type?: CoinselectAddressTypes
|
|
105
|
+
}[],
|
|
106
|
+
changeType: CoinselectAddressTypes
|
|
107
|
+
): number {
|
|
108
|
+
let size = TX_EMPTY_SIZE;
|
|
109
|
+
let isSegwit = false;
|
|
110
|
+
if(changeType!=="p2pkh") {
|
|
111
|
+
size += WITNESS_OVERHEAD;
|
|
112
|
+
let isSegwit = true;
|
|
113
|
+
}
|
|
114
|
+
for(let input of inputs) {
|
|
115
|
+
const {length, isWitness} = inputBytes(input);
|
|
116
|
+
size += length;
|
|
117
|
+
if(!isSegwit && isWitness) {
|
|
118
|
+
isSegwit = true;
|
|
119
|
+
size += WITNESS_OVERHEAD;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for(let output of outputs) {
|
|
123
|
+
size += outputBytes(output);
|
|
124
|
+
}
|
|
125
|
+
return Math.ceil(size);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function uintOrNaN(v: number): number {
|
|
129
|
+
if (typeof v !== 'number') return NaN;
|
|
130
|
+
if (!isFinite(v)) return NaN;
|
|
131
|
+
if (Math.floor(v) !== v) return NaN;
|
|
132
|
+
if (v < 0) return NaN;
|
|
133
|
+
return v;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function sumForgiving(range: {value: number}[]): number {
|
|
137
|
+
return range.reduce((a, x) => a + (isFinite(x.value) ? x.value : 0), 0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function sumOrNaN(range: {value: number}[]): number {
|
|
141
|
+
return range.reduce((a, x) => a + uintOrNaN(x.value), 0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function finalize(
|
|
145
|
+
inputs: CoinselectTxInput[],
|
|
146
|
+
outputs: CoinselectTxOutput[],
|
|
147
|
+
feeRate: number,
|
|
148
|
+
changeType: CoinselectAddressTypes
|
|
149
|
+
): {
|
|
150
|
+
inputs?: CoinselectTxInput[],
|
|
151
|
+
outputs?: CoinselectTxOutput[],
|
|
152
|
+
fee: number
|
|
153
|
+
} {
|
|
154
|
+
const bytesAccum = transactionBytes(inputs, outputs, changeType);
|
|
155
|
+
|
|
156
|
+
const feeAfterExtraOutput = feeRate * (bytesAccum + outputBytes({type: changeType}))
|
|
157
|
+
const remainderAfterExtraOutput = sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput)
|
|
158
|
+
|
|
159
|
+
// is it worth a change output?
|
|
160
|
+
if (remainderAfterExtraOutput >= dustThreshold({type: changeType})) {
|
|
161
|
+
outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType })
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const fee = sumOrNaN(inputs) - sumOrNaN(outputs)
|
|
165
|
+
if (!isFinite(fee)) return { fee: feeRate * bytesAccum }
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
inputs: inputs,
|
|
169
|
+
outputs: outputs,
|
|
170
|
+
fee: fee
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const utils = {
|
|
175
|
+
dustThreshold: dustThreshold,
|
|
176
|
+
finalize: finalize,
|
|
177
|
+
inputBytes: inputBytes,
|
|
178
|
+
outputBytes: outputBytes,
|
|
179
|
+
sumOrNaN: sumOrNaN,
|
|
180
|
+
sumForgiving: sumForgiving,
|
|
181
|
+
transactionBytes: transactionBytes,
|
|
182
|
+
uintOrNaN: uintOrNaN
|
|
183
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {RequestSchema, RequestSchemaResult} from "./SchemaVerifier";
|
|
2
|
+
|
|
3
|
+
export interface IParamReader {
|
|
4
|
+
|
|
5
|
+
getParams<T extends RequestSchema>(schema: T): Promise<RequestSchemaResult<T>>;
|
|
6
|
+
getExistingParamsOrNull<T extends RequestSchema>(schema: T): RequestSchemaResult<T>;
|
|
7
|
+
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {IParamWriter} from "./IParamWriter";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export class LegacyParamEncoder implements IParamWriter {
|
|
5
|
+
|
|
6
|
+
private readonly writeFN: (data: Buffer) => Promise<void>;
|
|
7
|
+
private readonly endFN: () => Promise<void>;
|
|
8
|
+
|
|
9
|
+
private obj = {};
|
|
10
|
+
|
|
11
|
+
constructor(write: (data: Buffer) => Promise<void>, end: () => Promise<void>) {
|
|
12
|
+
this.writeFN = write;
|
|
13
|
+
this.endFN = end;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
writeParams(data: any): Promise<void> {
|
|
17
|
+
for(let key in data) {
|
|
18
|
+
if(this.obj[key]==null) this.obj[key] = data[key];
|
|
19
|
+
}
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async end(): Promise<void> {
|
|
24
|
+
await this.writeFN(Buffer.from(JSON.stringify(this.obj)));
|
|
25
|
+
await this.endFN();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import {FieldTypeEnum, parseBN, RequestSchema, RequestSchemaResult, verifySchema} from "./SchemaVerifier";
|
|
2
|
+
import {IParamReader} from "./IParamReader";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class ParamDecoder implements IParamReader {
|
|
6
|
+
|
|
7
|
+
frameHeader: Buffer = null;
|
|
8
|
+
frameData: Buffer[] = [];
|
|
9
|
+
frameDataLength: number = 0;
|
|
10
|
+
|
|
11
|
+
closed: boolean = false;
|
|
12
|
+
|
|
13
|
+
params: {
|
|
14
|
+
[key: string]: {
|
|
15
|
+
promise: Promise<any>,
|
|
16
|
+
resolve: (data: any) => void,
|
|
17
|
+
reject: (err: any) => void,
|
|
18
|
+
value: any
|
|
19
|
+
}
|
|
20
|
+
} = {};
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private onFrameRead(data: Buffer) {
|
|
27
|
+
const obj = JSON.parse(data.toString());
|
|
28
|
+
for(let key in obj) {
|
|
29
|
+
if(this.params[key]==null) {
|
|
30
|
+
this.params[key] = {
|
|
31
|
+
promise: Promise.resolve(obj[key]),
|
|
32
|
+
resolve: null,
|
|
33
|
+
reject: null,
|
|
34
|
+
value: obj[key]
|
|
35
|
+
};
|
|
36
|
+
} else {
|
|
37
|
+
if(this.params[key].resolve!=null) {
|
|
38
|
+
this.params[key].resolve(obj[key]);
|
|
39
|
+
this.params[key].resolve = null;
|
|
40
|
+
this.params[key].reject = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onData(data: Buffer): void {
|
|
47
|
+
let leavesBuffer = data;
|
|
48
|
+
while(leavesBuffer!=null && leavesBuffer.length>0) {
|
|
49
|
+
if(this.frameHeader==null) {
|
|
50
|
+
if(leavesBuffer.length<=4) {
|
|
51
|
+
this.frameHeader = leavesBuffer;
|
|
52
|
+
leavesBuffer = null;
|
|
53
|
+
} else {
|
|
54
|
+
this.frameHeader = leavesBuffer.subarray(0, 4);
|
|
55
|
+
leavesBuffer = leavesBuffer.subarray(4);
|
|
56
|
+
}
|
|
57
|
+
} else if(this.frameHeader.length<4) {
|
|
58
|
+
const requiredLen = 4-this.frameHeader.length;
|
|
59
|
+
if(leavesBuffer.length<=requiredLen) {
|
|
60
|
+
this.frameHeader = Buffer.concat([this.frameHeader, leavesBuffer]);
|
|
61
|
+
leavesBuffer = null;
|
|
62
|
+
} else {
|
|
63
|
+
this.frameHeader = Buffer.concat([this.frameHeader, leavesBuffer.subarray(0, requiredLen)]);
|
|
64
|
+
leavesBuffer = leavesBuffer.subarray(requiredLen);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if(leavesBuffer==null) continue;
|
|
68
|
+
if(this.frameHeader==null || this.frameHeader.length<4) continue;
|
|
69
|
+
|
|
70
|
+
const frameLength = this.frameHeader.readUint32LE();
|
|
71
|
+
const requiredLen = frameLength-this.frameDataLength;
|
|
72
|
+
|
|
73
|
+
if(leavesBuffer.length<=requiredLen) {
|
|
74
|
+
this.frameData.push(leavesBuffer);
|
|
75
|
+
this.frameDataLength += leavesBuffer.length;
|
|
76
|
+
|
|
77
|
+
leavesBuffer = null;
|
|
78
|
+
} else {
|
|
79
|
+
this.frameData.push(leavesBuffer.subarray(0, requiredLen));
|
|
80
|
+
this.frameDataLength += requiredLen;
|
|
81
|
+
|
|
82
|
+
leavesBuffer = leavesBuffer.subarray(requiredLen);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if(frameLength===this.frameDataLength) {
|
|
86
|
+
//Message read success
|
|
87
|
+
this.onFrameRead(Buffer.concat(this.frameData));
|
|
88
|
+
this.frameHeader = null;
|
|
89
|
+
this.frameData = [];
|
|
90
|
+
this.frameDataLength = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onEnd(): void {
|
|
96
|
+
for(let key in this.params) {
|
|
97
|
+
if(this.params[key].reject!=null) {
|
|
98
|
+
this.params[key].reject(new Error("EOF before field seen!"));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.closed = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onError(e: any): void {
|
|
105
|
+
for(let key in this.params) {
|
|
106
|
+
if(this.params[key].reject!=null) {
|
|
107
|
+
this.params[key].reject(e);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.closed = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getParam(key: string): Promise<any> {
|
|
114
|
+
if(this.params[key]==null) {
|
|
115
|
+
if(this.closed) return Promise.reject(new Error("Stream already closed without param received!"));
|
|
116
|
+
let resolve: (data: any) => void;
|
|
117
|
+
let reject: (err: any) => void;
|
|
118
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
119
|
+
resolve = _resolve
|
|
120
|
+
reject = _reject;
|
|
121
|
+
});
|
|
122
|
+
this.params[key] = {
|
|
123
|
+
resolve,
|
|
124
|
+
reject,
|
|
125
|
+
promise,
|
|
126
|
+
value: null
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return this.params[key].promise;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getParams<T extends RequestSchema>(schema: T): Promise<RequestSchemaResult<T>> {
|
|
133
|
+
const resultSchema: any = {};
|
|
134
|
+
for(let fieldName in schema) {
|
|
135
|
+
const val: any = await this.getParam(fieldName);
|
|
136
|
+
const type: FieldTypeEnum | RequestSchema | ((val: any) => boolean) = schema[fieldName];
|
|
137
|
+
if(typeof(type)==="function") {
|
|
138
|
+
const result = type(val);
|
|
139
|
+
if(result==null) return null;
|
|
140
|
+
resultSchema[fieldName] = result;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if(val==null && (type as number)>=100) {
|
|
145
|
+
resultSchema[fieldName] = null;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if(type===FieldTypeEnum.Any || type===FieldTypeEnum.AnyOptional) {
|
|
150
|
+
resultSchema[fieldName] = val;
|
|
151
|
+
} else if(type===FieldTypeEnum.Boolean || type===FieldTypeEnum.BooleanOptional) {
|
|
152
|
+
if(typeof(val)!=="boolean") return null;
|
|
153
|
+
resultSchema[fieldName] = val;
|
|
154
|
+
} else if(type===FieldTypeEnum.Number || type===FieldTypeEnum.NumberOptional) {
|
|
155
|
+
if(typeof(val)!=="number") return null;
|
|
156
|
+
if(isNaN(val as number)) return null;
|
|
157
|
+
resultSchema[fieldName] = val;
|
|
158
|
+
} else if(type===FieldTypeEnum.BN || type===FieldTypeEnum.BNOptional) {
|
|
159
|
+
const result = parseBN(val);
|
|
160
|
+
if(result==null) return null;
|
|
161
|
+
resultSchema[fieldName] = result;
|
|
162
|
+
} else if(type===FieldTypeEnum.String || type===FieldTypeEnum.StringOptional) {
|
|
163
|
+
if(typeof(val)!=="string") return null;
|
|
164
|
+
resultSchema[fieldName] = val;
|
|
165
|
+
} else {
|
|
166
|
+
//Probably another request schema
|
|
167
|
+
const result = verifySchema(val, type as RequestSchema);
|
|
168
|
+
if(result==null) return null;
|
|
169
|
+
resultSchema[fieldName] = result;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return resultSchema;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getExistingParamsOrNull<T extends RequestSchema>(schema: T): RequestSchemaResult<T> {
|
|
176
|
+
const resultSchema: any = {};
|
|
177
|
+
for(let fieldName in schema) {
|
|
178
|
+
const val: any = this.params[fieldName]?.value;
|
|
179
|
+
|
|
180
|
+
if(val==null) {
|
|
181
|
+
resultSchema[fieldName] = null;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const type: FieldTypeEnum | RequestSchema | ((val: any) => boolean) = schema[fieldName];
|
|
186
|
+
if(typeof(type)==="function") {
|
|
187
|
+
const result = type(val);
|
|
188
|
+
if(result==null) return null;
|
|
189
|
+
resultSchema[fieldName] = result;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if(type===FieldTypeEnum.Any || type===FieldTypeEnum.AnyOptional) {
|
|
194
|
+
resultSchema[fieldName] = val;
|
|
195
|
+
} else if(type===FieldTypeEnum.Boolean || type===FieldTypeEnum.BooleanOptional) {
|
|
196
|
+
if(typeof(val)!=="boolean") return null;
|
|
197
|
+
resultSchema[fieldName] = val;
|
|
198
|
+
} else if(type===FieldTypeEnum.Number || type===FieldTypeEnum.NumberOptional) {
|
|
199
|
+
if(typeof(val)!=="number") return null;
|
|
200
|
+
if(isNaN(val as number)) return null;
|
|
201
|
+
resultSchema[fieldName] = val;
|
|
202
|
+
} else if(type===FieldTypeEnum.BN || type===FieldTypeEnum.BNOptional) {
|
|
203
|
+
const result = parseBN(val);
|
|
204
|
+
if(result==null) return null;
|
|
205
|
+
resultSchema[fieldName] = result;
|
|
206
|
+
} else if(type===FieldTypeEnum.String || type===FieldTypeEnum.StringOptional) {
|
|
207
|
+
if(typeof(val)!=="string") return null;
|
|
208
|
+
resultSchema[fieldName] = val;
|
|
209
|
+
} else {
|
|
210
|
+
//Probably another request schema
|
|
211
|
+
const result = verifySchema(val, type as RequestSchema);
|
|
212
|
+
if(result==null) return null;
|
|
213
|
+
resultSchema[fieldName] = result;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return resultSchema;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {IParamWriter} from "./IParamWriter";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export class ParamEncoder implements IParamWriter {
|
|
5
|
+
|
|
6
|
+
private readonly writeFN: (data: Buffer) => Promise<void>;
|
|
7
|
+
private readonly endFN: () => Promise<void>;
|
|
8
|
+
|
|
9
|
+
constructor(write: (data: Buffer) => Promise<void>, end: () => Promise<void>) {
|
|
10
|
+
this.writeFN = write;
|
|
11
|
+
this.endFN = end;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
writeParams(data: any): Promise<void> {
|
|
15
|
+
const serialized: Buffer = Buffer.from(JSON.stringify(data));
|
|
16
|
+
|
|
17
|
+
const frameLengthBuffer = Buffer.alloc(4);
|
|
18
|
+
frameLengthBuffer.writeUint32LE(serialized.length);
|
|
19
|
+
|
|
20
|
+
return this.writeFN(Buffer.concat([
|
|
21
|
+
frameLengthBuffer,
|
|
22
|
+
serialized
|
|
23
|
+
]));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
end(): Promise<void> {
|
|
27
|
+
return this.endFN();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as BN from "bn.js";
|
|
2
|
+
|
|
3
|
+
export function parseBN(str: string | number): BN | null {
|
|
4
|
+
if(str==null) return null;
|
|
5
|
+
if(typeof(str)!=="string" && typeof(str)!=="number") return null;
|
|
6
|
+
try {
|
|
7
|
+
return new BN(str);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum FieldTypeEnum {
|
|
14
|
+
String=0,
|
|
15
|
+
Boolean=1,
|
|
16
|
+
Number=2,
|
|
17
|
+
BN=3,
|
|
18
|
+
Any=4,
|
|
19
|
+
|
|
20
|
+
StringOptional=100,
|
|
21
|
+
BooleanOptional=101,
|
|
22
|
+
NumberOptional=102,
|
|
23
|
+
BNOptional=103,
|
|
24
|
+
AnyOptional=104,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type FieldType<T extends FieldTypeEnum | RequestSchema | ((val: any) => (string | boolean | number | BN | any))> =
|
|
28
|
+
T extends FieldTypeEnum.String ? string :
|
|
29
|
+
T extends FieldTypeEnum.Boolean ? boolean :
|
|
30
|
+
T extends FieldTypeEnum.Number ? number :
|
|
31
|
+
T extends FieldTypeEnum.BN ? BN :
|
|
32
|
+
T extends FieldTypeEnum.Any ? any :
|
|
33
|
+
T extends FieldTypeEnum.StringOptional ? string :
|
|
34
|
+
T extends FieldTypeEnum.BooleanOptional ? boolean :
|
|
35
|
+
T extends FieldTypeEnum.NumberOptional ? number :
|
|
36
|
+
T extends FieldTypeEnum.BNOptional ? BN :
|
|
37
|
+
T extends FieldTypeEnum.AnyOptional ? any :
|
|
38
|
+
T extends RequestSchema ? RequestSchemaResult<T> :
|
|
39
|
+
T extends ((val: any) => string) ? string :
|
|
40
|
+
T extends ((val: any) => boolean) ? boolean :
|
|
41
|
+
T extends ((val: any) => number) ? number :
|
|
42
|
+
T extends ((val: any) => BN) ? BN :
|
|
43
|
+
T extends ((val: any) => any) ? any :
|
|
44
|
+
never;
|
|
45
|
+
|
|
46
|
+
export type RequestSchemaResult<T extends RequestSchema> = {
|
|
47
|
+
[key in keyof T]: FieldType<T[key]>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type RequestSchema = {
|
|
51
|
+
[fieldName: string]: FieldTypeEnum | RequestSchema | ((val: any) => any)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function verifySchema<T extends RequestSchema>(req: any, schema: T): RequestSchemaResult<T> {
|
|
55
|
+
if(req==null) return null;
|
|
56
|
+
const resultSchema: any = {};
|
|
57
|
+
for(let fieldName in schema) {
|
|
58
|
+
const val: any = req[fieldName];
|
|
59
|
+
|
|
60
|
+
const type: FieldTypeEnum | RequestSchema | ((val: any) => boolean) = schema[fieldName];
|
|
61
|
+
if(typeof(type)==="function") {
|
|
62
|
+
const result = type(val);
|
|
63
|
+
if(result==null) return null;
|
|
64
|
+
resultSchema[fieldName] = result;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if(val==null && (type as number)>=100) {
|
|
69
|
+
resultSchema[fieldName] = null;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if(type===FieldTypeEnum.Any || type===FieldTypeEnum.AnyOptional) {
|
|
74
|
+
resultSchema[fieldName] = val;
|
|
75
|
+
} else if(type===FieldTypeEnum.Boolean || type===FieldTypeEnum.BooleanOptional) {
|
|
76
|
+
if(typeof(val)!=="boolean") return null;
|
|
77
|
+
resultSchema[fieldName] = val;
|
|
78
|
+
} else if(type===FieldTypeEnum.Number || type===FieldTypeEnum.NumberOptional) {
|
|
79
|
+
if(typeof(val)!=="number") return null;
|
|
80
|
+
if(isNaN(val as number)) return null;
|
|
81
|
+
resultSchema[fieldName] = val;
|
|
82
|
+
} else if(type===FieldTypeEnum.BN || type===FieldTypeEnum.BNOptional) {
|
|
83
|
+
const result = parseBN(val);
|
|
84
|
+
if(result==null) return null;
|
|
85
|
+
resultSchema[fieldName] = result;
|
|
86
|
+
} else if(type===FieldTypeEnum.String || type===FieldTypeEnum.StringOptional) {
|
|
87
|
+
if(typeof(val)!=="string") return null;
|
|
88
|
+
resultSchema[fieldName] = val;
|
|
89
|
+
} else {
|
|
90
|
+
//Probably another request schema
|
|
91
|
+
const result = verifySchema(val, type as RequestSchema);
|
|
92
|
+
if(result==null) return null;
|
|
93
|
+
resultSchema[fieldName] = result;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return resultSchema;
|
|
97
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {Request, Response} from "express";
|
|
2
|
+
import {RequestSchema, verifySchema} from "../SchemaVerifier";
|
|
3
|
+
import {ParamDecoder} from "../ParamDecoder";
|
|
4
|
+
import {ServerParamEncoder} from "./ServerParamEncoder";
|
|
5
|
+
import {IParamReader} from "../IParamReader";
|
|
6
|
+
|
|
7
|
+
export class RequestTimeoutError extends Error {
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super("Request timed out");
|
|
11
|
+
// Set the prototype explicitly.
|
|
12
|
+
Object.setPrototypeOf(this, RequestTimeoutError.prototype);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class RequestParsingError extends Error {
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super("Request cannot be parsed");
|
|
21
|
+
// Set the prototype explicitly.
|
|
22
|
+
Object.setPrototypeOf(this, RequestParsingError.prototype);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const serverParamDecoder = (timeoutMillis: number) => (req: Request, res: Response, next: () => void) => {
|
|
28
|
+
|
|
29
|
+
let timeout;
|
|
30
|
+
|
|
31
|
+
(res as any).responseStream = new ServerParamEncoder(res, 200, req);
|
|
32
|
+
|
|
33
|
+
if(req.headers['content-type']!=="application/x-multiple-json") {
|
|
34
|
+
|
|
35
|
+
const dataBuffers: Buffer[] = [];
|
|
36
|
+
req.on("data", (data: Buffer) => {
|
|
37
|
+
dataBuffers.push(data)
|
|
38
|
+
});
|
|
39
|
+
req.on("end", () => {
|
|
40
|
+
try {
|
|
41
|
+
const body = JSON.parse(Buffer.concat(dataBuffers).toString());
|
|
42
|
+
const paramReader: IParamReader = {
|
|
43
|
+
getParams: <T extends RequestSchema>(schema: T) => {
|
|
44
|
+
return Promise.resolve(verifySchema(body, schema));
|
|
45
|
+
},
|
|
46
|
+
getExistingParamsOrNull: <T extends RequestSchema>(schema: T) => {
|
|
47
|
+
return verifySchema(body, schema);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
(req as any).paramReader = paramReader;
|
|
51
|
+
next();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error("ServerParamDecoder: error reading legacy (non-streaming) http request", e);
|
|
54
|
+
req.destroy(new RequestParsingError());
|
|
55
|
+
res.destroy(new RequestParsingError());
|
|
56
|
+
}
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
});
|
|
59
|
+
req.on("error", (e) => {
|
|
60
|
+
console.error("ServerParamDecoder: error reading legacy (non-streaming) http request",e);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
timeout = setTimeout(() => {
|
|
64
|
+
req.destroy(new RequestTimeoutError());
|
|
65
|
+
res.destroy(new RequestTimeoutError());
|
|
66
|
+
}, timeoutMillis);
|
|
67
|
+
|
|
68
|
+
return;
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const decoder = new ParamDecoder();
|
|
73
|
+
req.on("data", (data: Buffer) => {
|
|
74
|
+
try {
|
|
75
|
+
decoder.onData(data);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error("ServerParamDecoder: error reading streaming http request: on(\"data\")", e);
|
|
78
|
+
req.destroy(new RequestParsingError());
|
|
79
|
+
res.destroy(new RequestParsingError());
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
req.on("end", () => {
|
|
83
|
+
try {
|
|
84
|
+
decoder.onEnd();
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error("ServerParamDecoder: error reading streaming http request: on(\"end\")", e);
|
|
87
|
+
req.destroy(new RequestParsingError());
|
|
88
|
+
res.destroy(new RequestParsingError());
|
|
89
|
+
}
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
});
|
|
92
|
+
req.on("error", (e) => {
|
|
93
|
+
try {
|
|
94
|
+
decoder.onError(e);
|
|
95
|
+
} catch(e) {
|
|
96
|
+
console.error("ServerParamDecoder: error reading streaming http request: on(\"error\")", e);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
timeout = setTimeout(() => {
|
|
101
|
+
try {
|
|
102
|
+
decoder.onEnd();
|
|
103
|
+
} catch(e) {
|
|
104
|
+
console.error("ServerParamDecoder: error reading streaming http request: timeout", e);
|
|
105
|
+
}
|
|
106
|
+
req.destroy(new RequestTimeoutError());
|
|
107
|
+
res.destroy(new RequestTimeoutError());
|
|
108
|
+
}, timeoutMillis);
|
|
109
|
+
|
|
110
|
+
(req as any).paramReader = decoder;
|
|
111
|
+
|
|
112
|
+
next();
|
|
113
|
+
return;
|
|
114
|
+
|
|
115
|
+
}
|