Skip to content
Merged
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
1 change: 1 addition & 0 deletions bsg-frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/**/package-lock.json
**/node_modules/
**/next-env.d.ts
out/
21 changes: 21 additions & 0 deletions bsg-frontend/app/extension/defaultPopup/contentScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import DefaultPopup from './page.tsx';

const container = document.createElement('div');
container.id = 'my-extension-root';
document.body.appendChild(container);

Object.assign(container.style, {
position: 'fixed',
top: '0',
right: '0',
width: '30%',
height: '100%',
backgroundColor: '#000',
zIndex: 9999,
overflow: 'auto'
});

const root = createRoot(container);
root.render(<DefaultPopup />);
14 changes: 7 additions & 7 deletions bsg-frontend/apps/extension/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
# Copy this file to .env.local and fill in your Firebase project values
# Get these values from Firebase Console > Project Settings

NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=
NEXT_PUBLIC_FIREBASE_API_KEY=""
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=""
NEXT_PUBLIC_FIREBASE_PROJECT_ID=""
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=""
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=""
NEXT_PUBLIC_FIREBASE_APP_ID=""
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=""
1 change: 1 addition & 0 deletions bsg-frontend/apps/extension/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
!.env.example
.env.local
.env
out/
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { app } from "../../../config";
import { getAuth, signInWithCredential, GoogleAuthProvider, User } from "firebase/auth";

const auth = getAuth(app);
import { getFirebaseAuth } from "../../../config";
import { signInWithCredential, GoogleAuthProvider, User } from "firebase/auth";

