Skip to content

Commit d5a93d2

Browse files
committed
Issues: #51 and #53
1 parent 6e27f16 commit d5a93d2

File tree

4 files changed

+268
-9
lines changed

4 files changed

+268
-9
lines changed

.vitepress/sidebars/guides.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export const guidesSidebar: DefaultTheme.SidebarItem[] = [
3333
text: "Customizing Context Menus",
3434
link: "/guides/components/menu",
3535
},
36+
{
37+
text: "Storing Frontend Data",
38+
link: "/guides/components/frontend_storage",
39+
},
3640
{
3741
text: "Using the Component Library",
3842
link: "/guides/components/styling",

src/_images/ui_themes.png

7.03 KB
Loading

src/guides/components/findings.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,30 @@ export function init(sdk: SDK<API>) {
5353
try {
5454
// Only create Findings for 200 responses.
5555
if (response.getCode() === 200) {
56-
await sdk.findings.create({
57-
title: `Success Response ${response.getCode()}`,
58-
description: `Request ID: ${request.getId()}\nResponse Code: ${response.getCode()}`,
59-
reporter: "Response Logger Plugin",
60-
request: request,
61-
dedupeKey: `${request.getPath()}-${response.getCode()}`
62-
});
56+
const dedupeKey = `${request.getPath()}-${response.getCode()}`;
57+
58+
// Check if Finding already exists.
59+
const exists = await sdk.findings.exists(dedupeKey);
60+
if (!exists) {
61+
await sdk.findings.create({
62+
title: `Success Response ${response.getCode()}`,
63+
description: `Request ID: ${request.getId()}\nResponse Code: ${response.getCode()}`,
64+
reporter: "Response Logger Plugin",
65+
request: request,
66+
dedupeKey
67+
});
6368

64-
sdk.console.log(`Created finding for successful request ${request.getId()}`);
69+
// Verify the Finding was created.
70+
const created = await sdk.findings.exists(dedupeKey);
71+
if (created) {
72+
sdk.console.log(`Created and verified finding for request ${request.getId()}.`);
73+
}
74+
} else {
75+
sdk.console.log(`Finding already exists for ${dedupeKey}.`);
76+
}
6577
}
6678
} catch (err) {
67-
sdk.console.error(`Error creating finding: ${err}`);
79+
sdk.console.error(`Error handling finding: ${err}`);
6880
}
6981
});
7082
}
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Storing Frontend Data
2+
3+
By default, the frontend component of a plugin is stateless, meaning data will be lost between Caido sessions. However, if your plugin needs to persist data in the frontend you can utilize the storage system through `sdk.storage`.
4+
5+
The storage system is defined by the [StorageSDK](/reference/sdks/frontend/#sdk) interface and provides these methods:
6+
7+
- `set()`: Puts new data into the storage.
8+
- `get()`: Fetches the current data from storage.
9+
- `onChange()`: Sets up a listener that runs when the storage changes.
10+
11+
## User Preferences
12+
13+
To demonstrate its usage, let's create a frontend interface that offers light and dark theme options by clicking on the associated buttons.
14+
15+
<img alt="Light and dark themes." src="/_images/ui_themes.png" center/>
16+
17+
::: tip
18+
To view the entire frontend script, expand the following:
19+
20+
<details>
21+
<summary>Full Script</summary>
22+
23+
``` ts
24+
import "./styles/index.css";
25+
26+
import type { FrontendSDK } from "./types";
27+
28+
export const init = async (sdk: FrontendSDK) => {
29+
const root = document.createElement("div");
30+
root.style.height = "100%";
31+
root.style.width = "100%";
32+
root.id = `plugin--frontend-vanilla`;
33+
34+
const container = document.createElement("div");
35+
36+
const lightButton = sdk.ui.button({
37+
variant: "primary",
38+
label: "Light",
39+
});
40+
41+
const darkButton = sdk.ui.button({
42+
variant: "primary",
43+
label: "Dark",
44+
});
45+
46+
// Function to apply theme.
47+
const applyTheme = (theme: "light" | "dark") => {
48+
if (theme === "dark") {
49+
root.style.backgroundColor = "#202227";
50+
} else {
51+
root.style.backgroundColor = "#D2D3D5";
52+
}
53+
};
54+
55+
// Add event listeners.
56+
lightButton.addEventListener("click", () => {
57+
applyTheme("light");
58+
});
59+
60+
darkButton.addEventListener("click", () => {
61+
applyTheme("dark");
62+
});
63+
64+
// Assemble UI.
65+
container.appendChild(lightButton);
66+
container.appendChild(darkButton);
67+
root.appendChild(container);
68+
69+
sdk.navigation.addPage("/frontend-storage-demo", {
70+
body: root,
71+
});
72+
73+
sdk.sidebar.registerItem("Frontend Storage Demo", "/frontend-storage-demo");
74+
};
75+
```
76+
77+
</details>
78+
:::
79+
80+
To persist the theme selection between Caido application closes and launches, we can add the following code to the file:
81+
82+
To save the theme choice to storage so it can be recalled, we can use `sdk.storage.set` on the `theme`. To account for the time it takes to write the data to storage, this `updateTheme` function will need to be `async`.
83+
84+
``` ts
85+
const updateTheme = async (theme: "light" | "dark") => {
86+
await sdk.storage.set({ theme });
87+
};
88+
```
89+
90+
Next, we use `sdk.storage.get` to check if a theme has been saved before and apply it to when the page loads.
91+
92+
``` ts
93+
const loadSettings = () => {
94+
const settings = sdk.storage.get() as { theme: "light" | "dark" } | null;
95+
if (settings?.theme) {
96+
applyTheme(settings.theme);
97+
}
98+
};
99+
```
100+
101+
We will also need to add the `updateTheme` function to the buttons. Now, clicking a button will both save the theme choice and change the color of the interface.
102+
103+
``` ts
104+
lightButton.addEventListener("click", () => {
105+
updateTheme("light");
106+
applyTheme("light");
107+
});
108+
109+
darkButton.addEventListener("click", () => {
110+
updateTheme("dark");
111+
applyTheme("dark");
112+
});
113+
```
114+
115+
By subscribing to any changes with `onChange`, we can add reactivity using the reference to disable the button of the currently selected theme.
116+
117+
``` ts
118+
sdk.storage.onChange((newSettings) => {
119+
const settings = newSettings as { theme: "light" | "dark" } | null;
120+
if (settings?.theme) {
121+
applyTheme(settings.theme);
122+
if (settings.theme === "light") {
123+
lightButton.setAttribute("disabled", "true");
124+
darkButton.removeAttribute("disabled");
125+
} else {
126+
lightButton.removeAttribute("disabled");
127+
darkButton.setAttribute("disabled", "true");
128+
}
129+
}
130+
});
131+
```
132+
133+
To load saved settings when the page reloads, we call the `loadSettings` function defined earlier.
134+
135+
``` ts
136+
loadSettings();
137+
```
138+
139+
::: tip
140+
To view the entire modified frontend script, expand the following:
141+
142+
<details>
143+
<summary>Full Script</summary>
144+
145+
``` ts
146+
import "./styles/index.css";
147+
148+
import type { FrontendSDK } from "./types";
149+
150+
export const init = async (sdk: FrontendSDK) => {
151+
const root = document.createElement("div");
152+
root.style.height = "100%";
153+
root.style.width = "100%";
154+
root.id = `plugin--frontend-vanilla`;
155+
156+
const container = document.createElement("div");
157+
158+
const lightButton = sdk.ui.button({
159+
variant: "primary",
160+
label: "Light",
161+
});
162+
163+
const darkButton = sdk.ui.button({
164+
variant: "primary",
165+
label: "Dark",
166+
});
167+
168+
169+
// Function to update theme.
170+
const updateTheme = async (theme: "light" | "dark") => {
171+
await sdk.storage.set({ theme });
172+
};
173+
174+
// Function to apply theme.
175+
const applyTheme = (theme: "light" | "dark") => {
176+
if (theme === "dark") {
177+
root.style.backgroundColor = "#202227";
178+
} else {
179+
root.style.backgroundColor = "#D2D3D5";
180+
}
181+
};
182+
183+
// Function to load settings.
184+
const loadSettings = () => {
185+
const settings = sdk.storage.get() as { theme: "light" | "dark" } | null;
186+
if (settings?.theme) {
187+
applyTheme(settings.theme);
188+
}
189+
};
190+
191+
// Add event listeners.
192+
lightButton.addEventListener("click", () => {
193+
updateTheme("light");
194+
applyTheme("light");
195+
});
196+
197+
darkButton.addEventListener("click", () => {
198+
updateTheme("dark");
199+
applyTheme("dark");
200+
});
201+
202+
// Subscribe to storage changes.
203+
sdk.storage.onChange((newSettings) => {
204+
const settings = newSettings as { theme: "light" | "dark" } | null;
205+
if (settings?.theme) {
206+
applyTheme(settings.theme);
207+
// Update button states based on current theme
208+
if (settings.theme === "light") {
209+
lightButton.setAttribute("disabled", "true");
210+
darkButton.removeAttribute("disabled");
211+
} else {
212+
lightButton.removeAttribute("disabled");
213+
darkButton.setAttribute("disabled", "true");
214+
}
215+
}
216+
});
217+
218+
// Assemble UI.
219+
container.appendChild(lightButton);
220+
container.appendChild(darkButton);
221+
222+
// Load saved settings on init.
223+
loadSettings();
224+
225+
root.appendChild(container);
226+
227+
sdk.navigation.addPage("/frontend-storage-demo", {
228+
body: root,
229+
});
230+
231+
sdk.sidebar.registerItem("Frontend Storage Demo", "/frontend-storage-demo");
232+
};
233+
```
234+
235+
</details>
236+
:::
237+
238+
::: info
239+
240+
- Although frontend storage actually exists in the backend, it is inaccessible by the backend component. To share data with the backend component of a plugin, you will need to [create and call a custom function](/guides/components/rpc.md).
241+
242+
- Stored data needs to be JSON serializable.
243+
:::

0 commit comments

Comments
 (0)