|
|
@ -0,0 +1,126 @@ |
|
|
|
import https, { RequestOptions } from "https"; |
|
|
|
|
|
|
|
/** |
|
|
|
* The API URL |
|
|
|
*/ |
|
|
|
const API_URL = "https://api.themoviedb.org/3"; |
|
|
|
|
|
|
|
/** |
|
|
|
* A status error is used to indicate responses with non-200 status codes |
|
|
|
*/ |
|
|
|
export class StatusError<T = any> extends Error |
|
|
|
{ |
|
|
|
/** |
|
|
|
* The resulting body of a response |
|
|
|
*/ |
|
|
|
public readonly response: T; |
|
|
|
|
|
|
|
/** |
|
|
|
* The resulting status code of a response |
|
|
|
*/ |
|
|
|
public readonly statusCode?: number; |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a new error indicating non-200 status |
|
|
|
*/ |
|
|
|
public constructor(response: T, statusCode: number) { |
|
|
|
super(); |
|
|
|
Object.setPrototypeOf(this, StatusError.prototype); |
|
|
|
this.response = response; |
|
|
|
this.statusCode = statusCode; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* A request manager with atomic/persistent request options |
|
|
|
*/ |
|
|
|
export default class ApiRequestManager |
|
|
|
{ |
|
|
|
private __api_key: string; |
|
|
|
|
|
|
|
/** |
|
|
|
* Store additional request options |
|
|
|
*/ |
|
|
|
protected options: RequestOptions; |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a new API request manager |
|
|
|
* |
|
|
|
* @param options Additional request options |
|
|
|
*/ |
|
|
|
public constructor(apiKey: string, options: RequestOptions = {}) { |
|
|
|
this.__api_key = apiKey; |
|
|
|
this.options = options; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Perform a generic HTTPS request |
|
|
|
* |
|
|
|
* @param method The HTTP method |
|
|
|
* @param url The URL to request |
|
|
|
* @param apiKey An optional bearer token |
|
|
|
* @param params Optional parameters |
|
|
|
* @param body Optional body |
|
|
|
*/ |
|
|
|
protected request<T>(method: string, url: string, params?: any, body?: string) |
|
|
|
{ |
|
|
|
return new Promise<T>((resolve, reject) => { |
|
|
|
// Create request options
|
|
|
|
let options = Object.assign({ method, headers: {} }, this.options); |
|
|
|
if (body) { |
|
|
|
options.headers["Content-Type"] = "application/json"; |
|
|
|
options.headers["Content-Length"] = body.length; |
|
|
|
} |
|
|
|
|
|
|
|
// Add search parameters if necessary
|
|
|
|
let requestUrl = new URL(url); |
|
|
|
requestUrl.searchParams.set("api_key", this.__api_key); |
|
|
|
if (params) { |
|
|
|
Object.keys(params).forEach((key) => { |
|
|
|
if (params[key] !== undefined) { |
|
|
|
requestUrl.searchParams.set(key, params[key]); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// Create the request
|
|
|
|
let request = https.request(<any>requestUrl, options, (res) => { |
|
|
|
let rawData: string = ""; |
|
|
|
res.setEncoding("utf8"); |
|
|
|
res.on("data", chunk => {rawData += chunk}); |
|
|
|
res.on("error", reject); |
|
|
|
res.on("end", () => { |
|
|
|
let response: T = JSON.parse(rawData); |
|
|
|
if (res.statusCode == 200) { |
|
|
|
resolve(response) |
|
|
|
} else { |
|
|
|
reject(new StatusError(response, <number>res.statusCode)); |
|
|
|
} |
|
|
|
}); |
|
|
|
}) |
|
|
|
.on("error", reject) |
|
|
|
.on("timeout", () => reject("timeout")); |
|
|
|
if (body) { |
|
|
|
request.write(body); |
|
|
|
} |
|
|
|
request.end(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Perform a generic GET request |
|
|
|
*/ |
|
|
|
public async get<T = any>(path: string, params?: any) { |
|
|
|
return await this.request<T>("GET", `${API_URL}${path}`, params); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Perform a generic POST request |
|
|
|
*/ |
|
|
|
public async post<T = any>(path: string, params?: any, body?: any) { |
|
|
|
if (body !== undefined) { |
|
|
|
body = JSON.stringify(body); |
|
|
|
} |
|
|
|
return await this.request<T>("POST", `${API_URL}${path}`, params, body); |
|
|
|
} |
|
|
|
} |