mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-18 06:03:49 +00:00
feat(extension): Auto Logout when token is invalidated, Account Section (#438)
## Changes: - Added token validation logic that checks if the current auth token is still valid - Implemented automatic logout when token is invalidated with appropriate user feedback - Added an Account section in the popup that displays the user's email - Improved error toast messages with clearer formatting and helper text 
This commit is contained in:
parent
5f09fe0cf1
commit
7e8b65b36d
5 changed files with 173 additions and 28 deletions
|
|
@ -1,11 +1,13 @@
|
|||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { useEffect, useState } from "react"
|
||||
import "./App.css"
|
||||
import { validateAuthToken } from "../../utils/api"
|
||||
import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants"
|
||||
import {
|
||||
useDefaultProject,
|
||||
useProjects,
|
||||
useSetDefaultProject,
|
||||
useUserData,
|
||||
} from "../../utils/query-hooks"
|
||||
import type { Project } from "../../utils/types"
|
||||
|
||||
|
|
@ -20,6 +22,7 @@ function App() {
|
|||
"save",
|
||||
)
|
||||
const [autoSearchEnabled, setAutoSearchEnabled] = useState<boolean>(false)
|
||||
const [authInvalidated, setAuthInvalidated] = useState<boolean>(false)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const { data: projects = [], isLoading: loadingProjects } = useProjects({
|
||||
|
|
@ -28,8 +31,12 @@ function App() {
|
|||
const { data: defaultProject } = useDefaultProject({
|
||||
enabled: userSignedIn,
|
||||
})
|
||||
const { data: userData, isLoading: loadingUserData } = useUserData({
|
||||
enabled: userSignedIn,
|
||||
})
|
||||
const setDefaultProjectMutation = useSetDefaultProject()
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: suppress dependency analysis
|
||||
useEffect(() => {
|
||||
const checkAuthStatus = async () => {
|
||||
try {
|
||||
|
|
@ -37,8 +44,28 @@ function App() {
|
|||
STORAGE_KEYS.BEARER_TOKEN,
|
||||
STORAGE_KEYS.AUTO_SEARCH_ENABLED,
|
||||
])
|
||||
const isSignedIn = !!result[STORAGE_KEYS.BEARER_TOKEN]
|
||||
setUserSignedIn(isSignedIn)
|
||||
const hasToken = !!result[STORAGE_KEYS.BEARER_TOKEN]
|
||||
|
||||
if (hasToken) {
|
||||
const isTokenValid = await validateAuthToken()
|
||||
|
||||
if (isTokenValid) {
|
||||
setUserSignedIn(true)
|
||||
setAuthInvalidated(false)
|
||||
} else {
|
||||
await chrome.storage.local.remove([
|
||||
STORAGE_KEYS.BEARER_TOKEN,
|
||||
STORAGE_KEYS.USER_DATA,
|
||||
STORAGE_KEYS.DEFAULT_PROJECT,
|
||||
])
|
||||
queryClient.clear()
|
||||
setUserSignedIn(false)
|
||||
setAuthInvalidated(true)
|
||||
}
|
||||
} else {
|
||||
setUserSignedIn(false)
|
||||
setAuthInvalidated(false)
|
||||
}
|
||||
|
||||
const autoSearchSetting =
|
||||
result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false
|
||||
|
|
@ -46,6 +73,7 @@ function App() {
|
|||
} catch (error) {
|
||||
console.error("Error checking auth status:", error)
|
||||
setUserSignedIn(false)
|
||||
setAuthInvalidated(false)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
|
|
@ -397,6 +425,34 @@ function App() {
|
|||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 min-h-[200px]">
|
||||
{/* Account Section */}
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-black mb-3">
|
||||
Account
|
||||
</h3>
|
||||
<div className="p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
{loadingUserData ? (
|
||||
<div className="text-sm text-gray-500">
|
||||
Loading account data...
|
||||
</div>
|
||||
) : userData?.email ? (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs font-medium text-black">
|
||||
Email
|
||||
</span>
|
||||
<span className="text-sm text-gray-600">
|
||||
{userData.email}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-gray-500">
|
||||
No email found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Integration Section */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-base font-semibold text-black mb-3">
|
||||
Chat Integration
|
||||
|
|
@ -480,23 +536,37 @@ function App() {
|
|||
</div>
|
||||
) : (
|
||||
<div className="text-center py-2">
|
||||
<div className="mb-8">
|
||||
<h2 className="m-0 mb-4 text-sm font-normal text-black leading-tight">
|
||||
Login to unlock all chrome extension features
|
||||
</h2>
|
||||
{authInvalidated ? (
|
||||
<div className="mb-8">
|
||||
<div className="p-3 mb-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<h2 className="m-0 mb-2 text-sm font-semibold text-red-800 leading-tight">
|
||||
Session Expired
|
||||
</h2>
|
||||
<p className="m-0 text-xs text-red-600 leading-tight">
|
||||
Logged out since authentication was invalidated. Please
|
||||
login again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-8">
|
||||
<h2 className="m-0 mb-4 text-sm font-normal text-black leading-tight">
|
||||
Login to unlock all chrome extension features
|
||||
</h2>
|
||||
|
||||
<ul className="list-none p-0 m-0 text-left">
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Save any page to your supermemory
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import all your Twitter / X Bookmarks
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import your ChatGPT Memories
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul className="list-none p-0 m-0 text-left">
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Save any page to your supermemory
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import all your Twitter / X Bookmarks
|
||||
</li>
|
||||
<li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
|
||||
Import your ChatGPT Memories
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8">
|
||||
<p className="m-0 mb-4 text-sm text-gray-500">
|
||||
|
|
|
|||
|
|
@ -99,6 +99,35 @@ export async function setDefaultProject(project: Project): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if current bearer token is still valid
|
||||
*/
|
||||
export async function validateAuthToken(): Promise<boolean> {
|
||||
try {
|
||||
await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects")
|
||||
return true
|
||||
} catch (error) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return false
|
||||
}
|
||||
console.error("Failed to validate auth token:", error)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user data from storage
|
||||
*/
|
||||
export async function getUserData(): Promise<{ email?: string } | null> {
|
||||
try {
|
||||
const result = await chrome.storage.local.get([STORAGE_KEYS.USER_DATA])
|
||||
return result[STORAGE_KEYS.USER_DATA] || null
|
||||
} catch (error) {
|
||||
console.error("Failed to get user data:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save memory to Supermemory API
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
|||
import {
|
||||
fetchProjects,
|
||||
getDefaultProject,
|
||||
getUserData,
|
||||
saveMemory,
|
||||
searchMemories,
|
||||
setDefaultProject,
|
||||
|
|
@ -15,6 +16,7 @@ import type { MemoryPayload } from "./types"
|
|||
export const queryKeys = {
|
||||
projects: ["projects"] as const,
|
||||
defaultProject: ["defaultProject"] as const,
|
||||
userData: ["userData"] as const,
|
||||
}
|
||||
|
||||
// Projects Query
|
||||
|
|
@ -37,6 +39,16 @@ export function useDefaultProject(options?: { enabled?: boolean }) {
|
|||
})
|
||||
}
|
||||
|
||||
// User Data Query
|
||||
export function useUserData(options?: { enabled?: boolean }) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.userData,
|
||||
queryFn: getUserData,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
enabled: options?.enabled ?? true,
|
||||
})
|
||||
}
|
||||
|
||||
// Set Default Project Mutation
|
||||
export function useSetDefaultProject() {
|
||||
const queryClient = useQueryClient()
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ export function createToast(state: ToastState): HTMLElement {
|
|||
const icon = document.createElement("div")
|
||||
icon.style.cssText = "width: 20px; height: 20px; flex-shrink: 0;"
|
||||
|
||||
const text = document.createElement("span")
|
||||
text.style.fontWeight = "500"
|
||||
let textElement: HTMLElement = document.createElement("span")
|
||||
textElement.style.fontWeight = "500"
|
||||
|
||||
// Configure toast based on state
|
||||
switch (state) {
|
||||
|
|
@ -113,17 +113,17 @@ export function createToast(state: ToastState): HTMLElement {
|
|||
</svg>
|
||||
`
|
||||
icon.style.animation = "spin 1s linear infinite"
|
||||
text.textContent = "Adding to Memory..."
|
||||
textElement.textContent = "Adding to Memory..."
|
||||
break
|
||||
|
||||
case "success": {
|
||||
const iconUrl = browser.runtime.getURL("/icon-16.png")
|
||||
icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />`
|
||||
text.textContent = "Added to Memory"
|
||||
textElement.textContent = "Added to Memory"
|
||||
break
|
||||
}
|
||||
|
||||
case "error":
|
||||
case "error": {
|
||||
icon.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" fill="#ef4444"/>
|
||||
|
|
@ -131,12 +131,29 @@ export function createToast(state: ToastState): HTMLElement {
|
|||
<path d="M9 9L15 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
`
|
||||
text.textContent = "Failed to save memory / Make sure you are logged in"
|
||||
const textContainer = document.createElement("div")
|
||||
textContainer.style.cssText =
|
||||
"display: flex; flex-direction: column; gap: 2px;"
|
||||
|
||||
const mainText = document.createElement("span")
|
||||
mainText.style.cssText = "font-weight: 500; line-height: 1.2;"
|
||||
mainText.textContent = "Failed to save memory"
|
||||
|
||||
const helperText = document.createElement("span")
|
||||
helperText.style.cssText =
|
||||
"font-size: 12px; color: #6b7280; font-weight: 400; line-height: 1.2;"
|
||||
helperText.textContent = "Make sure you are logged in"
|
||||
|
||||
textContainer.appendChild(mainText)
|
||||
textContainer.appendChild(helperText)
|
||||
|
||||
textElement = textContainer
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
toast.appendChild(icon)
|
||||
toast.appendChild(text)
|
||||
toast.appendChild(textElement)
|
||||
|
||||
return toast
|
||||
}
|
||||
|
|
@ -433,8 +450,25 @@ export const DOMUtils = {
|
|||
</svg>
|
||||
`
|
||||
icon.style.animation = ""
|
||||
text.textContent =
|
||||
"Failed to save memory / Make sure you are logged in"
|
||||
|
||||
const textContainer = document.createElement("div")
|
||||
textContainer.style.cssText =
|
||||
"display: flex; flex-direction: column; gap: 2px;"
|
||||
|
||||
const mainText = document.createElement("span")
|
||||
mainText.style.cssText = "font-weight: 500; line-height: 1.2;"
|
||||
mainText.textContent = "Failed to save memory"
|
||||
|
||||
const helperText = document.createElement("span")
|
||||
helperText.style.cssText =
|
||||
"font-size: 12px; color: #6b7280; font-weight: 400; line-height: 1.2;"
|
||||
helperText.textContent = "Make sure you are logged in"
|
||||
|
||||
textContainer.appendChild(mainText)
|
||||
textContainer.appendChild(helperText)
|
||||
|
||||
text.innerHTML = ""
|
||||
text.appendChild(textContainer)
|
||||
}
|
||||
|
||||
// Auto-dismiss
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default defineConfig({
|
|||
manifest: {
|
||||
name: "supermemory",
|
||||
homepage_url: "https://supermemory.ai",
|
||||
version: "6.0.002",
|
||||
version: "6.0.003",
|
||||
permissions: ["contextMenus", "storage", "activeTab", "webRequest", "tabs"],
|
||||
host_permissions: [
|
||||
"*://x.com/*",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue