يمكن لـ Web Locks API منع خمس علامات تبويب مفتوحة من إغراق خادم المصادقة (auth server) بطلبات تجديد الرمز (refresh-token) في وقت واحد، مما يحمي المستخدمين من تسجيل الخروج المفاجئ. ومن خلال تنسيق عمليات التجديد عبر علامات التبويب، يحل طلب واحد محل التدفق الذي يؤدي عادةً إلى إنهاء الجلسة عند استخدام خاصية "تدوير رمز التجديد" (Refresh Token Rotation).

الحمل الزائد الخفي في الجلسات متعددة علامات التبويب

تضيف تطبيقات الصفحة الواحدة (single-page app) التقليدية "مُعترض Axios" (Axios interceptor) يراقب استجابة 401، ويقوم بتغيير قيمة علم (flag) منطقي isRefreshing إلى true، ويضع أي طلبات صادرة في قائمة انتظار حتى يصل رمز JWT جديد. عند اختباره في علامة تبويب واحدة، يعمل هذا التدفق بشكل مثالي.

افتح التطبيق نفسه في خمس علامات تبويب، واترك رمز الوصول (access token) ينتهي، وستلاحظ جميع علامات التبويب الخمس استجابة 401 في نفس الميلي ثانية. تعتقد كل علامة تبويب أنها يجب أن تقوم بالتجديد، لذا تتسابق خمسة طلبات متطابقة لتجديد الرمز نحو خادم المصادقة. ومع استخدام "تدوير رمز التجديد" (Refresh Token Rotation) — وهو إجراء أمني يبطل مفعول رمز التجديد السابق بمجرد إصدار رمز جديد — يرى الخادم الطلب الثاني كأنه هجوم إعادة تشغيل (replay attack)، فيصنف الجلسة على أنها مخترقة، ثم يلغيها. والنتيجة هي تسجيل خروج المستخدم من جميع علامات التبويب في لحظة واحدة.

السبب الجذري هو نموذج العزل في JavaScript. فالمتغير مثل isRefreshing يعيش فقط في علامة التبويب التي ضبطته؛ وليس لدى علامات التبويب الأخرى أي وسيلة لمعرفة أن عملية التجديد قيد التنفيذ بالفعل. والنتيجة هي مشكلة تزامن (concurrency problem) كلاسيكية، لكن "العمليات" هنا هي علامات تبويب المتصفح بدلاً من الخيوط (threads).

لماذا يُعد القفل عبر علامات التبويب الأداة المناسبة

ما نحتاجه هو وسيلة لتتواصل علامات التبويب مع بعضها البعض بشأن مورد مشترك — وفي هذه الحالة، هو رمز JWT الجديد. توفر Web Locks API، التي تظهر عبر navigator.locks ، ذلك بالضبط. فهي تتيح للبرمجيات طلب قفل مسمى يفرضه المتصفح عبر جميع السياقات (contexts) التابعة لنفس الأصل (origin). إذا كان القفل محجوزاً بالفعل، يتم وضع المستدعين الآخرين في قائمة انتظار حتى يحرره صاحب القفل أو يقوم المتصفح بإلغائه (على سبيل المثال، عند تعطل علامة التبويب). لا حاجة لخادم خارجي، ولا استطلاع (polling)، مجرد تنسيق أصيل من المتصفح.

تنفيذ تدفق التجديد القائم على القفل

  1. اكتشاف 401 – يلتقط مُعترض Axios الاستجابة غير المصرح بها كما في السابق.
  2. طلب قفل حصري – تستدعي علامة التبويب navigator.locks.request('auth_token_refresh_lock', async lock => { … }). يمكن لعلامة تبويب واحدة فقط دخول دالة الاستدعاء (callback) في كل مرة.
  3. التحقق المزدوج قبل الاتصال بالشبكة – داخل القفل، اقرأ الطابع الزمني (أو الرمز نفسه) من localStorage. إذا كان الطابع الزمني أحدث ببضع ثوانٍ فقط، فهذا يعني أن علامة تبويب أخرى قد جددت الرمز بالفعل؛ لذا تتخطى علامة التبويب الحالية طلب الشبكة وتقرأ ببساطة رمز JWT الجديد من التخزين.
  4. التجديد عند الحاجة – إذا كان الطابع الزمني المخزن قديماً، أرسل طلب التجديد، وقم بتخزين الرمز الجديد والوقت الحالي في localStorage ، ثم حرر القفل عن طريق إنهاء دالة الاستدعاء.
  5. استئناف الطلبات المنتظرة – تحصل جميع علامات التبويب الأخرى التي كانت تنتظر على القفل واحدة تلو الأخرى، وترى الطابع الزمني الجديد، وتنهي العملية دون إجراء طلب آخر.
