---
url: https://docs.ogmabox.com/ar/plugins/README.md
description: >-
  أنشئ إضافات Ogma وغلّفها وثبّتها وفعّلها ووزّعها مع منطق خلفي ولوحات أمامية
  وأوامر وصلاحيات وبيانات وصفية لسوق الإضافات.
---

# نظام إضافات Ogma {#ogma-plugin-system}

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

هذا المستند هو المرجع الرئيسي لمطوّري الإضافات.

للبدء بأسرع طريقة ممكنة، استخدم [دليل البدء السريع للإضافات](/ar/plugins/quickstart).

***

## البدء السريع {#quick-start}

### إضافة خلفية مبسّطة {#minimal-backend-plugin}

```
my-plugin/
  manifest.json
  backend/script.js
```

`manifest.json`:

```json
{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "plugins": [
    {
      "kind": "backend",
      "id": "my-plugin-backend",
      "entrypoint": "backend/script.js"
    }
  ]
}
```

`backend/script.js` (ES2020؛ يمكن استخدام عبارات الاستيراد عند استخدام أداة تجميع):

```js
async function init(sdk) {
  sdk.console.log("my-plugin started");

  sdk.events.onInterceptResponse(function(req, res) {
    if (res.getCode() === 403) {
      sdk.console.warn("403 on " + req.getUrl());
    }
  });
}
```

هذا يكفي لإنشاء إضافة تعمل بالواجهة الخلفية فقط. أبقِها في ملف واحد في `backend/script.js`، ووجّه `manifest.json` إليه مباشرة.

### خطوات بناء خلال دقيقة واحدة (مصدر TypeScript) {#one-minute-build-flow-typescript-source}

إذا كنت تكتب الشيفرة باستخدام TypeScript، فاستخدم هذه البنية:

```text
my-plugin/
  manifest.json
  backend/
    src/index.ts
```

البناء:

```bash
pnpm add -D @ogmabox/ogma-sdk esbuild typescript
pnpm exec esbuild backend/src/index.ts --bundle --format=iife --platform=neutral --external:@ogma/sdk --external:@ogmabox/ogma-sdk --outfile=backend/script.js
```

التثبيت: **الإضافات > تثبيت**، ثم اختر المجلد `my-plugin/`. وبعد ذلك فعّل الإضافة.

***

## مرجع ملف التعريف {#manifest-reference}

يوجد `manifest.json` في المجلد الجذري للحزمة. جميع الحقول حسّاسة لحالة الأحرف.

### الحقول ذات المستوى الأعلى {#top-level-fields}

