بناء التطبيق

أدوات المتصفح (Browser Tools)

أداة المتصفح شيفرة تعمل داخل نظام تستخدمه المدرسة أصلاً — نظام نور مثلاً — لا داخل أوكتا. أنت تكتبها، وتُشحن داخل إصدار تطبيقك، وأوكتا تخدمها لإضافة «أدوات أوكتا» في متصفح المستخدم. صالحة لأي نوع تكامل: الأداة قدرةٌ يشحنها تطبيقك لا نوعاً من التطبيقات، فلا يشترطها integrationType ولا مفتاح آخر.

القاعدة الحاكمة: الإضافة وحدها تشغّل، وأوكتا تخدم

تغيّرت القاعدة عمّا كانت. كان الشريك يستضيف ملف .user.js وأوكتا تُدرج رابطه ليثبّته المستخدم في Tampermonkey أو أخواته. لم يعد ذلك مساراً مدعوماً:

  • المستخدم لا ينزّل شيئاً ولا يحتاج مدير userscripts.
  • إضافة «أدوات أوكتا» (متجر كروم) هي الطريق الوحيد لتشغيل أي أداة.
  • أنت تكتب جسم الأداة في حقل script داخل الإصدار، وأوكتا تخدمه من نطاقها للإضافة وحدها.

ما تكسبه من ذلك ثلاثة أشياء لم تكن ممكنة قبله:

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

الشكل القديم ما زال مقبولاً: إصدارٌ منشور يحمل url ينتهي بـ.user.js بلا script لا يُرفض ولا يختفي — إسقاطه كان سيخفي أداةً تعمل اليوم عند جهات مثبِّتة. لكنه لا يُستعمل لعمل جديد، ولا يستفيد من أيٍّ من الثلاثة أعلاه.

delivery: webstore لم يتغيّر: إضافةٌ توزّعها من متجر كروم، والرابط هو المنتج كلّه.

الإعلان

من صفحة تطبيقك في البوّابة: تبويب التكامل ← قسم أدوات المتصفح ← «إضافة أداة». الحقول:

💡 بديل عن النموذج: أداة MCP set_browser_tools تكتب القائمة مباشرةً على إصدار قابل للتعديل (أو على التطبيق)، وget_browser_tools تقرؤها مع تجاوزات كل إصدار. القائمة المُمرَّرة تستبدل المخزَّن كاملاً، و[] تمسح التصريح — وعلى إصدار تعني إلغاء التجاوز فيعود يرث قائمة التطبيق.

والفرق الذي يهمّك: النموذج يُسقط الصفّ المعطوب بصمت (وهو الصواب لبنّاء يحمي الـmanifest)، أما الأداة فترفض بالاسم — مفتاح مكرَّر، match غائبة عن userscript، world مجهول، رابط .user.js مُعلَن webstore — لأن الصفّ الساقط لا يظهر إلا كأداة غائبة من كتالوج الجهة بعد النشر.

الحقل إلزامي القاعدة
key ^[a-z][a-z0-9-]*$، ≤ 32 محرفاً، فريد داخل تطبيقك. أوكتا تعنون الأداة بـ<module>:<key>
name ≤ 80 محرفاً. و name_en اختياري بالحدّ نفسه
description — ≤ 300 محرفاً، و description_en مثلها
delivery userscript أو webstore
script مع userscript جسم الأداة، ≤ 128 كيلوبايت
world — isolated (الافتراضي) أو main. للمحقون فقط — اقرأ القسم التالي قبل أن تختار
url مع webstore https مطلق لصفحة الإضافة في المتجر، ≤ 500 محرفاً
icon — https مطلق. الأيقونة المكسورة تُجرَّد ويبقى الصفّ
version — ≤ 20 محرفاً، الافتراضي 1.0
match مع userscript سطر لكل نمط، ≤ 20 نمطاً، ≤ 200 محرف للنمط

بحدّ أقصى 10 أدوات لكل تطبيق. والترتيب في القائمة هو ترتيب العرض عند الجهة — وهو التحكّم الوحيد لديك في أيّها تراه أولاً.

world: أخطر حقل في هذا القسم

عالَم التنفيذ اختيار وظيفي لا تفضيل، وأثره لا يظهر إلا وقت التشغيل عند المستخدم:

القيمة ما تراه الأداة
isolated (الافتراضي) شجرة الـDOM فقط. لا ترى متغيّرات الصفحة العامّة ولا دوالّها
main عالَم الصفحة نفسه: متغيّراتها ودوالّها وأطرها

القاعدة العملية: إن كانت أداتك تنادي أي شيء تعرّفه الصفحة — $find في تطبيقات ASP.NET، أو jQuery، أو كائن التطبيق العميل — فهي تحتاج main. في isolated تجد تلك الدوالّ غير معرّفة، فتفشل بـis not a function بينما كل شيء آخر يبدو سليماً: الأداة مثبَّتة، والأزرار مرسومة، والصفحة تعمل. هذا بالضبط ما حدث مع أداة تصدير نور، وهو صنف الأعطال التي لا يشتكي منها المستخدم لأنه لا يعرف ما الذي كان يُفترض أن يحدث.

