إعداد تكامل HubSpot للدردشة

هل تريد دمج نظام إدارة علاقات العملاء (CSM) في Discourse؟ لِنرَ كيف ندمج دردشة HubSpot في Discourse!

  1. أنشئ حسابًا على HubSpot

  2. اختر Chat

  3. خصّص الواجهة والتوافر حسب رغبتك


  4. انسخ الرمز

  5. أنشئ مكون ثيم جديد والصقه في علامة التبويب Common - </body>. أضف المكون الجديد إلى الثيم (الثيمات) الرئيسية الخاصة بك

  6. أكمل التحقق على Hubspot لتفعيل الأداة المصغرة واذهب إلى موقعك

  7. تم :tada:

12 إعجابًا

مرحبًا دانييلا، لقد جربت هذه التكامل مع ثلاثة مزودي دردشة مختلفين وما زلت أحصل على نفس سياسة محتوى الأمان (CSP) بعد إضافة جميع الروابط التي تمكنت من العثور عليها إلى script-src في سياسة الأمان. هذه الحالة خاصة بـ Tidio، لكن الشيء نفسه حدث مع LiveChat و Pure Chat. هل لديك أي أفكار عما قد يحدث هنا؟

سياسة محتوى الأمان: منعت إعدادات الصفحة تحميل مورد في eval ("script-src"). المصدر: (function injected(eventName, injectedIntoContentWindow)
{
  let checkRequest;

  /*
   * غلاف سياق الإطار
   *
   * في بعض الحالات القصوى، لن يقوم Chrome بتشغيل نصوص المحتوى داخل الإطارات.
   * بدأت المواقع في إساءة استخدام هذه الحقيقة للوصول إلى واجهات برمجة التطبيقات غير المغلفة عبر
   * contentWindow الخاص بالإطار (#4586، 5207). لذلك، حتى يقوم Chrome بتشغيل نصوص المحتوى بشكل متسق لجميع الإطارات، يجب علينا الاهتمام بإعادة حقن أغلفتنا عند الوصول إلى 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"
    );

    // يبدو أن contentWindow في HTMLObjectElement.prototype غير موجود
    // في إصدارات Chrome القديمة مثل 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
   *
   * واجهة webRequest في Chrome لا تسمح بعد بحظر اتصالات WebRTC.
   * انظر https://bugs.chromium.org/p/chromium/issues/detail?id=707683
   */
  let RealCustomEvent = window.CustomEvent;

  // إذا تم حقننا في إطار عبر contentWindow، يمكننا ببساطة
  // التقاط نسخة من checkRequest التي تركتها لنا المستند الأصلي. وإلا
  // نحتاج إلى إعداده الآن، جنبًا إلى جنب مع دوال معالجة الأحداث.
  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}}));
    };
  }

  // يُستدعى فقط قبل كود الصفحة، وليس مقوى.
  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 خيار (media.peerconnection.enabled) لتعطيل WebRTC
  وفي هذه الحالة يكون RealRTCPeerConnection غير معرف.
  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;
    };

    // سيكون من الأسهل بكثير استخدام طريقة .getConfiguration للحصول على
    // التكوين المعيار والآمن من مثيل RTCPeerConnection.
    // للأسف، لم يتم تنفيذها حتى Chrome غير المستقر 59.
    // انظر 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 لا يتكرر عبر المصفوفات الوهمية لـ 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)
        {
          // استدعاء .close() يرمي خطأ إذا كان مغلقًا بالفعل.
          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 غير المستقر (تم اختباره مع 59) قد نفذ بالفعل
    // setConfiguration، لذا نحتاج إلى تغليفه إذا كان موجودًا أيضًا.
    // 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);

        // استدعاء الطريقة الحقيقية أولاً، للتحقق من التكوين لنا.
        // أيضًا، يمكننا ذلك لأن checkRequest غير متزامن على أي حال.
        realSetConfiguration(this, configuration);
        checkConfiguration(this, configuration);
      };
    }

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

      let configuration = protectConfiguration(args[0]);

      // بما أن مُنشئ webkitRTCPeerConnection القديم يأخذ حجة ثانية اختيارية
      // نحتاج إلى الحرص على تمريرها. ضروري
      // لإصدارات Chrome القديمة مثل 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

