Advertisement
Guest User

Untitled

a guest
Mar 13th, 2018
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.53 KB | None | 0 0
  1.  
  2. /**
  3. * @fileoverview Manages the connection to the CodeFights servers.
  4. * @author jakzo
  5. *
  6. * Since there doesn't seem to be a way to get an up-to-date list of challenges
  7. * using pure HTTP requests, I had to mimic what the browser does to set up a
  8. * WebSocket connection with CodeFights. I know nothing about the internals of
  9. * Meteor - I just reverse-engineered this using Chrome developer tools. This
  10. * means it's highly likely to break as soon as CodeFights or Meteor change
  11. * anything.
  12. */
  13.  
  14. /**
  15. * Connection class.
  16. * Creates a new connection to the CodeFights servers.
  17. */
  18. class Connection {
  19. constructor({ username: username, passHash: passHash }) {
  20. this.status = 'closed';
  21.  
  22. // Aparently it's possible to receive a server number, but I've never gotten
  23. // one - it always defaults to a random number between 0 and 999...
  24. this.server = 0; //Math.random() * 1000 | 0;
  25. // The random string is 8 alpha-numeric characters
  26. this.randomString = Math.random().toString(36).substr(2, 8);
  27. // The server seems to only accept secure WebSocket connections
  28. this.protocol = 'wss';
  29. this.host = 'codefights.com';
  30. this.path = '/sockjs/' + this.server + '/' + this.randomString + '/websocket';
  31. this.url = this.protocol + '://' + this.host + this.path;
  32.  
  33. // The current auto-incrementing ID used to identify request responses
  34. this.currentId = 0;
  35. // List of callbacks for requests by ID
  36. this.callbacks = {};
  37. // List of requests waiting to be sent
  38. this.queue = [];
  39. this.events = {};
  40.  
  41. this.openedAt = 0;
  42.  
  43. // Login
  44. this.loggedIn = !!username;
  45. this.username = username;
  46. this.passHash = passHash;
  47.  
  48. // Connect!
  49. this.connect();
  50. }
  51.  
  52. /**
  53. * Connects to the server.
  54. */
  55. connect() {
  56. if (this.status != 'closed') return;
  57. this.status = 'connecting';
  58. this.socket = new WebSocket(this.url);
  59. this.socket.addEventListener('open', (e) => {
  60. log('Socket opened!');
  61. this.status = 'connected';
  62. Connection.all.push(this);
  63. this.openedAt = Date.now();
  64.  
  65. // The server will not let us retreive the challenge list unless we send
  66. // this connection request
  67. this.socket.send(JSON.stringify([JSON.stringify({
  68. msg: 'connect',
  69. version: '1',
  70. support: [ '1' ]
  71. })]));
  72.  
  73. // Get the server time before finishing the connection phase
  74. this.send({
  75. msg: 'method',
  76. method: 'getServerTime',
  77. params: []
  78. }, (time) => {
  79. if (time > 0) {
  80. let now = performance.now();
  81. serverTimeOffset = Math.floor(time - now);
  82. }
  83. this.fireEvent('connect', null);
  84. this.sendPending();
  85. });
  86.  
  87. // Login if we have credentials
  88. if (this.username) {
  89. this.send({
  90. msg: 'method',
  91. method: 'login',
  92. params: [{
  93. user: {
  94. username: this.username
  95. },
  96. password: {
  97. digest: this.passHash,
  98. algorithm: 'sha-256'
  99. }
  100. }]
  101. });
  102. }
  103. }, false);
  104. this.socket.addEventListener('message', (e) => {
  105. // The first character specifies the type of the message
  106. var type = e.data[0],
  107. // The rest contains the message body
  108. body = e.data.slice(1);
  109. this.fireEvent('message', { type: type, body: body });
  110.  
  111. switch (type) {
  112.  
  113. // On open:
  114. case 'o':
  115. log('Socket open notification.');
  116. break;
  117.  
  118. // On data:
  119. case 'a':
  120. // Data comes as a JSON array of JSON strings
  121. // Parse the JSON-ception
  122. let data = JSON.parse(JSON.parse(body)[0]);
  123. log('Received:', data);
  124.  
  125. // If they send us a 'ping', we need to send back a 'pong' so that
  126. // they know we're still alive
  127. if (data.msg == 'ping') {
  128. this.socket.send(JSON.stringify([JSON.stringify({ msg: 'pong' })]));
  129. this.fireEvent('ping', null);
  130. }
  131.  
  132. // Requests and responses are linked using the 'id' property
  133. else {
  134. this.fireEvent('data', data);
  135. let index = this.queue.findIndex((item) => item.data.id == data.id);
  136. if (index >= 0) {
  137. let request = this.queue[index];
  138. this.queue.splice(index, 1);
  139. clearTimeout(request.timeout);
  140. if (request.onSuccess) request.onSuccess(data.result);
  141. }
  142. }
  143. break;
  144.  
  145. // On close:
  146. case 'c':
  147. // The server is telling us that they're closing the socket :'(
  148. log('Socket close notification.');
  149. break;
  150.  
  151. default:
  152. log('Unknown message received: ' + e.data);
  153. this.fireEvent('unknown', e.data);
  154. }
  155. }, false);
  156. this.socket.addEventListener('close', (e) => {
  157. log('Socket closed!');
  158. this.status = 'closed';
  159. Connection.all = Connection.all.filter((connection) => {
  160. return connection != this;
  161. });
  162.  
  163. // Remove requests waiting for a response from the queue
  164. this.queue = this.queue.filter((request) => {
  165. if (request.data.status == 'sent') {
  166. if (this.callbacks.hasOwnProperty(request.data.id)) {
  167. delete this.callbacks[request.data.id];
  168. }
  169. return false;
  170. }
  171. else return true;
  172. });
  173.  
  174. this.fireEvent('close', null);
  175. }, false);
  176. }
  177.  
  178. /**
  179. * Sends a message through the connection.
  180. * @param {any} data - The data to be converted to JSON and sent.
  181. * @param {Function(any)} onSuccess - The function to call with the response
  182. * when it is received.
  183. */
  184. send(data, onSuccess) {
  185.  
  186. // Push the request to the queue
  187. if (!data.id) data.id = ++this.currentId + '';
  188. let request = {
  189. status: 'pending',
  190. onSuccess: onSuccess,
  191. data: data
  192. };
  193. this.queue.push(request);
  194. this.sendPending();
  195. }
  196.  
  197. /**
  198. * Sends all pending requests from the queue.
  199. */
  200. sendPending() {
  201. if (this.status == 'connected') {
  202. this.queue.forEach((request, i) => {
  203. if (request.status == 'pending') {
  204. this.socket.send(JSON.stringify([ JSON.stringify(request.data) ]));
  205. if (request.data.name == 'unseenChallenges') {
  206. console.log(Date.now() / 1000 | 0, 'Resub sent');
  207. }
  208. request.status = 'sent';
  209. log('Sent:', request.data);
  210. setTimeout(() => {
  211. this.queue.splice(i, 1);
  212. }, 60 * 1000);
  213. }
  214. });
  215. }
  216. else if (this.status == 'closed') {
  217. this.connect();
  218. }
  219. }
  220.  
  221. /**
  222. * Closes the connection.
  223. */
  224. close() {
  225. if (this.socket) this.socket.close();
  226. }
  227.  
  228. /**
  229. * Subscribes to a connection event.
  230. */
  231. on(eventName, callback) {
  232. if (!this.events[eventName]) {
  233. this.events[eventName] = [];
  234. }
  235. this.events[eventName].push(callback);
  236. }
  237.  
  238. /**
  239. * Executes all the callbacks for a particular event.
  240. */
  241. fireEvent(eventName, data) {
  242. let callbacks = this.events[eventName];
  243. if (callbacks) {
  244. callbacks.forEach((callback) => callback(data));
  245. }
  246. }
  247.  
  248. /**
  249. * Password hash algorithm (taken from site source). Generates a hash of a
  250. * password which can then be sent to the server when logging in.
  251. * @param {string} password
  252. */
  253. static passwordHash(password) {
  254. let r = password;
  255.  
  256. function n(r, n) {
  257. var t = (65535 & r) + (65535 & n),
  258. e = (r >> 16) + (n >> 16) + (t >> 16);
  259. return e << 16 | 65535 & t
  260. }
  261. function t(r, n) {
  262. return r >>> n | r << 32 - n
  263. }
  264. function e(r, n) {
  265. return r >>> n
  266. }
  267. function o(r, n, t) {
  268. return r & n ^ ~r & t
  269. }
  270. function a(r, n, t) {
  271. return r & n ^ r & t ^ n & t
  272. }
  273. function u(r) {
  274. return t(r, 2) ^ t(r, 13) ^ t(r, 22)
  275. }
  276. function f(r) {
  277. return t(r, 6) ^ t(r, 11) ^ t(r, 25)
  278. }
  279. function c(r) {
  280. return t(r, 7) ^ t(r, 18) ^ e(r, 3)
  281. }
  282. function i(r) {
  283. return t(r, 17) ^ t(r, 19) ^ e(r, 10)
  284. }
  285. function g(r, t) {
  286. var e = new Array(1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298), g = new Array(1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225), h = new Array(64), C, v, d, m, A, l, S, k, P, y, w, b;
  287. r[t >> 5] |= 128 << 24 - t % 32,
  288. r[(t + 64 >> 9 << 4) + 15] = t;
  289. for (var P = 0; P < r.length; P += 16) {
  290. C = g[0],
  291. v = g[1],
  292. d = g[2],
  293. m = g[3],
  294. A = g[4],
  295. l = g[5],
  296. S = g[6],
  297. k = g[7];
  298. for (var y = 0; 64 > y; y++)
  299. 16 > y ? h[y] = r[y + P] : h[y] = n(n(n(i(h[y - 2]), h[y - 7]), c(h[y - 15])), h[y - 16]),
  300. w = n(n(n(n(k, f(A)), o(A, l, S)), e[y]), h[y]),
  301. b = n(u(C), a(C, v, d)),
  302. k = S,
  303. S = l,
  304. l = A,
  305. A = n(m, w),
  306. m = d,
  307. d = v,
  308. v = C,
  309. C = n(w, b);
  310. g[0] = n(C, g[0]),
  311. g[1] = n(v, g[1]),
  312. g[2] = n(d, g[2]),
  313. g[3] = n(m, g[3]),
  314. g[4] = n(A, g[4]),
  315. g[5] = n(l, g[5]),
  316. g[6] = n(S, g[6]),
  317. g[7] = n(k, g[7])
  318. }
  319. return g
  320. }
  321. function h(r) {
  322. for (var n = Array(), t = (1 << d) - 1, e = 0; e < r.length * d; e += d)
  323. n[e >> 5] |= (r.charCodeAt(e / d) & t) << 24 - e % 32;
  324. return n
  325. }
  326. function C(r) {
  327. for (var n = "", t = 0; t < r.length; t++) {
  328. var e = r.charCodeAt(t);
  329. 128 > e ? n += String.fromCharCode(e) : e > 127 && 2048 > e ? (n += String.fromCharCode(e >> 6 | 192),
  330. n += String.fromCharCode(63 & e | 128)) : (n += String.fromCharCode(e >> 12 | 224),
  331. n += String.fromCharCode(e >> 6 & 63 | 128),
  332. n += String.fromCharCode(63 & e | 128))
  333. }
  334. return n
  335. }
  336. function v(r) {
  337. for (var n = m ? "0123456789ABCDEF" : "0123456789abcdef", t = "", e = 0; e < 4 * r.length; e++)
  338. t += n.charAt(r[e >> 2] >> 8 * (3 - e % 4) + 4 & 15) + n.charAt(r[e >> 2] >> 8 * (3 - e % 4) & 15);
  339. return t
  340. }
  341. var d = 8
  342. , m = 0;
  343. return r = C(r),
  344. v(g(h(r), r.length * d))
  345. }
  346. }
  347.  
  348. /** List of all active connections */
  349. Connection.all = [];
  350.  
  351. /** Default connection for general requests */
  352. Connection.general = new Connection({
  353. username: settings.username,
  354. passHash: settings.passHash
  355. });
  356.  
  357. // Keep the connection open
  358. let reconnectInterval = null;
  359. let reconnect = () => {
  360. if (!reconnectInterval) {
  361. reconnectInterval = setInterval(reconnect, 60 * 1000);
  362. }
  363. Connection.general.connect();
  364. }
  365. Connection.general.on('connect', () => {
  366. clearInterval(reconnectInterval);
  367. reconnectInterval = null;
  368. });
  369. Connection.general.on('close', reconnect);
  370. reconnect();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement