Skip to content

Instantly share code, notes, and snippets.

@atomize
Last active October 5, 2018 16:35
Show Gist options
  • Save atomize/fa844855cb0767ae2f8a938f02df97da to your computer and use it in GitHub Desktop.
Save atomize/fa844855cb0767ae2f8a938f02df97da to your computer and use it in GitHub Desktop.
WIP CryptoCompare API+WebSockets example ES6
let imageUrls;
let globalmenu = {};
const appInfo = {
aggSubs: [],
allSubs: [],
actualSubs: new Set(),
pair: ["???", "???"]
};
const params = {
credentials: "omit",
headers: {},
referrer: "https://cryptoqween.github.io/streamer/trade/",
referrerPolicy: "no-referrer-when-downgrade",
body: null,
method: "GET",
mode: "cors"
};
function showImage(list) {
let fragment = document.createDocumentFragment();
list.forEach(function(element) {
var imgElem = document.createElement("img");
fragment.appendChild(imgElem);
imgElem.classList = "coinlogo";
imgElem.src = element;
});
container.appendChild(fragment);
}
const toggleSetVal = async (set, val) => {
if (set !== undefined) {
set.has(val) ? set.delete(val) : set.add(val);
}
};
function promiseMap(xs, f) {
const reducer = (ysAcc$, x) =>
ysAcc$.then(ysAcc =>
f(x).then(y => {
ysAcc[x] = y;
return ysAcc;
})
);
return xs.reduce(reducer, Promise.resolve({}));
}
function RateLimit(fn, delay, context) {
var queue = [],
timer = null;
function processQueue() {
var item = queue.shift();
if (item) fn.apply(item.context, item.arguments);
if (queue.length === 0) clearInterval(timer), (timer = null);
}
return function limited() {
queue.push({
context: context || this,
arguments: [].slice.call(arguments)
});
if (!timer) {
processQueue(); // start immediately on the first invocation
timer = setInterval(processQueue, delay);
}
};
}
function bigImg() {
var container = document.getElementById("container");
container.innerHTML = "";
chunkArray(unique(Array.from(imageUrls.values())), 30).then(arr => {
var bar = RateLimit(showImage, 500);
arr.map(x => bar(x));
});
//showImage(Array.from(imageUrls.values()))
}
const union = (setA, setB) => {
var _union = new Set(setA);
for (var elem of setB) {
_union.add(elem);
}
return _union;
};
const unique = arr => [...new Set(arr)];
const uniqueFilter = (arr, zeroTwoOrFive) => {
return unique(arr.filter(bases => bases.split("~")[0] === zeroTwoOrFive));
};
const reducer = (a, c) =>
Array.isArray(c) || (typeof c === "object" && c !== null)
? [...a, ...c]
: [...a, c];
const subReducer = res => {
let vals = Object.values(res);
vals[0] !== "Error"
? (vals = vals
.flat()
.map(y => {
return Object.values(y).reduce(reducer);
})
.reduce(reducer))
: (vals = []);
console.log("vals reduce: ", vals);
return vals;
};
const chunkArray = async (myArray, chunk_size) => {
let results = [];
while (myArray.length) {
results.push(myArray.splice(0, chunk_size));
}
return results;
};
const cccCoins = () =>
fetch("https://min-api.cryptocompare.com/data/all/coinlist", params).then(
res => res.json()
);
const cccSubs = (...parameters) => {
let fsym = parameters[0];
let dataUrl = "https://min-api.cryptocompare.com/data/subs?fsym=" + fsym;
return fetch(dataUrl, params)
.then(res => res.json())
.then(res => {
return parameters.includes(1) ? res : subReducer(res);
});
};
const cccaggOrCustom = () => {
if (appInfo.aggSubs !== undefined) {
if (
appInfo.aggSubs.size < appInfo.allSubs.size &&
appInfo.aggSubs.size !== 0
) {
updateOutput(2);
} else {
updateOutput(5);
}
}
};
const fsym = coin =>
cccSubs(coin).then(res => {
if (res.length !== 0) {
let menus = unique(makeMenus(res));
globalmenu = menus;
appInfo.aggSubs = new Set(globalmenu[4]);
appInfo.allSubs = new Set(globalmenu[4]);
appInfo.pair = [coin, "???"];
fillMenu(menus[1], "tsym");
updateOutput(5);
} else {
init(true);
}
});
const tsym = coin => {
filteredExs = unique(
globalmenu[2]
.filter(x => x.match(coin))
.filter(x => x.includes("2~"))
.map(exchanges => exchanges.split("~")[1])
).sort();
appInfo.aggSubs = new Set(filteredExs);
appInfo.allSubs = new Set(filteredExs);
appInfo.pair[1] = coin;
updateOutput(5);
};
const fillMenu = (list, selector) => {
let subsSelect = document.getElementById(selector);
subsSelect.innerHTML = "";
let fragment = document.createDocumentFragment();
list.sort();
list.unshift("Select...");
list.forEach(function(element) {
let opt = document.createElement("option");
opt.value = element;
opt.innerHTML = element;
fragment.appendChild(opt);
});
subsSelect.appendChild(fragment);
};
const makeMenus = res => {
let exs5 = uniqueFilter(res, "5");
let exs2 = uniqueFilter(res, "2")
.map(exchanges => exchanges.split("~")[1])
.sort();
let exs0 = uniqueFilter(res, "0").map(exchanges => exchanges.split("~")[1]);
let menus = [
res[0].split("~")[2],
unique(res.map(bases => bases.split("~")[3])),
res,
exs5,
exs2,
exs0
];
return menus;
};
const isChecked = lineEl => {
return appInfo.aggSubs.has(lineEl) ? "checked" : "";
};
const markupList = (lineEls, typeOf, delimiter = "") => {
let exchangemarkup = `<ul>
${lineEls
.map(
lineEl =>
`<li class='exchange-item'>
<input type = "checkbox"
class="exchange-checkbox"
name = "${lineEl}"
value ="${lineEl}"
${isChecked(lineEl)}>
${lineEl}
</li>`
)
.join(delimiter)}
</ul>`;
let subscriptionmarkup = `<ul>
${lineEls
.map(
lineEl =>
`<li class='subItem'>
${typeOf}~${lineEl}~${appInfo.pair[0]}~${appInfo.pair[1]}</li>`
)
.join(delimiter)}
</ul>`;
return typeOf === "exchange" ? exchangemarkup : subscriptionmarkup;
};
const updateOutput = (twoOrFive = 0) => {
if (twoOrFive === 5) {
document.getElementById("wsoutput2").innerHTML = `<ul><li>5~CCCAGG~${
appInfo.pair[0]
}~${appInfo.pair[1]}</li></ul>`;
} else {
document.getElementById("wsoutput2").innerHTML = markupList(
Array.from(appInfo.aggSubs).filter(x => x !== "Select..."),
twoOrFive
);
}
document.getElementById("exchanges").innerHTML = markupList(
Array.from(appInfo.allSubs).filter(x => x !== ("Select..." || "CCCAGG")),
"exchange"
);
};
const init = input => {
cccCoins().then(coins => {
["fsym", "tsym"].map(selectors => {
fillMenu(Object.keys(coins.Data), selectors);
});
imageUrls = new Map(
Object.entries(coins.Data).reduce((a, c, i) => {
a[i] = [c[0], `https://www.cryptocompare.com/${c[1].ImageUrl}`];
return a;
}, [])
);
});
if (input) {
updateOutput(5);
}
};
const createSubscriptionStrings = () => {
appInfo.actualSubs = new Set();
appInfo.actualSubs = union(appInfo.actualSubs, appInfo.aggSubs);
appInfo.actualSubs = Array.from(appInfo.actualSubs).reduce((a, c) => {
a.push(`2~${c}~${appInfo.pair[0]}~${appInfo.pair[1]}`);
return a;
}, []);
return appInfo.actualSubs.length === appInfo.allSubs.size
? (appInfo.actualSubs = [`5~CCCAGG~${appInfo.pair[0]}~${appInfo.pair[1]}`])
: appInfo.actualSubs;
};
document.addEventListener("change", function() {
if (event.target instanceof HTMLSelectElement) {
if (event.target.id === "fsym") {
fsym(event.target.value);
}
if (event.target.id === "tsym") {
tsym(event.target.value);
}
}
});
document.addEventListener("mouseup", function() {
event.target instanceof HTMLInputElement &&
event.target.getAttribute("type") == "checkbox"
? toggleSetVal(appInfo.aggSubs, event.target.value).then(cccaggOrCustom())
: console.log("other mouse up, not a checkbox!");
if (event.target.classList.contains("exchange-item")) {
console.log(event.target.querySelector(".exchange-checkbox").checked);
}
if (event.target.classList.contains("subscribe")) {
if (appInfo.actualSubs.length > 0) {
removeSubs(appInfo.actualSubs);
}
createSubscriptionStrings();
addSubs(appInfo.actualSubs);
}
});
var socket = io.connect("https://streamer.cryptocompare.com/");
const addSubs = subsArr => {
socket.emit("SubAdd", {
subs: subsArr
});
};
const removeSubs = subsArr => {
socket.emit("SubRemove", {
subs: subsArr
});
};
socket.on("m", function(message) {
console.log(message);
// var messageType = message.substring(0, message.indexOf("~"));
});
init();
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>CryptoCompare Example</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>CryptoCompare WebSocket CURRENT VALUE</h2>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<div class="grid-container-4">
<div class="grid-item">
<div class="grid-container-2">
<div class="grid-item">
<label for="fsym">From Coin</label>
<br>
<select id="fsym">
<option value="">Loading...</option>
</select>
</div>
<div class="grid-item">
<label for="tsym">To Coin</label>
<br>
<select id="tsym">
<option value="">Loading...</option>
</select>
</div>
</div>
</div>
<div class="grid-item">
Aggregated Exchanges
<br>
<div id="exchanges">Loading...</div>
</div>
<div class="grid-item">
WS Subscription
<br>
<div id="wsoutput2">
</div>
</div>
<div class="grid-item">
<button class="subscribe">Subscribe</button>
<button class="addsub">Add to Sub</button>
<button class="unsubscribe">Unsubscribe All</button>
</div>
</div>
<div class="grid-container-4">
<div id="container" class="grid-item">
</div>
<div class="grid-item"></div>
<div class="grid-item"></div>
<div class="grid-item"></div>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.slim.js'></script>
<script src="ccc.js" async defer></script>
</body>
</html>
.inline {
display: inline-block;
vertical-align: top;
text-align: center;
}
.twenty5 {
width: 20vw;
}
.fifty {
max-width: 30%;
}
#wsoutput {
min-width: 200px;
}
#exchange-container {
min-width: 200px;
max-width: 400px;
}
#exchanges {
max-height: 200px;
overflow-y: scroll;
}
.exchange-item {
cursor: pointer;
text-decoration: none;
}
.grid-container-4 {
display: grid;
grid-template-columns: auto auto auto auto;
background-color: #2196F3;
grid-gap: 10px;
padding: 5px;
}
.grid-container-2 {
display: grid;
grid-template-columns: auto auto;
background-color: #2196F3;
grid-gap: 10px;
padding: 5px;
}
.grid-item {
background-color: rgba(255, 255, 255, 0.8);
padding: 2px;
font-size: 10px;
text-align: center;
}
#wsoutput2 {
overflow-y: scroll;
max-height: 200px;
}
.coinlogo {
max-width: 20px !important;
max-height: 20px !important;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment