التقاط بيانات الطلبات وتعقيمها
في بعض الأحيان لا تكون الأخطاء في كود العميل الخاص بك واضحة بقدر ما تكون عندما تحصل على شاشة فارغة لأن أيًا من كود JavaScript الخاص بك لا يعمل. في بعض الأحيان تكون المشكلة في تطبيقك هي أنه عند إجراءات معينة يمكن للمستخدم القيام بها، فإنك تُكوِّن طلبًا إلى الخادم بشكل غير صحيح. كود العميل يعمل، ولا تحصل على أي أخطاء JS في وحدة التحكم (console)، لكن الواجهة الخلفية لديك لا تستسيغ حقًا ما ترسله إليها.
باستخدام OpenReplay، يمكنك التقاط الاتصال بين العميل والخادم كجزء من إعادة تشغيل الجلسة القياسية لديك، ومراجعته لاحقًا. لذا دعنا نلقي نظرة على كيفية القيام بذلك ونوع الفائدة التي يمكننا الحصول عليها منه.
تطبيق نموذجي
Section titled تطبيق نموذجيلأغراض هذا الدليل الإرشادي، أنشأت تطبيق React بسيطًا يستخدم Bored API. هذه واجهة برمجة تطبيقات (API) بسيطة جدًا تُرجع اقتراح نشاط عشوائي بناءً على بعض المعاملات (parameters). لذا أنشأت تطبيق “I’m bored App”، الذي يبدو هكذا:

ويمكنك أن تجده مباشرًا على Netlify هنا، أو إذا كنت ترغب في الاطلاع على الكود لفحصه بالتفصيل، فهو متاح بالكامل على GitHub.
يتكوّن هذا التطبيق من مكوّنين (components)، مكوّن SearchForm الذي يتولى عرض هذين الحقلين والزر، بالإضافة إلى إرسال الطلب الفعلي إلى الـ API.
ومكوّن Suggestion الذي يعرض الاقتراح ببساطة داخل صندوق أنيق.
سأركّز على المكوّن الأول، لأنه المكوّن الوحيد الذي يرسل الطلبات باستخدام دالة fetch.
لاحظ أن هذه التقنية تعمل أيضًا مع أي طلب يتم تنفيذه باستخدام Axios كذلك.
دعنا نلقي نظرة سريعة على المكوّن لفهم ما يفعله.
كود مكوّن SearchForm
Section titled كود مكوّن SearchFormهذا ليس مكوّنًا معقدًا، لكن هناك قسمًا ذا صلة خاصة بحالة الاستخدام هذه تحديدًا، لذا دعنا نلقي عليه نظرة سريعة.
import { Container, Col, Form, Row, Button } from 'react-bootstrap';
const SearchForm = ({setResult, fetcher}) => {
const getSomething = async (evt) => {
evt.preventDefault()
let form = evt.target
const API_URL = "/api/activity?"
let getParams = {}
if(form.participants.value !== '') {
getParams.participants = form.participants.value
}
if(form.priceRange.value !== '') {
let prices = form.priceRange.value.split("_")
getParams.minprice = prices[0]
getParams.maxprice = prices[1]
}
let results = await fetcher(API_URL + new URLSearchParams(getParams), {
mode: 'no-cors'
})
setResult(await results.json())
return false
}
return (
<Container>
<Form onSubmit={getSomething}>
<Row>
<Col>
<Form.Group controlId='participants' >
<Form.Label>Participants</Form.Label>
<Form.Control type='text' name="totalParticipants" placeholder='Leave empty if you dont care...'></Form.Control>
</Form.Group>
</Col>
<Col>
<Form.Group controlId='priceRangeId'>
<Form.Label>Price range</Form.Label>
<Form.Select name="priceRange" >
<option value="" >Select one or leave empty if you dont care</option>
<option value="0.0">Free</option>
<option value="0.1_0.5">Cheap</option>
<option value="0.6_1.0">Expensive</option>
</Form.Select>
</Form.Group>
</Col>
</Row>
<Row className='m-3'>
<Col>
<Form.Group>
<Button variant="primary" type="submit">Get me something!</Button>
</Form.Group>
</Col>
</Row>
</Form>
</Container>
)
}
export default SearchForm
لاحظ دالة getSomething، فهنا يحدث معظم السحر. يتم استدعاء الدالة عندما يتم تشغيل حدث submit الخاص بالنموذج.
عندما يحدث ذلك، تحصل الدالة على الحدث الاصطناعي (synthetic event) مع النموذج المرتبط داخل الخاصية target. نحن ببساطة نلتقط القيم من كل من عوامل التصفية (حقل الإدخال والقائمة المنسدلة) ثم ننفّذ الطلب باستخدام دالة fetch.
لاحظ أن عنوان الـ URL لا يستهدف نقطة النهاية (endpoint) الخاصة بـ BoredAPI مباشرة. وذلك لأنه، حتى يعمل الطلب ولا يتم حظره بسبب قيود CORS، قمت بتكوين بروكسي (proxy) على الواجهة الخلفية لإعادة توجيه جميع الطلبات من /api إلى الـ API الفعلي.
الآن وقد رأيت الكود، دعنا نلقي نظرة على ما ستحصل عليه إذا قمت بتثبيت متعقّب (tracker) OpenReplay دون إضافة fetch.
التقاط البيانات المعتاد مع OpenReplay
Section titled التقاط البيانات المعتاد مع OpenReplayلهذا المثال، سأستخدم إصدار NPM من الحزمة؛ وإذا كنت لا تعرف كيفية القيام بذلك، فراجع المستندات ثم عُد إلى هنا.

هذه هي واجهة المستخدم لإعادة تشغيل الجلسة بشكل افتراضي. لاحظ كيف أنني قد حددت بالفعل في النصف السفلي علامة التبويب “Network”، لكن رغم أنها تُظهر الطلبات التي يتم إجراؤها، إلا أنه لا توجد أي تفاصيل عنها. حتى لو نقرت على أحدها، فستحصل على الحد الأدنى من التفاصيل المتاحة:

إذًا ماذا يمكننا أن نفعل؟ يمكنك تمكين التقاط معلومات الطلب باستخدام كائن Network options. دعنا نلقي نظرة على ذلك.
التقاط بيانات الطلبات في عمليات إعادة تشغيل الجلسات الخاصة بك
Section titled التقاط بيانات الطلبات في عمليات إعادة تشغيل الجلسات الخاصة بكمن أجل ذلك، كل ما علينا فعله هو إضافة خيار تكوين عند إنشاء نسخة من المتعقّب.
لذا الآن، عندما تكتب سطر new tracker(...)، ستضيف خاصية جديدة:
import Tracker from '@openreplay/tracker';
const tracker = new Tracker({
projectKey: "<your project key>",
network: {
capturePayload: true //start capturing the payload of every request
}
});
هذا كل ما نحتاج إلى فعله؛ من الآن فصاعدًا، في كل مرة تنفّذ فيها طلبًا، سيتم تسجيل البيانات بواسطة المتعقّب. الآن انشر التغيير، واختبر التطبيق، وأغلق علامة التبويب، وانتظر بضع دقائق. من المفترض أن تظهر الجلسة قريبًا بما يكفي، ويمكنك الضغط على زر “play”.
فحص الاتصال بين العميل والخادم
Section titled فحص الاتصال بين العميل والخادملأغراض المثال، دعنا أيضًا نلقي نظرة على مشكلة بدأت ألاحظها بعد أن نشرت التطبيق.
لاحظ مربع التحذير الذي أحصل عليه في هذه الحالة:

بصفتي المطوّر الذي برمج هذا، أعرف ما الذي يجب فعله لاختباره وفهم مكان الخطأ. ومع ذلك، بصفتي مستخدمًا، فإن الخطأ لا يخبرني بالكثير حقًا، وقد لا أتمكن من إيصال هذا بطريقة يمكن لفريق التطوير فهمها. لذا بدلًا من ذلك، بصفتي مستخدمًا، يمكنني ببساطة أن أشتكي للشركة من أن تطبيقها لا يعمل، وأنت، بصفتك المطوّر المسؤول عن التطبيق، يمكنك إلقاء نظرة على جلستي وفحص الطلب الذي أرسله العميل والاستجابة الواردة من الخادم.

انظر الآن إلى واجهة المستخدم لإعادة تشغيل الجلسة. داخل علامة التبويب Network يمكنك أن ترى الطلبات التي كنا نجريها إلى الـ API الخارجي.
كل ما علينا فعله الآن هو إيجاد اللحظة التي نحصل فيها على استجابة الخطأ والنظر إلى الطلبات التي يتم إجراؤها. من المرجح أنك سترى المشكلة داخل تفاصيل الطلب. في حالتنا، يقول الخطأ “Failed to query due to error in arguments”، مما يعني أنه عندما نحدد خيار “Free” في القائمة المنسدلة، فإننا لا نرسل طلبًا صالحًا. لذا دعنا نلقي نظرة على تفاصيله.

هل ترى المشكلة؟ دعني أساعدك:

