A Chrome extension to quickly capture URLs into Semble Collections at https://semble.so
semble.so
at-proto
semble
chrome-extension
1/**
2 * Semble API Client Library
3 * Handles authentication and communication with Semble backend
4 */
5
6const SEMBLE_API_URL = 'https://api.semble.so';
7// For local development: 'http://127.0.0.1:3000'
8
9/**
10 * Login with Bluesky app password to get Semble tokens
11 * @param {string} identifier - User handle (e.g., user.bsky.social)
12 * @param {string} password - Bluesky app password
13 * @returns {Promise<{accessToken: string, refreshToken: string}>}
14 */
15async function createSession(identifier, password) {
16 const response = await fetch(`${SEMBLE_API_URL}/api/users/login/app-password`, {
17 method: 'POST',
18 headers: {
19 'Content-Type': 'application/json',
20 },
21 body: JSON.stringify({
22 identifier,
23 appPassword: password,
24 }),
25 });
26
27 if (!response.ok) {
28 const error = await response.json().catch(() => ({}));
29 throw new Error(`Authentication failed: ${error.message || response.statusText}`);
30 }
31
32 return await response.json();
33}
34
35/**
36 * Refresh Semble access token
37 * @param {string} refreshToken - Refresh token
38 * @returns {Promise<{accessToken: string, refreshToken: string}>}
39 */
40async function refreshSession(refreshToken) {
41 const response = await fetch(`${SEMBLE_API_URL}/api/users/oauth/refresh`, {
42 method: 'POST',
43 headers: {
44 'Content-Type': 'application/json',
45 },
46 body: JSON.stringify({
47 refreshToken,
48 }),
49 });
50
51 if (!response.ok) {
52 throw new Error('Token refresh failed');
53 }
54
55 return await response.json();
56}
57
58/**
59 * Get user's Semble collections
60 * @param {string} accessToken - Semble access token
61 * @returns {Promise<Array>}
62 */
63async function listCollections(accessToken) {
64 const response = await fetch(`${SEMBLE_API_URL}/api/collections`, {
65 headers: {
66 'Authorization': `Bearer ${accessToken}`,
67 },
68 });
69
70 if (!response.ok) {
71 const error = await response.json().catch(() => ({}));
72 throw new Error(`Failed to fetch collections: ${error.message || response.statusText}`);
73 }
74
75 const data = await response.json();
76 return data.collections || [];
77}
78
79/**
80 * Add URL to library via Semble API (creates card, fetches metadata, adds to collections)
81 * @param {string} accessToken - Semble access token
82 * @param {string} url - URL to add
83 * @param {string} [note] - Optional note
84 * @param {string[]} [collectionIds] - Optional collection IDs to add card to
85 * @returns {Promise<{urlCardId: string, noteCardId?: string}>}
86 */
87async function addUrlToLibrary(accessToken, url, note, collectionIds) {
88 const payload = {
89 url,
90 };
91
92 if (note && note.trim()) {
93 payload.note = note.trim();
94 }
95
96 if (collectionIds && collectionIds.length > 0) {
97 payload.collectionIds = collectionIds;
98 }
99
100 console.log('Adding URL to Semble library:', {
101 url,
102 hasNote: !!payload.note,
103 collectionCount: collectionIds?.length || 0,
104 });
105
106 const response = await fetch(`${SEMBLE_API_URL}/api/cards/library/urls`, {
107 method: 'POST',
108 headers: {
109 'Authorization': `Bearer ${accessToken}`,
110 'Content-Type': 'application/json',
111 },
112 body: JSON.stringify(payload),
113 });
114
115 if (!response.ok) {
116 const error = await response.json().catch(() => ({}));
117 console.error('Failed to add URL to library:', {
118 status: response.status,
119 statusText: response.statusText,
120 error: error,
121 });
122 throw new Error(`Failed to add URL: ${error.message || response.statusText}`);
123 }
124
125 const result = await response.json();
126 console.log('URL added to Semble successfully:', result);
127 return result;
128}
129
130// Legacy function names for compatibility - no longer used
131async function createRecord() {
132 throw new Error('createRecord is deprecated - use addUrlToLibrary instead');
133}
134
135async function createUrlCard() {
136 throw new Error('createUrlCard is deprecated - use addUrlToLibrary instead');
137}
138
139async function createNoteCard() {
140 throw new Error('createNoteCard is deprecated - note is now part of addUrlToLibrary');
141}
142
143async function createCollectionLink() {
144 throw new Error('createCollectionLink is deprecated - collectionIds is now part of addUrlToLibrary');
145}
146
147// Export functions for use in other scripts
148if (typeof module !== 'undefined' && module.exports) {
149 module.exports = {
150 createSession,
151 refreshSession,
152 listCollections,
153 addUrlToLibrary,
154 createRecord,
155 createUrlCard,
156 createNoteCard,
157 createCollectionLink,
158 };
159}