رفتن به محتوا

نظام افزونه‌های Ogma ​

افزونه‌های Ogma ابزار را با منطق سفارشی بک‌اند، پنل‌های رابط کاربری فرانت‌اند و مراحل گردش کار گسترش می‌دهند. افزونه‌ها به‌صورت محلی از یک پوشه روی دیسک نصب می‌شوند، برای هر پروژه فعال می‌شوند و در یک محیط ایزوله اجرا می‌شوند.

این سند مرجع اصلی نویسندگان افزونه است.

برای سریع‌ترین شروع ممکن، از شروع سریع افزونه‌ها استفاده کنید.


شروع سریع ​

افزونهٔ سادهٔ بک‌اند ​

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) ​

اگر با 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.json در پوشهٔ ریشهٔ بسته قرار دارد. تمام فیلدها به بزرگی و کوچکی حروف حساس هستند.

فیلدهای سطح بالا ​

فیلدالزامینوعتوضیحات
idبلهرشتهفقط حروف کوچک لاتین، رقم‌ها و خط تیره. حداکثر 64 نویسه. در میان افزونه‌های نصب‌شده یکتا است.
versionبلهرشتهنسخه‌بندی معنایی: MAJOR.MINOR.PATCH
nameخیررشتهنام نمایشی در رابط کاربری. پیش‌فرض آن id است.
descriptionخیررشتهخلاصهٔ یک‌خطی.
authorخیرشیء{ "name": "...", "email": "...", "url": "..." }
homepageخیررشتهنشانی URL مخزن منبع یا مستندات.
pluginsبلهآرایهیک یا چند مدخل جزء افزونه (پایین را ببینید).
permissionsخیرآرایهفهرست نام مجوزهای مورد نیاز (به مجوزها مراجعه کنید).

مدخل جزء افزونه ​

هر شیء در آرایهٔ 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) ​

شیء sdk بک‌اند به تابع init(sdk) شما داده می‌شود. همهٔ متدها همگام هستند، مگر اینکه با async مشخص شده باشند.

sdk.console ​

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

در بافر گزارش رویدادهای افزونه می‌نویسد (در زبانهٔ گزارش رویدادها دیده می‌شود). حداکثر 500 مدخل نگه داشته می‌شود. هر پیام در 1 KB کوتاه می‌شود.

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 ​

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 ​

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 استفاده کنید. این APIها در محیط اجرای افزونه به‌صورت همگام کار می‌کنند؛ آن‌ها ماژول کامل fs در Node.js نیستند. دسترسی افزونه به فایل به مجوز plugin_storage نیاز دارد و در پوشهٔ خصوصی داده‌های افزونه باقی می‌ماند. JavaScript گردش کار زمینهٔ متفاوتی برای سیستم فایل دارد؛ به دسترسی گردش کار به فایل مراجعه کنید.

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 ​

توابع فراخوانی برگشتی را برای رویدادهای 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 ​

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 در مانیفست اعلام و توسط کاربر اعطا شود. به مجوزها مراجعه کنید.

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 ​

توابع 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 افزونهٔ فرانت‌اند مراجعه کنید.

sdk.api.send رویدادها را به صفی مخصوص هر افزونه می‌فرستد (حداکثر 200 مدخل). فرانت‌اندها این صف را از طریق sdk.backend.onEvent پایش می‌کنند.

sdk.replay, sdk.projects, sdk.scope, sdk.workflows, sdk.matchReplace ​

این‌ها فضاهای نام جست‌وجوی فقط‌خواندنی هستند. برای امضای کامل متدها، به مرجع SDK بک‌اند مراجعه کنید.

کلاس‌های درخواست ​

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) ​

کد افزونهٔ فرانت‌اند در یک iframe ایزوله اجرا می‌شود که از /plugins/{id}/ui بارگذاری شده است. iframe برای ارتباط با میزبان Ogma از postMessage استفاده می‌کند و میزبان فراخوانی‌ها را به بک‌اند منتقل می‌کند.

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 ​

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

sdk.meta ​

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

sdk.requests ​

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 ​

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

به مجوز read_findings نیاز دارد (به‌طور خودکار اعطا می‌شود).

sdk.scope ​

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

sdk.projects ​

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

sdk.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 ​

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

یک صفحهٔ پیمایش ثبت می‌کند. در حال حاضر میزبان آن را تأیید می‌کند. یکپارچه‌سازی کامل مسیریاب در حال انجام است.

sdk.sidebar ​

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

یک مدخل نوار کناری ثبت می‌کند. در حال حاضر به پنل رابط کاربری افزونه محدود است - اتصال به جایگاه نوار کناری سراسری در حال انجام است.

sdk.commands ​

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

میزبان تأیید می‌کند. یکپارچه‌سازی با پالت فرمان در حال انجام است.

sdk.menu ​

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

میزبان تأیید می‌کند. افزودن به منوی زمینه‌ای در حال انجام است.

sdk.window ​

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

یک اعلان گذرا در پنل افزونه نمایش می‌دهد. گونه‌ها: info، success، warning، error.

sdk.ui ​

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

مجوزها ​

مجوزها را در manifest.json اعلام کنید:

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

مجوزهای خودکار (بدون نیاز به تأیید کاربر) ​

این مجوزها همیشه به هر افزونهٔ نصب‌شده اعطا می‌شوند:

