דילוג לתוכן

מערכת התוספים של 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 משתמש ב-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 ​

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' מותר כדי שהתוסף יוכל לשלוח 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 ​

מכיוון שצד השרת חייב להיות קובץ 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. ודאו שסקריפט צד הלקוח קורא ל-ogmaSDK.ready(cb) לפני גישה למתודת SDK כלשהי.

sdk.requests.send זורקת Permission denied: יש להצהיר על הרשאת send_requests במניפסט וגם לקבל את הענקתה בידי המשתמש בלשונית הרשאות.

לא ניתן לקרוא לפונקציות sdk.api.register מצד הלקוח: צד השרת חייב להיות מופעל (לא רק מותקן). שם הפונקציה חייב להתאים בדיוק (רגיש לאותיות גדולות וקטנות) למה שצד הלקוח מעביר אל sdk.backend.call.

אזהרות תאימות מוצגות כשגיאות: הן אינן חוסמות פעולה, אך מצביעות על פערים בכיסוי ה-API. ראו את טבלאות המיפוי של ה-SDK לעיל למידע על כיסוי ה-API.

תוכנה קניינית. כל הזכויות שמורות.