نعم، أنا أرسل قيمة undefined كقيمة للخاصية maxprice. لقد فاتني ذلك تمامًا في منطقي البرمجي، والتقطته أثناء فحص الطلب.
صحيح أنه إصلاح سهل الآن بعد أن عرفت مكان المشكلة، لكن بفضل هذه العملية كنت سأتمكن إما من رفع تقرير خطأ مفصّل جدًا، أو من مساعدة المطوّر مباشرة في تحديد المشكلة وحلها دون الاضطرار إلى اختبارها بنفسي وإعادة إنتاج المشكلة.
وضع الخصوصية على المحك
Section titled وضع الخصوصية على المحكحسنًا، دعنا نأخذ هذا المثال إلى أبعد قليلًا، فلنفترض أنني أحتاج أيضًا إلى رقم هاتف المستخدم لأجل هذا الطلب. من الواضح أنني لا أحتاجه، لكن جارني للحظة فقط.
سأضيف الحقل إلى النموذج، وسأحدّث الكود لالتقاط تلك القيمة وإرسالها كجزء من الطلب.
كود HTML الخاص بالنموذج هو مجرد إضافة عنصر Col جديد على هذا النحو:
<!-- previous code -->
<Col>
<Form.Group controlId='phoneNumber'>
<Form.Label>Phone Number</Form.Label>
<Form.Control type='number' name="phoneNumber" placeholder='Enter your phone number here please'></Form.Control>
</Form.Group>
</Col>
<!-- rest of the code -->
وإضافة محتويات هذا الحقل إلى الطلب الفعلي لا تتطلب سوى سطر واحد من الكود:
getParams.phonenumber = form.phoneNumber.value
الآن، ماذا يحدث إذا استخدمنا هذا الكود الجديد والتقطنا جلسة باستخدام OpenReplay؟ حسنًا، شيئان:
- إعادة التشغيل الفعلية التي تشاهدها ستقوم تلقائيًا بتعقيم محتوى حقل رقم الهاتف ولن يتم عرضه لأي شخص يشاهده.
- ومع ذلك، فإن معلومات الطلب التي التقطتها الإضافة (plugin) ستُظهر القيمة.
تُظهر لقطة الشاشة التالية ما وصفته للتو:

في الجزء الأيمن من الشاشة، يمكنك أن ترى رقم الهاتف كاملًا. يحدث هذا لأنه بينما يستطيع المتعقّب العادي أن يفهم أن حقل رقم الهاتف هو حقل رقمي، فإنه لن يلتقط ما يُدخل فيه تحسّبًا لأن يمثّل الرقم معلومات شخصية. لكن من جانب الطلب، لا يمكننا حقًا افتراض ذلك، إذ كان بإمكان المطوّر أن يفعل أي شيء بالبيانات، أو حتى باسم المعامل (parameter). إذًا فالسؤال هو: هل يمكننا حماية خصوصية مستخدمنا باستخدام هذه الإضافة؟
والإجابة، ويسعدني الإبلاغ بها، هي: نعم نستطيع.
تعقيم بيانات الطلب
Section titled تعقيم بيانات الطلبإذا عدت إلى بداية هذا الدرس، عندما قمت بتكوين خيارات network، فستلاحظ أنني لم أقل أي شيء عن التعقيم. ومع ذلك، كجزء من تلك الخيارات يمكنك تحديد دالة رد نداء (callback) مخصصة لتعقيم البيانات. تتلقى دالة رد النداء هذه خاصية واحدة تحتوي على كل من كائن الطلب وكائن الاستجابة. يمكنك بعد ذلك أن تختار تحريرهما بالطريقة التي تريدها، ولن يؤثرا على الطلب الفعلي لكنهما سيغيّران طريقة عرض البيانات في واجهة OpenReplay.
على سبيل المثال، لنفترض أنني أريد تغيير الخاصية “phonenumber” وإزالة الأرقام حتى نتجنب تسريب تلك المعلومة. يمكن القيام بذلك على هذا النحو:
const tracker = new Tracker({
projectKey: "<your project id>",
network: {
capturePayload: true,
sanitizer: (data) => { //we change the content of the "phonenumber" parameter from the url
data.url = data.url.replace(/phonenumber=([0-9]+)/, "phonenumber=XXXXXX")
return data
}
}
});
كما ترى، التغيير بسيط، فنحن نستبدل الأرقام فقط في هذه الخاصية، لذا يبدو الطلب الآن هكذا في واجهتنا:

الآن أصبحت بيانات مستخدمك آمنة مرة أخرى.
هل لديك أسئلة؟
Section titled هل لديك أسئلة؟إذا كنت ترغب في الاطلاع على الكود لفحص هذا المثال بالتفصيل، فيمكنك أن تجده هنا على GitHub. إذا واجهت أي مشكلات في إعداد إضافة Fetch أو المتعقّب (Tracker) نفسه، فيرجى التواصل معنا عبر مجتمع Slack الخاص بنا واطرح أسئلتك على مطوّرينا مباشرة!