| الحقل | مطلوب | النوع | ملاحظات |
|-------|----------|------|-------|
| `id` | نعم | سلسلة نصية | أحرف لاتينية صغيرة وأرقام وواصلات فقط. الحد الأقصى 64 محرفًا. فريد بين الإضافات المثبّتة. |
| `version` | نعم | سلسلة نصية | الإصدار الدلالي: `MAJOR.MINOR.PATCH` |
| `name` | لا | سلسلة نصية | اسم العرض الظاهر في واجهة المستخدم. قيمته الافتراضية `id`. |
| `description` | لا | سلسلة نصية | ملخّص من سطر واحد. |
| `author` | لا | كائن | `{ "name": "...", "email": "...", "url": "..." }` |
| `homepage` | لا | سلسلة نصية | عنوان URL لمستودع المصدر أو التوثيق. |
| `plugins` | نعم | مصفوفة | مدخل واحد أو أكثر لمكوّنات الإضافة (انظر أدناه). |
| `permissions` | لا | مصفوفة | قائمة أسماء الصلاحيات المطلوبة (راجع [الصلاحيات](#permissions)). |

### مدخل مكوّن الإضافة {#plugin-component-entry}

يصف كل كائن في مصفوفة `plugins` مكوّنًا واحدًا.

**المكوّن الخلفي:**

```json
{
  "kind": "backend",
  "id": "my-plugin-backend",
  "entrypoint": "backend/script.js",
  "runtime": "javascript",
  "assets": "backend/assets"
}
```

**المكوّن الأمامي:**

```json
{
  "kind": "frontend",
  "id": "my-plugin-frontend",
  "entrypoint": "frontend/script.js",
  "style": "frontend/style.css",
  "assets": "frontend/assets",
  "backend": { "id": "my-plugin-backend" }
}
```

| الحقل | مطلوب | ملاحظات |
|-------|----------|-------|
| `kind` | نعم | `"backend"` أو `"frontend"` |
| `id` | نعم | فريد داخل ملف التعريف. أحرف صغيرة وواصلات. |
| `entrypoint` | نعم | مسار نسبي لملف الدخول بلغة JS. |
| `style` | لا | ملف CSS يُحمّل في iframe الخاص بالإضافة. |
| `assets` | لا | مجلد للأصول الثابتة المقدّمة تحت `/plugins/{id}/assets/`. |
| `backend.id` | لا | يربط المكوّن الأمامي بمكوّنه الخلفي لاستدعاءات RPC عبر `sdk.backend.*`. |
| `runtime` | لا (للواجهة الخلفية فقط) | `"javascript"` (القيمة الافتراضية والوحيدة المدعومة). |

***

## API الإضافات الخلفية (sdk) {#backend-plugin-api-sdk}

يُمرّر كائن `sdk` الخلفي إلى دالتك `init(sdk)`. جميع الطرق متزامنة ما لم تُعلّم بـ`async`.

### `sdk.console` {#sdk-console}

```js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")
```

تكتب في مخزن سجلات الإضافة (المعروض تحت علامة تبويب السجلات). يُحتفظ بحد أقصى 500 سجل. تُقتطع كل رسالة عند 1 KB.

### `sdk.meta` {#sdk-meta}

```js
sdk.meta.id()          // > string: plugin id (e.g. "my-plugin")
sdk.meta.packageId()   // > string: same as id
sdk.meta.version()     // > string: semver (e.g. "1.0.0")
sdk.meta.path()        // > string: writable data directory for this plugin
```

يشير `sdk.meta.path()` إلى مجلد البيانات الخاص بالإضافة والقابل للكتابة، مثل `~/.local/share/ogma/plugins/my-plugin/data`. يُنشأ المجلد تلقائيًا ويمكن استخدامه للاحتفاظ ببيانات الإضافة عبر عمليات إعادة التشغيل.

تتوفّر أيضًا `sdk.storage` و`sdk.path` و`sdk.fs` لأدوات مساعدة للحالة والملفات.

### `sdk.storage` {#sdk-storage}

```js
sdk.storage.get("key")      // > string | null
sdk.storage.set("key", "value")
sdk.storage.delete("key")
sdk.storage.clear()
sdk.storage.keys()          // > string[]
```

يقتصر نطاق `sdk.storage` على معرّف الإضافة، وتُحفظ بياناته عبر عمليات إعادة تشغيل الإضافة.

### `sdk.fs` {#sdk-fs}

```js
sdk.fs.readFile("relative/file.txt")      // > string
sdk.fs.writeFile("relative/file.txt", "text")
sdk.fs.appendFile("relative/file.txt", "more")
sdk.fs.exists("relative/file.txt")        // > boolean
sdk.fs.existsSync("relative/file.txt")    // > boolean
sdk.fs.list("relative/dir")              // > string[]
sdk.fs.mkdir("relative/dir")
```

يقتصر `sdk.fs` على الملفات الموجودة تحت `sdk.meta.path()`.

يبقى `read` و`write` اسمين بديلين للتوافق مع `readFile` و`writeFile`. استخدم `exists` أو `existsSync` قبل إنشاء ملف لا تريد استبداله. تعمل هذه الواجهات بصورة متزامنة في بيئة تشغيل الإضافة؛ وهي ليست وحدة `fs` الكاملة في Node.js. يتطلّب وصول الإضافة إلى الملفات الصلاحية `plugin_storage`، ويظل داخل مجلد بيانات الإضافة الخاص. يستخدم JavaScript في سير العمل سياقًا مختلفًا لنظام الملفات؛ راجع [الوصول إلى الملفات في سير العمل](../app/workflows.md#javascript-and-files).

### `sdk.path` {#sdk-path}

```js
sdk.path.join("a", "b", "c")
sdk.path.basename("/tmp/file.txt")
sdk.path.dirname("/tmp/file.txt")
sdk.path.extname("file.txt")
sdk.path.resolve("/a", "b")
sdk.path.isAbsolute("/tmp/file.txt")
sdk.path.sep
```

### `sdk.events` {#sdk-events}

سجّل دوال استدعاء راجع لأحداث Ogma. تُستدعى جميع هذه الدوال بصورة متزامنة داخل بيئة QuickJS المعزولة.

```js
sdk.events.onInterceptRequest(function(req) {
  // req: RequestSpecRaw
  // Return a modified RequestSpecRaw to mutate the request.
  // Return null/undefined to pass through unchanged.
});

sdk.events.onInterceptResponse(function(req, res) {
  // req: Request (read-only), res: Response (read-only)
  // Return value is ignored.
});

sdk.events.onProjectChange(function() {
  // no callback args
});

sdk.events.onFindingCreated(function(finding) {
  // finding: { id, title, reporter }
});
```

### `sdk.requests` {#sdk-requests}

```js
// Get a single HTTP entry by id
var entry = sdk.requests.get("entry-id");
// entry: { id, method, host, path, query, tls, ... } or null

// Search HTTP history
var results = sdk.requests.search({ limit: 20, offset: 0 });
// results: { entries: [...], total: N }

// Send an HTTP request (requires send_requests permission)
var response = await sdk.requests.send(spec);
// spec: RequestSpecRaw (see below)
// response: Response
```

يتطلّب `sdk.requests.send` إعلان الصلاحية `send_requests` في ملف التعريف ومنحها من المستخدم. راجع [الصلاحيات](#permissions).

### `sdk.findings` {#sdk-findings}

```js
// Create a finding (requires write_findings permission)
sdk.findings.create({
  title: "SSRF via redirect",
  reporter: "my-plugin",
  dedupeKey: "ssrf-" + request.getId(),
  request: { id: request.getId() }
});

// Check if a finding already exists (dedup check)
var exists = sdk.findings.exists({ dedupeKey: "ssrf-abc" });

// List findings
var page = sdk.findings.list({ limit: 20, offset: 0 });

// Get a single finding
var finding = sdk.findings.get("finding-id");
```

حدود معدل `sdk.findings.create`: ‏10 في الدقيقة، و500 لكل جلسة إضافة، و3 لكل دالة استدعاء راجع لحدث.

### `sdk.api` {#sdk-api}

سجّل دوال RPC خلفية تستطيع الواجهة الأمامية استدعاءها عبر `sdk.backend.*`:

```js
// In backend init:
sdk.api.register("getScans", function(scanId) {
  return { scans: [] };
});

// Emit an event to connected frontends:
sdk.api.send("scan:complete", { scanId: 1, status: "ok" });
```

يتلقّى المعالج الوسائط الممرّرة من الواجهة الأمامية (لا يُحقن وسيط `sdk` إضافي). تُسلسَل القيم المُرجعة بصيغة JSON وتُرسل إلى المستدعي.

تستدعي الواجهة الأمامية هذه الدوال عبر `sdk.backend.getScans(scanId)` - راجع [API الإضافات الأمامية](#frontend-plugin-api-sdk).

يدفع `sdk.api.send` الأحداث إلى قائمة انتظار خاصة بكل إضافة (بحد أقصى 200 مدخل). تستطلع الواجهات الأمامية هذه القائمة عبر `sdk.backend.onEvent`.

### `sdk.replay`, `sdk.projects`, `sdk.scope`, `sdk.workflows`, `sdk.matchReplace` {#sdk-replay-sdk-projects-sdk-scope-sdk-workflows-sdk-matchreplace}

هذه مساحات أسماء للاستعلام للقراءة فقط. راجع [مرجع SDK الواجهة الخلفية](./backend-sdk.md) للاطّلاع على التواقيع الكاملة للطرق.

### أصناف الطلبات {#request-classes}

**`RequestSpecRaw`** - يمثّل طلبًا معترضًا. تتلقّاه في `onInterceptRequest`.

```js
spec.getMethod()          // > string
spec.setMethod("POST")
spec.getHost()            // > string
spec.setHost("example.com")
spec.getPort()            // > number
spec.getPath()            // > string
spec.setPath("/new/path")
spec.getQuery()           // > string
spec.getTls()             // > boolean
spec.getHeaders()         // > Record<string, string[]>
spec.setHeader("X-Foo", "bar")
spec.getBody()            // > Body | null
spec.setBody("new body")
spec.getRaw()             // > Uint8Array (raw bytes) or []
spec.setRaw(bytes)        // set raw bytes

// Create a new spec from a URL string:
var spec = new RequestSpecRaw("https://example.com/path?q=1");
```

**`Request`** - طلب ملتقط للقراءة فقط (من `sdk.requests.get`).

```js
req.getId()
req.getMethod()
req.getHost()
req.getPort()
req.getTls()
req.getPath()
req.getQuery()
req.getUrl()           // > full URL string
req.getHeaders()       // > Record<string, string>
req.getHeader("name")
req.getBody()          // > Body | null
req.getCreatedAt()     // > Date
req.toSpec()           // > RequestSpec (mutable copy)
```

**`Response`** - استجابة ملتقطة للقراءة فقط.

```js
res.getCode()            // > number (HTTP status)
res.getHeaders()         // > Record<string, string>
res.getHeader("name")
res.getBody()            // > Body | null
res.getRoundtripTime()   // > number (ms)
res.getCreatedAt()       // > Date
```

**`Body`**:

```js
body.toText()    // > string
body.toJson()    // > parsed object or null
body.toRaw()     // > Uint8Array
body.length      // > number (original size, may differ from toText() if truncated)
```

***

## API الإضافات الأمامية (sdk) {#frontend-plugin-api-sdk}

تعمل شيفرة الإضافة الأمامية في إطار iframe معزول يُحمّل من `/plugins/{id}/ui`. يستخدم iframe الدالة `postMessage` للتواصل مع مضيف Ogma، الذي يتوسّط في الاستدعاءات إلى الواجهة الخلفية.

يتوفّر SDK عبر `window.ogmaSDK`. استدعِ `ogmaSDK.ready(cb)` لتلقّي SDK الجاهز للعمل بعد إنشاء جسر المضيف:

```js
ogmaSDK.ready(function(sdk) {
  // sdk is the live SDK - safe to call any method here
  sdk.log.info("frontend ready");
});
```

تُرجع جميع طرق SDK كائنات Promise.

### `sdk.log` {#sdk-log}

```js
sdk.log.info("message")
sdk.log.warn("message")
sdk.log.error("message")
```

### `sdk.meta` {#sdk-meta-1}

```js
var meta = await sdk.meta.get();
// { pluginId, packageId, name, version, ogmaVersion }
```

### `sdk.requests` {#sdk-requests-1}

```js
var entry = await sdk.requests.get({ id: "entry-id" });
var result = await sdk.requests.search({ limit: 20, offset: 0, query: "host:example.com" });
```

تتطلّب الصلاحية `read_http_history` (يُمنح تلقائيًا؛ لا يحتاج إلى موافقة المستخدم).

### `sdk.findings` {#sdk-findings-1}

```js
var page = await sdk.findings.list({ limit: 20, offset: 0 });
```

تتطلّب الصلاحية `read_findings` (يُمنح تلقائيًا).

### `sdk.scope` {#sdk-scope}

```js
var scope = await sdk.scope.getActive();
```

### `sdk.projects` {#sdk-projects}

```js
var project = await sdk.projects.getCurrent();
```

### `sdk.backend` - استدعاءات RPC الخلفية {#sdk-backend-backend-rpc}

استدعِ الدوال المسجّلة باستخدام `sdk.api.register` في الواجهة الخلفية:

```js
// Call a named backend function
var result = await sdk.backend.call("getScans", [scanId]);

// Poll for backend-emitted events (sdk.api.send on the backend side)
var { events, next_since } = await sdk.backend.poll(since);
// events: [{ event: "scan:complete", args: [...] }]

// Register an event listener (uses polling internally)
sdk.backend.onEvent("scan:complete", function(data) {
  console.log("scan done", data);
});
```

يستخدم `sdk.backend.onEvent` داخليًا حلقة استطلاع بفاصل 2 ثانية. أوقف الاستماع باستدعاء دالة إلغاء الاشتراك المُرجعة:

```js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();
```

### `sdk.navigation` {#sdk-navigation}

```js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });
```

تسجّل صفحة تنقّل. يقرّ بها المضيف حاليًا. يجري العمل على التكامل الكامل مع موجّه المسارات.

### `sdk.sidebar` {#sdk-sidebar}

```js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });
```

تسجّل عنصرًا في الشريط الجانبي. يقتصر حاليًا على لوحة واجهة الإضافة - يجري العمل على ربطه بموضع الشريط الجانبي العام.

### `sdk.commands` {#sdk-commands}

```js
await sdk.commands.register("my-plugin:scan", {
  name: "Scan with My Plugin",
  handler: function(context) { /* ... */ }
});
```

يقرّ بها المضيف. يجري العمل على التكامل مع لوحة الأوامر.

### `sdk.menu` {#sdk-menu}

```js
await sdk.menu.registerItem({
  type: "Request",
  commandId: "my-plugin:scan",
  leadingIcon: "shield"
});
```

يقرّ بها المضيف. يجري العمل على إدراجها في القائمة السياقية.

### `sdk.window` {#sdk-window}

```js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });
```

تعرض إشعارًا عابرًا في لوحة الإضافة. الأنواع: `info` و`success` و`warning` و`error`.

### `sdk.ui` {#sdk-ui}

```js
sdk.ui.resize(600);                              // request iframe height change
sdk.ui.sidebar.registerItem("name", "/path");    // alias for sdk.sidebar.registerItem
```

***

## الصلاحيات {#permissions}

أعلن الصلاحيات في `manifest.json`:

```json
{
  "permissions": ["send_requests", "write_findings"]
}
```

### الصلاحيات الممنوحة تلقائيًا (لا تحتاج إلى موافقة المستخدم) {#auto-granted-permissions-no-user-approval-needed}

تُمنح هذه الصلاحيات دائمًا لأي إضافة مثبّتة:

| الصلاحية | ما يسمح به |
|------------|----------------|
| `read_http_history` | `sdk.requests.get`, `sdk.requests.search` |
| `read_findings` | `sdk.findings.get`, `sdk.findings.list` |
| `read_scope` | `sdk.scope.getActive` |
| `read_projects` | `sdk.projects.getCurrent`, `sdk.projects.list` |
| `plugin_storage` | `sdk.storage`, `sdk.fs`, `sdk.path` |

### الصلاحيات المحمية (تتطلّب موافقة المستخدم) {#protected-permissions-require-user-approval}

يجب إعلان هذه الصلاحيات في ملف التعريف ومنحها صراحةً من المستخدم عبر علامة تبويب الصلاحيات:

| الصلاحية | ما يسمح به |
|------------|----------------|
| `send_requests` | `sdk.requests.send` - إرسال طلبات HTTP صادرة |
| `write_findings` | `sdk.findings.create`, `sdk.findings.update` |

يرى المستخدم مطالبة عند تفعيل إضافة تعلن صلاحيات محمية. ويمكنه أيضًا منح الصلاحيات أو سحبها في أي وقت من علامة تبويب الصلاحيات.

***

## بنية حزمة الإضافة {#plugin-package-structure}

يمكن تثبيت حزمة إضافة من مجلد محلي، وكذلك من تصديرات `.zip` عند استخدام المتصفح.

```
my-plugin/
  manifest.json           - required
  backend/
    script.js             - bundled backend JS (ES2020)
  frontend/
    script.js             - bundled frontend JS
    style.css             - optional CSS
    assets/               - static assets (images, fonts, etc.)
```

### متطلّبات الملف البرمجي الخلفي {#backend-script-requirements}

* يجب أن يكون ملف JS واحدًا مستقلًا بذاته.
* لا يُدعم `require()` ولا `import()` الديناميكي.
* يجب تصدير دالة `init(sdk)` (أو تعريفها كدالة عامة).
* تدعم QuickJS مجموعة فرعية من ES2020: ‏`async/await` و`Promise` و`Map` و`Set` و`Symbol` و`Proxy` و`Date` و`RegExp` و`JSON`. لا يتوفّر `fetch` ولا `Buffer`.
* تُحل عبارات الاستيراد الثابتة المدعومة أثناء المعالجة المسبقة للإضافة: `@ogma/sdk` و`crypto` و`fs` و`path`.
* الحد الأقصى لحجم الملف: 256 KB.

### متطلّبات الملف البرمجي الأمامي {#frontend-script-requirements}

* يعمل داخل iframe معزول. يُسمح بـ`connect-src: 'self'` كي تتمكّن الإضافة من إرسال POST إلى `/plugins/{id}/api/*` واستطلاع `/plugins/{id}/events/poll`.
* سياسة CSP: ‏`default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'`.
* لا يوجد `allow-same-origin` في عزل iframe - لا تستطيع الإضافة الوصول إلى DOM الخاص بصفحة Ogma الأم أو ملفات تعريف الارتباط.
* استخدم `ogmaSDK.ready(cb)` للوصول إلى SDK؛ ولا تستدعِ طرق SDK قبل تنفيذ دالة الاستدعاء الراجع.

***

## بناء إضافة لـOgma {#building-a-plugin-for-ogma}

نظرًا إلى أنّ الواجهة الخلفية يجب أن تكون ملف JS مجمّعًا واحدًا، يجب تجميع مصدر TypeScript أو وحدات ES قبل التثبيت.

سلسلة الأدوات الموصى بها:

```bash
# Install dependencies
pnpm install

# Bundle backend (outputs a single CJS/IIFE file):
esbuild packages/backend/src/index.ts \
  --bundle \
  --platform=neutral \
  --format=iife \
  --global-name=_plugin \
  --outfile=dist/backend/script.js \
  --external:@ogma/sdk --external:@ogmabox/ogma-sdk

# Bundle frontend:
vite build packages/frontend --outDir ../../dist/frontend
```

إذا كنت تستخدم سلسلة أدوات تطوير Caido‏ (`@caido-community/dev`)، فنفّذ `caido-dev build` ثم انسخ الناتج إلى بنية متوافقة مع Ogma، مع وضع `manifest.json` في الجذر.

***

## تثبيت إضافة {#installing-a-plugin}

1. افتح **الإضافات** في الشريط الجانبي الأيسر.
2. انقر على **تثبيت** (أعلى علامة تبويب **المثبتة**).
3. في تطبيق سطح المكتب، انقر على **استعراض** لفتح منتقي مجلدات أصلي. في المتصفح، اكتب المسار الكامل لمجلد الإضافة على جانب الخادم.
4. انقر على **تحقق** لفحص ملف التعريف وقائمة الملفات.
5. انقر على **تثبيت** إذا نجح التحقّق.
6. اختر الإضافة من القائمة وانقر على **تفعيل**.
7. إذا كانت الإضافة تعلن صلاحيات محمية، فراجعها وامنحها من علامة تبويب **الصلاحيات** قبل التفعيل.

***

## استكشاف المشكلات وإصلاحها {#troubleshooting}

**فشل تهيئة الإضافة دون ظهور رسالة:** راجع علامة تبويب **السجلات**. الأسباب الأكثر شيوعًا:

* استُدعي `sdk.meta.path()` لكن تعذّر إنشاء مجلد البيانات.
* استثناء غير معالج في `init()`.
* استدعاء `sdk.*` مفقود أو مكتوب بصورة غير صحيحة.

**ظهور الواجهة الأمامية فارغة:** راجع وحدة تحكّم المتصفح بحثًا عن انتهاكات CSP. تأكّد من أنّ الملف البرمجي الأمامي يستدعي `ogmaSDK.ready(cb)` قبل الوصول إلى أي طريقة في SDK.

**رمي `sdk.requests.send` الخطأ `Permission denied`:** يجب إعلان الصلاحية `send_requests` في ملف التعريف، ويجب كذلك أن يمنحها المستخدم في علامة تبويب الصلاحيات.

**تعذّر استدعاء دوال `sdk.api.register` من الواجهة الأمامية:** يجب تفعيل الواجهة الخلفية (لا تثبيتها فقط). يجب أن يطابق اسم الدالة تمامًا ما تمرّره الواجهة الأمامية إلى `sdk.backend.call`، مع مراعاة حالة الأحرف.

**ظهور تحذيرات التوافق كأخطاء:** لا تمنع هذه التحذيرات التشغيل، لكنها تشير إلى فجوات في تغطية واجهة API. راجع جداول مطابقة SDK أعلاه لمعرفة تغطية API.
