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; বান্ডলার ব্যবহার করলে ইমপোর্ট করা যায়):
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 | হ্যাঁ | স্ট্রিং | Semver: 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 | না | প্লাগইন iframe-এ লোড করা CSS ফাইল। |
assets | না | /plugins/{id}/assets/-এর অধীনে পরিবেশিত স্ট্যাটিক অ্যাসেটের ডিরেক্টরি। |
backend.id | না | sdk.backend.* RPC-এর জন্য ফ্রন্টএন্ড উপাদানকে ব্যাকএন্ড উপাদানে যুক্ত করে। |
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-গুলো প্লাগইন রানটাইমে সিঙ্ক্রোনাসভাবে চলে; সম্পূর্ণ Node.js fs মডিউল নয়। ফাইল অ্যাক্সেসে 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
ফ্রন্টএন্ড sdk.backend.* দিয়ে কল করতে পারে এমন ব্যাকএন্ড RPC ফাংশন নিবন্ধন করুন:
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 দিয়ে সারিতে poll করে।
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)
ফ্রন্টএন্ড প্লাগইনের কোড /plugins/{id}/ui থেকে লোড করা স্যান্ডবক্স iframe-এ চলে। iframe postMessage দিয়ে Ogma হোস্টে যোগাযোগ করে, যা ব্যাকএন্ডে কল পাঠায়।
window.ogmaSDK দিয়ে SDK পাওয়া যায়। হোস্ট ব্রিজ স্থাপিত হলে লাইভ SDK পেতে ogmaSDK.ready(cb) কল করুন:
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 সেকেন্ডের polling চক্র ব্যবহার করে। ফেরত দেওয়া unsubscribe ফাংশন কল করে শোনা থামান:
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)ফাংশন এক্সপোর্ট করতে হবে (বা গ্লোবাল হিসেবে সংজ্ঞায়িত করতে হবে)।- QuickJS সমর্থিত ES2020 উপসেট:
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-এ poll করতে পারে। - CSP:
default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'. - iframe স্যান্ডবক্সে
allow-same-originনেই; প্লাগইন Ogma-র মূল DOM বা কুকিতে অ্যাক্সেস করতে পারে না। - 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/frontendCaido ডেভেলপমেন্ট টুলচেইন (@caido-community/dev) ব্যবহার করলে caido-dev build চালিয়ে আউটপুট Ogma-সামঞ্জস্যপূর্ণ বিন্যাসে কপি করুন, মূলে manifest.json রেখে।
প্লাগইন ইনস্টল
- বাম সাইডবারে প্লাগইন খুলুন।
- ইনস্টল করুন চাপুন (ইনস্টল করা ট্যাবের ওপরের অংশে)।
- ডেস্কটপ অ্যাপে নেটিভ ফোল্ডার নির্বাচন খুলতে বেছে নিন চাপুন। ব্রাউজারে প্লাগইন ডিরেক্টরির সম্পূর্ণ সার্ভার-পক্ষের পাথ লিখুন।
- ম্যানিফেস্ট ও ফাইলের তালিকা যাচাই করতে বৈধতা যাচাই করুন চাপুন।
- যাচাই পাস করলে ইনস্টল করুন চাপুন।
- তালিকায় প্লাগইন নির্বাচন করে চালু করুন চাপুন।
- সুরক্ষিত অনুমতি ঘোষণা করা থাকলে চালুর আগে অনুমতি ট্যাব থেকে পর্যালোচনা করে দিন।
সমস্যার সমাধান
প্লাগইনের init নীরবে ব্যর্থ: লগ ট্যাব দেখুন। সবচেয়ে সাধারণ কারণ:
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 সারণি দেখুন।