Merge pull request #176 from h3poteto/iss-173

refs #173 Use namespace for mastodon API client
This commit is contained in:
AkiraFukushima 2020-02-07 01:15:20 +09:00 committed by GitHub
commit a9bd8c431d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 464 additions and 470 deletions

View file

@ -1,6 +1,6 @@
import API from './mastodon/api_client'
import StreamListener, { StreamListenerError } from './mastodon/stream_listener'
import WebSocket from './mastodon/web_socket'
import MastodonAPI from './mastodon/api_client'
import StreamListener from './stream_listener'
import WebSocket from './web_socket'
import Response from './response'
import OAuth from './oauth'
import { isCancel, RequestCanceledError } from './cancel'
@ -41,8 +41,8 @@ import { Token } from './entities/token'
import { URLs } from './entities/urls'
export {
MastodonAPI,
StreamListener,
StreamListenerError,
WebSocket,
Response,
OAuth,
@ -85,4 +85,4 @@ export {
URLs
}
export default API
export default MastodonAPI.Client

View file

@ -1,7 +1,10 @@
import APIClient from './mastodon/api_client'
import MastodonAPI from './mastodon/api_client'
import { ProxyConfig } from './proxy_config'
import OAuth from './oauth'
import Response from './response'
import StreamListener from './stream_listener'
import WebSocket from './web_socket'
import MegalodonInterface, { NoImplementedError } from './megalodon'
import { Application } from './entities/application'
import { Account } from './entities/account'
import { Status } from './entities/status'
@ -26,9 +29,6 @@ import { Activity } from './entities/activity'
import { Emoji } from './entities/emoji'
import { PushSubscription } from './entities/push_subscription'
import { Token } from './entities/token'
import StreamListener from './mastodon/stream_listener'
import WebSocket from './mastodon/web_socket'
import MegalodonInterface, { NoImplementedError } from './megalodon'
const NO_REDIRECT = 'urn:ietf:wg:oauth:2.0:oob'
const DEFAULT_URL = 'https://mastodon.social'
@ -36,7 +36,7 @@ const DEFAULT_SCOPE = 'read write follow'
const DEFAULT_UA = 'megalodon'
export default class Mastodon implements MegalodonInterface {
public client: APIClient
public client: MastodonAPI.Client
/**
* @param accessToken access token from OAuth2 authorization
@ -50,7 +50,7 @@ export default class Mastodon implements MegalodonInterface {
userAgent: string = DEFAULT_UA,
proxyConfig: ProxyConfig | false = false
) {
this.client = new APIClient(accessToken, baseUrl, userAgent, proxyConfig)
this.client = new MastodonAPI.Client(accessToken, baseUrl, userAgent, proxyConfig)
}
/**
@ -70,7 +70,7 @@ export default class Mastodon implements MegalodonInterface {
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.AppData> {
return APIClient.registerApp(clientName, options, baseUrl, proxyConfig)
return MastodonAPI.Client.registerApp(clientName, options, baseUrl, proxyConfig)
}
/**
@ -91,7 +91,7 @@ export default class Mastodon implements MegalodonInterface {
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.AppData> {
return APIClient.createApp(client_name, options, baseUrl, proxyConfig)
return MastodonAPI.Client.createApp(client_name, options, baseUrl, proxyConfig)
}
/**
@ -111,7 +111,7 @@ export default class Mastodon implements MegalodonInterface {
},
baseUrl = DEFAULT_URL
): Promise<string> {
return APIClient.generateAuthUrl(clientId, clientSecret, options, baseUrl)
return MastodonAPI.Client.generateAuthUrl(clientId, clientSecret, options, baseUrl)
}
// ======================================
@ -145,7 +145,7 @@ export default class Mastodon implements MegalodonInterface {
redirect_uri = NO_REDIRECT,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.TokenData> {
return APIClient.fetchAccessToken(client_id, client_secret, code, baseUrl, redirect_uri, proxyConfig)
return MastodonAPI.Client.fetchAccessToken(client_id, client_secret, code, baseUrl, redirect_uri, proxyConfig)
}
/**
@ -166,7 +166,7 @@ export default class Mastodon implements MegalodonInterface {
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.TokenData> {
return APIClient.refreshToken(client_id, client_secret, refresh_token, baseUrl, proxyConfig)
return MastodonAPI.Client.refreshToken(client_id, client_secret, refresh_token, baseUrl, proxyConfig)
}
/**
@ -186,7 +186,7 @@ export default class Mastodon implements MegalodonInterface {
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<Response<{}>> {
return APIClient.post<{}>(
return MastodonAPI.Client.post<{}>(
'/oauth/revoke',
{
client_id,
@ -1468,21 +1468,21 @@ export default class Mastodon implements MegalodonInterface {
* GET /api/v1/instance
*/
public static getInstance(): Promise<Response<Instance>> {
return APIClient.get<Instance>('/api/v1/instance')
return MastodonAPI.Client.get<Instance>('/api/v1/instance')
}
/**
* GET /api/v1/instance/peers
*/
public static getInstancePeers(): Promise<Response<Array<string>>> {
return APIClient.get<Array<string>>('/api/v1/instance/peers')
return MastodonAPI.Client.get<Array<string>>('/api/v1/instance/peers')
}
/**
* GET /api/v1/instance/activity
*/
public static getInstanceActivity(): Promise<Response<Array<Activity>>> {
return APIClient.get<Array<Activity>>('/api/v1/instance/activity')
return MastodonAPI.Client.get<Array<Activity>>('/api/v1/instance/activity')
}
// ======================================
@ -1500,7 +1500,7 @@ export default class Mastodon implements MegalodonInterface {
limit
})
}
return APIClient.get<Array<Tag>>('/api/v1/trends', params)
return MastodonAPI.Client.get<Array<Tag>>('/api/v1/trends', params)
}
// ======================================
@ -1542,7 +1542,7 @@ export default class Mastodon implements MegalodonInterface {
local
})
}
return APIClient.get<Array<Account>>('/api/v1/directory', params)
return MastodonAPI.Client.get<Array<Account>>('/api/v1/directory', params)
}
// ======================================
@ -1554,7 +1554,7 @@ export default class Mastodon implements MegalodonInterface {
* @return Array of emojis.
*/
public static getInstanceCustomEmojis(): Promise<Response<Array<Emoji>>> {
return APIClient.get<Array<Emoji>>('/api/v1/custom_emojis')
return MastodonAPI.Client.get<Array<Emoji>>('/api/v1/custom_emojis')
}
// ======================================

View file

@ -1,8 +1,8 @@
import axios, { AxiosResponse, CancelTokenSource, AxiosRequestConfig } from 'axios'
import { OAuth2 } from 'oauth'
import StreamListener from './stream_listener'
import WebSocket from './web_socket'
import StreamListener from '../stream_listener'
import WebSocket from '../web_socket'
import Response from '../response'
import { RequestCanceledError } from '../cancel'
import proxyAgent, { ProxyConfig } from '../proxy_config'
@ -13,261 +13,202 @@ const DEFAULT_URL = 'https://mastodon.social'
const DEFAULT_SCOPE = 'read write follow'
const DEFAULT_UA = 'megalodon'
/**
* Interface
*/
export interface ApiInterface {
get<T = any>(path: string, params: object): Promise<Response<T>>
put<T = any>(path: string, params: object): Promise<Response<T>>
patch<T = any>(path: string, params: object): Promise<Response<T>>
post<T = any>(path: string, params: object): Promise<Response<T>>
del(path: string, params: object): Promise<Response<{}>>
cancel(): void
stream(path: string, reconnectInterval: number): StreamListener
socket(path: string, strea: string): WebSocket
}
/**
* Mastodon API client.
*
* Using axios for request, you will handle promises.
*/
export default class MastodonAPIClient implements ApiInterface {
static DEFAULT_SCOPE = DEFAULT_SCOPE
static DEFAULT_URL = DEFAULT_URL
static NO_REDIRECT = NO_REDIRECT
private accessToken: string
private baseUrl: string
private userAgent: string
private cancelTokenSource: CancelTokenSource
private proxyConfig: ProxyConfig | false = false
namespace MastodonAPI {
/**
* @param accessToken access token from OAuth2 authorization
* @param baseUrl hostname or base URL
* @param userAgent UserAgent is specified in header on request.
* @param proxyConfig Proxy setting, or set false if don't use proxy.
* Interface
*/
constructor(
accessToken: string,
baseUrl: string = DEFAULT_URL,
userAgent: string = DEFAULT_UA,
proxyConfig: ProxyConfig | false = false
) {
this.accessToken = accessToken
this.baseUrl = baseUrl
this.userAgent = userAgent
this.cancelTokenSource = axios.CancelToken.source()
this.proxyConfig = proxyConfig
export interface Interface {
get<T = any>(path: string, params: object): Promise<Response<T>>
put<T = any>(path: string, params: object): Promise<Response<T>>
patch<T = any>(path: string, params: object): Promise<Response<T>>
post<T = any>(path: string, params: object): Promise<Response<T>>
del(path: string, params: object): Promise<Response<{}>>
cancel(): void
stream(path: string, reconnectInterval: number): StreamListener
socket(path: string, strea: string): WebSocket
}
/**
* TODO: We have to move these methods to mastodon.ts
* Mastodon API client.
*
* Using axios for request, you will handle promises.
*/
public static async registerApp(
clientName: string,
options: Partial<{ scopes: string; redirect_uris: string; website: string }> = {
scopes: DEFAULT_SCOPE,
redirect_uris: NO_REDIRECT
},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.AppData> {
return this.createApp(clientName, options, baseUrl, proxyConfig).then(async appData => {
return this.generateAuthUrl(
appData.client_id,
appData.client_secret,
export class Client implements Interface {
static DEFAULT_SCOPE = DEFAULT_SCOPE
static DEFAULT_URL = DEFAULT_URL
static NO_REDIRECT = NO_REDIRECT
private accessToken: string
private baseUrl: string
private userAgent: string
private cancelTokenSource: CancelTokenSource
private proxyConfig: ProxyConfig | false = false
/**
* @param accessToken access token from OAuth2 authorization
* @param baseUrl hostname or base URL
* @param userAgent UserAgent is specified in header on request.
* @param proxyConfig Proxy setting, or set false if don't use proxy.
*/
constructor(
accessToken: string,
baseUrl: string = DEFAULT_URL,
userAgent: string = DEFAULT_UA,
proxyConfig: ProxyConfig | false = false
) {
this.accessToken = accessToken
this.baseUrl = baseUrl
this.userAgent = userAgent
this.cancelTokenSource = axios.CancelToken.source()
this.proxyConfig = proxyConfig
}
/**
* TODO: We have to move these methods to mastodon.ts
*/
public static async registerApp(
clientName: string,
options: Partial<{ scopes: string; redirect_uris: string; website: string }> = {
scopes: DEFAULT_SCOPE,
redirect_uris: NO_REDIRECT
},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.AppData> {
return this.createApp(clientName, options, baseUrl, proxyConfig).then(async appData => {
return this.generateAuthUrl(
appData.client_id,
appData.client_secret,
{
redirect_uri: appData.redirect_uri,
scope: options.scopes
},
baseUrl
).then(url => {
appData.url = url
return appData
})
})
}
public static async createApp(
client_name: string,
options: Partial<{ redirect_uris: string; scopes: string; website: string }> = {
redirect_uris: NO_REDIRECT,
scopes: DEFAULT_SCOPE
},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.AppData> {
const redirect_uris = options.redirect_uris || NO_REDIRECT
const scopes = options.scopes || DEFAULT_SCOPE
const params: {
client_name: string
redirect_uris: string
scopes: string
website?: string
} = {
client_name,
redirect_uris,
scopes
}
if (options.website) params.website = options.website
return this.post<OAuth.AppDataFromServer>(
'/api/v1/apps',
params,
baseUrl,
proxyConfig
).then((res: Response<OAuth.AppDataFromServer>) => OAuth.AppData.from(res.data))
}
public static generateAuthUrl(
clientId: string,
clientSecret: string,
options: Partial<{ redirect_uri: string; scope: string }> = {
redirect_uri: NO_REDIRECT,
scope: DEFAULT_SCOPE
},
baseUrl = DEFAULT_URL
): Promise<string> {
return new Promise(resolve => {
const oauth = new OAuth2(clientId, clientSecret, baseUrl, undefined, '/oauth/token')
const url = oauth.getAuthorizeUrl({
redirect_uri: options.redirect_uri,
response_type: 'code',
client_id: clientId,
scope: options.scope
})
resolve(url)
})
}
public static async fetchAccessToken(
client_id: string,
client_secret: string,
code: string,
baseUrl = DEFAULT_URL,
redirect_uri = NO_REDIRECT,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.TokenData> {
return this.post<OAuth.TokenDataFromServer>(
'/oauth/token',
{
redirect_uri: appData.redirect_uri,
scope: options.scopes
client_id,
client_secret,
code,
redirect_uri,
grant_type: 'authorization_code'
},
baseUrl
).then(url => {
appData.url = url
return appData
})
})
}
public static async createApp(
client_name: string,
options: Partial<{ redirect_uris: string; scopes: string; website: string }> = {
redirect_uris: NO_REDIRECT,
scopes: DEFAULT_SCOPE
},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.AppData> {
const redirect_uris = options.redirect_uris || NO_REDIRECT
const scopes = options.scopes || DEFAULT_SCOPE
const params: {
client_name: string
redirect_uris: string
scopes: string
website?: string
} = {
client_name,
redirect_uris,
scopes
baseUrl,
proxyConfig
).then((res: Response<OAuth.TokenDataFromServer>) => OAuth.TokenData.from(res.data))
}
if (options.website) params.website = options.website
return this.post<OAuth.AppDataFromServer>('/api/v1/apps', params, baseUrl, proxyConfig).then((res: Response<OAuth.AppDataFromServer>) =>
OAuth.AppData.from(res.data)
)
}
public static generateAuthUrl(
clientId: string,
clientSecret: string,
options: Partial<{ redirect_uri: string; scope: string }> = {
redirect_uri: NO_REDIRECT,
scope: DEFAULT_SCOPE
},
baseUrl = DEFAULT_URL
): Promise<string> {
return new Promise(resolve => {
const oauth = new OAuth2(clientId, clientSecret, baseUrl, undefined, '/oauth/token')
const url = oauth.getAuthorizeUrl({
redirect_uri: options.redirect_uri,
response_type: 'code',
client_id: clientId,
scope: options.scope
})
resolve(url)
})
}
public static async fetchAccessToken(
client_id: string,
client_secret: string,
code: string,
baseUrl = DEFAULT_URL,
redirect_uri = NO_REDIRECT,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.TokenData> {
return this.post<OAuth.TokenDataFromServer>(
'/oauth/token',
{
client_id,
client_secret,
code,
redirect_uri,
grant_type: 'authorization_code'
},
baseUrl,
proxyConfig
).then((res: Response<OAuth.TokenDataFromServer>) => OAuth.TokenData.from(res.data))
}
public static async refreshToken(
client_id: string,
client_secret: string,
refresh_token: string,
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.TokenData> {
return this.post<OAuth.TokenDataFromServer>(
'/oauth/token',
{
client_id,
client_secret,
refresh_token,
grant_type: 'refresh_token'
},
baseUrl,
proxyConfig
).then((res: Response<OAuth.TokenDataFromServer>) => OAuth.TokenData.from(res.data))
}
/**
* Unauthorized GET request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Query parameters
* @param baseUrl base URL of the target
* @param proxyConfig Proxy setting, or set false if don't use proxy.
*/
public static async get<T>(
path: string,
params = {},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<Response<T>> {
const apiUrl = baseUrl
let options: AxiosRequestConfig = {
params: params
public static async refreshToken(
client_id: string,
client_secret: string,
refresh_token: string,
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<OAuth.TokenData> {
return this.post<OAuth.TokenDataFromServer>(
'/oauth/token',
{
client_id,
client_secret,
refresh_token,
grant_type: 'refresh_token'
},
baseUrl,
proxyConfig
).then((res: Response<OAuth.TokenDataFromServer>) => OAuth.TokenData.from(res.data))
}
if (proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(proxyConfig)
})
}
return axios.get<T>(apiUrl + path, options).then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
/**
* Unauthorized GET request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Query parameters
* @param baseUrl base URL of the target
* @param proxyConfig Proxy setting, or set false if don't use proxy.
*/
public static async get<T>(
path: string,
params = {},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<Response<T>> {
const apiUrl = baseUrl
let options: AxiosRequestConfig = {
params: params
}
return res
})
}
public static async post<T>(
path: string,
params = {},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<Response<T>> {
let options: AxiosRequestConfig = {}
if (proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(proxyConfig)
})
}
const apiUrl = baseUrl
return axios.post<T>(apiUrl + path, params, options).then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
if (proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(proxyConfig)
})
}
return res
})
}
/**
* GET request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Query parameters
*/
public async get<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
},
params: params
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.get<T>(this.baseUrl + path, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
}
})
.then((resp: AxiosResponse<T>) => {
return axios.get<T>(apiUrl + path, options).then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
@ -276,35 +217,22 @@ export default class MastodonAPIClient implements ApiInterface {
}
return res
})
}
}
/**
* PUT request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data. If you want to post file, please use FormData()
*/
public async put<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
public static async post<T>(
path: string,
params = {},
baseUrl = DEFAULT_URL,
proxyConfig: ProxyConfig | false = false
): Promise<Response<T>> {
let options: AxiosRequestConfig = {}
if (proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(proxyConfig)
})
}
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.put<T>(this.baseUrl + path, params, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
}
})
.then((resp: AxiosResponse<T>) => {
const apiUrl = baseUrl
return axios.post<T>(apiUrl + path, params, options).then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
@ -313,35 +241,138 @@ export default class MastodonAPIClient implements ApiInterface {
}
return res
})
}
}
/**
* PATCH request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data. If you want to post file, please use FormData()
*/
public async patch<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
/**
* GET request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Query parameters
*/
public async get<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
},
params: params
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.get<T>(this.baseUrl + path, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
}
})
.then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
}
return res
})
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.patch<T>(this.baseUrl + path, params, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
/**
* PUT request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data. If you want to post file, please use FormData()
*/
public async put<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
})
.then((resp: AxiosResponse<T>) => {
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.put<T>(this.baseUrl + path, params, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
}
})
.then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
}
return res
})
}
/**
* PATCH request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data. If you want to post file, please use FormData()
*/
public async patch<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.patch<T>(this.baseUrl + path, params, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
}
})
.then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
}
return res
})
}
/**
* POST request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data
*/
public async post<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios.post<T>(this.baseUrl + path, params, options).then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
@ -350,119 +381,93 @@ export default class MastodonAPIClient implements ApiInterface {
}
return res
})
}
}
/**
* POST request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data
*/
public async post<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios.post<T>(this.baseUrl + path, params, options).then((resp: AxiosResponse<T>) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
}
return res
})
}
/**
* DELETE request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data
*/
public async del<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
data: params,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.delete(this.baseUrl + path, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
/**
* DELETE request to mastodon REST API.
* @param path relative path from baseUrl
* @param params Form data
*/
public async del<T>(path: string, params = {}): Promise<Response<T>> {
let options: AxiosRequestConfig = {
cancelToken: this.cancelTokenSource.token,
data: params,
headers: {
Authorization: `Bearer ${this.accessToken}`
}
})
.then((resp: AxiosResponse) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
}
return res
})
}
/**
* Cancel all requests in this instance.
* @returns void
*/
public cancel(): void {
return this.cancelTokenSource.cancel('Request is canceled by user')
}
/**
* Receive Server-sent Events from Mastodon Streaming API.
* Create streaming connection, and start streamin.
*
* @param path relative path from baseUrl
* @param reconnectInterval interval of reconnect
* @returns streamListener, which inherits from EventEmitter
*/
public stream(path: string, reconnectInterval = 1000): StreamListener {
const headers = {
'Cache-Control': 'no-cache',
Accept: 'text/event-stream',
'Content-Type': 'text/event-stream',
Authorization: `Bearer ${this.accessToken}`,
'User-Agent': this.userAgent
}
if (this.proxyConfig) {
options = Object.assign(options, {
httpsAgent: proxyAgent(this.proxyConfig)
})
}
return axios
.delete(this.baseUrl + path, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
} else {
throw err
}
})
.then((resp: AxiosResponse) => {
const res: Response<T> = {
data: resp.data,
status: resp.status,
statusText: resp.statusText,
headers: resp.headers
}
return res
})
}
const url = this.baseUrl + path + `?access_token=${this.accessToken}`
const streaming = new StreamListener(url, headers, this.proxyConfig, reconnectInterval)
process.nextTick(() => {
streaming.start()
})
return streaming
}
/**
* Get connection and receive websocket connection for Pleroma API.
*
* @param path relative path from baseUrl: normally it is `/streaming`.
* @param stream Stream name, please refer: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/mastodon_api/mastodon_socket.ex#L19-28
* @returns WebSocket, which inherits from EventEmitter
*/
public socket(path: string, stream: string, params: string | null = null): WebSocket {
const url = this.baseUrl + path
const streaming = new WebSocket(url, stream, params, this.accessToken, this.userAgent, this.proxyConfig)
process.nextTick(() => {
streaming.start()
})
return streaming
/**
* Cancel all requests in this instance.
* @returns void
*/
public cancel(): void {
return this.cancelTokenSource.cancel('Request is canceled by user')
}
/**
* Receive Server-sent Events from Mastodon Streaming API.
* Create streaming connection, and start streamin.
*
* @param path relative path from baseUrl
* @param reconnectInterval interval of reconnect
* @returns streamListener, which inherits from EventEmitter
*/
public stream(path: string, reconnectInterval = 1000): StreamListener {
const headers = {
'Cache-Control': 'no-cache',
Accept: 'text/event-stream',
'Content-Type': 'text/event-stream',
Authorization: `Bearer ${this.accessToken}`,
'User-Agent': this.userAgent
}
const url = this.baseUrl + path + `?access_token=${this.accessToken}`
const streaming = new StreamListener(url, headers, this.proxyConfig, reconnectInterval)
process.nextTick(() => {
streaming.start()
})
return streaming
}
/**
* Get connection and receive websocket connection for Pleroma API.
*
* @param path relative path from baseUrl: normally it is `/streaming`.
* @param stream Stream name, please refer: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/mastodon_api/mastodon_socket.ex#L19-28
* @returns WebSocket, which inherits from EventEmitter
*/
public socket(path: string, stream: string, params: string | null = null): WebSocket {
const url = this.baseUrl + path
const streaming = new WebSocket(url, stream, params, this.accessToken, this.userAgent, this.proxyConfig)
process.nextTick(() => {
streaming.start()
})
return streaming
}
}
}
export default MastodonAPI