ابدأ بـisolated — إن كانت أداتك تقرأ الـDOM وتكتب فيه فقط فهي تكفيها وهي الأضيق صلاحيةً. ولا ترفع إلى main إلا لهذا السبب بعينه.

قيمة غير معروفة (page مثلاً) تُسقط الأداة كاملةً ولا تُخفَّض إلى isolated: التخفيض الصامت يشحن العطل نفسه الموصوف أعلاه.

التقاطعات التي تُسقط الصفّ

بوّابة الشركاء تفحصها وتمنعك من الحفظ، لكن اعرفها لأنها سبب أكثر الأخطاء شيوعاً:

  1. delivery: userscript بلا script. الجسم يسافر داخل الإصدار، فأداةٌ بلا جسم ليست أداة. (الاستثناء الوحيد: الشكل القديم برابط .user.js.)
  2. script وurl معاً على صفّ واحد. مصدرٌ واحد للحقيقة لكل أداة — وإلا لم يعد ممكناً معرفة أي جسم تشغّله الجهة.
  3. delivery: webstore بجسم script. لا شيء يشغّله: إضافة المتجر هي المنتج، فالجسم هنا شيفرةٌ ميتة تُقرأ كأنها تعمل.
  4. delivery: userscript بلا نمط match. هي الإفصاح الوحيد للجهة عن المواقع التي تلمسها أداتك، وهي ما تطلب الإضافةُ الإذنَ عليه.
  5. delivery: webstore برابط ينتهي بـ.user.js. نوع تسليم مكتوب خطأً.

لماذا نمنعك بدل أن نقبل ونصحّح: BuildBrowserToolsBlock يُسقط أي صفّ لا يستطيع شحنه كاملاً. لو مرّرناه لحُفظ النموذج بنجاح، وخرج الـmanifest بأداةٍ أقل، ولاكتشفت ذلك من بلاغ جهة لم تظهر لها الأداة. فالرفض عند النموذج هو الفرق بين «البوّابة قالت إن أداتي ناقصة» و«أداتي اختفت بصمت».

أنماط match

بصيغة أنماط كروم: <scheme>://<host><path>.

https://noor.moe.gov.sa/*
https://*.moe.gov.sa/reports/*

ثلاث نقاط تُخطئ غالباً:

  • * في المخطّط تعني http|https فقط — لا «أي مخطّط».
  • *.example.com يشمل example.com نفسه لا نطاقاته الفرعية وحدها.
  • المسار إلزامي: https://noor.moe.gov.sa بلا / نمطٌ غير صالح.

اجعلها أضيق ما يكفي. النمط الواسع يعني نافذة إذنٍ أوسع أمام المستخدم، وسببٌ إضافي لرفضه تشغيل أداتك.

الشكل في الـmanifest

{
  "browser_tools": [
    {
      "key": "noor-helper",
      "name": "مساعد نور",
      "name_en": "Noor Helper",
      "description": "يختصر إدخال الدرجات في نور.",
      "delivery": "userscript",
      "script": "(function () { 'use strict'; /* ... */ })();",
      "world": "main",
      "icon": "https://partner.example/tools/icon.png",
      "version": "1.2",
      "match": ["https://noor.moe.gov.sa/*"]
    }
  ]
}

ترويسة ==UserScript== غير مطلوبة — الإضافة لا تقرأها؛ الـmatch والـworld في الإعلان هما ما يحكم الحقن. اكتبها إن شئت للتوثيق فحسب.

التجاوز على مستوى الإصدار

القائمة مملوكة على مستوى التطبيق، ويرثها كل إصدار. ولإصدارٍ أن يحمل قائمته الخاصة من محرّر الإصدار (بطاقة «أدوات المتصفح» ← «تخصيص القائمة لهذا الإصدار»).

اعرف ما يترتّب على ذلك قبل أن تفعله:

  • القائمة الخاصة تهزم قائمة التطبيق عند بناء الـmanifest. فأي أداة تضيفها لاحقاً على مستوى التطبيق لن تصل إلى ذلك الإصدار.
  • القائمة الخاصة الفارغة ليست وراثة، بل إعلانٌ صريح بأن هذا الإصدار يشحن بلا أي أداة. استعملها حين تسحب أداةً في إصدار وتُبقيها في غيره.
  • العودة: «العودة إلى قائمة التطبيق» في البطاقة نفسها تمحو التجاوز وتعيد الوراثة الديناميكية.

ولأن الجسم صار جزءاً من الإصدار، فالتجاوز هنا يعني أيضاً تجميد شيفرة الأداة على ذلك الإصدار.

تحديث أداة منشورة

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

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

ما لا تفعله الأداة

الأداة لا تصل إلى أوكتا في أي عالَم كانت: لا تحمل توكن التثبيت، ولا تقرأ نطاقاتك (scopes)، ولا تستدعي /api/apps/* بهوية تطبيقك. world: main يوسّع ما تراه من الصفحة المستهدفة لا ما تملكه من أوكتا. إن احتجت بيانات من أوكتا فذلك عمل تطبيقك نفسه لا أداته.


عرض الدليل كاملاً في صفحة واحدة · ↧ Markdown خام