import { createClient } from 'masto'; import store from './store'; import { getAccount, getCurrentAccount, saveAccount } from './store-utils'; // Default *fallback* instance const DEFAULT_INSTANCE = 'mastodon.social'; // Per-instance masto instance // Useful when only one account is logged in // I'm not sure if I'll ever allow multiple logged-in accounts but oh well... // E.g. apis['mastodon.social'] const apis = {}; // Per-account masto instance // Note: There can be many accounts per instance // Useful when multiple accounts are logged in or when certain actions require a specific account // Just in case if I need this one day. // E.g. accountApis['mastodon.social']['ACCESS_TOKEN'] const accountApis = {}; // Current account masto instance let currentAccountApi; export function initClient({ instance, accessToken }) { if (/^https?:\/\//.test(instance)) { instance = instance .replace(/^https?:\/\//, '') .replace(/\/+$/, '') .toLowerCase(); } const url = instance ? `https://${instance}` : `https://${DEFAULT_INSTANCE}`; const client = createClient({ url, accessToken, // Can be null disableVersionCheck: true, // Allow non-Mastodon instances timeout: 30_000, // Unfortunatly this is global instead of per-request }); client.__instance__ = instance; apis[instance] = client; if (!accountApis[instance]) accountApis[instance] = {}; if (accessToken) accountApis[instance][accessToken] = client; return client; } // Get the instance information // The config is needed for composing export async function initInstance(client) { const masto = client; // Request v2, fallback to v1 if fail let info; try { info = await masto.v2.instance.fetch(); } catch (e) {} if (!info) { try { info = await masto.v1.instances.fetch(); } catch (e) {} } if (!info) return; console.log(info); const { // v1 uri, urls: { streamingApi } = {}, // v2 domain, configuration: { urls: { streaming } = {} } = {}, } = info; if (uri || domain) { const instances = store.local.getJSON('instances') || {}; instances[ (domain || uri) .replace(/^https?:\/\//, '') .replace(/\/+$/, '') .toLowerCase() ] = info; store.local.setJSON('instances', instances); } // This is a weird place to put this but here's updating the masto instance with the streaming API URL set in the configuration // Reason: Streaming WebSocket URL may change, unlike the standard API REST URLs if (streamingApi || streaming) { console.log('🎏 Streaming API URL:', streaming || streamingApi); masto.config.props.streamingApiUrl = streaming || streamingApi; } } // Get the account information and store it export async function initAccount(client, instance, accessToken) { const masto = client; const mastoAccount = await masto.v1.accounts.verifyCredentials(); saveAccount({ info: mastoAccount, instanceURL: instance.toLowerCase(), accessToken, }); } // Get preferences export async function initPreferences(client) { try { const masto = client; const preferences = await masto.v1.preferences.fetch(); store.account.set('preferences', preferences); } catch (e) { // silently fail console.error(e); } } // Get the masto instance // If accountID is provided, get the masto instance for that account export function api({ instance, accessToken, accountID, account } = {}) { // Always lowercase and trim the instance if (instance) { instance = instance.toLowerCase().trim(); } // If instance and accessToken are provided, get the masto instance for that account if (instance && accessToken) { return { masto: accountApis[instance]?.[accessToken] || initClient({ instance, accessToken }), authenticated: true, instance, }; } // If account is provided, get the masto instance for that account if (account || accountID) { account = account || getAccount(accountID); if (account) { const accessToken = account.accessToken; const instance = account.instanceURL.toLowerCase().trim(); return { masto: accountApis[instance]?.[accessToken] || initClient({ instance, accessToken }), authenticated: true, instance, }; } else { throw new Error(`Account ${accountID} not found`); } } // If only instance is provided, get the masto instance for that instance if (instance) { const masto = apis[instance] || initClient({ instance }); return { masto, authenticated: !!masto.config.props.accessToken, instance, }; } // If no instance is provided, get the masto instance for the current account if (currentAccountApi) { return { masto: currentAccountApi, authenticated: true, instance: currentAccountApi.__instance__, }; } const currentAccount = getCurrentAccount(); if (currentAccount) { const { accessToken, instanceURL: instance } = currentAccount; currentAccountApi = accountApis[instance]?.[accessToken] || initClient({ instance, accessToken }); return { masto: currentAccountApi, authenticated: true, instance, }; } // If no instance is provided and no account is logged in, get the masto instance for DEFAULT_INSTANCE return { masto: apis[DEFAULT_INSTANCE] || initClient({ instance: DEFAULT_INSTANCE }), authenticated: false, instance: DEFAULT_INSTANCE, }; } window.__API__ = { currentAccountApi, apis, accountApis, };