Skip to content

Commit

Permalink
Add context menu generation and alert content script
Browse files Browse the repository at this point in the history
Closes #9
  • Loading branch information
Curetix committed Feb 14, 2024
1 parent 7dd1503 commit 735e821
Show file tree
Hide file tree
Showing 12 changed files with 427 additions and 98 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
},
"manifest": {
"permissions": [
"activeTab"
"activeTab",
"contextMenus"
],
"host_permissions": [
"*://api.cloudflare.com/*"
Expand Down
116 changes: 116 additions & 0 deletions src/background/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { AliasSettings } from "~utils/state";

import { Storage } from "@plasmohq/storage";

import { detectLocale, i18n } from "~i18n/i18n-util";
import { loadLocale } from "~i18n/i18n-util.sync";
import { CloudflareApiClient } from "~lib/cloudflare/api";
import { Alias } from "~utils/alias";
import { detectBrowserLocale, sendTabMessage } from "~utils/background";
import { StorageKeys } from "~utils/state";

const storage = new Storage({
area: "local",
});

const locale = detectLocale(detectBrowserLocale);
loadLocale(locale);
const LL = i18n()[locale];

export async function generateAliasInBackground(hostname: string): Promise<Alias> {
const apiToken = await storage.get<string>(StorageKeys.ApiToken);
const zoneId = await storage.get<string>(StorageKeys.ZoneId);
const aliasSettings = await storage.get<AliasSettings>(StorageKeys.AliasSettings);

if (!apiToken) {
throw new Error(LL.BG_ERROR_NOT_LOGGED_IN());
}
if (!zoneId) {
throw new Error(LL.BG_ERROR_NO_DOMAIN());
}
if (!aliasSettings?.destination) {
throw new Error(LL.BG_ERROR_NO_DESTINATION());
}
if (aliasSettings.format === "custom" || aliasSettings.prefixFormat === "custom") {
throw new Error(LL.BG_ERROR_CUSTOM());
}

const apiClient = new CloudflareApiClient(apiToken);

const zones = await apiClient.getZones();
const zone = zones.success ? zones.result.find((z) => z.id === zoneId) : undefined;

if (!zone) {
throw new Error(LL.BG_ERROR_INVALID_DOMAIN());
}

const alias = Alias.fromOptions(
{
...aliasSettings,
hostname,
},
zone.name,
aliasSettings.destination,
hostname,
);

const result = await apiClient.createEmailRule(zoneId, alias.toEmailRule());

if (result.success) {
console.log("Created new alias in background:", alias.address);
return alias;
} else {
throw new Error(result.errors[0].message);
}
}

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
console.debug("Context menu click:", info, tab);

if (info.menuItemId !== "mailflare-generate") return;

const loadingAlertId = Date.now();
await sendTabMessage(tab?.id, {
command: "showAlert",
alert: {
id: loadingAlertId,
message: LL.BG_ALERT_LOADING(),
isLoading: true,
},
});

try {
const alias = await generateAliasInBackground(new URL(info.pageUrl).hostname);
await sendTabMessage(tab?.id, {
command: "showAlert",
alert: {
id: Date.now(),
type: "success",
message: LL.BG_ALERT_CREATED({ alias: alias.address }),
timeout: 5000,
},
});
await sendTabMessage(tab?.id, {
command: "copyText",
text: alias.toString(),
});
} catch (error: any) {
await sendTabMessage(tab?.id, {
command: "showAlert",
alert: { id: Date.now(), type: "error", message: error.message },
});
} finally {
await sendTabMessage(tab?.id, {
command: "dismissAlert",
id: loadingAlertId,
});
}
});

chrome.runtime.onInstalled.addListener(function () {
chrome.contextMenus.create({
title: LL.CONTEXT_MENU_ENTRY_TEXT(),
contexts: ["page", "editable"],
id: "mailflare-generate",
});
});
94 changes: 33 additions & 61 deletions src/background/messages/generate-alias.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import type { PlasmoMessaging } from "@plasmohq/messaging";
import type { AliasSettings } from "~utils/state";

import { Storage } from "@plasmohq/storage";

import { CloudflareApiClient } from "~lib/cloudflare/api";
import { Alias } from "~utils/alias";
import { StorageKeys } from "~utils/state";

const storage = new Storage({
area: "local",
});
import { generateAliasInBackground } from "~background";
import { detectLocale, i18n } from "~i18n/i18n-util";
import { loadLocale } from "~i18n/i18n-util.sync";
import { detectBrowserLocale, getCurrentTab, sendTabMessage } from "~utils/background";

