ระบบปลั๊กอิน Ogma
ปลั๊กอิน Ogma ขยายความสามารถของเครื่องมือด้วยลอจิกแบ็กเอนด์ที่กำหนดเอง แผง UI ฟรอนต์เอนด์ และขั้นตอนเวิร์กโฟลว์ ปลั๊กอินติดตั้งภายในเครื่องจากไดเรกทอรีบนดิสก์ เปิดใช้งานแยกตามโครงการ และทำงานในสภาพแวดล้อมแซนด์บ็อกซ์
เอกสารนี้เป็นข้อมูลอ้างอิงหลักสำหรับผู้พัฒนาปลั๊กอิน
หากต้องการเริ่มต้นให้เร็วที่สุด ให้ใช้ คู่มือเริ่มต้นใช้งานปลั๊กอินอย่างรวดเร็ว
เริ่มต้นอย่างรวดเร็ว
ปลั๊กอินแบ็กเอนด์แบบเรียบง่าย
my-plugin/
manifest.json
backend/script.jsmanifest.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; ใช้คำสั่ง import ได้เมื่อใช้เครื่องมือรวมบันเดิล):
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 | ไม่ | สตริง | ชื่อที่แสดงใน UI ค่าเริ่มต้นคือ 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 pluginsdk.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 แยกขอบเขตตาม ID ปลั๊กอิน และเก็บข้อมูลไว้แม้ปลั๊กอินเริ่มทำงานใหม่
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.sepsdk.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: Responsesdk.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() // > DateBody:
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" });ลงทะเบียนรายการแถบด้านข้าง ปัจจุบันมีผลเฉพาะในแผง UI ของปลั๊กอิน การเชื่อมเข้ากับตำแหน่งแถบด้านข้างส่วนกลางยังอยู่ระหว่างดำเนินการ
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_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 |
สิทธิ์ที่มีการป้องกัน (ต้องได้รับการอนุมัติจากผู้ใช้)
ต้องประกาศสิทธิ์เหล่านี้ในมานิเฟสต์ และผู้ใช้ต้องอนุมัติอย่างชัดเจนจากแท็บสิทธิ์:
| สิทธิ์ | สิ่งที่อนุญาตให้ทำ |
|---|---|
send_requests | sdk.requests.send - ส่งคำขอ HTTP ขาออก |
write_findings | sdk.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 - import แบบคงที่ที่รองรับจะถูกแก้ไขการอ้างอิงระหว่างการประมวลผลปลั๊กอินล่วงหน้า:
@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 อยู่ที่ราก
การติดตั้งปลั๊กอิน
- เปิด ปลั๊กอิน ในแถบด้านข้างซ้าย
- คลิก ติดตั้ง (ด้านบนของแท็บ ติดตั้งแล้ว)
- ในแอปเดสก์ท็อป คลิก เรียกดู เพื่อเปิดตัวเลือกโฟลเดอร์ของระบบ ในเบราว์เซอร์ ให้พิมพ์พาธเต็มฝั่งเซิร์ฟเวอร์ของไดเรกทอรีปลั๊กอิน
- คลิก ตรวจสอบ เพื่อตรวจสอบมานิเฟสต์และรายการไฟล์
- คลิก ติดตั้ง หากการตรวจสอบผ่าน
- เลือกปลั๊กอินในรายการแล้วคลิก เปิดใช้งาน
- หากปลั๊กอินประกาศสิทธิ์ที่มีการป้องกัน ให้ตรวจสอบและอนุมัติจากแท็บ สิทธิ์ ก่อนเปิดใช้งาน
การแก้ไขปัญหา
ปลั๊กอินเริ่มทำงานไม่สำเร็จโดยไม่มีข้อความผิดพลาด: ตรวจสอบแท็บ บันทึก สาเหตุที่พบบ่อยที่สุด:
- เรียก
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