HubSpotチャット連携の設定

Discourse に CSM を統合しますか?Discourse に HubSpot のチャットを統合する方法を見てみましょう!

  1. HubSpot でアカウントを作成します

  2. チャットを選択

  3. インターフェースと利用可能時間を好きなようにカスタマイズします


  4. コードをコピーします

  5. 新しいテーマコンポーネントを作成し、Common - </body> タブに貼り付けます。新しいコンポーネントをメインテーマに追加します

  6. Hubspot で確認を完了してウィジェットを有効にし、ウェブサイトに移動します

  7. 完了です :tada:

「いいね!」 12

Hi Daniella, I have tried this integration with three different chat providers and keep getting the same CSP after adding all the links I could find to security policy script src. This particular one is for Tidio, but the same thing happened with LiveChat and Pure Chat. Any ideas what may be happening here?

Content Security Policy: The page’s settings blocked the loading of a resource at eval (“script-src”). Source: (function injected(eventName, injectedIntoContentWindow)
{
  let checkRequest;

  /*
   * Frame context wrapper
   *
   * For some edge-cases Chrome will not run content scripts inside of frames.
   * Website have started to abuse this fact to access unwrapped APIs via a
   * frame's contentWindow (#4586, 5207). Therefore until Chrome runs content
   * scripts consistently for all frames we must take care to (re)inject our
   * wrappers when the contentWindow is accessed.
   */
  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"
    );

    // Apparently in HTMLObjectElement.prototype.contentWindow does not exist
    // in older versions of Chrome such as 51.
    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
   *
   * The webRequest API in Chrome does not yet allow the blocking of
   * WebRTC connections.
   * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683
   */
  let RealCustomEvent = window.CustomEvent;

  // If we've been injected into a frame via contentWindow then we can simply
  // grab the copy of checkRequest left for us by the parent document. Otherwise
  // we need to set it up now, along with the event handling functions.
  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}}));
    };
  }

  // Only to be called before the page's code, not hardened.
  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 has the option (media.peerconnection.enabled) to disable WebRTC
  // in which case RealRTCPeerConnection is undefined.
  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;
    };

    // It would be much easier to use the .getConfiguration method to obtain
    // the normalized and safe configuration from the RTCPeerConnection
    // instance. Unfortunately its not implemented as of Chrome unstable 59.
    // See 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 doesn't iterate through pseudo Arrays of 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)
        {
          // Calling .close() throws if already closed.
          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 (tested with 59) has already implemented
    // setConfiguration, so we need to wrap that if it exists too.
    // 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);

        // Call the real method first, so that validates the configuration for
        // us. Also we might as well since checkRequest is asynchronous anyway.
        realSetConfiguration(this, configuration);
        checkConfiguration(this, configuration);
      };
    }

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

      let configuration = protectConfiguration(args[0]);

      // Since the old webkitRTCPeerConnection constructor takes an optional
      // second argument we need to take care to pass that through. Necessary
      // for older versions of Chrome such as 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

Another CSP:

Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”). Source: try { if (typeof Navigator.prototype.sendBeacon === 'function') { Navigator.prototype.sendBeacon = function(url, data) { return true; }; } } catch (exception) { console.error(exception); }. script.js:517:22

And one more (the link here has been added fyi)

Content Security Policy: The page’s settings blocked the loading of a resource at https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js (“script-src”). xgahvvrt0kwvb7p6crbxuolt4omnin1u.js:1:12450

その特定の URL をホワイトリストに登録しましたか?エラーメッセージには、通常、どの URL をホワイトリストに登録する必要があるかが明確に示されています。

「いいね!」 1

Jeff さん、ありがとうございます。URL を文字通り取りすぎて、https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js 全体を使っていました。

これで動作するようになりました。最初の CSP はまだ表示されていますが、動作しているなら、これは無視しても安全でしょうか?

「いいね!」 1

F12 コンソールで CSP エラーが表示される場合は、エラーに含まれるドメインをホワイトリストに追加してください。

最初の、URL が含まれていないものを指しています。Chrome のフレームコンテキストラッパーに関する問題についての警告ですが、奇妙なことにこれは Firefox には表示されるものの Chrome には表示されません。そのため、問題なく動作しているようなので、無視することにします。お手伝いいただきありがとうございます :+1:

私はこの特定のチャットを試したことがありません

私は昨日 LiveChat をテストしましたが、エラーなく動作しました

「いいね!」 2

皆さん、こんにちは。

素晴らしいガイドをありがとうございます!フィードバック調査を追加するためにガイドに従って設定したのですが、HubSpot が分析用リンク(js.hs-analytics/)を頻繁に変更するため、ホワイトリストに新しいリンクを追加し続ける必要が出てきています。

私が発見した、一貫して機能する唯一の解決策は、ポリシー全体を無効にすることですが、あまり安全ではないように思えます。

何か良いアイデアはありますか?

現在、あなたのサイトを閲覧していますが、[カスタマイズ] > [テーマ] で入力した調査コードが見つかりません。

追記:今すぐ修正されました。CSP を再度有効にしました。問題が発生した場合はお知らせください。

「いいね!」 1

@Dax さん、こんにちは!

ありがとうございます!どのように修正されたのか、また問題の原因は何だったのか教えていただけますか?

編集:ワイルドカードを追加されたのですね、素晴らしいです!そのボックスの下にコメントを追加して、ワイルドカードも選択肢の一つであることを明記するのがベストだと思います。私もそのような解決策を考えたのですが、バカなことに試すことを思い付きませんでした。

ありがとうございます!

「いいね!」 1

HubSpotのようなCRMをDiscourseコミュニティサイトに接続する価値は何でしょうか?