type Request = {
hostname: string;
Expand All @@ -21,68 +15,46 @@ type Response = {
data?: any;
};

const handler: PlasmoMessaging.MessageHandler<Request, Response> = async (req, res) => {
const apiToken = await storage.get<string>(StorageKeys.ApiToken);
const zoneId = await storage.get<string>(StorageKeys.ZoneId);
const aliasSettings = await storage.get<AliasSettings>(StorageKeys.AliasSettings);
const locale = detectLocale(detectBrowserLocale);
loadLocale(locale);
const LL = i18n()[locale];

const handler: PlasmoMessaging.MessageHandler<Request, Response> = async (req, res) => {
console.log("Received request for background alias generation:", req.body);

if (!apiToken) {
return res.send({
success: false,
message: "Not logged in",
});
}
if (!zoneId) {
return res.send({
success: false,
message: "No domain selected",
const tab = await getCurrentTab();
try {
const alias = await generateAliasInBackground(req.body!.hostname);

await sendTabMessage(tab.id, {
command: "showAlert",
alert: {
id: Date.now(),
type: "success",
message: LL.BG_ALERT_CREATED({ alias: alias.address }),
timeout: 5000,
},
});
}
if (!aliasSettings?.destination) {

return res.send({
success: false,
message: "No destination selected",
success: true,
message: LL.BG_ALERT_CREATED({ alias: alias.address }),
data: alias.address,
});
}
if (aliasSettings.format === "custom" || aliasSettings.prefixFormat === "custom") {
return res.send({
success: false,
message: "Cannot generate an alias when the (prefix) format is set to Custom",
} catch (error: any) {
await sendTabMessage(tab.id, {
command: "showAlert",
alert: {
id: Date.now(),
type: "error",
message: error.message,
},
});
}

const apiClient = new CloudflareApiClient(apiToken);

const zones = await apiClient.getZones();
const zone = zones.success ? zones.result.find((z) => z.id === zoneId) : undefined;

if (!zone) {
return res.send({
success: false,
message: "Domain not found",
data: zones,
message: error.message,
});
}

const alias = Alias.fromOptions(
{
...aliasSettings,
hostname: req.body!.hostname,
},
zone.name,
aliasSettings.destination,
req.body!.hostname,
);

const result = await apiClient.createEmailRule(zoneId, alias.toEmailRule());

return res.send({
success: result.success,
message: result.success ? "Alias created" : "Error",
data: result.success ? alias.address : result.errors,
});
};

export default handler;
74 changes: 74 additions & 0 deletions src/contents/alerts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// noinspection JSUnusedGlobalSymbols

import type { ContentScriptAlert, TabMessage } from "~utils/background";
import type { PlasmoCSConfig, PlasmoGetStyle } from "plasmo";

import { IconX } from "@tabler/icons-react";
import { useEffect, useState } from "react";
import cssText from "data-text:./content-scripts.css";

export const getStyle: PlasmoGetStyle = () => {
const style = document.createElement("style");
style.textContent = cssText;
return style;
};

export const config: PlasmoCSConfig = {
matches: ["https://*/*"],
};

export default function Inline() {
const [alerts, setAlerts] = useState<ContentScriptAlert[]>([]);

const removeAlert = (id: number) => setAlerts((current) => current.filter((a) => a.id !== id));

useEffect(() => {
async function onMessageListener(msg: TabMessage) {
console.log("[MailFlare] Received tab message:", msg);
switch (msg.command) {
case "copyText":
await window.navigator.clipboard.writeText(msg.text);
break;
case "clearClipboard":
await window.navigator.clipboard.writeText("\u0000");
break;
case "showAlert":
setAlerts((current) => [msg.alert, ...current]);
if (msg.alert.timeout) {
setTimeout(() => removeAlert(msg.alert.id), msg.alert.timeout);
}
break;
case "dismissAlert":
removeAlert(msg.id);
break;
default:
}
}

chrome.runtime.onMessage.addListener(onMessageListener);

return () => chrome.runtime.onMessage.removeListener(onMessageListener);
}, []);

return (
<div data-theme="mantine">
<div className="toast toast-center">
{alerts.map((alert) => (
<div
key={alert.id}
className={`alert ${alert.type ? (`alert-${alert.type}` as const) : ""}`}>
{alert.isLoading && <span className="loading loading-spinner loading-sm"></span>}
<span>{alert.message}</span>
<div>
<button
className="btn btn-square btn-sm btn-ghost"
onClick={() => removeAlert(alert.id)}>
<IconX size={14} />
</button>
</div>
</div>
))}
</div>
</div>
);
}
File renamed without changes.
Loading

0 comments on commit 735e821

Please sign in to comment.