aboutsummaryrefslogtreecommitdiff
path: root/apps/browser-extension/utils/api.ts
blob: 2a4c838ba883139a1042d455d445861a21e15104 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
 * API service for supermemory browser extension
 */
import { API_ENDPOINTS, STORAGE_KEYS } from "./constants"
import {
	AuthenticationError,
	type MemoryPayload,
	type Project,
	type ProjectsResponse,
	SupermemoryAPIError,
} from "./types"

/**
 * Get bearer token from storage
 */
async function getBearerToken(): Promise<string> {
	const result = await chrome.storage.local.get([STORAGE_KEYS.BEARER_TOKEN])
	const token = result[STORAGE_KEYS.BEARER_TOKEN]

	if (!token) {
		throw new AuthenticationError("Bearer token not found")
	}

	return token
}

/**
 * Make authenticated API request
 */
async function makeAuthenticatedRequest<T>(
	endpoint: string,
	options: RequestInit = {},
): Promise<T> {
	const token = await getBearerToken()

	const response = await fetch(`${API_ENDPOINTS.SUPERMEMORY_API}${endpoint}`, {
		...options,
		credentials: "omit",
		headers: {
			Authorization: `Bearer ${token}`,
			"Content-Type": "application/json",
			...options.headers,
		},
	})

	if (!response.ok) {
		if (response.status === 401) {
			throw new AuthenticationError("Invalid or expired token")
		}
		throw new SupermemoryAPIError(
			`API request failed: ${response.statusText}`,
			response.status,
		)
	}

	return response.json()
}

/**
 * Fetch all projects from API
 */
export async function fetchProjects(): Promise<Project[]> {
	try {
		const response =
			await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects")
		return response.projects
	} catch (error) {
		console.error("Failed to fetch projects:", error)
		throw error
	}
}

/**
 * Get default project from storage
 */
export async function getDefaultProject(): Promise<Project | null> {
	try {
		const result = await chrome.storage.local.get([
			STORAGE_KEYS.DEFAULT_PROJECT,
		])
		return result[STORAGE_KEYS.DEFAULT_PROJECT] || null
	} catch (error) {
		console.error("Failed to get default project:", error)
		return null
	}
}

/**
 * Set default project in storage
 */
export async function setDefaultProject(project: Project): Promise<void> {
	try {
		await chrome.storage.local.set({
			[STORAGE_KEYS.DEFAULT_PROJECT]: project,
		})
	} catch (error) {
		console.error("Failed to set default project:", error)
		throw error
	}
}

/**
 * Save memory to Supermemory API
 */
export async function saveMemory(payload: MemoryPayload): Promise<unknown> {
	try {
		const response = await makeAuthenticatedRequest<unknown>("/v3/documents", {
			method: "POST",
			body: JSON.stringify(payload),
		})
		return response
	} catch (error) {
		console.error("Failed to save memory:", error)
		throw error
	}
}

/**
 * Search memories using Supermemory API
 */
export async function searchMemories(query: string): Promise<unknown> {
	try {
		const response = await makeAuthenticatedRequest<unknown>("/v4/search", {
			method: "POST",
			body: JSON.stringify({ q: query, include: { relatedMemories: true } }),
		})
		return response
	} catch (error) {
		console.error("Failed to search memories:", error)
		throw error
	}
}

/**
 * Save tweet to Supermemory API (specific for Twitter imports)
 */
export async function saveAllTweets(
	documents: MemoryPayload[],
): Promise<unknown> {
	try {
		const response = await makeAuthenticatedRequest<unknown>(
			"/v3/documents/batch",
			{
				method: "POST",
				body: JSON.stringify({
					documents,
					metadata: {
						sm_source: "consumer",
						sm_internal_group_id: "twitter_bookmarks",
					},
				}),
			},
		)
		return response
	} catch (error) {
		if (error instanceof SupermemoryAPIError && error.statusCode === 409) {
			// Skip if already exists (409 Conflict)
			return
		}
		throw error
	}
}