استخدام إضافة GraphQL

تعلّم كيفية إعداد إضافة GraphQL في تطبيق React الخاص بك

استخدام إضافة GraphQL

إذا كنت تستخدم GraphQL كلغة استعلام لواجهات برمجة التطبيقات (APIs) الخاصة بك من داخل تطبيقك، فإن GraphQL plugin يمكن أن يساعدك في تتبّع كل من عمليات التغيير (mutations) والاستعلامات (queries) التي تُنفَّذ على خادم GraphQL الخاص بك.

في هذا الدرس، سنستخدم عميل Apollo Boost، ولكن طالما أن العميل الذي تستخدمه يتيح لك إعداد بعض الـ middleware، فسيكون بإمكانك استخدام الإضافة في شيفرتك.

إذا كنت ترغب في المتابعة خطوة بخطوة، يمكنك الاطلاع على هذا المستودع الذي يحتوي على كل من خادم GraphQL وتطبيق العميل.

قبل تثبيت إضافة GraphQL، ستحتاج إلى تثبيت Tracker. إذا كنت تعرف بالفعل كيفية القيام بذلك، فانتقل إلى القسم التالي، وإلا فتابع القراءة.

سنحفظ هذه الشيفرة داخل وحدة (module) منفصلة ستصدّر دالتين: init و start.

ستقوم الدالة الأولى بإنشاء نسخة من الـ tracker وإعداد جميع الإضافات؛ أما الثانية فستستدعي فقط الطريقة start.

import OpenReplay from '@openreplay/tracker';

let _tracker = null;

export function init({plugins}) {

    _tracker = new OpenReplay({
        projectKey: process.env.OPENREPLAY_PROJECT_KEY
    });


    let pluginResults = {}
    if(plugins) {
        Object.keys(plugins).forEach( pk => {
            pluginResults[pk] = _tracker.use(plugins[pk]())
        })
    }
    return pluginResults
}

export function start() {
    return _tracker.start()
}

الجانب المثير للاهتمام في الدالة init هو أنها تُرجع كائنًا مكوَّنًا من جميع القيم التي تُرجعها الإضافات. ستُرجع بعض إضافاتنا دالة سيتعيّن عليك استخدامها لاحقًا (كما هو الحال مع إضافة GraphQL). يتيح لك هذا النهج تهيئة الـ tracker بجميع الإضافات دفعة واحدة، ثم استخدام القيم المُرجَعة متى شئت.

بعد تثبيت الإضافة باستخدام npm i @openreplay/tracker-graphql، استخدم الشيفرة التالية لاستدعاء الدالة init التي عرّفناها للتو:

import trackerGraphQL from '@openreplay/tracker-graphql';
import {init} from './tracker/index'

const {graphqlTracker} = init({
  plugins: {
    graphqlTracker: trackerGraphQL
  }
})

يمكن أن يكون المفتاح graphqlTracker المستخدم هنا أي اسم تريده. طالما أن المفتاح المستخدم داخل قسم plugins هو نفسه المفتاح الذي تستخرجه عبر التفكيك (destructuring) من نتائج الدالة init، فلا مشكلة لديك.

إعداد الإضافة مع عميل Apollo

Section titled إعداد الإضافة مع عميل Apollo

في هذا الدرس، سنستخدم مكتبة Apollo Boost، التي تتيح لك تعديل تدفّق بيانات كل طلب من خلال ما يُسمّونه “links”.

هذه الـ links تشبه دوال الـ middleware التي يمكنك استخدامها لاعتراض تدفّق بيانات الطلب، وفي حالتنا، لتسجيله.

ستقوم الشيفرة التالية بإنشاء link جديد باستخدام الدالة ApolloLink. سيلتقط هذا الـ link بيانات العملية ونتائجها، ويستدعي دالتنا graphqlTracker (تلك التي أُرجِعت من استدعاء init أعلاه).

const trackerApolloLink = new ApolloLink((operation, forward) => {

  const operationDefinition = operation.query.definitions[0];
  let {operationName, variables} = operation
  const {kind, operation: op} = operationDefinition
  const opKind = kind === 'OperationDefinition' ? op : 'unknown?'

  let results = forward(operation).map((result) => {
    return graphqlTracker(opKind, operationName, variables, result);
  });
  if(results.length === 0) { //if there are no results, then we've not tracked anything so far...
    graphqlTracker(opKind, operationName, variables, {});
  }
  return results
});

بعد الانتهاء من ذلك، يمكننا استخدام الـ link الذي أنشأناه حديثًا على النحو التالي:

import {ApolloClient,  HttpLink } from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, from } from '@apollo/client';

const link = from([
  trackerApolloLink,
  new HttpLink({uri: () => 'http://localhost:4000/graphql'}),
]);

const client = new ApolloClient({
  link,
  cache: new InMemoryCache()
});

ReactDOM.render(<ApolloProvider client={client}>
  <App />
</ApolloProvider>, document.getElementById('root'));

