diff --git a/client/package-lock.json b/client/package-lock.json index e7be883..b570b70 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1180,7 +1180,6 @@ "integrity": "sha512-GAAbkWrbRJvysL7+HOWs5v/+TmnRcEQPeED2sUcDFTHpPvRYADEtScL6x8hWuKp0DKauJVaVJLTjQVy9e7cMiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -1220,7 +1219,6 @@ "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -1596,7 +1594,6 @@ "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1647,7 +1644,6 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -1865,7 +1861,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2216,7 +2211,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3332,7 +3326,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3360,7 +3353,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3494,7 +3486,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3511,7 +3502,6 @@ "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" @@ -3848,7 +3838,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.42.2.tgz", "integrity": "sha512-iSry5jsBHispVczyt9UrBX/1qu3HQ/UyKPAIjqlvlu3o/eUvc+kpyMyRS2O4HLLx4MvLurLGIUOyyP11pyD59g==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3942,8 +3931,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -4031,7 +4019,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4094,7 +4081,6 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index bd616e4..2670f3c 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -1,25 +1,41 @@ -import type { UserSettings, isProcessed, ProcessedEvents } from "./types"; +import { EnvironmentManager } from "./environment"; +import type { isProcessed, ProcessedEvents, UserSettings } from "./types"; export class API { - public static readonly baseUrl = 'https://heron-selected-literally.ngrok-free.app/api'; + private static async getBaseUrl(): Promise { + const baseUrl = await EnvironmentManager.getBaseUrl(); + return `${baseUrl}/api`; + } - public static async getJwtToken() { - const result = await chrome.storage.local.get('jwt_token'); - if (!result.jwt_token) { - throw new Error('No JWT token found'); - } - return result.jwt_token; + public static get baseUrl(): Promise { + return this.getBaseUrl(); + } + + public static async getJwtToken(): Promise { + const token = await EnvironmentManager.getJwtToken(); + return token; + } + + public static async checkFeatureFlag(flagName:string) { + const response = await fetch(`${this.baseUrl}/feature_flags/${flagName}`, { + method: 'GET' + }); + + const data = await response.json(); + return data.is_enabled; } public static async getTerms() { - const response = await fetch(`${this.baseUrl}/terms/current_and_next`, { + const baseUrl = await this.getBaseUrl(); + const response = await fetch(`${baseUrl}/terms/current_and_next`, { method: 'GET' }); return response.json(); } public static async getUserEmail() { - const response = await fetch(`${this.baseUrl}/user/email`, { + const baseUrl = await this.getBaseUrl(); + const response = await fetch(`${baseUrl}/user/email`, { method: 'GET', headers: { 'Authorization': `Bearer ${await this.getJwtToken()}` @@ -29,7 +45,8 @@ export class API { } public static async userSettings(settings?: UserSettings): Promise { - const url = `${this.baseUrl}/user/extension_config`; + const baseUrl = await this.getBaseUrl(); + const url = `${baseUrl}/user/extension_config`; const token = await this.getJwtToken(); const headers: HeadersInit = { 'Authorization': `Bearer ${token}` @@ -55,8 +72,9 @@ export class API { } public static async userIsProcessed(termUid: string): Promise { + const baseUrl = await this.getBaseUrl(); const token = await this.getJwtToken(); - const response = await fetch(`${this.baseUrl}/user/is_processed`, { + const response = await fetch(`${baseUrl}/user/is_processed`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, @@ -68,8 +86,9 @@ export class API { } public static async getProcessedEvents(termUid: string): Promise { + const baseUrl = await this.getBaseUrl(); const token = await this.getJwtToken(); - const response = await fetch(`${this.baseUrl}/user/processed_events`, { + const response = await fetch(`${baseUrl}/user/processed_events`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, @@ -81,8 +100,9 @@ export class API { } public static async getIcsUrl(): Promise<{ ics_url: string }> { + const baseUrl = await this.getBaseUrl(); const token = await this.getJwtToken(); - const response = await fetch(`${this.baseUrl}/user/ics_url`, { + const response = await fetch(`${baseUrl}/user/ics_url`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}` diff --git a/client/src/lib/components/Settings.svelte b/client/src/lib/components/Settings.svelte index 1d6179f..d2b3628 100644 --- a/client/src/lib/components/Settings.svelte +++ b/client/src/lib/components/Settings.svelte @@ -1,67 +1,152 @@ @@ -69,6 +154,31 @@