View file

@ -1,3 +1,5 @@
import StreamListener from './stream_listener'
import WebSocket from './web_socket'
import Response from './response'
import { Application } from './entities/application'
import { Account } from './entities/account'
@ -20,8 +22,6 @@ import { Notification } from './entities/notification'
import { Results } from './entities/results'
import { PushSubscription } from './entities/push_subscription'
import { Token } from './entities/token'
import StreamListener from './mastodon/stream_listener'
import WebSocket from './mastodon/web_socket'
export default interface MegalodonInterface {
// ======================================

View file

@ -1,13 +1,13 @@
import { EventEmitter } from 'events'
import { Status } from '../entities/status'
import { Notification } from '../entities/notification'
import { Conversation } from '../entities/conversation'
import { Status } from './entities/status'
import { Notification } from './entities/notification'
import { Conversation } from './entities/conversation'
/**
* Parser
* Parse response data in streaming.
**/
class Parser extends EventEmitter {
export class Parser extends EventEmitter {
private message: string
constructor() {
@ -86,5 +86,3 @@ class Parser extends EventEmitter {
this.message = chunk.slice(start, size)
}
}
export default Parser

View file

@ -1,6 +1,6 @@
import MegalodonInterface, { NoImplementedError } from './megalodon'
import Mastodon from './mastodon'
import StreamListener from './mastodon/stream_listener'
import StreamListener from './stream_listener'
import Response from './response'
import { Status } from './entities/status'
import { Relationship } from './entities/relationship'

View file

@ -1,7 +0,0 @@
import MastodonAPIClient, { ApiInterface } from '../mastodon/api_client'
const PleromaAPIClient = MastodonAPIClient
export { ApiInterface }
export default PleromaAPIClient

View file

@ -1,11 +1,11 @@
import { EventEmitter } from 'events'
import axios, { CancelTokenSource, AxiosRequestConfig } from 'axios'
import httpAdapter from 'axios/lib/adapters/http'
import Parser from './parser'
import { Status } from '../entities/status'
import { Notification } from '../entities/notification'
import { Conversation } from '../entities/conversation'
import proxyAgent, { ProxyConfig } from '../proxy_config'
import { Parser } from './parser'
import { Status } from './entities/status'
import { Notification } from './entities/notification'
import { Conversation } from './entities/conversation'
import proxyAgent, { ProxyConfig } from './proxy_config'
const STATUS_CODES_TO_ABORT_ON: Array<number> = [400, 401, 403, 404, 406, 410, 422]
@ -24,7 +24,7 @@ export class StreamListenerError extends Error {
* EventStream
* Listener of text/event-stream. It receives data, and parse when streaming.
*/
class StreamListener extends EventEmitter {
export default class StreamListener extends EventEmitter {
public url: string
public headers: object
public parser: Parser
@ -185,5 +185,3 @@ class StreamListener extends EventEmitter {
})
}
}
export default StreamListener

View file

@ -1,10 +1,10 @@
import WS from 'ws'
import moment, { Moment } from 'moment'
import { EventEmitter } from 'events'
import { Status } from '../entities/status'
import { Notification } from '../entities/notification'
import { Conversation } from '../entities/conversation'
import proxyAgent, { ProxyConfig } from '../proxy_config'
import { Status } from './entities/status'
import { Notification } from './entities/notification'
import { Conversation } from './entities/conversation'
import proxyAgent, { ProxyConfig } from './proxy_config'
/**
* WebSocket

View file

@ -1,4 +1,4 @@
import APIClient from '@/mastodon/api_client'
import MastodonAPI from '@/mastodon/api_client'
import Worker from 'jest-worker'
jest.mock('axios', () => {
@ -20,7 +20,7 @@ jest.mock('axios', () => {
return mockAxios
})
const worker = async (client: APIClient) => {
const worker = async (client: MastodonAPI.Client) => {
const w: any = new Worker(require.resolve('./cancelWorker.ts'))
await w.cancel(client)
}
@ -29,7 +29,7 @@ const worker = async (client: APIClient) => {
// I'm waiting for resolve this issue.
// https://github.com/facebook/jest/issues/8872
describe.skip('cancel', () => {
const client = new APIClient('testToken', 'https://pleroma.io/api/v1')
const client = new MastodonAPI.Client('testToken', 'https://pleroma.io/api/v1')
it('should be raised', async () => {
const getPromise = client.get<{}>('/timelines/home')
worker(client)

View file

@ -1,5 +1,5 @@
import APIClient from '@/mastodon/api_client'
import MastodonAPI from '@/mastodon/api_client'
export function cancel(client: APIClient) {
export function cancel(client: MastodonAPI.Client) {
return client.cancel()
}

View file

@ -1,4 +1,4 @@
import APIClient from '@/mastodon/api_client'
import MastodonAPI from '@/mastodon/api_client'
import { Account } from '@/entities/account'
import { Status } from '@/entities/status'
import { Application } from '@/entities/application'
@ -67,7 +67,7 @@ const status: Status = {
})
describe('get', () => {
const client = new APIClient('testToken', 'https://pleroma.io/api/v1')
const client = new MastodonAPI.Client('testToken', 'https://pleroma.io/api/v1')
const mockResponse: AxiosResponse<Array<Status>> = {
data: [status],
status: 200,
@ -83,7 +83,7 @@ describe('get', () => {
})
describe('put', () => {
const client = new APIClient('testToken', 'https://pleroma.io/api/v1')
const client = new MastodonAPI.Client('testToken', 'https://pleroma.io/api/v1')
const mockResponse: AxiosResponse<Account> = {
data: account,
status: 200,
@ -101,7 +101,7 @@ describe('put', () => {
})
describe('patch', () => {
const client = new APIClient('testToken', 'https://pleroma.io/api/v1')
const client = new MastodonAPI.Client('testToken', 'https://pleroma.io/api/v1')
const mockResponse: AxiosResponse<Account> = {
data: account,
status: 200,
@ -119,7 +119,7 @@ describe('patch', () => {
})
describe('post', () => {
const client = new APIClient('testToken', 'https://pleroma.io/api/v1')
const client = new MastodonAPI.Client('testToken', 'https://pleroma.io/api/v1')
const mockResponse: AxiosResponse<Status> = {
data: status,
status: 200,
@ -137,7 +137,7 @@ describe('post', () => {
})
describe('del', () => {
const client = new APIClient('testToken', 'https://pleroma.io/api/v1')
const client = new MastodonAPI.Client('testToken', 'https://pleroma.io/api/v1')
const mockResponse: AxiosResponse<{}> = {
data: {},
status: 200,

View file

@ -1,4 +1,4 @@
import Parser from '@/mastodon/parser'
import { Parser } from '@/parser'
import { Account } from '@/entities/account'
import { Status } from '@/entities/status'
import { Application } from '@/entities/application'

View file

@ -1,4 +1,4 @@
import { Parser } from '@/mastodon/web_socket'
import { Parser } from '@/web_socket'
import { Account } from '@/entities/account'
import { Status } from '@/entities/status'
import { Application } from '@/entities/application'