constructor() {
    // #endregion ---- ---- ---- ----
    // #region private class vars
    // How many times has the loaded instance connected to Socket.IO (detect if not a new load?)
    __publicField(this, "connectedNum", 0);
    // event listener callbacks by property name
    // #events = {}
    // Socket.IO channel names
    __publicField(this, "_ioChannels", { control: "uiBuilderControl", client: "uiBuilderClient", server: "uiBuilder" });
    /** setInterval holder for pings @type {function|undefined} */
    __privateAdd(this, _pingInterval);
    // onChange event callbacks
    __privateAdd(this, _propChangeCallbacks, {});
    // onTopic event callbacks
    __privateAdd(this, _msgRecvdByTopicCallbacks, {});
    // Is Vue available?
    __publicField(this, "isVue", false);
    // What version? Set in startup if Vue is loaded. Won't always work
    __publicField(this, "vueVersion");
    /** setInterval id holder for Socket.IO checkConnect
     * @type {number|null}
     */
    __privateAdd(this, _timerid, null);
    // Holds the reference ID for the internal msg change event handler so that it can be cancelled
    __privateAdd(this, _MsgHandler);
    // Placeholder for io.socket - can't make a # var until # fns allowed in all browsers
    __publicField(this, "_socket");
    // Placeholder for an observer that watches the whole DOM for changes - can't make a # var until # fns allowed in all browsers
    __publicField(this, "_htmlObserver");
    // Has showMsg been turned on?
    __privateAdd(this, _isShowMsg, false);
    // Has showStatus been turned on?
    __privateAdd(this, _isShowStatus, false);
    // If true, URL hash changes send msg back to node-red. Controlled by watchUrlHash()
    __privateAdd(this, _sendUrlHash, false);
    // Used to help create unique element ID's if one hasn't been provided, increment on use
    __privateAdd(this, _uniqueElID, 0);
    // Externally accessible command functions (NB: Case must match) - remember to update _uibCommand for new commands
    __privateAdd(this, _extCommands, [
      "elementExists",
      "get",
      "getManagedVarList",
      "getWatchedVars",
      "htmlSend",
      "include",
      "navigate",
      "scrollTo",
      "set",
      "showMsg",
      "showStatus",
      "uiGet",
      "uiWatch",
      "watchUrlHash"
    ]);
    /** @type {Object<string, string>} Managed uibuilder variables */
    __privateAdd(this, _managedVars, {});
    // What status variables to show via showStatus()
    __privateAdd(this, _showStatus, {
      online: { var: "online", label: "Online?", description: "Is the browser online?" },
      ioConnected: { var: "ioConnected", label: "Socket.IO connected?", description: "Is Socket.IO connected?" },
      connectedNum: { var: "connectedNum", label: "# reconnections", description: "How many times has Socket.IO had to reconnect since last page load?" },
      clientId: { var: "clientId", label: "Client ID", description: "Static client unique id set in Node-RED. Only changes when browser is restarted." },
      tabId: { var: "tabId", label: "Browser tab ID", description: "Static unique id for the browser's current tab" },
      cookies: { var: "cookies", label: "Cookies", description: "Cookies set in Node-RED" },
      httpNodeRoot: { var: "httpNodeRoot", label: "httpNodeRoot", description: "From Node-RED' settings.js, affects URL's. May be wrong for pages in sub-folders" },
      pageName: { var: "pageName", label: "Page name", description: "Actual name of this page" },
      ioNamespace: { var: "ioNamespace", label: "SIO namespace", description: "Socket.IO namespace - unique to each uibuilder node instance" },
      // ioPath: { 'var': 'ioPath', 'label': 'SIO path', 'description': '', }, // no longer needed in the modern client
      socketError: { var: "socketError", label: "Socket error", description: "If the Socket.IO connection has failed, says why" },
      msgsSent: { var: "msgsSent", label: "# msgs sent", description: "How many standard messages have been sent to Node-RED?" },
      msgsReceived: { var: "msgsReceived", label: "# msgs received", description: "How many standard messages have been received from Node-RED?" },
      msgsSentCtrl: { var: "msgsSentCtrl", label: "# control msgs sent", description: "How many control messages have been sent to Node-RED?" },
      msgsCtrlReceived: { var: "msgsCtrlReceived", label: "# control msgs received", description: "How many control messages have been received from Node-RED?" },
      originator: { var: "originator", label: "Node Originator", description: "If the last msg from Node-RED was from a `uib-sender` node, this will be its node id so that messasges can be returned to it" },
      topic: { var: "topic", label: "Default topic", description: "Optional default topic to be included in outgoing standard messages" },
      started: { var: "started", label: "Has uibuilder client started?", description: "Whether `uibuilder.start()` ran successfully. This should self-run and should not need to be run manually" },
      version: { var: "version", label: "uibuilder client version", description: "The version of the loaded uibuilder client library" },
      serverTimeOffset: { var: "serverTimeOffset", label: "Server time offset (Hrs)", description: "The number of hours difference between the Node-red server and the client" }
    });
    // Track ui observers (see uiWatch)
    __privateAdd(this, _uiObservers, {});
    // List of uib specific attributes that will be watched and processed dynamically
    __publicField(this, "uibAttribs", ["uib-topic", "data-uib-topic"]);
    __privateAdd(this, _uibAttrSel, "[".concat(this.uibAttribs.join("], ["), "]"));
    //#endregion
    // #region public class vars
    // TODO Move to proper getters
    // #region ---- Externally read-only (via .get method) ---- //
    // version - moved to _meta
    /** Client ID set by uibuilder on connect */
    __publicField(this, "clientId", "");
    /** The collection of cookies provided by uibuilder */
    __publicField(this, "cookies", {});
    /** Copy of last control msg object received from sever */
    __publicField(this, "ctrlMsg", {});
    /** Is Socket.IO client connected to the server? */
    __publicField(this, "ioConnected", false);
    // Is the library running from a minified version?
    __publicField(this, "isMinified", isMinified);
    // Is the browser tab containing this page visible or not?
    __publicField(this, "isVisible", false);
    // Remember the last page (re)load/navigation type: navigate, reload, back_forward, prerender
    __publicField(this, "lastNavType", "");
    // Max msg size that can be sent over Socket.IO - updated by "client connect" msg receipt
    __publicField(this, "maxHttpBufferSize", 1048576);
    /** Last std msg received from Node-RED */
    __publicField(this, "msg", {});
    /** number of messages sent to server since page load */
    __publicField(this, "msgsSent", 0);
    /** number of messages received from server since page load */
    __publicField(this, "msgsReceived", 0);
    /** number of control messages sent to server since page load */
    __publicField(this, "msgsSentCtrl", 0);
    /** number of control messages received from server since page load */
    __publicField(this, "msgsCtrlReceived", 0);
    /** Is the client online or offline? */
    __publicField(this, "online", navigator.onLine);
    /** last control msg object sent via uibuilder.send() @since v2.0.0-dev3 */
    __publicField(this, "sentCtrlMsg", {});
    /** last std msg object sent via uibuilder.send() */
    __publicField(this, "sentMsg", {});
    /** placeholder to track time offset from server, see fn socket.on(ioChannels.server ...) */
    __publicField(this, "serverTimeOffset", null);
    /** placeholder for a socket error message */
    __publicField(this, "socketError", null);
    // tab identifier from session storage
    __publicField(this, "tabId", "");
    // Actual name of current page (set in constructor)
    __publicField(this, "pageName", null);
    // Is the DOMPurify library loaded? Updated in start()
    __publicField(this, "purify", false);
    // Is the Markdown-IT library loaded? Updated in start()
    __publicField(this, "markdown", false);
    // Current URL hash. Initial set is done from start->watchHashChanges via a set to make it watched
    __publicField(this, "urlHash", location.hash);
    // #endregion ---- ---- ---- ---- //
    // TODO Move to proper getters/setters
    // #region ---- Externally Writable (via .set method, read via .get method) ---- //
    /** Default originator node id - empty string by default
     * @type {string}
     */
    __publicField(this, "originator", "");
    /** Default topic - used by send if set and no topic provided
     * @type {(string|undefined)}
     */
    __publicField(this, "topic");
    /** Either undefined or a reference to a uib router instance
     * Set by uibrouter, do not set manually.
     */
    __publicField(this, "uibrouterinstance");
    /** Set by uibrouter, do not set manually */
    __publicField(this, "uibrouter_CurrentRoute");
    // #endregion ---- ---- ---- ---- //
    // #region ---- These are unlikely to be needed externally: ----
    __publicField(this, "autoSendReady", true);
    __publicField(this, "httpNodeRoot", "");
    // Node-RED setting (via cookie)
    __publicField(this, "ioNamespace", "");
    __publicField(this, "ioPath", "");
    __publicField(this, "retryFactor", 1.5);
    // starting delay factor for subsequent reconnect attempts
    __publicField(this, "retryMs", 2e3);
    // starting retry ms period for manual socket reconnections workaround
    __publicField(this, "storePrefix", "uib_");
    // Prefix for all uib-related localStorage
    __publicField(this, "started", false);
    // NOTE: These can only change when a client (re)connects
    __publicField(this, "socketOptions", {
      path: this.ioPath,
      // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
      // https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API
      // https://socket.io/get-started/webtransport
      // NOTE: webtransport requires HTTP/3 and TLS. HTTP/2 & 3 not yet available in Node.js
      // transports: ['polling', 'websocket', 'webtransport'],
      transports: ["polling", "websocket"],
      // Using callback so that they are updated automatically on (re)connect
      // Only put things in here that will be valid for a websocket connected session
      auth: (cb) => {
        cb({
          clientVersion: version,
          clientId: this.clientId,
          pathName: window.location.pathname,
          urlParams: Object.fromEntries(new URLSearchParams(location.search)),
          pageName: this.pageName,
          tabId: this.tabId,
          lastNavType: this.lastNavType,
          connectedNum: ++this.connectedNum,
          // Used to calculate the diff between the server and client connection timestamps - reported if >1 minute
          browserConnectTimestamp: (/* @__PURE__ */ new Date()).toISOString()
        });
      },
      transportOptions: {
        // Can only set headers when polling
        polling: {
          extraHeaders: {
            "x-clientid": "".concat(_a2._meta.displayName, "; ").concat(_a2._meta.type, "; ").concat(_a2._meta.version, "; ").concat(this.clientId)
          }
        }
      }
    });
    // --- End of elementIsVisible --- //
    // #endregion -------- -------- -------- //
    // #region ------- UI handlers --------- //
    // #region -- Direct to _ui --
    // ! NOTE: Direct assignments change the target `this` to here. Use with caution
    // However, also note that the window/jsdom and the window.document
    // references are now static in _ui so not impacted by this.
    /** Simplistic jQuery-like document CSS query selector, returns an HTML Element
     * NOTE that this fn returns the element itself. Use $$ to get the properties of 1 or more elements.
     * If the selected element is a <template>, returns the first child element.
     * type {HTMLElement}
     * @param {string} cssSelector A CSS Selector that identifies the element to return
     * @returns {HTMLElement|null} Selected HTML element or null
     */
    __publicField(this, "$", _ui.$);
    /** CSS query selector that returns ALL found selections. Matches the Chromium DevTools feature of the same name.
     * NOTE that this fn returns an array showing the PROPERTIES of the elements whereas $ returns the element itself
     * @param {string} cssSelector A CSS Selector that identifies the elements to return
     * @returns {HTMLElement[]} Array of DOM elements/nodes. Array is empty if selector is not found.
     */
    __publicField(this, "$$", _ui.$$);
    /** Reference to the full ui library */
    __publicField(this, "$ui", _ui);
    /** Add 1 or several class names to an element
     * @param {string|string[]} classNames Single or array of classnames
     * @param {HTMLElement} el HTML Element to add class(es) to
     */
    __publicField(this, "addClass", _ui.addClass);
    /** Apply a source template tag to a target html element
     * NOTES:
     * - styles in ALL templates are accessible to all templates.
     * - scripts in templates are run AT TIME OF APPLICATION (so may run multiple times).
     * - scripts in templates are applied in order of application, so variables may not yet exist if defined in subsequent templates
     * @param {HTMLElement} source The source element
     * @param {HTMLElement} target The target element
     * @param {boolean} onceOnly If true, the source will be adopted (the source is moved)
     */
    __publicField(this, "applyTemplate", _ui.applyTemplate);
    /** Remove All, 1 or more class names from an element
     * @param {undefined|null|""|string|string[]} classNames Single or array of classnames. If undefined, "" or null, remove all classes
     * @param {HTMLElement} el HTML Element to add class(es) to
     */
    __publicField(this, "removeClass", _ui.removeClass);
    log("trace", "Uib:constructor", "Starting")();
    window.addEventListener("offline", (e) => {
      this.set("online", false);
      this.set("ioConnected", false);
      log("warn", "Browser", "DISCONNECTED from network")();
    });
    window.addEventListener("online", (e) => {
      this.set("online", true);
      log("warn", "Browser", "Reconnected to network")();
      this._checkConnect();
    });
    document.cookie.split(";").forEach((c) => {
      const splitC = c.split("=");
      this.cookies[splitC[0].trim()] = splitC[1];
    });
    this.set("clientId", this.cookies["uibuilder-client-id"]);
    log("trace", "Uib:constructor", "Client ID: ", this.clientId)();
    this.set("tabId", window.sessionStorage.getItem("tabId"));
    if (!this.tabId) {
      this.set("tabId", "t".concat(Math.floor(Math.random() * 1e6)));
      window.sessionStorage.setItem("tabId", this.tabId);
    }
    document.addEventListener("load", () => {
      this.set("isVisible", true);
    });
    document.addEventListener("visibilitychange", () => {
      this.set("isVisible", document.visibilityState === "visible");
      this.sendCtrl({ uibuilderCtrl: "visibility", isVisible: this.isVisible });
    });
    document.addEventListener("uibuilder:propertyChanged", (event2) => {
      if (!__privateGet(this, _isShowStatus)) return;
      if (event2.detail.prop in __privateGet(this, _showStatus)) {
        document.querySelector('td[data-vartype="'.concat(event2.detail.prop, '"]')).innerText = JSON.stringify(event2.detail.value);
      }
    });
    this.set("ioNamespace", this._getIOnamespace());
    if ("uibuilder-webRoot" in this.cookies) {
      this.set("httpNodeRoot", this.cookies["uibuilder-webRoot"]);
      log("trace", "Uib:constructor", 'httpNodeRoot set by cookie to "'.concat(this.httpNodeRoot, '"'))();
    } else {
      const fullPath = window.location.pathname.split("/").filter(function(t) {
        return t.trim() !== "";
      });
      if (fullPath.length > 0 && fullPath[fullPath.length - 1].endsWith(".html")) fullPath.pop();
      fullPath.pop();
      this.set("httpNodeRoot", "/".concat(fullPath.join("/")));
      log("trace", "[Uib:constructor]", 'httpNodeRoot set by URL parsing to "'.concat(this.httpNodeRoot, '". NOTE: This may fail for pages in sub-folders.'))();
    }
    this.set("ioPath", this.urlJoin(this.httpNodeRoot, _a2._meta.displayName, "vendor", "socket.io"));
    log("trace", "Uib:constructor", 'ioPath: "'.concat(this.ioPath, '"'))();
    this.set("pageName", window.location.pathname.replace("".concat(this.ioNamespace, "/"), ""));
    if (this.pageName.endsWith("/")) this.set("pageName", "".concat(this.pageName, "index.html"));
    if (this.pageName === "") this.set("pageName", "index.html");
    try {
      const autoloadVars = this.getStore("_uibAutoloadVars");
      if (Object.keys(autoloadVars).length > 0) {
        Object.keys(autoloadVars).forEach((id) => {
          this.set(id, this.getStore(id));
        });
      }
    } catch (e) {
    }
    this._dispatchCustomEvent("uibuilder:constructorComplete");
    log("trace", "Uib:constructor", "Ending")();
  }
Wrap an object in a JS proxy
WARNING: Sadly, `let x = uib.createProxy( [1,2] ); x.push(3);` Does not trigger a send because that is classed as a
GET, not a SET.
param {*} target The target object to proxy
returns {Proxy} A proxied version of the target object