-
Notifications
You must be signed in to change notification settings - Fork 19
/
index.js
171 lines (142 loc) · 4.38 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
const crypto = require('crypto');
const https = require('https');
const querystring = require('querystring');
const version = require('./package.json').version;
const name = require('./package.json').name;
const USER_AGENT = `${name}@${version}`;
const DEFAULT_ENDPOINT = 'ftx.com';
const DEFAULT_HEADER_PREFIX = 'FTX';
class FTXRest {
constructor(config) {
this.ua = USER_AGENT;
this.timeout = 90 * 1000;
this.agent = new https.Agent({
keepAlive: true,
timeout: 90 * 1000,
keepAliveMsecs: 1000 * 60
});
if(!config) {
return;
}
if(config.key && config.secret) {
this.key = config.key;
this.secret = config.secret;
}
if(config.timeout) {
this.timeout = config.timeout;
}
if(config.subaccount) {
this.subaccount = config.subaccount;
}
if(config.userAgent) {
this.ua += ' | ' + config.userAgent;
}
this.endpoint = config.endpoint || DEFAULT_ENDPOINT;
this.headerPrefix = config.headerPrefix || DEFAULT_HEADER_PREFIX;
}
// this fn can easily take more than 0.15ms due to heavy crypto functions
// if your application is _very_ latency sensitive prepare the drafts
// before you realize you want to send them.
createDraft({path, method, data, timeout, subaccount}) {
if(!timeout) {
timeout = this.timeout;
}
path = '/api' + path;
let payload = '';
if(method === 'GET' && data) {
path += '?' + querystring.stringify(data);
} else if(method === 'DELETE' && typeof data === 'number') {
// cancel single order
path += data;
} else if(data) {
payload = JSON.stringify(data);
}
const start = +new Date;
const signature = crypto.createHmac('sha256', this.secret)
.update(start + method + path + payload).digest('hex');
const options = {
host: this.endpoint,
path: path,
method,
agent: this.agent,
headers: {
'User-Agent': this.ua,
'content-type' : 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
[this.headerPrefix + '-TS']: start,
[this.headerPrefix + '-KEY']: this.key,
[this.headerPrefix + '-SIGN']: signature
},
// merely passed through for requestDraft
timeout,
payload
};
// required for delete all
if(method === 'DELETE' && payload !== '') {
options.headers['Content-Length'] = payload.length;
}
if(this.subaccount || subaccount) {
options.headers[this.headerPrefix + '-SUBACCOUNT'] = this.subaccount || subaccount
}
return options;
}
// a draft is an option object created (potentially previously) with createDraft
requestDraft(draft) {
return new Promise((resolve, reject) => {
const req = https.request(draft, res => {
res.setEncoding('utf8');
let buffer = '';
res.on('data', function(data) {
// TODO: we receive this event up to ~0.6ms before the end
// event, though if this is valid json & doesn't contain
// an error we can return from here, since we dont care
// about status code.
buffer += data;
});
res.on('end', function() {
if (res.statusCode >= 300) {
let message;
let data;
try {
data = JSON.parse(buffer);
message = data
} catch(e) {
message = buffer;
}
console.error('ERROR!', res.statusCode, message);
const error = new Error(message.error)
error.statusCode = res.statusCode;
return reject(error);
}
let data;
try {
data = JSON.parse(buffer);
} catch (err) {
console.error('JSON ERROR!', buffer);
return reject(new Error('Json error'));
}
resolve(data);
});
});
req.on('error', err => {
reject(err);
});
req.on('socket', socket => {
if(socket.connecting) {
socket.setNoDelay(true);
socket.setTimeout(draft.timeout);
socket.on('timeout', function() {
req.abort();
});
}
});
req.end(draft.payload);
});
}
// props: {path, method, data, timeout}
request(props) {
return this.requestDraft(this.createDraft(props));
}
};
module.exports = FTXRest;