Configurar integração do chat do HubSpot

Quer integrar um CSM no Discourse? Vamos ver como integrar o chat do HubSpot no Discourse!

  1. crie uma conta no HubSpot

  2. selecione Chat

  3. personalize a interface e a disponibilidade como desejar


  4. Copie o código

  5. Crie um novo componente de tema e cole-o na aba Common - </body>. Adicione o novo componente ao seu(s) tema(s) principal(is)

  6. Complete a Verificação no Hubspot para ativar o widget e acesse seu site

  7. Pronto :tada:

12 curtidas

Olá Daniella, tentei essa integração com três provedores de chat diferentes e continuo recebendo o mesmo erro de CSP mesmo após adicionar todos os links que consegui encontrar ao script-src da política de segurança. Este caso específico é para o Tidio, mas o mesmo problema ocorreu com o LiveChat e o Pure Chat. Alguma ideia do que possa estar acontecendo?

Content Security Policy: A configuração da página bloqueou o carregamento de um recurso em eval ("script-src"). Origem: (function injected(eventName, injectedIntoContentWindow)
{
  let checkRequest;

  /*
   * Wrapper de contexto de frame
   *
   * Para alguns casos extremos, o Chrome não executa scripts de conteúdo dentro de frames.
   * Sites começaram a abusar desse fato para acessar APIs não encapsuladas via
   * contentWindow de um frame (#4586, 5207). Portanto, até que o Chrome execute scripts
   * de conteúdo de forma consistente para todos os frames, devemos garantir (re)injetar
   * nossos wrappers quando o contentWindow for acessado.
   */
  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"
    );

    // Aparentemente, em HTMLObjectElement.prototype.contentWindow não existe
    // em versões mais antigas do Chrome, como a 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);
  }

  /*
   * Wrapper RTCPeerConnection
   *
   * A API webRequest no Chrome ainda não permite o bloqueio de
   * conexões WebRTC.
   * Veja https://bugs.chromium.org/p/chromium/issues/detail?id=707683
   */
  let RealCustomEvent = window.CustomEvent;

  // Se fomos injetados em um frame via contentWindow, podemos simplesmente
  // pegar a cópia de checkRequest deixada para nós pelo documento pai. Caso
  // contrário, precisamos configurá-la agora, junto com as funções de tratamento de eventos.
  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}}));
    };
  }

  // Apenas para ser chamado antes do código da página, não é endurecido.
  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;

  // O Firefox tem a opção (media.peerconnection.enabled) para desabilitar o WebRTC,
  // caso em que RealRTCPeerConnection é indefinido.
  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;
    };

    // Seria muito mais fácil usar o método .getConfiguration para obter
    // a configuração normalizada e segura da instância RTCPeerConnection.
    // Infelizmente, isso não foi implementado no Chrome unstable 59.
    // Veja 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 não itera sobre pseudo Arrays de 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)
        {
          // Chamar .close() lança erro se já estiver fechado.
          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]);
            }
          }
        }
      }
    };

    // O Chrome unstable (testado com 59) já implementou
    // setConfiguration, então precisamos envolver isso se ele também existir.
    // 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);

        // Chame o método real primeiro, para que ele valide a configuração para
        // nós. Também podemos fazer isso, já que checkRequest é assíncrono de qualquer forma.
        realSetConfiguration(this, configuration);
        checkConfiguration(this, configuration);
      };
    }

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

      let configuration = protectConfiguration(args[0]);

      // Como o construtor antigo webkitRTCPeerConnection aceita um segundo
      // argumento opcional, precisamos garantir que ele seja passado. Necessário
      // para versões mais antigas do Chrome, como a 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

Outro CSP:

Content Security Policy: A configuração da página bloqueou o carregamento de um recurso inline ("script-src"). Origem: try { if (typeof Navigator.prototype.sendBeacon === 'function') { Navigator.prototype.sendBeacon = function(url, data) { return true; }; } } catch (exception) { console.error(exception); }. script.js:517:22

E mais um (o link aqui foi adicionado, FYI):

Content Security Policy: A configuração da página bloqueou o carregamento de um recurso em https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js ("script-src"). xgahvvrt0kwvb7p6crbxuolt4omnin1u.js:1:12450

Você adicionou essa URL específica à lista de permissões? As mensagens de erro geralmente são claras sobre quais URLs precisam ser incluídas na lista de permissões.

1 curtida

Obrigado, Jeff. Eu estava interpretando isso de forma muito literal e usando a URL completa de https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js

Agora está funcionando. Ainda estou recebendo o primeiro CSP; se estiver funcionando, posso assumir que é seguro ignorar isso?

1 curtida

Se você estiver recebendo erros de CSP no console F12, adicione o domínio relatado no erro à lista de permissões.

Estava me referindo ao primeiro, que não tem URL; é mais um aviso sobre problemas no Chrome com o wrapper de contexto de frame. Estranhamente, isso aparece no Firefox, mas não no Chrome. Então, acho que vou simplesmente ignorar, já que parece estar funcionando bem. Agradeço sua ajuda :+1:

Nunca testei este chat em particular

Testei o LiveChat ontem e funciona sem erros

2 curtidas

Olá a todos,

Obrigado pelo ótimo guia! Segui as instruções para adicionar uma pesquisa de feedback, mas o HubSpot continua alterando o link de analytics (js.hs-analytics/), então preciso constantemente adicionar novos links à lista de permissões.

A única solução que encontrei que funciona consistentemente é desativar a política completamente, mas isso não parece muito seguro.

Alguma ideia?

Estou visualizando seu site agora mesmo, mas não consigo encontrar o código da pesquisa que você inseriu em Personalizar > Temas

Edição: Deve estar corrigido agora. Reativei a CSP. Me avise se tiver problemas.

1 curtida

Ei @Dax,

Obrigado! Você pode me dizer como você corrigiu e qual era o problema?

Edição: Vejo que você adicionou um curinga, incrível! Acredito que seria melhor adicionar um comentário abaixo dessa caixa, para que saibamos que curingas também são uma opção! Pensei em uma solução assim, mas eu, bobo, não tentei.

Obrigado!

1 curtida

Tenho curiosidade, qual é o valor de conectar um CRM como o HubSpot ao nosso site da comunidade Discourse?