الشيفرة أعلاه مأخوذة من وثائق Apollo، وعند هذه المرحلة يكون قد تم إعداد الـ tracker والإضافة بالفعل، لذا لا داعي للقلق بشأن أي شيء آخر فعليًا.

وبمجرد الانتهاء، ستُظهر تسجيلات الإعادة (replays) الخاصة بك قسمًا جديدًا يسرد جميع عمليات GraphQL.

The GraphQL UI inside the Session Replay

ومع ذلك، فإن المعلومات الحساسة التي يعقّمها الـ tracker تلقائيًا (مثل عناوين البريد الإلكتروني) لن تُعقَّم بواسطة الإضافة. لذا ستواجه مواقف كالتالي حيث يحتوي الـ DOM على البيانات المعقّمة، بينما تُظهر تفاصيل العملية البيانات الفعلية.

Sanitized vs Not Sanitized data

ورغم أن الإضافة نفسها لا توفّر أي دالة للتعقيم، فما زال بإمكاننا إضافة شيفرة تُخفي المعلومات الشخصية والخاصة من تسجيل الإعادة للمساعدة في الحفاظ على خصوصية مستخدمك.

تعقيم البيانات المسجَّلة

Section titled تعقيم البيانات المسجَّلة

إذا نظرت إلى عيّنة الشيفرة التي أنشأتُ فيها الكائن trackerApolloLink، فسترى أن كل ما أفعله هو استدعاء دالة الـ tracker التي تحفظ المعلومات في الـ tracker.

إذا لم أغيّر البيانات، فسيُحفَظ كل شيء دون تغيير. لذا، لتعقيم البيانات في تسجيل الإعادة مع إبقاء العملية دون تغيير، نحتاج إلى استنساخ المتغيرات الأساسية قبل استدعاء الـ tracker. وهذا يعني استنساخ المتغيرات والنتائج الخاصة بالعملية، وهذا كل ما نريده.

إذًا، إليك مقتطف شيفرة سيُنشئ الـ ApolloLink ويُبقي البيانات سرّية ضمن بيانات تسجيل الإعادة:

/**
 * Sanitize the result from a GraphQL operation
 * @returns Returns the result object but with the sanitized fields changed.
 */
function sanitizeResult(res) {
  //deep clonning needs to happen to make sure this only affects the new object and not
  //the original object.
  let sanitized = JSON.parse(JSON.stringify(res))

  let ops = Object.keys(sanitized.data)
  ops.forEach( o => {
    if(Array.isArray(sanitized.data[o])) { //mutations don't really return arrays
      sanitized.data[o] = sanitized.data[o].map( sanitizeData )
    }
  })
  return sanitized
}

// We only want to hide the content of othe "email" field for now.
function sanitizeData(vars) {
  let newVars = {...vars}
  if(newVars.email) {
    newVars.email = "****@***.***"
  }
  return newVars
}

const trackerApolloLink = new ApolloLink((operation, forward) => {

  const operationDefinition = operation.query.definitions[0];
  let {operationName, variables} = operation
  const {kind, operation: op} = operationDefinition
  const opKind = kind === 'OperationDefinition' ? op : 'unknown?'

  let trackedVariables = sanitizeData({...variables})
  let results = forward(operation).map((result) => {
    let trackeresults = sanitizeResult(result)
    graphqlTracker(opKind, operationName, trackedVariables, trackeresults);
    return result //we have to return the original "result" object here, not the sanitized one
  });
  if(results.length === 0) { //if there are no results, then we've not tracked anything so far...
    graphqlTracker(opKind, operationName, trackedVariables, {});
  }
  return results
});

الجوانب الأساسية لهذه الشيفرة هي:

  1. أضفنا دالتين، واحدة لتعقيم الحقل email من كائن، وأخرى لتعقيم نتائج عملية GraphQL.
  2. داخل دالة رد النداء (callback) الخاصة بـ map (من دالة الـ link)، لم نعد الآن نُرجع مخرجات graphqlTracker، لأن تلك الدالة ستُرجع قيمة النتيجة التي استقبلتها دون تغيير. 1. لكن تلك النتيجة ستُعاد إلى تطبيق العميل، وإذا كنا نعقّم النتيجة، فسيرى المستخدم النسخة المعقّمة من مجموعة البيانات. بدلاً من ذلك، نحتاج إلى استنساخ النتيجة لتعديل تلك التي يجري تتبّعها وإرجاع النسخة الأصلية.
  3. تقوم الدالة sanitizeResult باستنساخ عميق للكائن، لأن تعديله بغير ذلك سيغيّر النتيجة نفسها.

Sanitized data everywhere

يمكنك الاطلاع على هذا المستودع للحصول على الشيفرة المصدرية الكاملة لتطبيق فعّال مبني على GraphQL مع الـ Tracker.

إذا واجهت أي مشكلات في إعداد الـ Tracker في مشروع GraphQL الخاص بك، فيرجى التواصل معنا عبر مجتمعنا على Slack وطرح أسئلتك على مطوّرينا مباشرةً!