Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 112 additions & 16 deletions entrypoints/background.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { getHostnameFromUrl, normalizeDomain } from "../src/utils/domain";
import { detectCategory } from "../src/utils/categoryDetector";
import {
buildPlusAlias,
generateAliasSuggestions,
} from "../src/utils/aliasGenerator";
import { loadAliasData, touchAlias } from "../src/utils/storage";

export default defineBackground(() => {
console.log("Gmail Alias Toolkit background started");

Expand All @@ -22,7 +30,7 @@ export default defineBackground(() => {
(key) =>
key.startsWith("gmail_alias_recent_") ||
key.startsWith("alias_stats_") ||
key === "email_accounts"
key === "email_accounts",
);
if (shouldUpdateBadge) {
await updateBadge();
Expand All @@ -38,6 +46,34 @@ export default defineBackground(() => {
contexts: ["editable"],
});

browser.contextMenus.create({
id: "insert-suggested-alias",
parentId: "gmail-alias-parent",
title: "Insert suggested alias",
contexts: ["editable"],
});

browser.contextMenus.create({
id: "copy-suggested-alias",
parentId: "gmail-alias-parent",
title: "Copy suggested alias",
contexts: ["editable"],
});

browser.contextMenus.create({
id: "use-previous-alias",
parentId: "gmail-alias-parent",
title: "Use previous alias for this site",
contexts: ["editable"],
});

browser.contextMenus.create({
id: "generate-random-alias",
parentId: "gmail-alias-parent",
title: "Generate random alias",
contexts: ["editable"],
});

// Random email submenu
browser.contextMenus.create({
id: "fill-random-email",
Expand Down Expand Up @@ -122,7 +158,7 @@ export default defineBackground(() => {

if (result.email_accounts && Array.isArray(result.email_accounts)) {
const activeAccount = result.email_accounts.find(
(acc: any) => acc.isActive
(acc: any) => acc.isActive,
);
if (activeAccount) {
baseEmail = activeAccount.email;
Expand All @@ -133,8 +169,20 @@ export default defineBackground(() => {

const [username, domain] = baseEmail.split("@");
let emailToFill = "";
const siteAliasMenuIds = new Set([
"insert-suggested-alias",
"copy-suggested-alias",
"use-previous-alias",
"generate-random-alias",
]);

if (info.menuItemId === "fill-random-email") {
if (siteAliasMenuIds.has(String(info.menuItemId))) {
emailToFill = await resolveWebsiteAlias(
String(info.menuItemId),
tab.url,
baseEmail,
);
} else if (info.menuItemId === "fill-random-email") {
// Generate random email
const format = result.app_settings?.randomFormat || "private-mail";
let randomTag = "";
Expand All @@ -144,14 +192,14 @@ export default defineBackground(() => {
const chars = "abcdefghijklmnopqrstuvwxyz";
randomTag = Array.from(
{ length: 8 },
() => chars[Math.floor(Math.random() * chars.length)]
() => chars[Math.floor(Math.random() * chars.length)],
).join("");
break;
case "alphanumeric":
const alphanum = "abcdefghijklmnopqrstuvwxyz0123456789";
randomTag = Array.from(
{ length: 10 },
() => alphanum[Math.floor(Math.random() * alphanum.length)]
() => alphanum[Math.floor(Math.random() * alphanum.length)],
).join("");
break;
case "words":
Expand Down Expand Up @@ -199,14 +247,62 @@ export default defineBackground(() => {
// Save to history and statistics
await saveToHistory(emailToFill, result.app_settings?.maxHistory || 20);

// Send message to content script to fill the input
browser.tabs.sendMessage(tab.id, {
action: "fillEmail",
email: emailToFill,
});
if (info.menuItemId === "copy-suggested-alias") {
await navigator.clipboard.writeText(emailToFill).catch(() => undefined);
} else {
// Send message to content script to fill the input
const response = await browser.tabs
.sendMessage(tab.id, {
action: "autofillAlias",
email: emailToFill,
})
.catch(() => ({ ok: false }));
if (!response?.ok)
await navigator.clipboard
.writeText(emailToFill)
.catch(() => undefined);
}

const hostname = tab.url ? getHostnameFromUrl(tab.url) : null;
if (hostname) await touchAlias(hostname);
}
});

async function resolveWebsiteAlias(
menuItemId: string,
tabUrl: string | undefined,
baseEmail: string,
): Promise<string> {
const hostname = tabUrl ? getHostnameFromUrl(tabUrl) : null;
const existingAliases = await loadAliasData().catch(() => null);

if (
hostname &&
existingAliases?.siteAliases[hostname] &&
menuItemId !== "generate-random-alias"
) {
return existingAliases.siteAliases[hostname].alias;
}

if (!hostname) return "";

const keyword = normalizeDomain(hostname);
if (menuItemId === "generate-random-alias") {
return buildPlusAlias(
baseEmail,
`${keyword}-${Math.random().toString(36).slice(2, 6)}`,
);
}

return (
generateAliasSuggestions({
baseEmail,
domainKeyword: keyword,
category: detectCategory(keyword, hostname),
})[0]?.alias || ""
);
}

// Helper function to update badge
async function updateBadge() {
try {
Expand All @@ -232,7 +328,7 @@ export default defineBackground(() => {
Array.isArray(accountResult.email_accounts)
) {
const activeAccount = accountResult.email_accounts.find(
(acc: any) => acc.isActive
(acc: any) => acc.isActive,
);
if (activeAccount) {
activeEmail = activeAccount.email;
Expand All @@ -244,7 +340,7 @@ export default defineBackground(() => {
// Get history for active account
const historyKey = getAccountStorageKey(
activeEmail,
"gmail_alias_recent"
"gmail_alias_recent",
);
const statsKey = getAccountStorageKey(activeEmail, "alias_stats");
const result = await browser.storage.local.get([historyKey, statsKey]);
Expand All @@ -265,16 +361,16 @@ export default defineBackground(() => {
const today = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate()
now.getDate(),
).getTime();
count = recentAliases.filter((a: any) => a.timestamp >= today).length;
break;
case "week":
const weekAgo = new Date(
now.getTime() - 7 * 24 * 60 * 60 * 1000
now.getTime() - 7 * 24 * 60 * 60 * 1000,
).getTime();
count = recentAliases.filter(
(a: any) => a.timestamp >= weekAgo
(a: any) => a.timestamp >= weekAgo,
).length;
break;
}
Expand Down Expand Up @@ -312,7 +408,7 @@ export default defineBackground(() => {
Array.isArray(accountResult.email_accounts)
) {
const activeAccount = accountResult.email_accounts.find(
(acc: any) => acc.isActive
(acc: any) => acc.isActive,
);
if (activeAccount) {
activeEmail = activeAccount.email;
Expand Down
49 changes: 17 additions & 32 deletions entrypoints/content.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,30 @@
import { autofillEmail } from "../src/utils/autofill";

export default defineContentScript({
matches: ["<all_urls>"],
main() {
// Listen for messages from background script
// Listen for messages from background script and popup.
browser.runtime.onMessage.addListener((message) => {
if (message.action === "fillEmail" && message.email) {
// Get the active element (the input field that was right-clicked)
if (
(message.action === "fillEmail" ||
message.action === "autofillAlias") &&
message.email
) {
const ok = autofillEmail(message.email);
const activeElement = document.activeElement;

if (
activeElement &&
(activeElement.tagName === "INPUT" ||
activeElement.tagName === "TEXTAREA" ||
activeElement.isContentEditable)
) {
if (
activeElement instanceof HTMLInputElement ||
activeElement instanceof HTMLTextAreaElement
) {
// Fill input or textarea
activeElement.value = message.email;

// Trigger input event for frameworks like React/Vue
activeElement.dispatchEvent(new Event("input", { bubbles: true }));
activeElement.dispatchEvent(new Event("change", { bubbles: true }));
} else if (activeElement.isContentEditable) {
// Fill contentEditable element
activeElement.textContent = message.email;

// Trigger input event
activeElement.dispatchEvent(new Event("input", { bubbles: true }));
}

// Flash effect to show it was filled
const originalBg = (activeElement as HTMLElement).style
.backgroundColor;
(activeElement as HTMLElement).style.backgroundColor = "#d1fae5";
if (ok && activeElement instanceof HTMLElement) {
const originalBg = activeElement.style.backgroundColor;
activeElement.style.backgroundColor = "#d1fae5";
setTimeout(() => {
(activeElement as HTMLElement).style.backgroundColor = originalBg;
activeElement.style.backgroundColor = originalBg;
}, 500);
}

return Promise.resolve({ ok });
}

return undefined;
});
},
});
18 changes: 18 additions & 0 deletions entrypoints/popup/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,21 @@
.animate-fade-in {
animation: fade-in 0.2s ease-out;
}

:root {
--alias-bg: #ffffff;
--alias-text: #111827;
--alias-muted: #6b7280;
--alias-border: #e5e7eb;
--alias-card: #f9fafb;
--alias-primary: #2563eb;
}

[data-theme="dark"] {
--alias-bg: #111827;
--alias-text: #f9fafb;
--alias-muted: #9ca3af;
--alias-border: #374151;
--alias-card: #1f2937;
--alias-primary: #60a5fa;
}
Loading