this repo has no description
at hotfix/infinite-loop-intersection-observer 321 lines 9.2 kB view raw
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};