github.com/llimllib/devd@v0.0.0-20230426145215-4d29fc25f909/livereload/static/client.js (about)

     1  (function() {
     2      if (!('WebSocket' in window)) {
     3          return;
     4      }
     5  
     6      function DevdReconnectingWebSocket(url, protocols, options) {
     7  
     8          // Default settings
     9          var settings = {
    10  
    11              /** Whether this instance should log debug messages. */
    12              debug: false,
    13  
    14              /** Whether or not the websocket should attempt to connect immediately upon instantiation. */
    15              automaticOpen: true,
    16  
    17              /** The number of milliseconds to delay before attempting to reconnect. */
    18              reconnectInterval: 1000,
    19              /** The maximum number of milliseconds to delay a reconnection attempt. */
    20              maxReconnectInterval: 30000,
    21              /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */
    22              reconnectDecay: 1.5,
    23  
    24              /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */
    25              timeoutInterval: 2000,
    26  
    27              /** The maximum number of reconnection attempts to make. Unlimited if null. */
    28              maxReconnectAttempts: null,
    29  
    30              /** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
    31              binaryType: 'blob'
    32          }
    33          if (!options) {
    34              options = {};
    35          }
    36  
    37          // Overwrite and define settings with options if they exist.
    38          for (var key in settings) {
    39              if (typeof options[key] !== 'undefined') {
    40                  this[key] = options[key];
    41              } else {
    42                  this[key] = settings[key];
    43              }
    44          }
    45  
    46          // These should be treated as read-only properties
    47  
    48          /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */
    49          this.url = url;
    50  
    51          /** The number of attempted reconnects since starting, or the last successful connection. Read only. */
    52          this.reconnectAttempts = 0;
    53  
    54          /**
    55           * The current state of the connection.
    56           * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
    57           * Read only.
    58           */
    59          this.readyState = WebSocket.CONNECTING;
    60  
    61          /**
    62           * A string indicating the name of the sub-protocol the server selected; this will be one of
    63           * the strings specified in the protocols parameter when creating the WebSocket object.
    64           * Read only.
    65           */
    66          this.protocol = null;
    67  
    68          // Private state variables
    69  
    70          var self = this;
    71          var ws;
    72          var forcedClose = false;
    73          var timedOut = false;
    74          var eventTarget = document.createElement('div');
    75  
    76          // Wire up "on*" properties as event handlers
    77  
    78          eventTarget.addEventListener('open', function(event) {
    79              self.onopen(event);
    80          });
    81          eventTarget.addEventListener('close', function(event) {
    82              self.onclose(event);
    83          });
    84          eventTarget.addEventListener('connecting', function(event) {
    85              self.onconnecting(event);
    86          });
    87          eventTarget.addEventListener('message', function(event) {
    88              self.onmessage(event);
    89          });
    90          eventTarget.addEventListener('error', function(event) {
    91              self.onerror(event);
    92          });
    93  
    94          // Expose the API required by EventTarget
    95  
    96          this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
    97          this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
    98          this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
    99  
   100          /**
   101           * This function generates an event that is compatible with standard
   102           * compliant browsers and IE9 - IE11
   103           *
   104           * This will prevent the error:
   105           * Object doesn't support this action
   106           *
   107           * http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563
   108           * @param s String The name that the event should use
   109           * @param args Object an optional object that the event will use
   110           */
   111          function generateEvent(s, args) {
   112              var evt = document.createEvent("CustomEvent");
   113              evt.initCustomEvent(s, false, false, args);
   114              return evt;
   115          };
   116  
   117          this.open = function(reconnectAttempt) {
   118              ws = new WebSocket(self.url, protocols || []);
   119              ws.binaryType = this.binaryType;
   120  
   121              if (reconnectAttempt) {
   122                  if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
   123                      return;
   124                  }
   125              } else {
   126                  eventTarget.dispatchEvent(generateEvent('connecting'));
   127                  this.reconnectAttempts = 0;
   128              }
   129  
   130              if (self.debug || DevdReconnectingWebSocket.debugAll) {
   131                  console.debug('DevdReconnectingWebSocket', 'attempt-connect', self.url);
   132              }
   133  
   134              var localWs = ws;
   135              var timeout = setTimeout(function() {
   136                  if (self.debug || DevdReconnectingWebSocket.debugAll) {
   137                      console.debug('DevdReconnectingWebSocket', 'connection-timeout', self.url);
   138                  }
   139                  timedOut = true;
   140                  localWs.close();
   141                  timedOut = false;
   142              }, self.timeoutInterval);
   143  
   144              ws.onopen = function(event) {
   145                  clearTimeout(timeout);
   146                  if (self.debug || DevdReconnectingWebSocket.debugAll) {
   147                      console.debug('DevdReconnectingWebSocket', 'onopen', self.url);
   148                  }
   149                  self.protocol = ws.protocol;
   150                  self.readyState = WebSocket.OPEN;
   151                  self.reconnectAttempts = 0;
   152                  var e = generateEvent('open');
   153                  e.isReconnect = reconnectAttempt;
   154                  reconnectAttempt = false;
   155                  eventTarget.dispatchEvent(e);
   156              };
   157  
   158              ws.onclose = function(event) {
   159                  clearTimeout(timeout);
   160                  ws = null;
   161                  if (forcedClose) {
   162                      self.readyState = WebSocket.CLOSED;
   163                      eventTarget.dispatchEvent(generateEvent('close'));
   164                  } else {
   165                      self.readyState = WebSocket.CONNECTING;
   166                      var e = generateEvent('connecting');
   167                      e.code = event.code;
   168                      e.reason = event.reason;
   169                      e.wasClean = event.wasClean;
   170                      eventTarget.dispatchEvent(e);
   171                      if (!reconnectAttempt && !timedOut) {
   172                          if (self.debug || DevdReconnectingWebSocket.debugAll) {
   173                              console.debug('DevdReconnectingWebSocket', 'onclose', self.url);
   174                          }
   175                          eventTarget.dispatchEvent(generateEvent('close'));
   176                      }
   177  
   178                      var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
   179                      setTimeout(function() {
   180                          self.reconnectAttempts++;
   181                          self.open(true);
   182                      }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
   183                  }
   184              };
   185              ws.onmessage = function(event) {
   186                  if (self.debug || DevdReconnectingWebSocket.debugAll) {
   187                      console.debug('DevdReconnectingWebSocket', 'onmessage', self.url, event.data);
   188                  }
   189                  var e = generateEvent('message');
   190                  e.data = event.data;
   191                  eventTarget.dispatchEvent(e);
   192              };
   193              ws.onerror = function(event) {
   194                  if (self.debug || DevdReconnectingWebSocket.debugAll) {
   195                      console.debug('DevdReconnectingWebSocket', 'onerror', self.url, event);
   196                  }
   197                  eventTarget.dispatchEvent(generateEvent('error'));
   198              };
   199          }
   200  
   201          // Whether or not to create a websocket upon instantiation
   202          if (this.automaticOpen == true) {
   203              this.open(false);
   204          }
   205  
   206          /**
   207           * Transmits data to the server over the WebSocket connection.
   208           *
   209           * @param data a text string, ArrayBuffer or Blob to send to the server.
   210           */
   211          this.send = function(data) {
   212              if (ws) {
   213                  if (self.debug || DevdReconnectingWebSocket.debugAll) {
   214                      console.debug('DevdReconnectingWebSocket', 'send', self.url, data);
   215                  }
   216                  return ws.send(data);
   217              } else {
   218                  throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
   219              }
   220          };
   221  
   222          /**
   223           * Closes the WebSocket connection or connection attempt, if any.
   224           * If the connection is already CLOSED, this method does nothing.
   225           */
   226          this.close = function(code, reason) {
   227              // Default CLOSE_NORMAL code
   228              if (typeof code == 'undefined') {
   229                  code = 1000;
   230              }
   231              forcedClose = true;
   232              if (ws) {
   233                  ws.close(code, reason);
   234              }
   235          };
   236  
   237          /**
   238           * Additional public API method to refresh the connection if still open (close, re-open).
   239           * For example, if the app suspects bad data / missed heart beats, it can try to refresh.
   240           */
   241          this.refresh = function() {
   242              if (ws) {
   243                  ws.close();
   244              }
   245          };
   246      }
   247  
   248      /**
   249       * An event listener to be called when the WebSocket connection's readyState changes to OPEN;
   250       * this indicates that the connection is ready to send and receive data.
   251       */
   252      DevdReconnectingWebSocket.prototype.onopen = function(event) {};
   253      /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */
   254      DevdReconnectingWebSocket.prototype.onclose = function(event) {};
   255      /** An event listener to be called when a connection begins being attempted. */
   256      DevdReconnectingWebSocket.prototype.onconnecting = function(event) {};
   257      /** An event listener to be called when a message is received from the server. */
   258      DevdReconnectingWebSocket.prototype.onmessage = function(event) {};
   259      /** An event listener to be called when an error occurs. */
   260      DevdReconnectingWebSocket.prototype.onerror = function(event) {};
   261  
   262      /**
   263       * Whether all instances of DevdReconnectingWebSocket should log debug messages.
   264       * Setting this to true is the equivalent of setting all instances of DevdReconnectingWebSocket.debug to true.
   265       */
   266      DevdReconnectingWebSocket.debugAll = false;
   267  
   268      DevdReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
   269      DevdReconnectingWebSocket.OPEN = WebSocket.OPEN;
   270      DevdReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
   271      DevdReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
   272  
   273      window.DevdReconnectingWebSocket = DevdReconnectingWebSocket;
   274  
   275      var proto = "ws://";
   276      if (window.location.protocol == "https:") {
   277          proto = "wss://";
   278      }
   279  
   280      ws = new DevdReconnectingWebSocket(
   281          proto + window.location.host + "/.devd.livereload",
   282          null,
   283          {
   284              debug: true,
   285              maxReconnectInterval: 3000,
   286          }
   287      )
   288      ws.onmessage = function(event) {
   289          if (event.data == "page") {
   290              ws.close();
   291              location.reload();
   292          } else if (event.data == "css") {
   293              // This snippet pinched from quickreload, under the MIT license:
   294              // https://github.com/bjoerge/quickreload/blob/master/client.js
   295              var killcache = '__devd=' + new Date().getTime();
   296              var stylesheets = Array.prototype.slice.call(
   297                  document.querySelectorAll('link[rel="stylesheet"]')
   298              );
   299              stylesheets.forEach(function (el) {
   300                  var href = el.href.replace(/(&|\?)__devd\=\d+/, '');
   301                  el.href = '';
   302                  el.href = href + (href.indexOf("?") == -1 ? '?' : '&') + killcache;
   303              });
   304          }
   305      }
   306      window.addEventListener("beforeunload", function(e) {
   307          ws.close();
   308          delete e.returnValue;
   309          return;
   310      });
   311  })();