From 4d143639cb52c8ff9e3ddfca62ab8b415f7887d0 Mon Sep 17 00:00:00 2001
From: AkiraFukushima
Date: Fri, 18 Oct 2019 22:45:05 +0900
Subject: [PATCH] refs #69 Add support proxy connection for REST endpoint
---
example/typescript/proxy_instance.ts | 21 +++
example/typescript/proxy_timeline.ts | 26 ++++
example/typescript/timeline.ts | 2 +-
example/typescript/yarn.lock | 35 +++++
package.json | 1 +
src/index.ts | 3 +-
src/mastodon.ts | 189 +++++++++++++++++++--------
yarn.lock | 29 +++-
8 files changed, 246 insertions(+), 60 deletions(-)
create mode 100644 example/typescript/proxy_instance.ts
create mode 100644 example/typescript/proxy_timeline.ts
diff --git a/example/typescript/proxy_instance.ts b/example/typescript/proxy_instance.ts
new file mode 100644
index 0000000..60a669e
--- /dev/null
+++ b/example/typescript/proxy_instance.ts
@@ -0,0 +1,21 @@
+import Mastodon, { Instance, ProxyConfig } from 'megalodon'
+
+declare var process: {
+ env: {
+ PROXY_HOST: string
+ PROXY_PORT: number
+ PROXY_PROTOCOL: string
+ }
+}
+
+const BASE_URL: string = 'http://mastodon.social'
+
+const proxy: ProxyConfig = {
+ host: process.env.PROXY_HOST,
+ port: process.env.PROXY_PORT,
+ protocol: process.env.PROXY_PROTOCOL
+}
+
+Mastodon.get('/api/v1/instance', {}, BASE_URL, proxy).then(res => {
+ console.log(res)
+})
diff --git a/example/typescript/proxy_timeline.ts b/example/typescript/proxy_timeline.ts
new file mode 100644
index 0000000..cdfd514
--- /dev/null
+++ b/example/typescript/proxy_timeline.ts
@@ -0,0 +1,26 @@
+import Mastodon, { Status, Response, ProxyConfig } from 'megalodon'
+
+declare var process: {
+ env: {
+ MASTODON_ACCESS_TOKEN: string
+ PROXY_HOST: string
+ PROXY_PORT: number
+ PROXY_PROTOCOL: string
+ }
+}
+
+const BASE_URL: string = 'https://mastodon.social'
+
+const access_token: string = process.env.MASTODON_ACCESS_TOKEN
+
+const proxy: ProxyConfig = {
+ host: process.env.PROXY_HOST,
+ port: process.env.PROXY_PORT,
+ protocol: process.env.PROXY_PROTOCOL
+}
+
+const client = new Mastodon(access_token, BASE_URL + '/api/v1', 'megalodon', proxy)
+
+client.get>('/timelines/public').then((resp: Response>) => {
+ console.log(resp.data)
+})
diff --git a/example/typescript/timeline.ts b/example/typescript/timeline.ts
index aa0a83a..026933f 100644
--- a/example/typescript/timeline.ts
+++ b/example/typescript/timeline.ts
@@ -12,6 +12,6 @@ const access_token: string = process.env.MASTODON_ACCESS_TOKEN
const client = new Mastodon(access_token, BASE_URL + '/api/v1')
-client.get>('/timelines/home').then((resp: Response>) => {
+client.get>('/timelines/public').then((resp: Response>) => {
console.log(resp.data)
})
diff --git a/example/typescript/yarn.lock b/example/typescript/yarn.lock
index f76cbfa..6bd7cf5 100644
--- a/example/typescript/yarn.lock
+++ b/example/typescript/yarn.lock
@@ -48,6 +48,13 @@
dependencies:
"@types/node" "*"
+agent-base@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+ integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+ dependencies:
+ es6-promisify "^5.0.0"
+
ajv@^5.3.0:
version "5.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
@@ -153,6 +160,13 @@ debug@=3.1.0:
dependencies:
ms "2.0.0"
+debug@^3.1.0:
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+ integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+ dependencies:
+ ms "^2.1.1"
+
debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
@@ -173,6 +187,18 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
+es6-promise@^4.0.3:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+ integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+ integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+ dependencies:
+ es6-promise "^4.0.3"
+
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@@ -267,6 +293,14 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
+https-proxy-agent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz#0106efa5d63d6d6f3ab87c999fa4877a3fd1ff97"
+ integrity sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==
+ dependencies:
+ agent-base "^4.3.0"
+ debug "^3.1.0"
+
is-buffer@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
@@ -337,6 +371,7 @@ log4js@^5.2.2:
"@types/request" "^2.47.0"
"@types/ws" "^6.0.1"
axios "^0.18.1"
+ https-proxy-agent "^3.0.0"
moment "^2.24.0"
oauth "^0.9.15"
request "^2.87.0"
diff --git a/package.json b/package.json
index be73e66..0a83c55 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"@types/request": "^2.47.0",
"@types/ws": "^6.0.1",
"axios": "^0.18.1",
+ "https-proxy-agent": "^3.0.0",
"moment": "^2.24.0",
"oauth": "^0.9.15",
"request": "^2.87.0",
diff --git a/src/index.ts b/src/index.ts
index ddcd3bb..2d24b96 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,4 @@
-import Mastodon, { MegalodonInstance } from './mastodon'
+import Mastodon, { MegalodonInstance, ProxyConfig } from './mastodon'
import StreamListener from './stream_listener'
import WebSocket from './web_socket'
import Response from './response'
@@ -43,6 +43,7 @@ export {
MegalodonInstance,
RequestCanceledError,
isCancel,
+ ProxyConfig,
/**
* Entities
*/
diff --git a/src/mastodon.ts b/src/mastodon.ts
index 34e8079..04de19d 100644
--- a/src/mastodon.ts
+++ b/src/mastodon.ts
@@ -1,5 +1,6 @@
import { OAuth2 } from 'oauth'
import axios, { AxiosResponse, CancelTokenSource } from 'axios'
+import HttpsProxyAgent from 'https-proxy-agent'
import StreamListener from './stream_listener'
import WebSocket from './web_socket'
@@ -26,6 +27,16 @@ export interface MegalodonInstance {
socket(path: string, strea: string): WebSocket
}
+export type ProxyConfig = {
+ host: string
+ port: number
+ auth?: {
+ username: string
+ password: string
+ }
+ protocol: string
+}
+
/**
* Mastodon API client.
*
@@ -40,16 +51,23 @@ export default class Mastodon implements MegalodonInstance {
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
*/
- constructor(accessToken: string, baseUrl = DEFAULT_URL, userAgent = DEFAULT_UA) {
+ 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
}
/**
@@ -211,26 +229,46 @@ export default class Mastodon implements MegalodonInstance {
* @param params Query parameters
* @param baseUrl base URL of the target
*/
- public static async get(path: string, params = {}, baseUrl = DEFAULT_URL): Promise> {
+ public static async get(
+ path: string,
+ params = {},
+ baseUrl = DEFAULT_URL,
+ proxyConfig: ProxyConfig | false = false
+ ): Promise> {
const apiUrl = baseUrl
- return axios
- .get(apiUrl + path, {
- params
- })
- .then((resp: AxiosResponse) => {
- const res: Response = {
- data: resp.data,
- status: resp.status,
- statusText: resp.statusText,
- headers: resp.headers
- }
- return res
+ let options = {
+ params: params
+ }
+ if (proxyConfig) {
+ options = Object.assign(options, {
+ httpsAgent: this._proxyAgent(proxyConfig)
})
+ }
+ return axios.get(apiUrl + path, options).then((resp: AxiosResponse) => {
+ const res: Response = {
+ data: resp.data,
+ status: resp.status,
+ statusText: resp.statusText,
+ headers: resp.headers
+ }
+ return res
+ })
}
- private static async _post(path: string, params = {}, baseUrl = DEFAULT_URL): Promise> {
+ private static async _post(
+ path: string,
+ params = {},
+ baseUrl = DEFAULT_URL,
+ proxyConfig: ProxyConfig | false = false
+ ): Promise> {
+ let options = {}
+ if (proxyConfig) {
+ options = Object.assign(options, {
+ httpsAgent: this._proxyAgent(proxyConfig)
+ })
+ }
const apiUrl = baseUrl
- return axios.post(apiUrl + path, params).then((resp: AxiosResponse) => {
+ return axios.post(apiUrl + path, params, options).then((resp: AxiosResponse) => {
const res: Response = {
data: resp.data,
status: resp.status,
@@ -247,14 +285,20 @@ export default class Mastodon implements MegalodonInstance {
* @param params Query parameters
*/
public async get(path: string, params = {}): Promise> {
- return axios
- .get(this.baseUrl + path, {
- cancelToken: this.cancelTokenSource.token,
- headers: {
- Authorization: `Bearer ${this.accessToken}`
- },
- params
+ let options = {
+ cancelToken: this.cancelTokenSource.token,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`
+ },
+ params: params
+ }
+ if (this.proxyConfig) {
+ options = Object.assign(options, {
+ httpsAgent: Mastodon._proxyAgent(this.proxyConfig)
})
+ }
+ return axios
+ .get(this.baseUrl + path, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
@@ -279,13 +323,19 @@ export default class Mastodon implements MegalodonInstance {
* @param params Form data. If you want to post file, please use FormData()
*/
public async put(path: string, params = {}): Promise> {
- return axios
- .put(this.baseUrl + path, params, {
- cancelToken: this.cancelTokenSource.token,
- headers: {
- Authorization: `Bearer ${this.accessToken}`
- }
+ let options = {
+ cancelToken: this.cancelTokenSource.token,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`
+ }
+ }
+ if (this.proxyConfig) {
+ options = Object.assign(options, {
+ httpsAgent: Mastodon._proxyAgent(this.proxyConfig)
})
+ }
+ return axios
+ .put(this.baseUrl + path, params, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
@@ -310,13 +360,19 @@ export default class Mastodon implements MegalodonInstance {
* @param params Form data. If you want to post file, please use FormData()
*/
public async patch(path: string, params = {}): Promise> {
- return axios
- .patch(this.baseUrl + path, params, {
- cancelToken: this.cancelTokenSource.token,
- headers: {
- Authorization: `Bearer ${this.accessToken}`
- }
+ let options = {
+ cancelToken: this.cancelTokenSource.token,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`
+ }
+ }
+ if (this.proxyConfig) {
+ options = Object.assign(options, {
+ httpsAgent: Mastodon._proxyAgent(this.proxyConfig)
})
+ }
+ return axios
+ .patch(this.baseUrl + path, params, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
@@ -341,22 +397,26 @@ export default class Mastodon implements MegalodonInstance {
* @param params Form data
*/
public async post(path: string, params = {}): Promise> {
- return axios
- .post(this.baseUrl + path, params, {
- cancelToken: this.cancelTokenSource.token,
- headers: {
- Authorization: `Bearer ${this.accessToken}`
- }
- })
- .then((resp: AxiosResponse) => {
- const res: Response = {
- data: resp.data,
- status: resp.status,
- statusText: resp.statusText,
- headers: resp.headers
- }
- return res
+ let options = {
+ cancelToken: this.cancelTokenSource.token,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`
+ }
+ }
+ if (this.proxyConfig) {
+ options = Object.assign(options, {
+ httpsAgent: Mastodon._proxyAgent(this.proxyConfig)
})
+ }
+ return axios.post(this.baseUrl + path, params, options).then((resp: AxiosResponse) => {
+ const res: Response = {
+ data: resp.data,
+ status: resp.status,
+ statusText: resp.statusText,
+ headers: resp.headers
+ }
+ return res
+ })
}
/**
@@ -365,14 +425,20 @@ export default class Mastodon implements MegalodonInstance {
* @param params Form data
*/
public async del(path: string, params = {}): Promise> {
- return axios
- .delete(this.baseUrl + path, {
- cancelToken: this.cancelTokenSource.token,
- data: params,
- headers: {
- Authorization: `Bearer ${this.accessToken}`
- }
+ let options = {
+ cancelToken: this.cancelTokenSource.token,
+ data: params,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`
+ }
+ }
+ if (this.proxyConfig) {
+ options = Object.assign(options, {
+ httpsAgent: Mastodon._proxyAgent(this.proxyConfig)
})
+ }
+ return axios
+ .delete(this.baseUrl + path, options)
.catch((err: Error) => {
if (axios.isCancel(err)) {
throw new RequestCanceledError(err.message)
@@ -437,4 +503,13 @@ export default class Mastodon implements MegalodonInstance {
})
return streaming
}
+
+ private static _proxyAgent(proxyConfig: ProxyConfig): HttpsProxyAgent {
+ let auth = ''
+ if (proxyConfig.auth) {
+ auth = `${proxyConfig.auth.username}:${proxyConfig.auth.password}@`
+ }
+ const agent = new HttpsProxyAgent(`${proxyConfig.protocol}://${auth}${proxyConfig.host}:${proxyConfig.port}`)
+ return agent
+ }
}
diff --git a/yarn.lock b/yarn.lock
index 5dac188..d2b9226 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -496,6 +496,13 @@ acorn@^6.0.1, acorn@^6.0.7:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
+agent-base@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+ integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+ dependencies:
+ es6-promisify "^5.0.0"
+
ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
@@ -1001,7 +1008,7 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
dependencies:
ms "2.0.0"
-debug@^3.2.6:
+debug@^3.1.0, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -1172,6 +1179,18 @@ es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
+es6-promise@^4.0.3:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+ integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+ integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+ dependencies:
+ es6-promise "^4.0.3"
+
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -1817,6 +1836,14 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
+https-proxy-agent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz#0106efa5d63d6d6f3ab87c999fa4877a3fd1ff97"
+ integrity sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==
+ dependencies:
+ agent-base "^4.3.0"
+ debug "^3.1.0"
+
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"