Sending Events from the Backend to Frontend
In this guide, you will learn how to facilitate event-driven communication between the backend and the frontend components of a plugin.
This can be accomplished using the onInterceptResponse()
method provided by the Caido SDK.
The event can then be subscribed to on the frontend using sdk.backend.onEvent()
.
Registering an API Endpoint and Event
To demonstrate, we will create a plugin that emits an event containing response data to the frontend.
/packages/backend/src/index.ts
import { SDK, DefineAPI, DefineEvents } from "caido:plugin";
type RequestEvent = {
id: string;
status: number;
url: string;
};
export type BackendEvents = DefineEvents<{
"request-completed": (data: RequestEvent) => void;
}>;
export type BackendAPI = DefineAPI<{}>;
export function init(sdk: SDK<BackendAPI, BackendEvents>) {
sdk.events.onInterceptResponse((sdk, request, response) => {
sdk.api.send("request-completed", {
id: response.getId(),
status: response.getCode(),
url: `${request.getHost()}:${request.getPort()}${request.getPath()}${request.getQuery()}`
});
});
}
Script Breakdown
First, import the required dependencies. SDK
is the interface used to interact with Caido. DefineAPI
is used to structure the API: definining what methods or endpoints are available, the parameters those methods accept and what types of values they return. DefineEvents
is used to structure events that can be sent between the backend and frontend.
import { SDK, DefineAPI, DefineEvents } from "caido:plugin";
Next, create a type alias that defines the data and data type of the event message that will be sent to the frontend.
type RequestEvent = {
id: string;
status: number;
url: string;
};
Using the DefineEvents
utility, we state the event named request-completed
will carry a data
object using the RequestEvent
type declarations. This is made available by exporting BackendEvents
.
export type BackendEvents = DefineEvents<{
"request-completed": (data: RequestEvent) => void;
}>;
Next, we define an empty API type using DefineAPI
to satisy the SDK
parameter requirements.
TIP
To learn how to create custom API endpoints, click here.
export type BackendAPI = DefineAPI<{}>;
The init
function includes the BackendAPI
and BackendEvents
as SDK
types, which tells the SDK what API calls and events are available to use. When a response to a request is received with onInterceptResponse()
, the callback function will use various get
methods available to request and response objects to extract the data. Once the data is collected, the event is sent to the frontend using the sdk.api.send()
method.
export function init(sdk: SDK<BackendAPI, BackendEvents>) {
sdk.events.onInterceptResponse((sdk, request, response) => {
sdk.api.send("request-completed", {
id: response.getId(),
status: response.getCode(),
url: `${request.getHost()}:${request.getPort()}${request.getPath()}${request.getQuery()}`
});
});
}
Receiving the Event
Now that we've created our endpoint in the backend plugin, we can call sendRequest
which will execute the function and emit the request-completed
event with the response data.
The API and events are made available to the frontend script by extending the Caido
inferface with the imports.
export type CaidoSDK = Caido<BackendAPI, BackendEvents>;
The event is subscribed to using the sdk.backend.onEvent()
method that listens for the request-completed
event and uses its data
to print to a list.
sdk.backend.onEvent("request-completed", (data) => {
const listItem = document.createElement("li");
listItem.textContent = `Request to ${data.url} completed with status ${data.status} (ID: ${data.id})`;
resultsList.insertBefore(listItem, resultsList.firstChild);
});
/frontend/src/index.ts
TIP
To view the entire frontend script, including the UI - expand the following:
Example
import type { Caido } from "@caido/sdk-frontend";
import type { BackendAPI, BackendEvents } from "../../backend/src";
import "./styles/index.css";
export type CaidoSDK = Caido<BackendAPI, BackendEvents>;
const createPage = (sdk: CaidoSDK) => {
const resultsList = document.createElement("ul");
resultsList.style.listStyle = "none";
resultsList.style.padding = "1rem";
// Subscribe to backend events.
sdk.backend.onEvent("request-completed", (data) => {
const listItem = document.createElement("li");
listItem.textContent = `Request to ${data.url} completed with status ${data.status} (ID: ${data.id})`;
resultsList.insertBefore(listItem, resultsList.firstChild);
});
// Create card.
const card = sdk.ui.card({
body: resultsList,
});
// Add page.
sdk.navigation.addPage("/request-monitor", {
body: card,
});
};
export const init = (sdk: CaidoSDK) => {
createPage(sdk);
sdk.sidebar.registerItem("Request Monitor", "/request-monitor", {
icon: "fas fa-globe",
});
};