Configuration de l'intégration de chat HubSpot

Voulez-vous intégrer un CSM sur Discourse ? Voyons comment intégrer le chat HubSpot sur Discourse !

  1. créez un compte sur HubSpot

  2. sélectionnez Chat

  3. personnalisez l’interface et la disponibilité comme vous le souhaitez


  4. Copiez le code

  5. Créez un nouveau composant de thème et collez-le dans l’onglet Common - </body>. Ajoutez le nouveau composant à vos thèmes principaux

  6. Terminez la Vérification sur Hubspot pour activer le widget et accédez à votre site web

  7. Terminé :tada:

12 « J'aime »

Bonjour Daniella,

J’ai essayé cette intégration avec trois fournisseurs de chat différents et je continue d’obtenir la même erreur CSP après avoir ajouté tous les liens que j’ai pu trouver à script-src de la politique de sécurité. Celle-ci concerne Tidio, mais la même chose s’est produite avec LiveChat et Pure Chat. Avez-vous des idées sur ce qui pourrait se passer ici ?

Politique de sécurité du contenu : Les paramètres de la page ont bloqué le chargement d'une ressource à eval ("script-src"). Source : (function injected(eventName, injectedIntoContentWindow)
{
  let checkRequest;

  /*
   * Wrapper de contexte de frame
   *
   * Pour certains cas limites, Chrome n'exécute pas les scripts de contenu
   * à l'intérieur des frames. Les sites web ont commencé à abuser de ce fait
   * pour accéder à des API non enveloppées via le contentWindow d'une frame
   * (#4586, 5207). Par conséquent, tant que Chrome n'exécute pas les scripts
   * de contenu de manière cohérente pour toutes les frames, nous devons veiller
   * à (ré)injecter nos wrappers lorsque le contentWindow est accédé.
   */
  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"
    );

    // Apparemment, dans HTMLObjectElement.prototype.contentWindow n'existe
    // pas dans les anciennes versions de Chrome comme la 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
   *
   * L'API webRequest dans Chrome ne permet pas encore de bloquer
   * les connexions WebRTC.
   * Voir https://bugs.chromium.org/p/chromium/issues/detail?id=707683
   */
  let RealCustomEvent = window.CustomEvent;

  // Si nous avons été injectés dans une frame via contentWindow, nous pouvons simplement
  // récupérer la copie de checkRequest laissée pour nous par le document parent. Sinon,
  // nous devons la configurer maintenant, ainsi que les fonctions de gestion d'événements.
  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}}));
    };
  }

  // À appeler uniquement avant le code de la page, non durci.
  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 a l'option (media.peerconnection.enabled) pour désactiver WebRTC
  // auquel cas RealRTCPeerConnection est indéfini.
  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;
    };

    // Il serait beaucoup plus facile d'utiliser la méthode .getConfiguration pour obtenir
    // la configuration normalisée et sécurisée de l'instance RTCPeerConnection.
    // Malheureusement, elle n'est pas implémentée dans Chrome unstable 59.
    // Voir 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'itére pas sur les tableaux 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)
        {
          // Appeler .close() lève une exception si déjà fermé.
          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 (testé avec 59) a déjà implémenté
    // setConfiguration, donc nous devons aussi l'envelopper s'il existe.
    // 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);

        // Appelez la vraie méthode en premier, pour qu'elle valide la configuration
        // pour nous. De plus, peu importe puisque checkRequest est asynchrone de toute façon.
        realSetConfiguration(this, configuration);
        checkConfiguration(this, configuration);
      };
    }

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

      let configuration = protectConfiguration(args[0]);

      // Puisque l'ancien constructeur webkitRTCPeerConnection prend un deuxième
      // argument optionnel, nous devons veiller à le transmettre. Nécessaire
      // pour les anciennes versions de Chrome comme la 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

Une autre CSP :

Politique de sécurité du contenu : Les paramètres de la page ont bloqué le chargement d'une ressource en 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

Et un dernier (le lien ici a été ajouté pour information)

Politique de sécurité du contenu : Les paramètres de la page ont bloqué le chargement d'une ressource à https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js ("script-src"). xgahvvrt0kwvb7p6crbxuolt4omnin1u.js:1:12450

Avez-vous mis cette URL spécifique sur la liste blanche ? Les messages d’erreur indiquent généralement clairement quelles URL doivent être autorisées.

1 « J'aime »

Merci Jeff, je prenais cela trop au pied de la lettre et j’utilisais l’URL complète de https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js

Cela fonctionne maintenant. Je reçois toujours la première CSP ; si cela fonctionne, je suppose qu’il est sûr de l’ignorer ?

1 « J'aime »

Si vous rencontrez des erreurs CSP dans votre console F12, vous devez ajouter le domaine indiqué dans l’erreur à la liste blanche.

Je faisais référence au premier, qui n’a pas d’URL, plutôt à un avertissement concernant des problèmes dans Chrome liés à l’enveloppe du contexte de cadre. Étrangement, cela apparaît dans Firefox mais pas dans Chrome. Je pense donc que je vais simplement l’ignorer, car tout semble fonctionner correctement. Merci pour votre aide :+1:

Je n’ai jamais essayé ce chat en particulier.

J’ai testé LiveChat hier et cela fonctionne sans erreur.

2 « J'aime »

Bonjour à tous,

Merci pour ce super guide ! J’ai suivi les étapes pour ajouter un sondage de feedback, mais HubSpot modifie constamment le lien d’analyse (js.hs-analytics/), ce qui m’oblige à ajouter continuellement de nouveaux liens à la liste blanche.

La seule solution que j’ai trouvée pour fonctionner de manière cohérente est de désactiver complètement la politique, mais cela ne semble pas très sécurisé.

Avez-vous des idées ?

Je consulte votre site en ce moment, mais je ne trouve pas le code de sondage que vous avez saisi dans Personnaliser > Thèmes

Édition : Cela devrait être corrigé maintenant, j’ai réactivé la CSP. Faites-moi savoir si vous rencontrez des problèmes.

1 « J'aime »

Salut @Dax,

Merci ! Peux-tu s’il te plaît m’expliquer comment tu as résolu le problème et quelle était la cause ?

Édit : Je vois que tu as ajouté un joker, super ! Je pense qu’il serait préférable d’ajouter un commentaire sous ce champ, afin que nous sachions que les jokers sont également une option ! J’avais envisagé une telle solution, mais moi, bêtement, je n’ai pas essayé.

Merci !

1 « J'aime »

Je suis curieux, quelle est la valeur de connecter un CRM tel que HubSpot à notre site communautaire Discourse ?