Currently signed in as: {email}

+
+
+

Environment

+

+ {#each Object.values(ENVIRONMENTS) as env} + {#if authenticatedEnvironments.includes(env.name)} + ✓ {env.displayName} + {:else} + ○ {env.displayName} + {/if} + {#if env.name !== 'prod'}  {/if} + {/each} +

+
+
+ +
+

Default Lecture Color

diff --git a/client/src/lib/environment.ts b/client/src/lib/environment.ts new file mode 100644 index 0000000..56f9d1f --- /dev/null +++ b/client/src/lib/environment.ts @@ -0,0 +1,102 @@ +export type Environment = 'dev' | 'staging' | 'prod'; + +export interface EnvironmentConfig { + name: Environment; + displayName: string; + baseUrl: string; +} + +export const ENVIRONMENTS: Record = { + dev: { + name: 'dev', + displayName: 'Development', + baseUrl: 'https://heron-selected-literally.ngrok-free.app' + }, + staging: { + name: 'staging', + displayName: 'Staging', + baseUrl: 'https://staging-calendar.witcc.dev' + }, + prod: { + name: 'prod', + displayName: 'Production', + baseUrl: 'https://server-calendar.witcc.dev' + } +}; + +interface StoredEnvironmentData { + current_environment: Environment; + jwt_tokens: Partial>; +} + +export class EnvironmentManager { + private static readonly STORAGE_KEY = 'environment_data'; + + public static async getEnvironmentData(): Promise { + const result = await chrome.storage.local.get(this.STORAGE_KEY); + return result[this.STORAGE_KEY] || { + current_environment: 'prod', + jwt_tokens: {} + }; + } + + public static async getCurrentEnvironment(): Promise { + const data = await this.getEnvironmentData(); + return data.current_environment; + } + + public static async getCurrentEnvironmentConfig(): Promise { + const env = await this.getCurrentEnvironment(); + return ENVIRONMENTS[env]; + } + + public static async getBaseUrl(): Promise { + const config = await this.getCurrentEnvironmentConfig(); + return config.baseUrl; + } + + public static async getJwtToken(environment?: Environment): Promise { + const data = await this.getEnvironmentData(); + const env = environment || data.current_environment; + return data.jwt_tokens[env]; + } + + public static async setJwtToken(token: string, environment?: Environment): Promise { + const data = await this.getEnvironmentData(); + const env = environment || data.current_environment; + data.jwt_tokens[env] = token; + await chrome.storage.local.set({ [this.STORAGE_KEY]: data }); + } + + public static async switchEnvironment(environment: Environment): Promise { + const data = await this.getEnvironmentData(); + data.current_environment = environment; + await chrome.storage.local.set({ [this.STORAGE_KEY]: data }); + + return !!data.jwt_tokens[environment]; + } + + public static async clearJwtToken(environment?: Environment): Promise { + const data = await this.getEnvironmentData(); + const env = environment || data.current_environment; + delete data.jwt_tokens[env]; + await chrome.storage.local.set({ [this.STORAGE_KEY]: data }); + } + + public static async clearAllData(): Promise { + await chrome.storage.local.remove(this.STORAGE_KEY); + } + + public static async getAuthenticatedEnvironments(): Promise { + const data = await this.getEnvironmentData(); + return Object.keys(data.jwt_tokens) as Environment[]; + } + + public static async migrateOldJwtToken(): Promise { + const result = await chrome.storage.local.get('jwt_token'); + if (result.jwt_token) { + await this.setJwtToken(result.jwt_token, 'dev'); + await chrome.storage.local.remove('jwt_token'); + } + } +} diff --git a/client/src/lib/types.ts b/client/src/lib/types.ts index 5172e37..4c046d1 100644 --- a/client/src/lib/types.ts +++ b/client/src/lib/types.ts @@ -13,6 +13,16 @@ interface Course { meeting_times: MeetingTime[]; } +const FEATUE_FLAGS = [ + "v1", + "v2" +] + +interface FeatureFlagEnabled { + feature_name: string; + is_enabled: boolean; +} + interface Location { building: Building; room: string; @@ -100,24 +110,24 @@ interface EventPreferences { } interface TemplateVariables { - title: string; - course_code: string; - subject: string; - course_number: string; - section_number: string; - crn: string; - room: string; - building: string; - location: string; + title: string; + course_code: string; + subject: string; + course_number: string; + section_number: string; + crn: string; + room: string; + building: string; + location: string; faculty: string; - faculty_email: string; + faculty_email: string; all_faculty: string; - start_time: string; - end_time: string; - day: string; - day_abbr: string; - term: string; - schedule_type: string; + start_time: string; + end_time: string; + day: string; + day_abbr: string; + term: string; + schedule_type: string; } interface ResolvedData { @@ -158,27 +168,9 @@ interface NotificationSetting { } export { - type Building, - type Course, - type Location, - type MeetingTime, - type Professor, - type ResponseData, - type Term, - type UserSettings, - type CurrentTerm, - type NextTerm, - type TermResponse, - type isProcessed, - type ProcessedEvents, - type DayItem, - type EventPreferences, - type ReminderSettings, - type GetPreferencesResponse, - type Preview, - type TemplateVariables, - type ResolvedData, - type NotificationType, - type NotificationSetting, - type NotificationMethod + FEATUE_FLAGS, + type Building, + type Course, type CurrentTerm, type DayItem, + type EventPreferences, type GetPreferencesResponse, type isProcessed, type Location, + type MeetingTime, type NextTerm, type NotificationMethod, type NotificationSetting, type NotificationType, type Preview, type ProcessedEvents, type Professor, type ReminderSettings, type ResolvedData, type ResponseData, type TemplateVariables, type Term, type TermResponse, type UserSettings }; diff --git a/client/src/routes/calendar/+page.svelte b/client/src/routes/calendar/+page.svelte index 47257fe..4f80ec1 100644 --- a/client/src/routes/calendar/+page.svelte +++ b/client/src/routes/calendar/+page.svelte @@ -236,6 +236,7 @@ async function fetchFromCurrentPage(term: string | undefined): Promise<{ ics_url: string } | undefined> { if (!term) return; + const baseUrl = await API.baseUrl; let tabToUse: any; let shouldCloseTab = false; try { @@ -301,7 +302,7 @@ }); const registrationData = results[0]?.result ?? []; - const newData = await fetch(`${API.baseUrl}/process_courses`, { + const newData = await fetch(`${baseUrl}/process_courses`, { method: 'POST', body: JSON.stringify(registrationData), headers: { @@ -365,7 +366,8 @@ } async function getEventPerfs(eventId: number) { - const res = await fetch(`${API.baseUrl}/meeting_times/${eventId}/preference`, { + const baseUrl = await API.baseUrl; + const res = await fetch(`${baseUrl}/meeting_times/${eventId}/preference`, { method: 'GET', headers: { 'Authorization': `Bearer ${jwt_token}` @@ -377,10 +379,11 @@ async function refreshAllEventPrefsForCurrentTerm() { if (!selected || !jwt_token || !processedData) return; + const baseUrl = await API.baseUrl; const ids = Array.from(new Set(processedData.flatMap(c => c.meeting_times.map(mt => mt.id)))); const responses = await Promise.all(ids.map(async (id) => { try { - const res = await fetch(`${API.baseUrl}/meeting_times/${id}/preference`, { + const res = await fetch(`${baseUrl}/meeting_times/${id}/preference`, { method: 'GET', headers: { 'Authorization': `Bearer ${jwt_token}` } }); @@ -465,6 +468,7 @@ } async function saveEventPerfs() { + const baseUrl = await API.baseUrl; const event_preference: Partial<{ title_template: string; description_template: string; @@ -527,7 +531,7 @@ } const payload = { event_preference }; - const put = await fetch(`${API.baseUrl}/meeting_times/${activeMeeting?.id}/preference`, { + const put = await fetch(`${baseUrl}/meeting_times/${activeMeeting?.id}/preference`, { method: 'PUT', body: JSON.stringify(payload), headers: { @@ -626,7 +630,20 @@ } } - let tab = $state("a"); + // Check if returning to settings after environment switch (before render) + let shouldReturnToSettings = browser && sessionStorage.getItem('returnToSettings') === 'true'; + let shouldClearData = browser && sessionStorage.getItem('clearCalendarData') === 'true'; + let tab = $state(shouldReturnToSettings ? "settings" : "a"); + + // Clear data immediately if switching environments (before render) + if (shouldClearData && browser) { + sessionStorage.removeItem('returnToSettings'); + sessionStorage.removeItem('clearCalendarData'); + // Clear stores immediately to prevent old data from showing + localStorage.removeItem('processedData'); + localStorage.removeItem('userSettings'); + localStorage.removeItem('icsUrl'); + } let notifications = $state([]); let courseColor = $state("#d50000"); @@ -637,12 +654,61 @@ let editDescriptionManual = $state(""); let editLocationManual = $state(""); + async function listenforEnvironmentChanges() { + chrome.storage.onChanged.addListener((changes: any) => { + Object.entries(changes).forEach(async ([key]) => { + if (key === 'environment_data') { + checkBetaAccess(); + jwt_token = await API.getJwtToken(); + if (!jwt_token) { + // No JWT token for current environment, redirect to welcome page + goto('/'); + return; + } + + // IMPORTANT: Clear data FIRST before fetching anything for environment switches + if (shouldClearData) { + // Clear stored data to force refetch for new environment + storedProcessedData.set([]); + storedUserSettings.set(undefined); + storedIcsUrl.set(undefined); + attemptedTerms = new Set(); + refreshedTerms = new Set(); + } + + // Now fetch fresh data for the current environment + terms = await API.getTerms(); + storedUserSettings.set(await API.userSettings()); + otherCalUser = checkIsOtherCalendar(); + } + }); + }); + } + onMount(async () => { checkBetaAccess(); jwt_token = await API.getJwtToken(); + if (!jwt_token) { + // No JWT token for current environment, redirect to welcome page + goto('/'); + return; + } + + // IMPORTANT: Clear data FIRST before fetching anything for environment switches + if (shouldClearData) { + // Clear stored data to force refetch for new environment + storedProcessedData.set([]); + storedUserSettings.set(undefined); + storedIcsUrl.set(undefined); + attemptedTerms = new Set(); + refreshedTerms = new Set(); + } + + // Now fetch fresh data for the current environment terms = await API.getTerms(); storedUserSettings.set(await API.userSettings()); otherCalUser = checkIsOtherCalendar(); + listenforEnvironmentChanges(); }); $effect(() => { @@ -695,7 +761,7 @@
- {#if !processedData} + {#if !processedData && tab === "a"}

Get Your Calendar

@@ -721,7 +787,7 @@ {/if}
- {:else} + {:else if processedData || tab !== "a"}

Your Calendar

@@ -739,7 +805,7 @@
@@ -817,8 +882,7 @@
- {/if} - {:else if tab === "settings"} + {:else if tab === "settings"} {:else if tab === "help"} diff --git a/client/src/routes/feature-access-denied/+page.svelte b/client/src/routes/feature-access-denied/+page.svelte new file mode 100644 index 0000000..4f836b7 --- /dev/null +++ b/client/src/routes/feature-access-denied/+page.svelte @@ -0,0 +1,58 @@ + + +
+
+ +
+ + + +
+ + +

Feature Access Denied

+ + +

+ You don't have access to this feature! +

+

+ Please contact support if you believe you should have access. +

+ + +
+

Contact Support (Jasper)

+ mayonej@wit.edu +
+ + +
+ +
+
+
diff --git a/client/src/routes/gcalendar/+page.svelte b/client/src/routes/gcalendar/+page.svelte index dfdbdc7..fffbef3 100644 --- a/client/src/routes/gcalendar/+page.svelte +++ b/client/src/routes/gcalendar/+page.svelte @@ -50,9 +50,10 @@ } async function submitEmail() { - const emailToUse = emailToSignInWith || emailToSubmit; - - const response = await fetch(`${API.baseUrl}/user/gcal`, { + const emailToUse = emailToSignInWith || emailToSubmit; + const baseUrl = await API.baseUrl; + + const response = await fetch(`${baseUrl}/user/gcal`, { method: 'POST', body: JSON.stringify({email: emailToUse}), headers: { @@ -89,6 +90,11 @@ onMount(async () => { checkBetaAccess(); jwt_token = await API.getJwtToken(); + if (!jwt_token) { + // No JWT token for current environment, redirect to welcome page + goto('/'); + return; + } checkGcalStatus(); tryForEmail(); setupListener(); diff --git a/client/src/routes/loading/+page.svelte b/client/src/routes/loading/+page.svelte index 9d0d83f..7cd1b2d 100644 --- a/client/src/routes/loading/+page.svelte +++ b/client/src/routes/loading/+page.svelte @@ -3,12 +3,15 @@ import { goto } from '$app/navigation'; import { LoadingIndicator, Button } from 'm3-svelte'; import { API } from '$lib/api'; + import { EnvironmentManager } from '$lib/environment'; import { snackbar } from 'm3-svelte'; let schoolEmail = $state(''); let error = $state(null); - onMount(() => { + onMount(async () => { + // Migrate old JWT token format if needed + await EnvironmentManager.migrateOldJwtToken(); fetchSchoolEmail(); }); @@ -82,7 +85,8 @@ async function signIn() { try { - const response = await fetch(`${API.baseUrl}/user/onboard`, { + const baseUrl = await API.baseUrl; + const response = await fetch(`${baseUrl}/user/onboard`, { method: 'POST', body: JSON.stringify({email: schoolEmail}), headers: { @@ -100,9 +104,7 @@ if (response.ok) { if (data.jwt) { - await chrome.storage.local.set({ - jwt_token: data.jwt, - }); + await EnvironmentManager.setJwtToken(data.jwt); } } else { throw new Error(data.message || 'Server is (probably) down! Please email mayonej@wit.edu!'); diff --git a/client/static/manifest.json b/client/static/manifest.json index 5e32082..9865340 100644 --- a/client/static/manifest.json +++ b/client/static/manifest.json @@ -30,6 +30,7 @@ }, "host_permissions": [ "https://selfservice.wit.edu/*", + "https://*.witcc.dev/*", "https://heron-selected-literally.ngrok-free.app/*" ] } diff --git a/client/static/service-worker.js b/client/static/service-worker.js index c7edbbc..8360757 100644 --- a/client/static/service-worker.js +++ b/client/static/service-worker.js @@ -1,17 +1,25 @@ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { - if (changeInfo.status === 'complete' && - tab.url && - tab.url.includes('https://heron-selected-literally.ngrok-free.app/oauth/success')) { - - console.log('OAuth success page detected'); - - try { - await chrome.storage.local.set({ - oauth_status: 'success', - }); - chrome.tabs.remove(tabId); - } catch (error) { - console.error('Error during authentication:', error); + if (changeInfo.status === 'complete' && tab.url) { + // Check if the URL matches any environment's OAuth success page + const oauthSuccessPatterns = [ + 'https://heron-selected-literally.ngrok-free.app/oauth/success', + 'https://staging-calendar.witcc.dev/oauth/success', + 'https://server-calendar.witcc.dev/oauth/success' + ]; + + const isOAuthSuccess = oauthSuccessPatterns.some(pattern => tab.url.includes(pattern)); + + if (isOAuthSuccess) { + console.log('OAuth success page detected'); + + try { + await chrome.storage.local.set({ + oauth_status: 'success', + }); + chrome.tabs.remove(tabId); + } catch (error) { + console.error('Error during authentication:', error); + } } } });