Connection.js 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import {shortTopicUrl, topicUrlWs, topicUrlWsWithSince} from "./utils";
  2. const retryBackoffSeconds = [5, 10, 15, 20, 30, 45];
  3. class Connection {
  4. constructor(subscriptionId, baseUrl, topic, since, onNotification) {
  5. this.subscriptionId = subscriptionId;
  6. this.baseUrl = baseUrl;
  7. this.topic = topic;
  8. this.since = since;
  9. this.shortUrl = shortTopicUrl(baseUrl, topic);
  10. this.onNotification = onNotification;
  11. this.ws = null;
  12. this.retryCount = 0;
  13. this.retryTimeout = null;
  14. }
  15. start() {
  16. // Don't fetch old messages; we do that as a poll() when adding a subscription;
  17. // we don't want to re-trigger the main view re-render potentially hundreds of times.
  18. const wsUrl = (this.since === 0)
  19. ? topicUrlWs(this.baseUrl, this.topic)
  20. : topicUrlWsWithSince(this.baseUrl, this.topic, this.since.toString());
  21. console.log(`[Connection, ${this.shortUrl}] Opening connection to ${wsUrl}`);
  22. this.ws = new WebSocket(wsUrl);
  23. this.ws.onopen = (event) => {
  24. console.log(`[Connection, ${this.shortUrl}] Connection established`, event);
  25. this.retryCount = 0;
  26. }
  27. this.ws.onmessage = (event) => {
  28. console.log(`[Connection, ${this.shortUrl}] Message received from server: ${event.data}`);
  29. try {
  30. const data = JSON.parse(event.data);
  31. const relevantAndValid =
  32. data.event === 'message' &&
  33. 'id' in data &&
  34. 'time' in data &&
  35. 'message' in data;
  36. if (!relevantAndValid) {
  37. console.log(`[Connection, ${this.shortUrl}] Message irrelevant or invalid. Ignoring.`);
  38. return;
  39. }
  40. this.since = data.time + 1; // Sigh. This works because on reconnect, we wait 5+ seconds anyway.
  41. this.onNotification(this.subscriptionId, data);
  42. } catch (e) {
  43. console.log(`[Connection, ${this.shortUrl}] Error handling message: ${e}`);
  44. }
  45. };
  46. this.ws.onclose = (event) => {
  47. if (event.wasClean) {
  48. console.log(`[Connection, ${this.shortUrl}] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
  49. this.ws = null;
  50. } else {
  51. const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length-1)];
  52. this.retryCount++;
  53. console.log(`[Connection, ${this.shortUrl}] Connection died, retrying in ${retrySeconds} seconds`);
  54. this.retryTimeout = setTimeout(() => this.start(), retrySeconds * 1000);
  55. }
  56. };
  57. this.ws.onerror = (event) => {
  58. console.log(`[Connection, ${this.shortUrl}] Error occurred: ${event}`, event);
  59. };
  60. }
  61. close() {
  62. console.log(`[Connection, ${this.shortUrl}] Closing connection`);
  63. const socket = this.ws;
  64. const retryTimeout = this.retryTimeout;
  65. if (socket !== null) {
  66. socket.close();
  67. }
  68. if (retryTimeout !== null) {
  69. clearTimeout(retryTimeout);
  70. }
  71. this.retryTimeout = null;
  72. this.ws = null;
  73. }
  74. }
  75. export default Connection;