HubSpot Chat Integration einrichten

Möchten Sie einen CSM in Discourse integrieren? Sehen wir uns an, wie Sie den HubSpot-Chat in Discourse integrieren!

  1. Erstellen Sie ein Konto unter HubSpot

  2. Wählen Sie Chat

  3. Passen Sie die Benutzeroberfläche und die Verfügbarkeit nach Wunsch an


  4. Kopieren Sie den Code

  5. Erstellen Sie eine neue Theme-Komponente und fügen Sie sie in die Registerkarte „Common - </body>“ ein. Fügen Sie die neue Komponente zu Ihren Hauptthemen hinzu

  6. Schließen Sie die Verifizierung in Hubspot ab, um das Widget zu aktivieren, und rufen Sie Ihre Website auf

  7. Fertig :tada:

12 „Gefällt mir“

Hallo Daniella, ich habe diese Integration mit drei verschiedenen Chat-Anbietern ausprobiert und erhalte nach dem Hinzufügen aller gefundenen Links zu security policy script src weiterhin denselben CSP-Fehler. Dieser betrifft zwar Tidio, aber das gleiche Problem trat auch bei LiveChat und Pure Chat auf. Hast du eine Idee, was hier schiefgehen könnte?

Content Security Policy: Das Seiten-Setup hat das Laden einer Ressource an eval („script-src“) blockiert. Quelle: (function injected(eventName, injectedIntoContentWindow)
{
  let checkRequest;

  /*
   * Frame-Kontext-Wrapper
   *
   * In einigen Randfällen führt Chrome Content-Skripte nicht innerhalb von Frames aus.
   * Webseiten haben begonnen, dies auszunutzen, um über das contentWindow eines Frames
   * auf nicht gekapselte APIs zuzugreifen (#4586, 5207). Daher müssen wir, bis Chrome
   * Content-Skripte konsistent für alle Frames ausführt, sicherstellen, dass unsere
   * Wrapper beim Zugriff auf das contentWindow (erneut) injiziert werden.
   */
  let injectedToString = Function.prototype.toString.bind(injected);
  let injectedFrames = new WeakSet();
  let injectedFramesAdd = WeakSet.prototype.add.bind(injectedFrames);
  let injectedFramesHas = WeakSet.prototype.has.bind(injectedFrames);

  function injectIntoContentWindow(contentWindow)
  {
    if (contentWindow && !injectedFramesHas(contentWindow))
    {
      injectedFramesAdd(contentWindow);
      try
      {
        contentWindow[eventName] = checkRequest;
        contentWindow.eval(
          "(" + injectedToString() + ")('" + eventName + "', true);"
        );
        delete contentWindow[eventName];
      }
      catch (e) {}
    }
  }

  for (let element of [HTMLFrameElement, HTMLIFrameElement, HTMLObjectElement])
  {
    let contentDocumentDesc = Object.getOwnPropertyDescriptor(
      element.prototype, "contentDocument"
    );
    let contentWindowDesc = Object.getOwnPropertyDescriptor(
      element.prototype, "contentWindow"
    );

    // Anscheinend existiert in älteren Chrome-Versionen wie 51
    // HTMLObjectElement.prototype.contentWindow nicht.
    if (!contentWindowDesc)
      continue;

    let getContentDocument = Function.prototype.call.bind(
      contentDocumentDesc.get
    );
    let getContentWindow = Function.prototype.call.bind(
      contentWindowDesc.get
    );

    contentWindowDesc.get = function()
    {
      let contentWindow = getContentWindow(this);
      injectIntoContentWindow(contentWindow);
      return contentWindow;
    };
    contentDocumentDesc.get = function()
    {
      injectIntoContentWindow(getContentWindow(this));
      return getContentDocument(this);
    };
    Object.defineProperty(element.prototype, "contentWindow",
                          contentWindowDesc);
    Object.defineProperty(element.prototype, "contentDocument",
                          contentDocumentDesc);
  }

  /*
   * RTCPeerConnection-Wrapper
   *
   * Die webRequest-API in Chrome erlaubt derzeit noch nicht das Blockieren
   * von WebRTC-Verbindungen.
   * Siehe https://bugs.chromium.org/p/chromium/issues/detail?id=707683
   */
  let RealCustomEvent = window.CustomEvent;

  // Wenn wir über contentWindow in einen Frame injiziert wurden, können wir
  // einfach die Kopie von checkRequest nehmen, die uns vom Hauptdokument
  * hinterlassen wurde. Andernfalls müssen wir sie jetzt einrichten, zusammen
  * mit den Event-Handler-Funktionen.
  if (injectedIntoContentWindow)
    checkRequest = window[eventName];
  else
  {
    let addEventListener = document.addEventListener.bind(document);
    let dispatchEvent = document.dispatchEvent.bind(document);
    let removeEventListener = document.removeEventListener.bind(document);
    checkRequest = (url, callback) =>
    {
      let incomingEventName = eventName + "-" + url;

      function listener(event)
      {
        callback(event.detail);
        removeEventListener(incomingEventName, listener);
      }
      addEventListener(incomingEventName, listener);

      dispatchEvent(new RealCustomEvent(eventName, {detail: {url}}));
    };
  }

  // Nur vor dem Code der Seite aufzurufen, nicht gehärtet.
  function copyProperties(src, dest, properties)
  {
    for (let name of properties)
    {
      if (Object.prototype.hasOwnProperty.call(src, name))
      {
        Object.defineProperty(dest, name,
                              Object.getOwnPropertyDescriptor(src, name));
      }
    }
  }

  let RealRTCPeerConnection = window.RTCPeerConnection ||
                              window.webkitRTCPeerConnection;

  // Firefox hat die Option (media.peerconnection.enabled), WebRTC zu deaktivieren,
  // in welchem Fall RealRTCPeerConnection undefiniert ist.
  if (typeof RealRTCPeerConnection != "undefined")
  {
    let closeRTCPeerConnection = Function.prototype.call.bind(
      RealRTCPeerConnection.prototype.close
    );
    let RealArray = Array;
    let RealString = String;
    let {create: createObject, defineProperty} = Object;

    let normalizeUrl = url =>
    {
      if (typeof url != "undefined")
        return RealString(url);
    };

    let safeCopyArray = (originalArray, transform) =>
    {
      if (originalArray == null || typeof originalArray != "object")
        return originalArray;

      let safeArray = RealArray(originalArray.length);
      for (let i = 0; i < safeArray.length; i++)
      {
        defineProperty(safeArray, i, {
          configurable: false, enumerable: false, writable: false,
          value: transform(originalArray[i])
        });
      }
      defineProperty(safeArray, "length", {
        configurable: false, enumerable: false, writable: false,
        value: safeArray.length
      });
      return safeArray;
    };

    // Es wäre viel einfacher, die .getConfiguration-Methode zu verwenden, um
    * die normalisierte und sichere Konfiguration von der RTCPeerConnection-
    * Instanz zu erhalten. Leider ist dies in Chrome unstable 59 noch nicht
    * implementiert.
    // Siehe https://www.chromestatus.com/feature/5271355306016768
    let protectConfiguration = configuration =>
    {
      if (configuration == null || typeof configuration != "object")
        return configuration;

      let iceServers = safeCopyArray(
        configuration.iceServers,
        iceServer =>
        {
          let {url, urls} = iceServer;

          // RTCPeerConnection iteriert nicht durch pseudo-Arrays von urls.
          if (typeof urls != "undefined" && !(urls instanceof RealArray))
            urls = [urls];

          return createObject(iceServer, {
            url: {
              configurable: false, enumerable: false, writable: false,
              value: normalizeUrl(url)
            },
            urls: {
              configurable: false, enumerable: false, writable: false,
              value: safeCopyArray(urls, normalizeUrl)
            }
          });
        }
      );

      return createObject(configuration, {
        iceServers: {
          configurable: false, enumerable: false, writable: false,
          value: iceServers
        }
      });
    };

    let checkUrl = (peerconnection, url) =>
    {
      checkRequest(url, blocked =>
      {
        if (blocked)
        {
          // Ein Aufruf von .close() wirft eine Ausnahme, wenn bereits geschlossen.
          try
          {
            closeRTCPeerConnection(peerconnection);
          }
          catch (e) {}
        }
      });
    };

    let checkConfiguration = (peerconnection, configuration) =>
    {
      if (configuration && configuration.iceServers)
      {
        for (let i = 0; i < configuration.iceServers.length; i++)
        {
          let iceServer = configuration.iceServers[i];
          if (iceServer)
          {
            if (iceServer.url)
              checkUrl(peerconnection, iceServer.url);

            if (iceServer.urls)
            {
              for (let j = 0; j < iceServer.urls.length; j++)
                checkUrl(peerconnection, iceServer.urls[j]);
            }
          }
        }
      }
    };

    // Chrome unstable (getestet mit 59) hat setConfiguration bereits implementiert,
    * daher müssen wir diese Methode ebenfalls umschließen, falls vorhanden.
    // https://www.chromestatus.com/feature/5596193748942848
    if (RealRTCPeerConnection.prototype.setConfiguration)
    {
      let realSetConfiguration = Function.prototype.call.bind(
        RealRTCPeerConnection.prototype.setConfiguration
      );

      RealRTCPeerConnection.prototype.setConfiguration = function(configuration)
      {
        configuration = protectConfiguration(configuration);

        // Rufen wir zuerst die echte Methode auf, damit sie die Konfiguration
        * für uns validiert. Außerdem ist es ohnehin sinnvoll, da checkRequest
        * asynchron ist.
        realSetConfiguration(this, configuration);
        checkConfiguration(this, configuration);
      };
    }

    let WrappedRTCPeerConnection = function(...args)
    {
      if (!(this instanceof WrappedRTCPeerConnection))
        return RealRTCPeerConnection();

      let configuration = protectConfiguration(args[0]);

      // Da der alte webkitRTCPeerConnection-Konstruktor ein optionales
      * zweites Argument erwartet, müssen wir darauf achten, dieses weiterzureichen.
      * Notwendig für ältere Chrome-Versionen wie 51.
      let constraints = undefined;
      if (args.length > 1)
        constraints = args[1];

      let peerconnection = new RealRTCPeerConnection(configuration,
                                                     constraints);
      checkConfiguration(peerconnection, configuration);
      return peerconnection;
    };

    WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;

    let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
    copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
                   ["generateCertificate", "name", "prototype"]);
    RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;

    if ("RTCPeerConnection" in window)
      window.RTCPeerConnection = boundWrappedRTCPeerConnection;
    if ("webkitRTCPeerConnection" in window)
      window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
  }
})('abp-request-aic05wltexc', true);. 468b7929-46c3-4249-b516-189e58962157:27:22