سياسة محتوى أمان أخرى:

سياسة محتوى الأمان: منعت إعدادات الصفحة تحميل مورد مضمن ("script-src"). المصدر: try { if (typeof Navigator.prototype.sendBeacon === 'function') { Navigator.prototype.sendBeacon = function(url, data) { return true; }; } } catch (exception) { console.error(exception); }. script.js:517:22

وواحدة أخرى (تمت إضافة الرابط هنا علما)

سياسة محتوى الأمان: منعت إعدادات الصفحة تحميل مورد في https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js ("script-src"). xgahvvrt0kwvb7p6crbxuolt4omnin1u.js:1:12450

هل قمت بإضافة هذا الرابط المحدد إلى قائمة السماح؟ عادةً ما تكون رسائل الخطأ واضحة بشأن عناوين URL التي يجب إضافتها إلى قائمة السماح.

إعجاب واحد (1)

شكرًا لك، جيف. كنت أفسر الأمر حرفيًا جدًا واستخدمت رابط URL الكامل لـ https://widget-v4.tidiochat.com//1_23_3/static/js/widget.a6a6e2b4c2401b7c523f.js

الآن يعمل بشكل صحيح. لا يزال يظهر لي أول تحذير CSP، ولكن إذا كان يعمل، أفترض أنه من الآمن تجاهله؟

إعجاب واحد (1)

إذا كنت تتلقى أخطاء CSP في وحدة تحكم F12، فيجب عليك إضافة النطاق المذكور في الخطأ إلى القائمة البيضاء.

كنت أشير إلى العنصر الأول الذي لا يحتوي على رابط URL، وهو أكثر تحذيرًا بشأن مشاكل في Chrome تتعلق بـ frame context wrapper. غريبًا، يظهر هذا التحذير في Firefox ولا يظهر في Chrome، لذا أعتقد أنني سأتجاهله لأنه يبدو أنه يعمل بشكل صحيح. أقدر مساعدتك :+1:

لم أجرب هذا الدردشة المحددة من قبل

لقد اختبرت LiveChat أمس ويعمل دون أخطاء

إعجابَين (2)

مرحباً بالجميع،

شكراً للدليل الممتاز! اتبعت الدليل لإضافة استبيان للتغذية الراجعة، لكن HubSpot يغير باستمرار رابط التحليلات (js.hs-analytics/)، مما يضطرني لإضافة روابط جديدة إلى القائمة البيضاء باستمرار.

الحل الوحيد الذي وجدته يعمل بشكل متسق هو تعطيل السياسة بالكامل، لكنه لا يبدو آمناً بما يكفي.

هل لديكم أي أفكار؟

أبحث الآن في موقعك، لكنني لا أستطيع العثور على رمز الاستبيان الذي أدخلته في تخصيص > السمات

تعديل: يجب أن يكون الأمر قد تم إصلاحه الآن، لقد قمت بتفعيل سياسة محتوى الأمان (CSP) مرة أخرى. أخبرني إذا واجهت أي مشاكل.

إعجاب واحد (1)

مرحبًا @Dax،

شكرًا لك! هل يمكنك إخباري كيف أصلحتها وما كانت المشكلة؟

تعديل: أرى أنك أضفت حرفًا جامدًا (wildcard)، رائع! أعتقد أنه سيكون من الأفضل إضافة تعليق أسفل ذلك المربع، حتى نعرف أن الأحرف الجامدة خيار متاح أيضًا! لقد فكّرت في مثل هذا الحل، لكنني لم أجربه، يا لها من غفلة مني.

شكرًا لك!

إعجاب واحد (1)

أنا فضولي، ما قيمة ربط نظام إدارة علاقات العملاء (CRM) مثل HubSpot بموقع Discourse الخاص بمجتمعنا؟