وثائق المطورين

حزمة رسمية للـ TypeScript وأخرى لـ PHP. نفس العقد، نفس المسارات، نفس الصلاحيات التي يفحصها تطبيق قُرب نفسه.

TypeScript1.7.0PHP1.7

مفاتيح API

المفتاح هو هويتك وصلاحيتك ونطاقك في آنٍ واحد. هذا القسم يشرح شكله، ومن يصدره، وماذا يحدث حين يتسرّب.

شكل المفتاح

ثلاثة أجزاء يفصلها شرطة سفلية. الجزء الأوسط معرّف المفتاح — وهو ما تستخدمه للإلغاء وما يظهر في السجلات؛ الجزء الأخير هو السر ولا يُعرض إلا مرة واحدة.

text
gurb_a1b2c3d4e5f6_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
└──┘ └──────────┘ └─────────────────────────────────────────┘
 │         │                          └─ secret — shown ONCE, never again
 │         └─ key id — revoke by this; safe in logs
 └─ prefix. gurb_sa_ instead means a PLATFORM key
الجزءمعناه
gurb_community key
gurb_sa_platform (super-admin) key
a1b2c3d4e5f612 hex — the key id
xxxx…43 chars — the secret

نوعان، لا يتبادلان

مفتاح المجتمع مثبّت على مجتمع واحد ولا يمكن توجيهه لغيره. مفتاح المنصة لا مجتمع له إطلاقاً — ينشئ المجتمعات ويصدر المفاتيح. كل عميل يرفض مفتاح الآخر عند الإنشاء لا عند أول نداء.

ts
createGurbClient({ apiKey: 'gurb_sa_...' })
// → That is a super-admin key, not a community API key. Use GurbAdminClient…

createGurbAdminClient({ adminKey: 'gurb_...' })
// → That is a community API key, not a super-admin key. Use GurbClient…

النطاقات

المفتاح يحمل read أو read وwrite. الافتراضي read وحده، وهذا مقصود: أغلب التكاملات تقرأ فقط، ومفتاح للقراءة يتسرّب حادثة تُغلق. اطلب write حين يكتب التكامل فعلاً.

ts
// Read-only — the default, and the right choice for most integrations
await admin.apiKeys.create({ communityId, name: 'zapier-prod' })

// Explicitly asking for write. One extra argument, and it should feel
// like a decision.
await admin.apiKeys.create({ communityId, name: 'sync-worker', scopes: ['read', 'write'] })

النطاق يُفحص قبل الصلاحية

مفتاح للقراءة يحاول الكتابة يُرفض بسبب نطاقه، حتى لو كان صاحبه مديراً يملك كل صلاحية. الرفضان يبدوان متشابهين ويرسلانك لمكانين مختلفين تماماً.

من يصدر المفتاح، ولحساب من يعمل

مفتاح المنصة لا يصدره إلا مشرف عام. فبدون تحديد صاحب، المفتاح يعمل لحساب من أصدره — أي مشرف المنصة — وكل مجتمع تنشئه به يصير ملكه لا ملكك. هذي أكثر مفاجأة مكلفة في التكامل، ولها فحص بنداء واحد.

ts
const me = await admin.me()

if (me.actsForIssuer) {
  // Everything you create will belong to the platform admin, not to you.
  throw new Error('This key names no subject — ask for one that does.')
}

أين تحفظه

في متغيّر بيئة. لا في مستودع، ولا في متغيّر قالب، ولا في استجابة JSON يراها متصفّح. الحزمة ترفض العمل في متصفّح أصلاً، وترفض أن يُطبع مفتاح في صفحة — لكن هذي حواجز أخيرة لا بديل عن الحفظ الصحيح.

bash
# .env — never committed
GURB_API_KEY=gurb_a1b2c3d4e5f6_...

# ✗ never
const gurb = createGurbClient({ apiKey: 'gurb_a1b2c3d4e5f6_...' })   // in source
res.json({ apiKey })                                                 // to a browser

التدوير والإلغاء

الإلغاء فوري ونهائي ولا رجعة فيه — بالتصميم، ليكون لسؤال «هل هذا المفتاح حيّ؟» جواب واحد لا يعتمد على وقت السؤال. للتدوير بلا انقطاع: أصدر الجديد، انشره، تحقق أنه يعمل، ثم ألغِ القديم. بهذا الترتيب.

ts
// 1. mint the replacement
const next = await admin.apiKeys.create({ communityId, name: 'zapier-prod-2' })

// 2. deploy it, 3. confirm it works, THEN:
await admin.apiKeys.revoke(oldKeyId)   // immediate, permanent, no undo

إذا تسرّب مفتاح

ألغِه أولاً، وحقّق بعده. مفتاح مجتمع مسرّب يقدر ينشئ أعضاء وهميين وينشر محتوى في مجتمع واحد؛ مفتاح منصة مسرّب يقدر ينشئ مجتمعات ويصدر مفاتيح أخرى إلى ما لا نهاية. الفرق بينهما مراتب، وهو سبب وجود عميلين منفصلين.

لا يوجد مسار لاسترجاع السر

غياب هذا المسار متعمّد: لو أمكن جلب السر مرة ثانية، لصار وصول القراءة إلى لوحتك مكافئاً لحيازة كل مفتاح أصدرته. سلّمه لصاحبه الآن، أو أتلفه وأصدر غيره.

ts
const key = await admin.apiKeys.create({ communityId, name: 'zapier' })
console.log(key.key)   // gurb_a1b2… — present here and nowhere else, ever

await admin.apiKeys.list()   // summaries only: id, name, scopes, lastUsedAt