Ein weiterer CSP:

Content Security Policy: Das Seiten-Setup hat das Laden einer Ressource inline („script-src“) blockiert. Quelle: try { if (typeof Navigator.prototype.sendBeacon === 'function') { Navigator.prototype.sendBeacon = function(url, data) { return true; }; } } catch (exception) { console.error(exception); }. script.js:517:22

Und noch einer (der Link wurde hier der Information halber hinzugefügt):

Content Security Policy: Das Seiten-Setup hat das Laden einer Ressource an https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js („script-src“) blockiert. xgahvvrt0kwvb7p6crbxuolt4omnin1u.js:1:12450

Hast du diese spezifische URL freigegeben? Die Fehlermeldungen geben normalerweise deutlich an, welche URLs freigegeben werden müssen.

1 „Gefällt mir“

Danke, Jeff. Ich habe es zu wörtlich genommen und die gesamte URL von https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js verwendet.

Es funktioniert jetzt. Ich erhalte immer noch die erste CSP. Wenn es funktioniert, nehme ich an, dass es sicher ist, dies zu ignorieren?

1 „Gefällt mir“

Wenn Sie in Ihrer F12-Konsole CSP-Fehler erhalten, sollten Sie die im Fehler genannte Domain zur Whitelist hinzufügen.

Ich bezog mich auf den ersten Eintrag, der keine URL enthält – eher eine Warnung vor Problemen in Chrome mit dem Frame-Context-Wrapper. Seltsamerweise erscheint diese Warnung in Firefox, nicht aber in Chrome. Daher werde ich sie einfach ignorieren, da alles einwandfrei zu funktionieren scheint. Vielen Dank für deine Hilfe :+1:

