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:
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 };
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user