this repo has no description
1import { createRestAPIClient, createStreamingAPIClient } from 'masto';
2
3import store from './store';
4import {
5 getAccount,
6 getAccountByAccessToken,
7 getAccountByInstance,
8 getCurrentAccount,
9 saveAccount,
10} from './store-utils';
11
12// Default *fallback* instance
13const DEFAULT_INSTANCE = 'mastodon.social';
14
15// Per-instance masto instance
16// Useful when only one account is logged in
17// I'm not sure if I'll ever allow multiple logged-in accounts but oh well...
18// E.g. apis['mastodon.social']
19const apis = {};
20
21// Per-account masto instance
22// Note: There can be many accounts per instance
23// Useful when multiple accounts are logged in or when certain actions require a specific account
24// Just in case if I need this one day.
25// E.g. accountApis['mastodon.social']['ACCESS_TOKEN']
26const accountApis = {};
27window.__ACCOUNT_APIS__ = accountApis;
28
29// Current account masto instance
30let currentAccountApi;
31
32export function initClient({ instance, accessToken }) {
33 if (/^https?:\/\//.test(instance)) {
34 instance = instance
35 .replace(/^https?:\/\//, '')
36 .replace(/\/+$/, '')
37 .toLowerCase();
38 }
39 const url = instance ? `https://${instance}` : `https://${DEFAULT_INSTANCE}`;
40
41 const masto = createRestAPIClient({
42 url,
43 accessToken, // Can be null
44 timeout: 30_000, // Unfortunatly this is global instead of per-request
45 });
46
47 const client = {
48 masto,
49 instance,
50 accessToken,
51 };
52 apis[instance] = client;
53 if (!accountApis[instance]) accountApis[instance] = {};
54 if (accessToken) accountApis[instance][accessToken] = client;
55
56 return client;
57}
58
59// Get the instance information
60// The config is needed for composing
61export async function initInstance(client, instance) {
62 console.log('INIT INSTANCE', client, instance);
63 const { masto, accessToken } = client;
64 // Request v2, fallback to v1 if fail
65 let info;
66 try {
67 info = await masto.v2.instance.fetch();
68 } catch (e) {}
69 if (!info) {
70 try {
71 info = await masto.v1.instance.fetch();
72 } catch (e) {}
73 }
74 if (!info) return;
75 console.log(info);
76 const {
77 // v1
78 uri,
79 urls: { streamingApi } = {},
80 // v2
81 domain,
82 configuration: { urls: { streaming } = {} } = {},
83 } = info;
84 const instances = store.local.getJSON('instances') || {};
85 if (uri || domain) {
86 instances[
87 (domain || uri)
88 .replace(/^https?:\/\//, '')
89 .replace(/\/+$/, '')
90 .toLowerCase()
91 ] = info;
92 }
93 if (instance) {
94 instances[instance.toLowerCase()] = info;
95 }
96 store.local.setJSON('instances', instances);
97 // This is a weird place to put this but here's updating the masto instance with the streaming API URL set in the configuration
98 // Reason: Streaming WebSocket URL may change, unlike the standard API REST URLs
99 const supportsWebSocket = 'WebSocket' in window;
100 if (supportsWebSocket && (streamingApi || streaming)) {
101 console.log('🎏 Streaming API URL:', streaming || streamingApi);
102 // masto.config.props.streamingApiUrl = streaming || streamingApi;
103 // Legacy masto.ws
104 const streamClient = createStreamingAPIClient({
105 streamingApiUrl: streaming || streamingApi,
106 accessToken,
107 implementation: WebSocket,
108 });
109 client.streaming = streamClient;
110 // masto.ws = streamClient;
111 console.log('🎏 Streaming API client:', client);
112 }
113}
114
115// Get the account information and store it
116export async function initAccount(client, instance, accessToken, vapidKey) {
117 const { masto } = client;
118 const mastoAccount = await masto.v1.accounts.verifyCredentials();
119
120 console.log('CURRENTACCOUNT SET', mastoAccount.id);
121 store.session.set('currentAccount', mastoAccount.id);
122
123 saveAccount({
124 info: mastoAccount,
125 instanceURL: instance.toLowerCase(),
126 accessToken,
127 vapidKey,
128 });
129}
130
131// Get preferences
132export async function initPreferences(client) {
133 try {
134 const { masto } = client;
135 const preferences = await masto.v1.preferences.fetch();
136 store.account.set('preferences', preferences);
137 } catch (e) {
138 // silently fail
139 console.error(e);
140 }
141}
142
143// Get the masto instance
144// If accountID is provided, get the masto instance for that account
145export function api({ instance, accessToken, accountID, account } = {}) {
146 // Always lowercase and trim the instance
147 if (instance) {
148 instance = instance.toLowerCase().trim();
149 }
150
151 // If instance and accessToken are provided, get the masto instance for that account
152 if (instance && accessToken) {
153 const client =
154 accountApis[instance]?.[accessToken] ||
155 initClient({ instance, accessToken });
156 const { masto, streaming } = client;
157 return {
158 masto,
159 streaming,
160 client,
161 authenticated: true,
162 instance,
163 };
164 }
165
166 if (accessToken) {
167 // If only accessToken is provided, get the masto instance for that accessToken
168 console.log('X 1', accountApis);
169 for (const instance in accountApis) {
170 if (accountApis[instance][accessToken]) {
171 console.log('X 2', accountApis, instance, accessToken);
172 const client = accountApis[instance][accessToken];
173 const { masto, streaming } = client;
174 return {
175 masto,
176 streaming,
177 client,
178 authenticated: true,
179 instance,
180 };
181 } else {
182 console.log('X 3', accountApis, instance, accessToken);
183 const account = getAccountByAccessToken(accessToken);
184 if (account) {
185 const accessToken = account.accessToken;
186 const instance = account.instanceURL.toLowerCase().trim();
187 const client = initClient({ instance, accessToken });
188 const { masto, streaming } = client;
189 return {
190 masto,
191 streaming,
192 client,
193 authenticated: true,
194 instance,
195 };
196 } else {
197 throw new Error(`Access token not found`);
198 }
199 }
200 }
201 }
202
203 // If account is provided, get the masto instance for that account
204 if (account || accountID) {
205 account = account || getAccount(accountID);
206 if (account) {
207 const accessToken = account.accessToken;
208 const instance = account.instanceURL.toLowerCase().trim();
209 const client =
210 accountApis[instance]?.[accessToken] ||
211 initClient({ instance, accessToken });
212 const { masto, streaming } = client;
213 return {
214 masto,
215 streaming,
216 client,
217 authenticated: true,
218 instance,
219 };
220 } else {
221 throw new Error(`Account ${accountID} not found`);
222 }
223 }
224
225 const currentAccount = getCurrentAccount();
226
227 // If only instance is provided, get the masto instance for that instance
228 if (instance) {
229 if (currentAccountApi?.instance === instance) {
230 return {
231 masto: currentAccountApi.masto,
232 streaming: currentAccountApi.streaming,
233 client: currentAccountApi,
234 authenticated: true,
235 instance,
236 };
237 }
238
239 if (currentAccount?.instanceURL === instance) {
240 const { accessToken } = currentAccount;
241 currentAccountApi =
242 accountApis[instance]?.[accessToken] ||
243 initClient({ instance, accessToken });
244 return {
245 masto: currentAccountApi.masto,
246 streaming: currentAccountApi.streaming,
247 client: currentAccountApi,
248 authenticated: true,
249 instance,
250 };
251 }
252
253 const instanceAccount = getAccountByInstance(instance);
254 if (instanceAccount) {
255 const accessToken = instanceAccount.accessToken;
256 const client =
257 accountApis[instance]?.[accessToken] ||
258 initClient({ instance, accessToken });
259 const { masto, streaming } = client;
260 return {
261 masto,
262 streaming,
263 client,
264 authenticated: true,
265 instance,
266 };
267 }
268
269 const client = apis[instance] || initClient({ instance });
270 const { masto, streaming, accessToken } = client;
271 return {
272 masto,
273 streaming,
274 client,
275 authenticated: !!accessToken,
276 instance,
277 };
278 }
279
280 // If no instance is provided, get the masto instance for the current account
281 if (currentAccountApi) {
282 return {
283 masto: currentAccountApi.masto,
284 streaming: currentAccountApi.streaming,
285 client: currentAccountApi,
286 authenticated: true,
287 instance: currentAccountApi.instance,
288 };
289 }
290 if (currentAccount) {
291 const { accessToken, instanceURL: instance } = currentAccount;
292 currentAccountApi =
293 accountApis[instance]?.[accessToken] ||
294 initClient({ instance, accessToken });
295 return {
296 masto: currentAccountApi.masto,
297 streaming: currentAccountApi.streaming,
298 client: currentAccountApi,
299 authenticated: true,
300 instance,
301 };
302 }
303
304 // If no instance is provided and no account is logged in, get the masto instance for DEFAULT_INSTANCE
305 const client =
306 apis[DEFAULT_INSTANCE] || initClient({ instance: DEFAULT_INSTANCE });
307 const { masto, streaming } = client;
308 return {
309 masto,
310 streaming,
311 client,
312 authenticated: false,
313 instance: DEFAULT_INSTANCE,
314 };
315}
316
317window.__API__ = {
318 currentAccountApi,
319 apis,
320 accountApis,
321};