feat(browser-ext): move browser extension to browser-ext/ and update API client to tRPC
- Create browser-ext/ with full extension code (MV3 manifest, background service worker, content script, popup, options page) - Add tRPC API client that communicates with unified monolith endpoints - Implement cache, settings, and phishing detection utilities - Create extension tRPC router in web app (getAuthStatus, linkDevice, reportPhishing) - Configure Vite build with manifest V3 support - Write unit tests for cache, phishing detector, and API client - All 20 tests passing, TypeScript lint clean
This commit is contained in:
@@ -10,6 +10,7 @@ import { removebrokersRouter } from "./routers/removebrokers";
|
||||
import { correlationRouter } from "./routers/correlation";
|
||||
import { reportsRouter } from "./routers/reports";
|
||||
import { schedulerRouter } from "./routers/scheduler";
|
||||
import { extensionRouter } from "./routers/extension";
|
||||
import { createTRPCRouter } from "./utils";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
@@ -25,6 +26,7 @@ export const appRouter = createTRPCRouter({
|
||||
correlation: correlationRouter,
|
||||
reports: reportsRouter,
|
||||
scheduler: schedulerRouter,
|
||||
extension: extensionRouter,
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
55
web/src/server/api/routers/extension.ts
Normal file
55
web/src/server/api/routers/extension.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { wrap } from "@typeschema/valibot";
|
||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
||||
import { GetAuthStatusSchema, LinkDeviceSchema, ReportPhishingSchema } from "../schemas/extension";
|
||||
import { db } from "~/server/db";
|
||||
import { deviceTokens } from "~/server/db/schema/auth";
|
||||
|
||||
export const extensionRouter = createTRPCRouter({
|
||||
getAuthStatus: publicProcedure.input(wrap(GetAuthStatusSchema)).query(async ({ ctx }) => {
|
||||
if (ctx.user) {
|
||||
return { linked: true, userId: ctx.user.id, email: ctx.user.email };
|
||||
}
|
||||
if (ctx.apiKey) {
|
||||
return { linked: true, apiKey: ctx.apiKey };
|
||||
}
|
||||
return { linked: false };
|
||||
}),
|
||||
|
||||
linkDevice: publicProcedure.input(wrap(LinkDeviceSchema)).mutation(async ({ ctx, input }) => {
|
||||
if (!ctx.user) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required to link device" });
|
||||
}
|
||||
|
||||
const existing = await db.query.deviceTokens.findFirst({
|
||||
where: eq(deviceTokens.token, input.extensionId),
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
await db
|
||||
.update(deviceTokens)
|
||||
.set({ lastUsedAt: new Date(), appName: input.deviceName ?? null })
|
||||
.where(eq(deviceTokens.id, existing.id));
|
||||
return { linked: true, deviceId: existing.id };
|
||||
}
|
||||
|
||||
const [newDevice] = await db
|
||||
.insert(deviceTokens)
|
||||
.values({
|
||||
userId: ctx.user.id,
|
||||
deviceType: "desktop",
|
||||
platform: "web",
|
||||
token: input.extensionId,
|
||||
appName: input.deviceName ?? "ShieldAI Browser Extension",
|
||||
})
|
||||
.returning();
|
||||
|
||||
return { linked: true, deviceId: newDevice.id };
|
||||
}),
|
||||
|
||||
reportPhishing: publicProcedure.input(wrap(ReportPhishingSchema)).mutation(async ({ input }) => {
|
||||
console.log(`[Phishing Report] URL: ${input.url}, Source: ${input.source ?? "unknown"}`);
|
||||
return { reported: true, url: input.url };
|
||||
}),
|
||||
});
|
||||
15
web/src/server/api/schemas/extension.ts
Normal file
15
web/src/server/api/schemas/extension.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { object, string, optional } from "valibot";
|
||||
|
||||
export const GetAuthStatusSchema = object({
|
||||
apiKey: optional(string()),
|
||||
});
|
||||
|
||||
export const LinkDeviceSchema = object({
|
||||
extensionId: string(),
|
||||
deviceName: optional(string()),
|
||||
});
|
||||
|
||||
export const ReportPhishingSchema = object({
|
||||
url: string(),
|
||||
source: optional(string()),
|
||||
});
|
||||
Reference in New Issue
Block a user