-
Notifications
You must be signed in to change notification settings - Fork 1
/
mux.js
132 lines (122 loc) · 4.38 KB
/
mux.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
// Message router exposes two APIS:
// "req" signs and submits a BitMEX API request.
// "ws" signs a BitMEX websocket authentication request.
// Using Symbol as a key to cache encryption key in memory
// to bypass JSON serialization.
const signingKey = Symbol();
var mux = {
// The "req" action handler signs and relays a BitMEX API request.
// A valid message is expected to have the following properties:
// "acc" - account name
// "method" - http verb (optional, default "GET")
// "path" - request path after "/api/v1"
// By design, the base path and the hostname cannot be customized.
// "data" - additional data for POST requests (optional)
// Non-string values will be JSON-serialized.
// "headers" - additional headers (optional)
"req": async msg => {
// Look up account by name.
const acc = config.accounts.findByName(msg.acc);
if (!acc) {
throw Error("Account not found");
}
// Prepare parameters for fetch().
const method = msg.method || "GET";
const path = "/api/v1" + msg.path;
const expires = Math.round(Date.now() / 1000) + 60; // 1 min in the future
// Prepare request body.
let data = msg.data || "";
if (data && !isStr(data)) {
data = JSON.stringify(data);
}
// Prepare request headers.
if (!acc[signingKey]) {
acc[signingKey] = await importKeyForSigning(acc.secret);
}
const sig = await signHMAC(acc[signingKey], method + path + expires + data);
const headers = new Headers();
if (msg.headers) {
for (let k in msg.headers) {
headers.set(k, msg.headers[k]);
}
}
headers.set("content-type", "application/json");
headers.set("accept", "application/json");
headers.set("api-expires", expires);
headers.set("api-key", acc.key);
headers.set("api-signature", sig);
let opt = {method, headers, cache: "no-store"};
if (data) {
opt.body = data;
}
const url = Accounts.getHost(acc) + path;
// Submit request and parse errors.
let r;
try {
r = await fetch(url, opt);
const j = await r.json();
if (isObj(j) && j.error) {
throw Error(isObj(j.error) ? j.error.message : j.error);
}
return {result: j, status: r.status};
} catch (e) {
if (isErr(e) && r && r.status) {
e.status = r.status;
}
throw e;
}
},
// The "ws" action handler returns a signature and an expiration timestamp to be
// used in an authKeyExpires request for BitMEX's websocket API.
// A valid message is expected to have the following property:
// "acc" - account name
"ws": async msg => {
// Look up account by name.
const acc = config.accounts.findByName(msg.acc);
if (!acc) {
throw Error("Account not found");
}
if (!acc[signingKey]) {
acc[signingKey] = await importKeyForSigning(acc.secret);
}
const expires = Math.round(Date.now() / 1000) + 60; // 1 min in the future
const sig = await signHMAC(acc[signingKey], "GET/realtime" + expires);
return {
result: {expires, sig}
};
}
};
// Process messages from host pages.
// A valid message is expected to be an object with at least
// a string-typed "action" property.
// The only accepted "action" value is "req" at the moment.
// Messages are always processed asynchronously.
// The return value for the host page is a JSON object with either
// an "error" property or a "result" property. It may also
// contain a "status" property for HTTP status code.
async function processMessage(msg) {
let res, err;
if (!isObj(msg)) {
err = "invalid message";
} else {
const fn = mux[msg.action];
if (typeof fn === "function") {
try {
res = await fn(msg);
} catch (e) {
err = e;
}
} else {
err = "invalid action";
}
}
if (err) {
res = {error: isErr(err) ? err.message : (err + "")};
if (isErr(err) && err.status) {
res.status = err.status;
}
}
if (res) {
notifyPort(this, res, msg);
}
}