Imposta l'integrazione della chat di HubSpot

Vuoi integrare un CSM su Discourse? Vediamo come integrare la chat di HubSpot su Discourse!

  1. crea un account su HubSpot

  2. seleziona Chat

  3. personalizza l’interfaccia e la disponibilità come desideri


  4. Copia il codice

  5. Crea un nuovo tema componente e incollalo nella scheda Common - </body>. Aggiungi il nuovo componente al tuo tema principale

  6. Completa la Verifica su Hubspot per attivare il widget e vai sul tuo sito web

  7. Fatto :tada:

12 Mi Piace

Ciao Daniella, ho provato questa integrazione con tre diversi provider di chat e continuo a ricevere lo stesso errore CSP dopo aver aggiunto tutti i link che ho trovato a security policy script src. Questo caso specifico riguarda Tidio, ma la stessa cosa è successa con LiveChat e Pure Chat. Hai qualche idea su cosa possa star succedendo?

Content Security Policy: Le impostazioni della pagina hanno bloccato il caricamento di una risorsa in eval ("script-src"). Origine: (function injected(eventName, injectedIntoContentWindow)
{
  let checkRequest;

  /*
   * Wrapper per il contesto del frame
   *
   * In alcuni casi limite, Chrome non esegue gli script di contenuto all'interno dei frame.
   * Alcuni siti web hanno iniziato a sfruttare questo fatto per accedere ad API non incapsulate
   * tramite il contentWindow di un frame (#4586, 5207). Pertanto, finché Chrome non eseguirà
   * gli script di contenuto in modo coerente per tutti i frame, dobbiamo assicurarci di
   * (ri)iniettare i nostri wrapper quando viene accesso al contentWindow.
   */
  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"
    );

    // Apparentemente in HTMLObjectElement.prototype.contentWindow non esiste
    // nelle versioni più vecchie di Chrome, come 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 per RTCPeerConnection
   *
   * L'API webRequest in Chrome non permette ancora di bloccare
   * le connessioni WebRTC.
   * Vedi https://bugs.chromium.org/p/chromium/issues/detail?id=707683
   */
  let RealCustomEvent = window.CustomEvent;

  // Se siamo stati iniettati in un frame tramite contentWindow, possiamo semplicemente
  // prendere la copia di checkRequest lasciataci dal documento padre. Altrimenti
  // dobbiamo impostarla ora, insieme alle funzioni di gestione degli eventi.
  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}}));
    };
  }

  // Da chiamare solo prima del codice della pagina, non è hardennato.
  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 ha l'opzione (media.peerconnection.enabled) per disabilitare WebRTC,
  // nel qual caso RealRTCPeerConnection è 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;
    };

    // Sarebbe molto più semplice usare il metodo .getConfiguration per ottenere
    // la configurazione normalizzata e sicura dall'istanza RTCPeerConnection.
    // Purtroppo non è implementato in Chrome unstable 59.
    // Vedi 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 non itera attraverso pseudo-array di 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)
        {
          // Chiamare .close() lancia un'eccezione se già chiuso.
          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 (testato con la 59) ha già implementato
    // setConfiguration, quindi dobbiamo avvolgerlo anche se esiste.
    // 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);

        // Chiama prima il metodo reale, così da convalidare la configurazione per noi.
        // Inoltre, dato che checkRequest è asincrono, tanto vale farlo.
        realSetConfiguration(this, configuration);
        checkConfiguration(this, configuration);
      };
    }

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

      let configuration = protectConfiguration(args[0]);

      // Poiché il vecchio costruttore webkitRTCPeerConnection accetta un secondo
      // argomento opzionale, dobbiamo assicurarci di passarlo. Necessario
      // per versioni più vecchie di Chrome, come 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

Un altro CSP:

Content Security Policy: Le impostazioni della pagina hanno bloccato il caricamento di una risorsa inline ("script-src"). Origine: 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 un altro ancora (il link qui è stato aggiunto, FYI)

Content Security Policy: Le impostazioni della pagina hanno bloccato il caricamento di una risorsa in https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js ("script-src"). xgahvvrt0kwvb7p6crbxuolt4omnin1u.js:1:12450

Hai messo in whitelist quell’URL specifico? I messaggi di errore sono solitamente chiari riguardo agli URL che devono essere messi in whitelist.

1 Mi Piace

Grazie Jeff, stavo prendendo la cosa troppo alla lettera e stavo usando l’intero URL di https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js

Ora funziona. Ricevo ancora il primo CSP; se sta funzionando, immagino sia sicuro ignorarlo?

1 Mi Piace

Se ricevi errori CSP nella console F12, dovresti aggiungere il dominio indicato nell’errore alla whitelist.

Mi riferivo al primo, che non ha un URL; è più un avviso riguardo problemi in Chrome con il frame context wrapper. Stranamente, questo appare in Firefox ma non in Chrome, quindi penso che lo ignorerò, dato che sembra funzionare correttamente. Apprezzo il tuo aiuto :+1:

Non ho mai provato questa chat in particolare

Ho testato LiveChat ieri e funziona senza errori

2 Mi Piace

Ciao a tutti,

Grazie per la guida eccellente! Ho seguito la guida per aggiungere un sondaggio di feedback, ma HubSpot continua a modificare il link di analytics (js.hs-analytics/), quindi devo costantemente aggiungere nuovi link alla whitelist.

L’unica soluzione che ho trovato per funzionare in modo coerente è disabilitare completamente la policy, ma non sembra molto sicura.

Qualche idea?

Sto guardando il tuo sito in questo momento, ma non riesco a trovare il codice del sondaggio che hai inserito in Personalizza > Temi

Modifica: Dovrebbe essere risolto ora, ho riattivato la CSP. Fammi sapere se hai problemi

1 Mi Piace

Ciao @Dax,

Grazie! Puoi dirmi come hai risolto il problema e qual era l’errore?

Modifica: Vedo che hai aggiunto un carattere jolly, fantastico! Credo che sarebbe meglio aggiungere un commento sotto quella casella, così sappiamo che anche i caratteri jolly sono un’opzione! Avevo pensato a una soluzione del genere, ma sono stato sciocco e non l’ho provata.

Grazie!

1 Mi Piace

Sono curioso, qual è il valore di collegare un CRM come HubSpot al nostro sito di community Discourse?