Ich habe diesen speziellen Chat noch nie ausprobiert.

Ich habe LiveChat gestern getestet und es funktioniert ohne Fehler.

2 „Gefällt mir“

Hallo zusammen,

tolle Anleitung, vielen Dank! Ich habe die Anleitung befolgt, um eine Feedback-Umfrage hinzuzufügen, aber Hubspot ändert ständig den Analytics-Link (js.hs-analytics/), sodass ich ständig neue Links zur Whitelist hinzufügen muss.

Die einzige Lösung, die ich gefunden habe, die zuverlässig funktioniert, ist die Richtlinie komplett zu deaktivieren, aber das scheint nicht sehr sicher zu sein.

Habt ihr Ideen?

Ich sehe mir gerade deine Website an, kann aber den Survey-Code, den du unter Anpassen > Themes eingegeben hast, nicht finden.

Edit: Es sollte jetzt behoben sein. Ich habe die CSP wieder aktiviert. Lass mich wissen, falls du Probleme hast.

1 „Gefällt mir“

Hey @Dax,

Vielen Dank! Könntest du mir bitte sagen, wie du es behoben hast und was das Problem war?

Edit: Ich sehe, dass du einen Wildcard hinzugefügt hast, toll! Ich denke, es wäre am besten, unter diesem Feld einen Kommentar hinzuzufügen, damit wir wissen, dass Wildcards ebenfalls eine Option sind! Ich habe über eine solche Lösung nachgedacht, aber ich war dumm genug, es nicht auszuprobieren.

Vielen Dank!

1 „Gefällt mir“

Ich bin neugierig, welchen Wert hat die Verbindung eines CRM wie HubSpot mit unserer Community-Discourse-Seite?