async function refreshIfNeeded() {
  await navigator.locks.request('auth_token_refresh_lock', async lock => {
    const lastRefresh = Number(localStorage.getItem('token_refreshed_at') || 0);
    const now = Date.now();
    if (now - lastRefresh < 5_000) return; // another tab already refreshed

    const newToken = await callRefreshEndpoint(); // actual network call
    localStorage.setItem('jwt', newToken);
    localStorage.setItem('token_refreshed_at', now.toString());
  });
}

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

فوائد يمكنك قياسها

  • كفاءة الشبكة – طلب واحد يحل محل خمسة، مما يقلل من استهلاك النطاق الترددي وحمل الخادم بشكل كبير.
  • أمان الجلسة – مع خاصية "تدوير رمز التجديد" (Refresh Token Rotation)، يرى الخادم استخداماً واحداً فقط لرمز التجديد القديم، لذا لن يصنف الجلسة أبداً على أنها مخترقة.
  • المرونة – إذا تعطلت علامة التبويب التي تحتفظ بالقفل، يقوم المتصفح بتحرير القفل تلقائياً، مما يمنع حدوث حالة "جمود" (deadlock) قد تؤدي إلى توقف جميع علامات التبويب.
  • القابلية للتوسع – يمكن للمستخدمين فتح عشرات علامات التبويب دون المخاطرة بتسجيل خروج متسلسل، لأن التنسيق يظل داخل المتصفح.

الجانب الآخر: دعم المتصفحات والحلول البديلة

تُعد Web Locks API ميزة جديدة نسبياً. تدعمها المتصفحات الحديثة القائمة على Chromium والإصدارات الأخيرة من Firefox، ولكن المتصفحات القديمة وSafari تفتقر إلى الدعم الأصيل لها. في البيئات التي لا تتوفر فيها هذه الواجهة البرمجية (API)، يجب على المطورين اللجوء إلى تقنية أقل موثوقية — مثل بث حدث مخصص عبر localStorage أو استخدام "عامل مشترك" (shared worker) — لمحاكاة عملية الإرسال عبر علامات التبويب. تفتقر هذه الحلول البديلة إلى الحماية التلقائية من حالة الجمود (dead-lock) التي توفرها navigator.locks ، لذا يجب استخدامها بحذر.

ما يجب مراقبته لاحقاً

  • تقدم عملية التقييس – راقب منحنى اعتماد واجهة برمجة التطبيقات (API)؛ فالدعم الأوسع سيجعل النهج القائم على القفل هو الخيار الافتراضي لأي تنسيق بين علامات التبويب.
  • أغلفة المكتبات – تقوم بالفعل بعض الأدوات مفتوحة المصدر بتجريد نمط طلب القفل، مما يسهل دمجها في اعتراضات Axios الحالية.
  • عمليات التدقيق الأمني – بينما يحل القفل مشكلة التزامن، يجب أن تظل نقطة نهاية التحديث (refresh endpoint) تفرض تدوير الرموز (token rotation) وتحديد معدل الطلبات (rate limiting) بشكل صحيح، حيث يمكن لعلامة تبويب خبيثة واحدة أن تغرق الخادم بالطلبات إذا تم تجاوز القفل.

الخلاصة بسيطة: تعامل مع مجموعة من علامات التبويب المفتوحة كنظام موزع وامنحها وسيلة مزامنة أصلية. من خلال ربط Web Locks API بتدفق تحديث الرمز، يقضي المطورون على "فخ رموز علامات تبويب المتصفح" ويحافظون على بقاء المستخدمين مسجلين الدخول، بغض النظر عن عدد علامات التبويب التي يستخدمونها.