phanpy/src/utils/api.js

323 lines
9.0 KiB
JavaScript

import { createRestAPIClient, createStreamingAPIClient } from 'masto';
import store from './store';
import {
getAccount,
getAccountByAccessToken,
getAccountByInstance,
getCurrentAccount,
saveAccount,
setCurrentAccountID,
} 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 = {};
window.__ACCOUNT_APIS__ = 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 masto = createRestAPIClient({
url,
accessToken, // Can be null
timeout: 30_000, // Unfortunatly this is global instead of per-request
});
const client = {
masto,
instance,
accessToken,
};
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, instance) {
console.log('INIT INSTANCE', client, instance);
const { masto, accessToken } = 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.instance.fetch();
} catch (e) {}
}
if (!info) return;
console.log(info);
const {
// v1
uri,
urls: { streamingApi } = {},
// v2
domain,
configuration: { urls: { streaming } = {} } = {},
} = info;
const instances = store.local.getJSON('instances') || {};
if (uri || domain) {
instances[
(domain || uri)
.replace(/^https?:\/\//, '')
.replace(/\/+$/, '')
.toLowerCase()
] = info;
}
if (instance) {
instances[instance.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
const supportsWebSocket = 'WebSocket' in window;
if (supportsWebSocket && (streamingApi || streaming)) {
console.log('🎏 Streaming API URL:', streaming || streamingApi);
// masto.config.props.streamingApiUrl = streaming || streamingApi;
// Legacy masto.ws
const streamClient = createStreamingAPIClient({
streamingApiUrl: streaming || streamingApi,
accessToken,
implementation: WebSocket,
});
client.streaming = streamClient;
// masto.ws = streamClient;
console.log('🎏 Streaming API client:', client);
}
}
// Get the account information and store it
export async function initAccount(client, instance, accessToken, vapidKey) {
const { masto } = client;
const mastoAccount = await masto.v1.accounts.verifyCredentials();
console.log('CURRENTACCOUNT SET', mastoAccount.id);
setCurrentAccountID(mastoAccount.id);
saveAccount({
info: mastoAccount,
instanceURL: instance.toLowerCase(),
accessToken,
vapidKey,
});
}
// 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) {
const client =
accountApis[instance]?.[accessToken] ||
initClient({ instance, accessToken });
const { masto, streaming } = client;
return {
masto,
streaming,
client,
authenticated: true,
instance,
};
}
if (accessToken) {
// If only accessToken is provided, get the masto instance for that accessToken
console.log('X 1', accountApis);
for (const instance in accountApis) {
if (accountApis[instance][accessToken]) {
console.log('X 2', accountApis, instance, accessToken);
const client = accountApis[instance][accessToken];
const { masto, streaming } = client;
return {
masto,
streaming,
client,
authenticated: true,
instance,
};
} else {
console.log('X 3', accountApis, instance, accessToken);
const account = getAccountByAccessToken(accessToken);
if (account) {
const accessToken = account.accessToken;
const instance = account.instanceURL.toLowerCase().trim();
const client = initClient({ instance, accessToken });
const { masto, streaming } = client;
return {
masto,
streaming,
client,
authenticated: true,
instance,
};
} else {
throw new Error(`Access token not found`);
}
}
}
}
// 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();
const client =
accountApis[instance]?.[accessToken] ||
initClient({ instance, accessToken });
const { masto, streaming } = client;
return {
masto,
streaming,
client,
authenticated: true,
instance,
};
} else {
throw new Error(`Account ${accountID} not found`);
}
}
const currentAccount = getCurrentAccount();
// If only instance is provided, get the masto instance for that instance
if (instance) {
if (currentAccountApi?.instance === instance) {
return {
masto: currentAccountApi.masto,
streaming: currentAccountApi.streaming,
client: currentAccountApi,
authenticated: true,
instance,
};
}
if (currentAccount?.instanceURL === instance) {
const { accessToken } = currentAccount;
currentAccountApi =
accountApis[instance]?.[accessToken] ||
initClient({ instance, accessToken });
return {
masto: currentAccountApi.masto,
streaming: currentAccountApi.streaming,
client: currentAccountApi,
authenticated: true,
instance,
};
}
const instanceAccount = getAccountByInstance(instance);
if (instanceAccount) {
const accessToken = instanceAccount.accessToken;
const client =
accountApis[instance]?.[accessToken] ||
initClient({ instance, accessToken });
const { masto, streaming } = client;
return {
masto,
streaming,
client,
authenticated: true,
instance,
};
}
const client = apis[instance] || initClient({ instance });
const { masto, streaming, accessToken } = client;
return {
masto,
streaming,
client,
authenticated: !!accessToken,
instance,
};
}
// If no instance is provided, get the masto instance for the current account
if (currentAccountApi) {
return {
masto: currentAccountApi.masto,
streaming: currentAccountApi.streaming,
client: currentAccountApi,
authenticated: true,
instance: currentAccountApi.instance,
};
}
if (currentAccount) {
const { accessToken, instanceURL: instance } = currentAccount;
currentAccountApi =
accountApis[instance]?.[accessToken] ||
initClient({ instance, accessToken });
return {
masto: currentAccountApi.masto,
streaming: currentAccountApi.streaming,
client: currentAccountApi,
authenticated: true,
instance,
};
}
// If no instance is provided and no account is logged in, get the masto instance for DEFAULT_INSTANCE
const client =
apis[DEFAULT_INSTANCE] || initClient({ instance: DEFAULT_INSTANCE });
const { masto, streaming } = client;
return {
masto,
streaming,
client,
authenticated: false,
instance: DEFAULT_INSTANCE,
};
}
window.__API__ = {
currentAccountApi,
apis,
accountApis,
};