From fd0db4f564d9c93a199de1b79e8efd89ab19cc75 Mon Sep 17 00:00:00 2001 From: HUNG NGO Date: Thu, 25 Dec 2025 23:26:08 -0500 Subject: [PATCH 1/6] done --- .../src/components/root/ContentScript.tsx | 2 + extension/src/constants/index.ts | 7 ++++ extension/src/hooks/useDevAutoJoin.ts | 40 ++++++++++++++++++ .../services/controllers/RoomController.ts | 7 +++- .../services/controllers/WebRtcController.ts | 8 +++- extension/src/services/db/firebase/index.ts | 17 +++++--- extension/src/services/index.ts | 42 ++++++------------- extension/src/store/roomStore.ts | 22 ++++++---- extension/src/types/db.ts | 3 +- extension/src/types/index.ts | 4 ++ extension/src/utils/index.ts | 2 + extension/src/utils/localstorage.ts | 29 +++++++++++++ extension/src/utils/sessionstorage.ts | 22 ++++++++++ 13 files changed, 157 insertions(+), 48 deletions(-) create mode 100644 extension/src/hooks/useDevAutoJoin.ts create mode 100644 extension/src/utils/localstorage.ts create mode 100644 extension/src/utils/sessionstorage.ts diff --git a/extension/src/components/root/ContentScript.tsx b/extension/src/components/root/ContentScript.tsx index 6ca71940..e3295336 100644 --- a/extension/src/components/root/ContentScript.tsx +++ b/extension/src/components/root/ContentScript.tsx @@ -7,6 +7,7 @@ import { ResizablePanel } from "@cb/components/panel/ResizablePanel"; import SignInPanel from "@cb/components/panel/SignInPanel"; import { useAuthenticate } from "@cb/hooks/auth"; import { useToast } from "@cb/hooks/toasts"; +import { useDevAutoJoin } from "@cb/hooks/useDevAutoJoin"; import { AppStatus, useApp } from "@cb/store"; import React from "react"; import { Toaster } from "sonner"; @@ -16,6 +17,7 @@ export const ContentScript = () => { useAuthenticate({}); useToast(); + useDevAutoJoin(); const root = React.useMemo(() => { switch (auth.status) { diff --git a/extension/src/constants/index.ts b/extension/src/constants/index.ts index 19244691..2b9e48c2 100644 --- a/extension/src/constants/index.ts +++ b/extension/src/constants/index.ts @@ -112,3 +112,10 @@ export const PANEL = { DEFAULT_WIDTH: 350, // px COLLAPSED_WIDTH: 40, // px }; + +export const DEV_ROOM = { + ID: "dev-room-codebuddy", + NAME: "Dev Room", +}; + +export const STORAGE_PREFIX = "codebuddy"; diff --git a/extension/src/hooks/useDevAutoJoin.ts b/extension/src/hooks/useDevAutoJoin.ts new file mode 100644 index 00000000..0c93af4f --- /dev/null +++ b/extension/src/hooks/useDevAutoJoin.ts @@ -0,0 +1,40 @@ +import { DEV_ROOM } from "@cb/constants"; +import db from "@cb/services/db"; +import { AppStatus, RoomStatus, useApp, useRoom } from "@cb/store"; +import { getSessionStorage, setSessionStorage } from "@cb/utils"; +import { useEffect } from "react"; + +export const useDevAutoJoin = () => { + const authStatus = useApp((s) => s.auth.status); + const roomStatus = useRoom((s) => s.status); + + useEffect(() => { + const hasAttempted = getSessionStorage("devAutoJoinAttempted") === true; + + if ( + !import.meta.env.DEV || + hasAttempted || + authStatus !== AppStatus.AUTHENTICATED || + roomStatus !== RoomStatus.HOME + ) + return; + + setSessionStorage("devAutoJoinAttempted", true); + + const autoJoin = async () => { + const roomActions = useRoom.getState().actions.room; + + const existingRoom = await db.room.get(DEV_ROOM.ID); + + if (existingRoom) { + await roomActions.join(DEV_ROOM.ID); + } else { + await roomActions.create( + { name: DEV_ROOM.NAME, isPublic: false }, + DEV_ROOM.ID + ); + } + }; + autoJoin(); + }, [authStatus, roomStatus]); +}; diff --git a/extension/src/services/controllers/RoomController.ts b/extension/src/services/controllers/RoomController.ts index 64e1486c..45cc5a52 100644 --- a/extension/src/services/controllers/RoomController.ts +++ b/extension/src/services/controllers/RoomController.ts @@ -271,12 +271,15 @@ export class RoomController { this.appStore = appStore; } - public async create(room: Pick) { + public async create( + room: Pick, + id?: Id + ) { if (this.room != null) { return this.room; } const { username: me } = this.appStore.getState().actions.getAuthUser(); - const doc = await this.database.create(room); + const doc = await this.database.create(room, id); this.room = new RoomLifeCycle(this.database, this.emitter, doc, me); return this.room; } diff --git a/extension/src/services/controllers/WebRtcController.ts b/extension/src/services/controllers/WebRtcController.ts index 8996216e..a9cdd16c 100644 --- a/extension/src/services/controllers/WebRtcController.ts +++ b/extension/src/services/controllers/WebRtcController.ts @@ -242,8 +242,12 @@ export class WebRtcController { connection.isSettingRemoteAnswerPending); const offerCollision = data.type === "offer" && !readyOffer; - connection.ignoreOffer = !polite && offerCollision; - if (connection.ignoreOffer) return; + const ignoreOffer = !polite && offerCollision; + const ignoreAnswer = + data.type === "answer" && pc.signalingState === "stable"; + connection.ignoreOffer = ignoreOffer; + + if (ignoreOffer || ignoreAnswer) return; try { connection.isSettingRemoteAnswerPending = data.type === "answer"; diff --git a/extension/src/services/db/firebase/index.ts b/extension/src/services/db/firebase/index.ts index 70bc85d2..4d5b3ce9 100644 --- a/extension/src/services/db/firebase/index.ts +++ b/extension/src/services/db/firebase/index.ts @@ -131,20 +131,27 @@ const getUserRef = (roomId: string, username: string) => export const firebaseDatabaseServiceImpl: DatabaseService = { room: { - async create(room) { + async create(room, id) { const doc = { ...room, users: {}, version: 0, }; + + if (id) { + const ref = getRoomRef(id); + await setDoc(ref, { + ...doc, + createdAt: serverTimestamp(), + }); + return { id, ...doc }; + } + const ref = await addDoc(getRoomRefs(), { ...doc, createdAt: serverTimestamp(), }); - return { - id: ref.id, - ...doc, - }; + return { id: ref.id, ...doc }; }, get(id) { diff --git a/extension/src/services/index.ts b/extension/src/services/index.ts index a3d40a1a..35e64125 100644 --- a/extension/src/services/index.ts +++ b/extension/src/services/index.ts @@ -1,41 +1,25 @@ import { WEB_RTC_ICE_SERVERS } from "@cb/constants"; import { AppStore, RoomStore, useApp, useRoom } from "@cb/store"; -import { DatabaseService, LocalStorage } from "@cb/types"; +import { DatabaseService } from "@cb/types"; import background, { BackgroundProxy } from "./background"; import { MessageDispatcher } from "./controllers/MessageDispatcher"; import { RoomController } from "./controllers/RoomController"; import { WebRtcController } from "./controllers/WebRtcController"; import db from "./db"; import { emitter, EventEmitter } from "./events"; +export { + clearLocalStorage, + clearLocalStorageForRoom, + getLocalStorage, + removeLocalStorage, + setLocalStorage, +} from "@cb/utils/localstorage"; -const LOCAL_STORAGE_PREFIX = "codebuddy"; -// todo(nickbar01234): Need a more robust typescript solution -const LOCAL_STORAGE: Array = ["signIn"]; - -export const getLocalStorage = (key: K) => { - const maybeItem = localStorage.getItem(LOCAL_STORAGE_PREFIX + key); - return maybeItem == null - ? undefined - : (JSON.parse(maybeItem) as LocalStorage[K]); -}; - -export const removeLocalStorage = (key: K) => { - localStorage.removeItem(LOCAL_STORAGE_PREFIX + key); -}; - -export const setLocalStorage = ( - key: K, - value: LocalStorage[K] -) => { - localStorage.setItem(LOCAL_STORAGE_PREFIX + key, JSON.stringify(value)); -}; - -export const clearLocalStorage = (ignore: Array = []) => - LOCAL_STORAGE.filter((key) => !ignore.includes(key)).forEach( - removeLocalStorage - ); - -export const clearLocalStorageForRoom = () => clearLocalStorage(["signIn"]); +export { + getSessionStorage, + removeSessionStorage, + setSessionStorage, +} from "@cb/utils/sessionstorage"; interface Controllers { emitter: EventEmitter; diff --git a/extension/src/store/roomStore.ts b/extension/src/store/roomStore.ts index ca0bd3ca..d8ade7db 100644 --- a/extension/src/store/roomStore.ts +++ b/extension/src/store/roomStore.ts @@ -95,7 +95,8 @@ interface RoomAction { args: Omit< NonNullable, "id" | "questions" | "usernames" - > + >, + id?: Id ) => Promise; join: (id: Id) => Promise; leave: () => Promise; @@ -324,7 +325,7 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { }, actions: { room: { - create: async (args) => { + create: async (args, id) => { try { get().actions.room.loading(); const metadata = await getProblemMetaBySlugServer( @@ -335,20 +336,23 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { ) { throw new Error(`Graphql metadata errors ${metadata}`); } - const room = await getOrCreateControllers().room.create({ - ...args, - questions: [metadata.data], - }); - const { id, name, isPublic, users } = room.getRoom(); + const room = await getOrCreateControllers().room.create( + { + ...args, + questions: [metadata.data], + }, + id + ); + const { id: roomId, name, isPublic, users } = room.getRoom(); setRoom({ - id, + id: roomId, name, isPublic, questions: [metadata.data], usernames: Object.keys(users), }); setSelfProgressForCurrentUrl(metadata.data); - await initializeChatMessages(id); + await initializeChatMessages(roomId); } catch (error) { toast.error("Failed to create room. Please try again."); console.error("Failed to create room", error); diff --git a/extension/src/types/db.ts b/extension/src/types/db.ts index a3c378a5..c6b0dfb4 100644 --- a/extension/src/types/db.ts +++ b/extension/src/types/db.ts @@ -141,7 +141,8 @@ interface DatabaseRoomObserver { interface DatabaseRoomService { create( - room: Pick + room: Pick, + id?: Id ): Promise>; get(id: Id): Promise; addUser(id: Id, user: User): Promise; diff --git a/extension/src/types/index.ts b/extension/src/types/index.ts index 7f92ef4e..337b5fa4 100644 --- a/extension/src/types/index.ts +++ b/extension/src/types/index.ts @@ -31,3 +31,7 @@ export interface LocalStorage { tabId: number; }; } + +export interface SessionStorage { + devAutoJoinAttempted: boolean; +} diff --git a/extension/src/utils/index.ts b/extension/src/utils/index.ts index d5ef532a..41b9d77e 100644 --- a/extension/src/utils/index.ts +++ b/extension/src/utils/index.ts @@ -2,5 +2,7 @@ export * from "./cast"; export * from "./dom"; export * from "./error"; export * from "./events"; +export * from "./localstorage"; +export * from "./sessionstorage"; export * from "./url"; export * from "./validation"; diff --git a/extension/src/utils/localstorage.ts b/extension/src/utils/localstorage.ts new file mode 100644 index 00000000..258fc064 --- /dev/null +++ b/extension/src/utils/localstorage.ts @@ -0,0 +1,29 @@ +import { STORAGE_PREFIX } from "@cb/constants"; +import { LocalStorage } from "@cb/types"; + +const LOCAL_STORAGE: Array = ["signIn"]; + +export const getLocalStorage = (key: K) => { + const maybeItem = localStorage.getItem(STORAGE_PREFIX + key); + return maybeItem == null + ? undefined + : (JSON.parse(maybeItem) as LocalStorage[K]); +}; + +export const removeLocalStorage = (key: K) => { + localStorage.removeItem(STORAGE_PREFIX + key); +}; + +export const setLocalStorage = ( + key: K, + value: LocalStorage[K] +) => { + localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value)); +}; + +export const clearLocalStorage = (ignore: Array = []) => + LOCAL_STORAGE.filter((key) => !ignore.includes(key)).forEach( + removeLocalStorage + ); + +export const clearLocalStorageForRoom = () => clearLocalStorage(["signIn"]); diff --git a/extension/src/utils/sessionstorage.ts b/extension/src/utils/sessionstorage.ts new file mode 100644 index 00000000..f2ccf7a4 --- /dev/null +++ b/extension/src/utils/sessionstorage.ts @@ -0,0 +1,22 @@ +import { STORAGE_PREFIX } from "@cb/constants"; +import { SessionStorage } from "@cb/types"; + +export const getSessionStorage = (key: K) => { + const maybeItem = sessionStorage.getItem(STORAGE_PREFIX + key); + return maybeItem == null + ? undefined + : (JSON.parse(maybeItem) as SessionStorage[K]); +}; + +export const removeSessionStorage = ( + key: K +) => { + sessionStorage.removeItem(STORAGE_PREFIX + key); +}; + +export const setSessionStorage = ( + key: K, + value: SessionStorage[K] +) => { + sessionStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value)); +}; From 7ce76e2abf19ac8bef3a72b80c0cad1b7e0d4f10 Mon Sep 17 00:00:00 2001 From: HUNG NGO Date: Tue, 13 Jan 2026 14:58:32 -0500 Subject: [PATCH 2/6] change a bit --- extension/wxt.config.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index 9300a62f..5054c4f7 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -15,8 +15,11 @@ export default defineConfig({ }, hooks: { "server:started": async (_wxt, server) => { + // Wait for browser to fully connect, then reload to inject content script setTimeout(() => { - console.log("[WXT] Reloading extension after startup..."); + console.log( + "[WXT] Reloading extension for content script injection..." + ); server.reloadExtension(); }, 10000); }, @@ -27,6 +30,8 @@ export default defineConfig({ "--disable-web-security", `--user-data-dir=./.wxt/chrome-data/${USER_PROFILE}`, "--auto-open-devtools-for-tabs", + "--hide-crash-restore-bubble", + "--test-type", ], }, manifestVersion: 3, From 94164cd0ece384887a764873b1737adafb559618 Mon Sep 17 00:00:00 2001 From: HUNG NGO Date: Tue, 13 Jan 2026 15:58:17 -0500 Subject: [PATCH 3/6] fixing reloading --- extension/src/components/root/ContentScript.tsx | 4 ++-- .../src/hooks/{useDevAutoJoin.ts => useDev.ts} | 17 ++++++++++++----- extension/wxt.config.ts | 9 --------- 3 files changed, 14 insertions(+), 16 deletions(-) rename extension/src/hooks/{useDevAutoJoin.ts => useDev.ts} (71%) diff --git a/extension/src/components/root/ContentScript.tsx b/extension/src/components/root/ContentScript.tsx index e3295336..5cacafde 100644 --- a/extension/src/components/root/ContentScript.tsx +++ b/extension/src/components/root/ContentScript.tsx @@ -7,7 +7,7 @@ import { ResizablePanel } from "@cb/components/panel/ResizablePanel"; import SignInPanel from "@cb/components/panel/SignInPanel"; import { useAuthenticate } from "@cb/hooks/auth"; import { useToast } from "@cb/hooks/toasts"; -import { useDevAutoJoin } from "@cb/hooks/useDevAutoJoin"; +import { useDev } from "@cb/hooks/useDev"; import { AppStatus, useApp } from "@cb/store"; import React from "react"; import { Toaster } from "sonner"; @@ -17,7 +17,7 @@ export const ContentScript = () => { useAuthenticate({}); useToast(); - useDevAutoJoin(); + useDev(); const root = React.useMemo(() => { switch (auth.status) { diff --git a/extension/src/hooks/useDevAutoJoin.ts b/extension/src/hooks/useDev.ts similarity index 71% rename from extension/src/hooks/useDevAutoJoin.ts rename to extension/src/hooks/useDev.ts index 0c93af4f..02d968af 100644 --- a/extension/src/hooks/useDevAutoJoin.ts +++ b/extension/src/hooks/useDev.ts @@ -4,23 +4,30 @@ import { AppStatus, RoomStatus, useApp, useRoom } from "@cb/store"; import { getSessionStorage, setSessionStorage } from "@cb/utils"; import { useEffect } from "react"; -export const useDevAutoJoin = () => { +export const useDev = () => { const authStatus = useApp((s) => s.auth.status); const roomStatus = useRoom((s) => s.status); useEffect(() => { + if (!import.meta.env.DEV) return; + const hasAttempted = getSessionStorage("devAutoJoinAttempted") === true; + // First load: reload page to ensure content scripts are injected + if (!hasAttempted) { + setSessionStorage("devAutoJoinAttempted", true); + console.log("[DEV] Reloading page for content script injection..."); + window.location.reload(); + return; + } + + // Second load: proceed with auto-join if ( - !import.meta.env.DEV || - hasAttempted || authStatus !== AppStatus.AUTHENTICATED || roomStatus !== RoomStatus.HOME ) return; - setSessionStorage("devAutoJoinAttempted", true); - const autoJoin = async () => { const roomActions = useRoom.getState().actions.room; diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index 6f888f61..3546fa81 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -14,15 +14,6 @@ export default defineConfig({ warn: () => {}, }, hooks: { - "server:started": async (_wxt, server) => { - // Wait for browser to fully connect, then reload to inject content script - setTimeout(() => { - console.log( - "[WXT] Reloading extension for content script injection..." - ); - server.reloadExtension(); - }, 10000); - }, "build:manifestGenerated": (wxt, manifest) => { if (wxt.config.mode === "development") { manifest.name = `[DEV] ${manifest.name}`; From ab9704a7c4b2f2dc99cca65f614e1081d5f52551 Mon Sep 17 00:00:00 2001 From: HUNG NGO Date: Tue, 13 Jan 2026 16:55:02 -0500 Subject: [PATCH 4/6] fixing useDev --- extension/src/hooks/useDev.ts | 19 +++++++++------ extension/src/services/index.ts | 9 +++---- extension/src/utils/index.ts | 3 +-- extension/src/utils/localstorage.ts | 29 ---------------------- extension/src/utils/sessionstorage.ts | 22 ----------------- extension/src/utils/storage.ts | 35 +++++++++++++++++++++++++++ extension/src/vite-env.d.ts | 3 +++ extension/wxt.config.ts | 4 +++ 8 files changed, 58 insertions(+), 66 deletions(-) delete mode 100644 extension/src/utils/localstorage.ts delete mode 100644 extension/src/utils/sessionstorage.ts create mode 100644 extension/src/utils/storage.ts diff --git a/extension/src/hooks/useDev.ts b/extension/src/hooks/useDev.ts index 02d968af..d524e291 100644 --- a/extension/src/hooks/useDev.ts +++ b/extension/src/hooks/useDev.ts @@ -13,7 +13,6 @@ export const useDev = () => { const hasAttempted = getSessionStorage("devAutoJoinAttempted") === true; - // First load: reload page to ensure content scripts are injected if (!hasAttempted) { setSessionStorage("devAutoJoinAttempted", true); console.log("[DEV] Reloading page for content script injection..."); @@ -21,7 +20,6 @@ export const useDev = () => { return; } - // Second load: proceed with auto-join if ( authStatus !== AppStatus.AUTHENTICATED || roomStatus !== RoomStatus.HOME @@ -30,16 +28,23 @@ export const useDev = () => { const autoJoin = async () => { const roomActions = useRoom.getState().actions.room; + const isHost = IS_HOST; - const existingRoom = await db.room.get(DEV_ROOM.ID); - - if (existingRoom) { - await roomActions.join(DEV_ROOM.ID); - } else { + if (isHost) { await roomActions.create( { name: DEV_ROOM.NAME, isPublic: false }, DEV_ROOM.ID ); + } else { + const existingRoom = await db.room.get(DEV_ROOM.ID); + if (existingRoom) { + await roomActions.join(DEV_ROOM.ID); + } else { + await roomActions.create( + { name: DEV_ROOM.NAME, isPublic: false }, + DEV_ROOM.ID + ); + } } }; autoJoin(); diff --git a/extension/src/services/index.ts b/extension/src/services/index.ts index 35e64125..9fd5d451 100644 --- a/extension/src/services/index.ts +++ b/extension/src/services/index.ts @@ -11,15 +11,12 @@ export { clearLocalStorage, clearLocalStorageForRoom, getLocalStorage, - removeLocalStorage, - setLocalStorage, -} from "@cb/utils/localstorage"; - -export { getSessionStorage, + removeLocalStorage, removeSessionStorage, + setLocalStorage, setSessionStorage, -} from "@cb/utils/sessionstorage"; +} from "@cb/utils/storage"; interface Controllers { emitter: EventEmitter; diff --git a/extension/src/utils/index.ts b/extension/src/utils/index.ts index 41b9d77e..d435570c 100644 --- a/extension/src/utils/index.ts +++ b/extension/src/utils/index.ts @@ -2,7 +2,6 @@ export * from "./cast"; export * from "./dom"; export * from "./error"; export * from "./events"; -export * from "./localstorage"; -export * from "./sessionstorage"; +export * from "./storage"; export * from "./url"; export * from "./validation"; diff --git a/extension/src/utils/localstorage.ts b/extension/src/utils/localstorage.ts deleted file mode 100644 index 258fc064..00000000 --- a/extension/src/utils/localstorage.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { STORAGE_PREFIX } from "@cb/constants"; -import { LocalStorage } from "@cb/types"; - -const LOCAL_STORAGE: Array = ["signIn"]; - -export const getLocalStorage = (key: K) => { - const maybeItem = localStorage.getItem(STORAGE_PREFIX + key); - return maybeItem == null - ? undefined - : (JSON.parse(maybeItem) as LocalStorage[K]); -}; - -export const removeLocalStorage = (key: K) => { - localStorage.removeItem(STORAGE_PREFIX + key); -}; - -export const setLocalStorage = ( - key: K, - value: LocalStorage[K] -) => { - localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value)); -}; - -export const clearLocalStorage = (ignore: Array = []) => - LOCAL_STORAGE.filter((key) => !ignore.includes(key)).forEach( - removeLocalStorage - ); - -export const clearLocalStorageForRoom = () => clearLocalStorage(["signIn"]); diff --git a/extension/src/utils/sessionstorage.ts b/extension/src/utils/sessionstorage.ts deleted file mode 100644 index f2ccf7a4..00000000 --- a/extension/src/utils/sessionstorage.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { STORAGE_PREFIX } from "@cb/constants"; -import { SessionStorage } from "@cb/types"; - -export const getSessionStorage = (key: K) => { - const maybeItem = sessionStorage.getItem(STORAGE_PREFIX + key); - return maybeItem == null - ? undefined - : (JSON.parse(maybeItem) as SessionStorage[K]); -}; - -export const removeSessionStorage = ( - key: K -) => { - sessionStorage.removeItem(STORAGE_PREFIX + key); -}; - -export const setSessionStorage = ( - key: K, - value: SessionStorage[K] -) => { - sessionStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value)); -}; diff --git a/extension/src/utils/storage.ts b/extension/src/utils/storage.ts new file mode 100644 index 00000000..b094a69d --- /dev/null +++ b/extension/src/utils/storage.ts @@ -0,0 +1,35 @@ +import { STORAGE_PREFIX } from "@cb/constants"; +import { LocalStorage, SessionStorage } from "@cb/types"; + +const createStorageHelpers = (storage: Storage) => ({ + get: (key: K): T[K] | undefined => { + const maybeItem = storage.getItem(STORAGE_PREFIX + String(key)); + return maybeItem == null ? undefined : (JSON.parse(maybeItem) as T[K]); + }, + set: (key: K, value: T[K]) => { + storage.setItem(STORAGE_PREFIX + String(key), JSON.stringify(value)); + }, + remove: (key: K) => { + storage.removeItem(STORAGE_PREFIX + String(key)); + }, +}); + +const local = createStorageHelpers(localStorage); +const session = createStorageHelpers(sessionStorage); + +export const getLocalStorage = local.get; +export const setLocalStorage = local.set; +export const removeLocalStorage = local.remove; + +export const getSessionStorage = session.get; +export const setSessionStorage = session.set; +export const removeSessionStorage = session.remove; + +const LOCAL_STORAGE_KEYS: Array = ["signIn"]; + +export const clearLocalStorage = (ignore: Array = []) => + LOCAL_STORAGE_KEYS.filter((key) => !ignore.includes(key)).forEach( + removeLocalStorage + ); + +export const clearLocalStorageForRoom = () => clearLocalStorage(["signIn"]); diff --git a/extension/src/vite-env.d.ts b/extension/src/vite-env.d.ts index 11f02fe2..7501db4c 100644 --- a/extension/src/vite-env.d.ts +++ b/extension/src/vite-env.d.ts @@ -1 +1,4 @@ /// + +declare const USER_PROFILE: string; +declare const IS_HOST: boolean; diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index 3546fa81..1d45a0b6 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -70,6 +70,10 @@ export default defineConfig({ }, vite: () => ({ plugins: [react()], + define: { + USER_PROFILE: JSON.stringify(USER_PROFILE), + IS_HOST: JSON.stringify(USER_PROFILE === "code"), + }, css: { postcss: { plugins: [tailwindcss()], From c0768f0481ffe7d5190556af73b883c60899b311 Mon Sep 17 00:00:00 2001 From: HUNG NGO Date: Wed, 14 Jan 2026 16:41:46 -0500 Subject: [PATCH 5/6] update typing --- extension/src/utils/storage.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/extension/src/utils/storage.ts b/extension/src/utils/storage.ts index b094a69d..9d08e8af 100644 --- a/extension/src/utils/storage.ts +++ b/extension/src/utils/storage.ts @@ -1,21 +1,24 @@ import { STORAGE_PREFIX } from "@cb/constants"; import { LocalStorage, SessionStorage } from "@cb/types"; -const createStorageHelpers = (storage: Storage) => ({ +const createStorageHelpers = (storage: () => Storage) => ({ get: (key: K): T[K] | undefined => { - const maybeItem = storage.getItem(STORAGE_PREFIX + String(key)); + const maybeItem = storage().getItem(STORAGE_PREFIX + String(key)); return maybeItem == null ? undefined : (JSON.parse(maybeItem) as T[K]); }, set: (key: K, value: T[K]) => { - storage.setItem(STORAGE_PREFIX + String(key), JSON.stringify(value)); + storage().setItem(STORAGE_PREFIX + String(key), JSON.stringify(value)); }, remove: (key: K) => { - storage.removeItem(STORAGE_PREFIX + String(key)); + storage().removeItem(STORAGE_PREFIX + String(key)); }, }); -const local = createStorageHelpers(localStorage); -const session = createStorageHelpers(sessionStorage); +const getLocalStore = () => localStorage; +const getSessionStore = () => sessionStorage; + +const local = createStorageHelpers(getLocalStore); +const session = createStorageHelpers(getSessionStore); export const getLocalStorage = local.get; export const setLocalStorage = local.set; From fb5983057ae16ceb2bc89f4965f1fb5ce7567553 Mon Sep 17 00:00:00 2001 From: HUNG NGO Date: Wed, 14 Jan 2026 17:17:36 -0500 Subject: [PATCH 6/6] revert changes --- .../src/components/root/ContentScript.tsx | 2 - extension/src/constants/index.ts | 7 --- extension/src/hooks/useDev.ts | 52 ------------------- .../services/controllers/RoomController.ts | 7 +-- extension/src/services/db/firebase/index.ts | 17 ++---- extension/src/services/index.ts | 41 +++++++++++---- extension/src/store/roomStore.ts | 22 ++++---- extension/src/types/db.ts | 3 +- extension/src/types/index.ts | 4 -- extension/src/utils/index.ts | 1 - extension/src/utils/storage.ts | 38 -------------- extension/src/vite-env.d.ts | 3 -- extension/wxt.config.ts | 10 ++-- 13 files changed, 53 insertions(+), 154 deletions(-) delete mode 100644 extension/src/hooks/useDev.ts delete mode 100644 extension/src/utils/storage.ts diff --git a/extension/src/components/root/ContentScript.tsx b/extension/src/components/root/ContentScript.tsx index 5cacafde..6ca71940 100644 --- a/extension/src/components/root/ContentScript.tsx +++ b/extension/src/components/root/ContentScript.tsx @@ -7,7 +7,6 @@ import { ResizablePanel } from "@cb/components/panel/ResizablePanel"; import SignInPanel from "@cb/components/panel/SignInPanel"; import { useAuthenticate } from "@cb/hooks/auth"; import { useToast } from "@cb/hooks/toasts"; -import { useDev } from "@cb/hooks/useDev"; import { AppStatus, useApp } from "@cb/store"; import React from "react"; import { Toaster } from "sonner"; @@ -17,7 +16,6 @@ export const ContentScript = () => { useAuthenticate({}); useToast(); - useDev(); const root = React.useMemo(() => { switch (auth.status) { diff --git a/extension/src/constants/index.ts b/extension/src/constants/index.ts index 2b9e48c2..19244691 100644 --- a/extension/src/constants/index.ts +++ b/extension/src/constants/index.ts @@ -112,10 +112,3 @@ export const PANEL = { DEFAULT_WIDTH: 350, // px COLLAPSED_WIDTH: 40, // px }; - -export const DEV_ROOM = { - ID: "dev-room-codebuddy", - NAME: "Dev Room", -}; - -export const STORAGE_PREFIX = "codebuddy"; diff --git a/extension/src/hooks/useDev.ts b/extension/src/hooks/useDev.ts deleted file mode 100644 index d524e291..00000000 --- a/extension/src/hooks/useDev.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { DEV_ROOM } from "@cb/constants"; -import db from "@cb/services/db"; -import { AppStatus, RoomStatus, useApp, useRoom } from "@cb/store"; -import { getSessionStorage, setSessionStorage } from "@cb/utils"; -import { useEffect } from "react"; - -export const useDev = () => { - const authStatus = useApp((s) => s.auth.status); - const roomStatus = useRoom((s) => s.status); - - useEffect(() => { - if (!import.meta.env.DEV) return; - - const hasAttempted = getSessionStorage("devAutoJoinAttempted") === true; - - if (!hasAttempted) { - setSessionStorage("devAutoJoinAttempted", true); - console.log("[DEV] Reloading page for content script injection..."); - window.location.reload(); - return; - } - - if ( - authStatus !== AppStatus.AUTHENTICATED || - roomStatus !== RoomStatus.HOME - ) - return; - - const autoJoin = async () => { - const roomActions = useRoom.getState().actions.room; - const isHost = IS_HOST; - - if (isHost) { - await roomActions.create( - { name: DEV_ROOM.NAME, isPublic: false }, - DEV_ROOM.ID - ); - } else { - const existingRoom = await db.room.get(DEV_ROOM.ID); - if (existingRoom) { - await roomActions.join(DEV_ROOM.ID); - } else { - await roomActions.create( - { name: DEV_ROOM.NAME, isPublic: false }, - DEV_ROOM.ID - ); - } - } - }; - autoJoin(); - }, [authStatus, roomStatus]); -}; diff --git a/extension/src/services/controllers/RoomController.ts b/extension/src/services/controllers/RoomController.ts index 45cc5a52..64e1486c 100644 --- a/extension/src/services/controllers/RoomController.ts +++ b/extension/src/services/controllers/RoomController.ts @@ -271,15 +271,12 @@ export class RoomController { this.appStore = appStore; } - public async create( - room: Pick, - id?: Id - ) { + public async create(room: Pick) { if (this.room != null) { return this.room; } const { username: me } = this.appStore.getState().actions.getAuthUser(); - const doc = await this.database.create(room, id); + const doc = await this.database.create(room); this.room = new RoomLifeCycle(this.database, this.emitter, doc, me); return this.room; } diff --git a/extension/src/services/db/firebase/index.ts b/extension/src/services/db/firebase/index.ts index 4d5b3ce9..70bc85d2 100644 --- a/extension/src/services/db/firebase/index.ts +++ b/extension/src/services/db/firebase/index.ts @@ -131,27 +131,20 @@ const getUserRef = (roomId: string, username: string) => export const firebaseDatabaseServiceImpl: DatabaseService = { room: { - async create(room, id) { + async create(room) { const doc = { ...room, users: {}, version: 0, }; - - if (id) { - const ref = getRoomRef(id); - await setDoc(ref, { - ...doc, - createdAt: serverTimestamp(), - }); - return { id, ...doc }; - } - const ref = await addDoc(getRoomRefs(), { ...doc, createdAt: serverTimestamp(), }); - return { id: ref.id, ...doc }; + return { + id: ref.id, + ...doc, + }; }, get(id) { diff --git a/extension/src/services/index.ts b/extension/src/services/index.ts index 9fd5d451..a3d40a1a 100644 --- a/extension/src/services/index.ts +++ b/extension/src/services/index.ts @@ -1,22 +1,41 @@ import { WEB_RTC_ICE_SERVERS } from "@cb/constants"; import { AppStore, RoomStore, useApp, useRoom } from "@cb/store"; -import { DatabaseService } from "@cb/types"; +import { DatabaseService, LocalStorage } from "@cb/types"; import background, { BackgroundProxy } from "./background"; import { MessageDispatcher } from "./controllers/MessageDispatcher"; import { RoomController } from "./controllers/RoomController"; import { WebRtcController } from "./controllers/WebRtcController"; import db from "./db"; import { emitter, EventEmitter } from "./events"; -export { - clearLocalStorage, - clearLocalStorageForRoom, - getLocalStorage, - getSessionStorage, - removeLocalStorage, - removeSessionStorage, - setLocalStorage, - setSessionStorage, -} from "@cb/utils/storage"; + +const LOCAL_STORAGE_PREFIX = "codebuddy"; +// todo(nickbar01234): Need a more robust typescript solution +const LOCAL_STORAGE: Array = ["signIn"]; + +export const getLocalStorage = (key: K) => { + const maybeItem = localStorage.getItem(LOCAL_STORAGE_PREFIX + key); + return maybeItem == null + ? undefined + : (JSON.parse(maybeItem) as LocalStorage[K]); +}; + +export const removeLocalStorage = (key: K) => { + localStorage.removeItem(LOCAL_STORAGE_PREFIX + key); +}; + +export const setLocalStorage = ( + key: K, + value: LocalStorage[K] +) => { + localStorage.setItem(LOCAL_STORAGE_PREFIX + key, JSON.stringify(value)); +}; + +export const clearLocalStorage = (ignore: Array = []) => + LOCAL_STORAGE.filter((key) => !ignore.includes(key)).forEach( + removeLocalStorage + ); + +export const clearLocalStorageForRoom = () => clearLocalStorage(["signIn"]); interface Controllers { emitter: EventEmitter; diff --git a/extension/src/store/roomStore.ts b/extension/src/store/roomStore.ts index a0821a1b..e7302f3c 100644 --- a/extension/src/store/roomStore.ts +++ b/extension/src/store/roomStore.ts @@ -94,8 +94,7 @@ interface RoomAction { args: Omit< NonNullable, "id" | "questions" | "usernames" - >, - id?: Id + > ) => Promise; join: (id: Id) => Promise; leave: () => Promise; @@ -324,7 +323,7 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { }, actions: { room: { - create: async (args, id) => { + create: async (args) => { try { get().actions.room.loading(); const metadata = await getProblemMetaBySlugServer( @@ -335,23 +334,20 @@ const createRoomStore = (background: BackgroundProxy, appStore: AppStore) => { ) { throw new Error(`Graphql metadata errors ${metadata}`); } - const room = await getOrCreateControllers().room.create( - { - ...args, - questions: [metadata.data], - }, - id - ); - const { id: roomId, name, isPublic, users } = room.getRoom(); + const room = await getOrCreateControllers().room.create({ + ...args, + questions: [metadata.data], + }); + const { id, name, isPublic, users } = room.getRoom(); setRoom({ - id: roomId, + id, name, isPublic, questions: [metadata.data], usernames: Object.keys(users), }); setSelfProgressForCurrentUrl(metadata.data); - await initializeChatMessages(roomId); + await initializeChatMessages(id); } catch (error) { toast.error("Failed to create room. Please try again."); console.error("Failed to create room", error); diff --git a/extension/src/types/db.ts b/extension/src/types/db.ts index 7f57b916..ae4f6983 100644 --- a/extension/src/types/db.ts +++ b/extension/src/types/db.ts @@ -141,8 +141,7 @@ interface DatabaseRoomObserver { interface DatabaseRoomService { create( - room: Pick, - id?: Id + room: Pick ): Promise>; get(id: Id): Promise; addUser(id: Id, user: User): Promise; diff --git a/extension/src/types/index.ts b/extension/src/types/index.ts index 337b5fa4..7f92ef4e 100644 --- a/extension/src/types/index.ts +++ b/extension/src/types/index.ts @@ -31,7 +31,3 @@ export interface LocalStorage { tabId: number; }; } - -export interface SessionStorage { - devAutoJoinAttempted: boolean; -} diff --git a/extension/src/utils/index.ts b/extension/src/utils/index.ts index d435570c..d5ef532a 100644 --- a/extension/src/utils/index.ts +++ b/extension/src/utils/index.ts @@ -2,6 +2,5 @@ export * from "./cast"; export * from "./dom"; export * from "./error"; export * from "./events"; -export * from "./storage"; export * from "./url"; export * from "./validation"; diff --git a/extension/src/utils/storage.ts b/extension/src/utils/storage.ts deleted file mode 100644 index 9d08e8af..00000000 --- a/extension/src/utils/storage.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { STORAGE_PREFIX } from "@cb/constants"; -import { LocalStorage, SessionStorage } from "@cb/types"; - -const createStorageHelpers = (storage: () => Storage) => ({ - get: (key: K): T[K] | undefined => { - const maybeItem = storage().getItem(STORAGE_PREFIX + String(key)); - return maybeItem == null ? undefined : (JSON.parse(maybeItem) as T[K]); - }, - set: (key: K, value: T[K]) => { - storage().setItem(STORAGE_PREFIX + String(key), JSON.stringify(value)); - }, - remove: (key: K) => { - storage().removeItem(STORAGE_PREFIX + String(key)); - }, -}); - -const getLocalStore = () => localStorage; -const getSessionStore = () => sessionStorage; - -const local = createStorageHelpers(getLocalStore); -const session = createStorageHelpers(getSessionStore); - -export const getLocalStorage = local.get; -export const setLocalStorage = local.set; -export const removeLocalStorage = local.remove; - -export const getSessionStorage = session.get; -export const setSessionStorage = session.set; -export const removeSessionStorage = session.remove; - -const LOCAL_STORAGE_KEYS: Array = ["signIn"]; - -export const clearLocalStorage = (ignore: Array = []) => - LOCAL_STORAGE_KEYS.filter((key) => !ignore.includes(key)).forEach( - removeLocalStorage - ); - -export const clearLocalStorageForRoom = () => clearLocalStorage(["signIn"]); diff --git a/extension/src/vite-env.d.ts b/extension/src/vite-env.d.ts index 7501db4c..11f02fe2 100644 --- a/extension/src/vite-env.d.ts +++ b/extension/src/vite-env.d.ts @@ -1,4 +1 @@ /// - -declare const USER_PROFILE: string; -declare const IS_HOST: boolean; diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index 1d45a0b6..8c6ba40f 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -14,6 +14,12 @@ export default defineConfig({ warn: () => {}, }, hooks: { + "server:started": async (_wxt, server) => { + setTimeout(() => { + console.log("[WXT] Reloading extension after startup..."); + server.reloadExtension(); + }, 10000); + }, "build:manifestGenerated": (wxt, manifest) => { if (wxt.config.mode === "development") { manifest.name = `[DEV] ${manifest.name}`; @@ -70,10 +76,6 @@ export default defineConfig({ }, vite: () => ({ plugins: [react()], - define: { - USER_PROFILE: JSON.stringify(USER_PROFILE), - IS_HOST: JSON.stringify(USER_PROFILE === "code"), - }, css: { postcss: { plugins: [tailwindcss()],