export async function SignInWithChromeIdentity(): Promise<User> {
return new Promise((resolve, reject) => {
Expand All @@ -22,8 +20,12 @@ export async function SignInWithChromeIdentity(): Promise<User> {
}

try {
// Use the token to get user info from Google API
//const userInfo = await getUserInfoFromToken(token);
// get auth lazily (may be null during server/build)
const auth = getFirebaseAuth();
if (!auth) {
reject(new Error('Firebase auth not available in this environment'));
return;
}

// Create a Firebase credential using the token
const credential = GoogleAuthProvider.credential(null, token);
Expand All @@ -40,7 +42,7 @@ export async function SignInWithChromeIdentity(): Promise<User> {
});
}

async function getUserInfoFromToken(token: string) {
export async function getUserInfoFromToken(token: string) {
const response = await fetch(`https://www.googleapis.com/oauth2/v2/userinfo?access_token=${token}`);
if (!response.ok) {
throw new Error('Failed to get user info');
Expand All @@ -59,9 +61,7 @@ export async function SignOutFromChrome(): Promise<void> {
chrome.identity.getAuthToken({ interactive: false }, (token) => {

const tokenToRevoke = token.toString();
const requestBody = new URLSearchParams({
token: tokenToRevoke,
}).toString();
const requestBody = new URLSearchParams({ token: tokenToRevoke }).toString();

fetch('https://oauth2.googleapis.com/revoke', {
method:'POST',
Expand All @@ -74,10 +74,17 @@ export async function SignOutFromChrome(): Promise<void> {
if(response.ok){
console.log("Reached google servers")

chrome.identity.clearAllCachedAuthTokens(() => {
auth.signOut().then(resolve).catch(reject);
}
)
chrome.identity.clearAllCachedAuthTokens(async () => {
const auth = getFirebaseAuth();
if (auth) {
try {
await auth.signOut();
} catch (e) {
// ignore sign out error
}
}
resolve();
});

}
else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { app } from "../../../config";
import { getAuth,
signInWithRedirect,
signInWithPopup,
getRedirectResult,
User,
} from "firebase/auth";
import { getFirebaseAuth } from "../../../config";
import {
signInWithRedirect,
signInWithPopup,
getRedirectResult,
User,
} from "firebase/auth";

import { provider } from './googleSignIn';


const auth = getAuth(app);


export async function SignInWithGoogleRedirect(): Promise<void> {

try{
const auth = getFirebaseAuth();
if (!auth) throw new Error('Firebase auth not available in this environment');
await signInWithRedirect(auth, provider);
}
catch(error){
Expand All @@ -26,6 +25,8 @@ export async function SignInWithGoogleRedirect(): Promise<void> {

export async function SignInWithGooglePopup(): Promise<User> {
try {
const auth = getFirebaseAuth();
if (!auth) throw new Error('Firebase auth not available in this environment');
const result = await signInWithPopup(auth, provider);
return result.user;
} catch (error) {
Expand All @@ -38,6 +39,9 @@ export async function HandleAuthRedirectedResult(): Promise<User>{

try{

const auth = getFirebaseAuth();
if (!auth) throw new Error('Firebase auth not available in this environment');

const result = await getRedirectResult(auth, provider);

if(result){
Expand Down
25 changes: 12 additions & 13 deletions bsg-frontend/apps/extension/firebase/auth/signOut.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { signOut, getAuth } from "firebase/auth";
import { app } from "../config";
import { signOut } from "firebase/auth";
import { getFirebaseAuth } from "../config";

export async function signOutOfAccount(): Promise<void> {
const auth = getFirebaseAuth();
if (!auth) {
// Nothing to do when auth isn't available (server/build environment)
return;
}

export async function signOutOfAccount(): Promise<void>{

const auth = getAuth(app);

try{
try {
return await signOut(auth);

}catch(error){

console.log(error.code)
console.log(error.message)
} catch (error: any) {
console.log(error?.code);
console.log(error?.message);
throw error;
}

}
44 changes: 35 additions & 9 deletions bsg-frontend/apps/extension/firebase/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAuth, setPersistence, browserSessionPersistence } from "firebase/auth";


import { initializeApp, FirebaseApp } from "firebase/app";
import { getAuth, setPersistence, browserSessionPersistence, Auth } from "firebase/auth";

const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
Expand All @@ -14,11 +12,39 @@ const firebaseConfig = {
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID
};

// Initialize Firebase
export const app = initializeApp(firebaseConfig);
let firebaseApp: FirebaseApp | null = null;

/**
* Lazily initialize and return the Firebase app instance.
* Returns null when run on the server or if the required env is missing.
*/
export function getFirebaseApp(): FirebaseApp | null {
if (typeof window === 'undefined') return null;
if (firebaseApp) return firebaseApp;

if (!firebaseConfig.apiKey) {
// Do not initialize Firebase in environments without an API key.
// This prevents build-time/server-side initialization which causes
// auth/invalid-api-key errors during next build when env files aren't loaded.
return null;
}

firebaseApp = initializeApp(firebaseConfig);
return firebaseApp;
}

// Initialize Auth with session persistence
export const auth = getAuth(app);
setPersistence(auth, browserSessionPersistence);
/**
* Lazily return an Auth instance, or null if unavailable (server or missing config).
*/
export function getFirebaseAuth(): Auth | null {
const app = getFirebaseApp();
if (!app) return null;
const auth = getAuth(app);
// Only set browser persistence in a real browser environment
if (typeof window !== 'undefined') {
setPersistence(auth, browserSessionPersistence).catch(() => {});
}
return auth;
}


106 changes: 106 additions & 0 deletions bsg-frontend/apps/extension/hooks/useChatSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useEffect, useRef, useState, useCallback } from 'react';

const RTC_SERVICE_URL = 'ws://localhost:8080/ws';

export type Message = {
userHandle: string;
data: string;
roomID: string;
isSystem?: boolean;
}

export const useChatSocket = (userEmail: string | null | undefined) => {
const socketRef = useRef<WebSocket | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
if (!userEmail) return;

const ws = new WebSocket(RTC_SERVICE_URL);
socketRef.current = ws;

ws.onopen = () => {
console.log('Connected to RTC service');
setIsConnected(true);
};

ws.onmessage = (event) => {
try {
const response = JSON.parse(event.data);

if (response.status === 'ok') {
const { message, responseType } = response;

if (responseType === 'chat-message') {
setMessages(prev => [...prev, {
userHandle: message.userHandle,
data: message.data,
roomID: message.roomID,
isSystem: false
}]);
} else if (responseType === 'system-announcement') {
setMessages(prev => [...prev, {
userHandle: 'System',
data: message.data,
roomID: message.roomID,
isSystem: true
}]);
}
} else if (response.status === 'error') {
console.error('RTC Error:', response.message);
}
} catch (e) {
console.error('Failed to parse WS message', e);
}
};

ws.onclose = () => {
console.log('Disconnected from RTC service');
setIsConnected(false);
};

return () => {
ws.close();
};
}, [userEmail]);

const joinRoom = useCallback((roomID: string) => {
// Clear messages when joining a new room so we don't see chat history from previous rooms
setMessages([]);

if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN && userEmail) {
const payload = {
name: userEmail,
"request-type": "join-room",
data: JSON.stringify({
userHandle: userEmail,
roomID: roomID
})
};
socketRef.current.send(JSON.stringify(payload));
}
}, [userEmail]);

const sendChatMessage = useCallback((roomID: string, message: string) => {
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN && userEmail) {
const payload = {
name: userEmail,
"request-type": "chat-message",
data: JSON.stringify({
userHandle: userEmail,
roomID: roomID,
message: message
})
};
socketRef.current.send(JSON.stringify(payload));
}
}, [userEmail]);

return {
messages,
isConnected,
joinRoom,
sendChatMessage
};
};
2 changes: 1 addition & 1 deletion bsg-frontend/apps/extension/out/404.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/next/static/media/eafabf029ad39a43-s.p.woff2" as="font" type="font/woff2" crossorigin="anonymous" data-next-font="size-adjust"/><link rel="preload" href="/next/static/css/df5027b2d5e645cb.css" as="style" crossorigin=""/><link rel="stylesheet" href="/next/static/css/df5027b2d5e645cb.css" crossorigin="" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" crossorigin="" nomodule="" src="/next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/next/static/chunks/webpack-fa99431b15635937.js" defer="" crossorigin=""></script><script src="/next/static/chunks/framework-8e279965036b6169.js" defer="" crossorigin=""></script><script src="/next/static/chunks/main-e7dd4fa71e3f71a3.js" defer="" crossorigin=""></script><script src="/next/static/chunks/pages/_app-541fac8f2ba82a1d.js" defer="" crossorigin=""></script><script src="/next/static/chunks/pages/_error-1ac136249be010d2.js" defer="" crossorigin=""></script><script src="/next/static/rEwlCCw22kfFkb8ibmkc1/_buildManifest.js" defer="" crossorigin=""></script><script src="/next/static/rEwlCCw22kfFkb8ibmkc1/_ssgManifest.js" defer="" crossorigin=""></script></head><body><div id="_next"><div class="__className_657e4b"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div></div><script id="__NEXT_DATA__" type="application/json" crossorigin="">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"rEwlCCw22kfFkb8ibmkc1","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/next/static/media/eafabf029ad39a43-s.p.woff2" as="font" type="font/woff2" crossorigin="anonymous" data-next-font="size-adjust"/><link rel="preload" href="/next/static/css/163e8d7003cdbfe1.css" as="style" crossorigin=""/><link rel="stylesheet" href="/next/static/css/163e8d7003cdbfe1.css" crossorigin="" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" crossorigin="" nomodule="" src="/next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/next/static/chunks/webpack-445a5fe7cadeec28.js" defer="" crossorigin=""></script><script src="/next/static/chunks/framework-8e279965036b6169.js" defer="" crossorigin=""></script><script src="/next/static/chunks/main-e7dd4fa71e3f71a3.js" defer="" crossorigin=""></script><script src="/next/static/chunks/pages/_app-cd43db7e9d1a1c5b.js" defer="" crossorigin=""></script><script src="/next/static/chunks/pages/_error-1ac136249be010d2.js" defer="" crossorigin=""></script><script src="/next/static/ni_aOkU9471XFmrhexFlb/_buildManifest.js" defer="" crossorigin=""></script><script src="/next/static/ni_aOkU9471XFmrhexFlb/_ssgManifest.js" defer="" crossorigin=""></script></head><body><div id="_next"><div class="__className_657e4b min-h-screen bg-[#262626] flex items-center justify-center px-4 py-8"><div class="bg-[#333333] border border-gray-700 rounded-xl shadow-2xl w-full max-w-md p-8 pt-16 space-y-8"><div class="flex justify-center mb-2"><span class="text-5xl font-extrabold tracking-wide text-white drop-shadow-lg">BSG_</span></div><div class="flex flex-col justify-center items-center gap-y-4"><button class="whitespace-nowrap text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 w-full flex items-center justify-center space-x-2 px-4 py-2 rounded-lg text-white bg-[hsl(90,72%,39%)] hover:bg-[hsl(90,72%,34%)] transition-colors"><svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="google" class="svg-inline--fa fa-google " role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 488 512"><path fill="currentColor" d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"></path></svg><span>Sign in with Google</span></button></div></div></div></div><script id="__NEXT_DATA__" type="application/json" crossorigin="">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"ni_aOkU9471XFmrhexFlb","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
Loading