مجوزآنچه مجاز می‌کند
read_http_historysdk.requests.get, sdk.requests.search
read_findingssdk.findings.get, sdk.findings.list
read_scopesdk.scope.getActive
read_projectssdk.projects.getCurrent, sdk.projects.list
plugin_storagesdk.storage, sdk.fs, sdk.path

مجوزهای محافظت‌شده (نیازمند تأیید کاربر) ​

این مجوزها باید در مانیفست اعلام شوند و کاربر از زبانهٔ مجوزها صریحاً آن‌ها را اعطا کند:

مجوزآنچه مجاز می‌کند
send_requestssdk.requests.send - ارسال درخواست‌های HTTP خروجی
write_findingssdk.findings.create, sdk.findings.update

کاربر هنگام فعال‌سازی افزونه‌ای که مجوزهای محافظت‌شده اعلام می‌کند، یک پیام درخواست مجوز می‌بیند. او همچنین می‌تواند هر زمان از زبانهٔ مجوزها مجوزها را اعطا یا لغو کند.


ساختار بستهٔ افزونه ​

بستهٔ افزونه را می‌توان از یک پوشهٔ محلی و در روندهای مرورگر، از خروجی‌های .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.)

الزامات اسکریپت بک‌اند ​

  • باید یک فایل JS واحد و مستقل باشد.
  • require() و import() پویا پشتیبانی نمی‌شوند.
  • باید تابع init(sdk) را صادر کند (یا آن را به‌صورت سراسری تعریف کند).
  • زیرمجموعهٔ ES2020 پشتیبانی‌شده در QuickJS: ‏async/await، Promise، Map، Set، Symbol، Proxy، Date، RegExp، JSON. بدون fetch و بدون Buffer.
  • واردسازی‌های ایستای پشتیبانی‌شده در پیش‌پردازش افزونه حل می‌شوند: @ogma/sdk، crypto، fs، path.
  • حداکثر اندازهٔ فایل: 256 KB.

الزامات اسکریپت فرانت‌اند ​

  • در یک iframe ایزوله اجرا می‌شود. connect-src: 'self' مجاز است تا افزونه بتواند به /plugins/{id}/api/* درخواست POST بفرستد و /plugins/{id}/events/poll را پایش کند.
  • سیاست CSP: ‏default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'.
  • در محیط ایزولهٔ iframe، ‏allow-same-origin وجود ندارد - افزونه نمی‌تواند به DOM والد Ogma یا کوکی‌ها دسترسی داشته باشد.
  • برای دسترسی به SDK از ogmaSDK.ready(cb) استفاده کنید؛ پیش از اجرای تابع فراخوانی برگشتی، متدهای SDK را فراخوانی نکنید.

ساخت افزونه برای 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 در ریشهٔ آن قرار دارد.


نصب افزونه ​

  1. افزونه‌ها را در نوار کناری سمت چپ باز کنید.
  2. روی نصب کلیک کنید (بالای زبانهٔ نصب‌شده).
  3. در برنامهٔ دسکتاپ، روی مرور کلیک کنید تا انتخابگر بومی پوشه باز شود. در مرورگر، مسیر کامل پوشهٔ افزونه در سمت سرور را وارد کنید.
  4. روی اعتبارسنجی کلیک کنید تا مانیفست و فهرست فایل‌ها بررسی شوند.
  5. اگر اعتبارسنجی موفق بود، روی نصب کلیک کنید.
  6. افزونه را از فهرست انتخاب کنید و روی فعال‌سازی کلیک کنید.
  7. اگر افزونه مجوزهای محافظت‌شده اعلام می‌کند، پیش از فعال‌سازی آن‌ها را از زبانهٔ مجوزها بررسی و اعطا کنید.

رفع اشکال ​

راه‌اندازی افزونه بدون پیام خطا شکست می‌خورد: زبانهٔ گزارش رویدادها را بررسی کنید. رایج‌ترین دلایل:

  • sdk.meta.path() فراخوانی شده اما پوشهٔ داده ایجاد نشده است.
  • یک استثنای مدیریت‌نشده در init().
  • فراخوانی sdk.* وجود ندارد یا اشتباه نوشته شده است.

فرانت‌اند خالی نمایش داده می‌شود: کنسول مرورگر را برای نقض‌های CSP بررسی کنید. مطمئن شوید اسکریپت فرانت‌اند پیش از دسترسی به هر متد SDK، ‏ogmaSDK.ready(cb) را فراخوانی می‌کند.

sdk.requests.send خطای Permission denied می‌دهد: مجوز send_requests باید هم در مانیفست اعلام شود و هم توسط کاربر در زبانهٔ مجوزها اعطا شود.

توابع sdk.api.register از فرانت‌اند قابل فراخوانی نیستند: بک‌اند باید فعال باشد (نه صرفاً نصب‌شده). نام تابع باید دقیقاً با آنچه فرانت‌اند به sdk.backend.call می‌دهد مطابقت داشته باشد (حساس به بزرگی و کوچکی حروف).

هشدارهای سازگاری به‌صورت خطا نمایش داده می‌شوند: این هشدارها مانع اجرا نمی‌شوند، اما نشان‌دهندهٔ کمبود در پوشش API هستند. برای پوشش API به جدول‌های نگاشت SDK در بالا مراجعه کنید.

نرم‌افزار اختصاصی. تمامی حقوق محفوظ است.