mirror of
https://github.com/gosticks/DefinitelyTyped.git
synced 2026-10-06 15:57:13 +00:00
Merge pull request #19502 from sindilevich/needle-2.0
Definitions for needle 2.0
This commit is contained in:
Vendored
+319
-110
@@ -1,118 +1,327 @@
|
||||
// Type definitions for needle 1.4
|
||||
// Type definitions for needle 2.0
|
||||
// Project: https://github.com/tomas/needle
|
||||
// Definitions by: San Chen <https://github.com/bigsan>, Niklas Mollenhauer <https://github.com/nikeee>
|
||||
// Definitions by: San Chen <https://github.com/bigsan>, Niklas Mollenhauer <https://github.com/nikeee>, Matanel Sindilevich <https://github.com/sindilevich>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.2
|
||||
|
||||
/// <reference types="node" />
|
||||
|
||||
declare module "needle" {
|
||||
import * as http from 'http';
|
||||
import * as Buffer from 'buffer';
|
||||
import * as https from 'https';
|
||||
namespace Needle {
|
||||
interface NeedleResponse extends http.IncomingMessage {
|
||||
body: any;
|
||||
raw: Buffer;
|
||||
bytes: number;
|
||||
}
|
||||
import * as http from 'http';
|
||||
import * as Buffer from 'buffer';
|
||||
import * as https from 'https';
|
||||
|
||||
type ReadableStream = NodeJS.ReadableStream;
|
||||
|
||||
type NeedleCallback = (error: Error, response: NeedleResponse, body: any) => void;
|
||||
|
||||
interface Cookies {
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
type NeedleOptions = RequestOptions & ResponseOptions & RedirectOptions & https.RequestOptions;
|
||||
|
||||
interface RequestOptions {
|
||||
open_timeout?: number;
|
||||
read_timeout?: number;
|
||||
/**
|
||||
* Alias for open_timeout
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
follow_max?: number;
|
||||
/**
|
||||
* Alias for follow_max
|
||||
*/
|
||||
follow?: number;
|
||||
|
||||
multipart?: boolean;
|
||||
agent?: http.Agent | boolean;
|
||||
proxy?: string;
|
||||
headers?: {};
|
||||
auth?: "auto" | "digest" | "basic";
|
||||
json?: boolean;
|
||||
|
||||
// These properties are overwritten by those in the 'headers' field
|
||||
cookies?: Cookies;
|
||||
compressed?: boolean;
|
||||
// Overwritten if present in the URI
|
||||
username?: string;
|
||||
password?: string;
|
||||
accept?: string;
|
||||
connection?: string;
|
||||
user_agent?: string;
|
||||
}
|
||||
|
||||
interface ResponseOptions {
|
||||
decode_response?: boolean;
|
||||
/**
|
||||
* Alias for decode_response
|
||||
*/
|
||||
decode?: boolean;
|
||||
parse_response?: boolean;
|
||||
/**
|
||||
* Alias for parse_response
|
||||
*/
|
||||
parse?: boolean;
|
||||
|
||||
parse_cookies?: boolean;
|
||||
output?: string;
|
||||
}
|
||||
|
||||
interface RedirectOptions {
|
||||
follow_set_cookie?: boolean;
|
||||
follow_set_referer?: boolean;
|
||||
follow_keep_method?: boolean;
|
||||
follow_if_same_host?: boolean;
|
||||
follow_if_same_protocol?: boolean;
|
||||
}
|
||||
|
||||
interface KeyValue {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type BodyData = Buffer | KeyValue | NodeJS.ReadableStream | string | null;
|
||||
|
||||
interface NeedleStatic {
|
||||
defaults(options: NeedleOptions): void;
|
||||
|
||||
head(url: string, callback?: NeedleCallback): ReadableStream;
|
||||
head(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
get(url: string, callback?: NeedleCallback): ReadableStream;
|
||||
get(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
post(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
post(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
put(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
put(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
patch(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
patch(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
delete(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
delete(url: string, data: BodyData, options?: NeedleOptions, callback ?: NeedleCallback): ReadableStream;
|
||||
|
||||
request(method: string, url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
request(method: string, url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
}
|
||||
declare namespace core {
|
||||
interface NeedleResponse extends http.IncomingMessage {
|
||||
body: any;
|
||||
raw: Buffer;
|
||||
bytes: number;
|
||||
}
|
||||
const needle: Needle.NeedleStatic;
|
||||
export = needle;
|
||||
|
||||
type ReadableStream = NodeJS.ReadableStream;
|
||||
|
||||
type NeedleCallback = (error: Error, response: NeedleResponse, body: any) => void;
|
||||
|
||||
interface Cookies {
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
type NeedleOptions = RequestOptions & ResponseOptions & RedirectOptions & https.RequestOptions;
|
||||
|
||||
type NeedleReadonlyHttpVerbs = 'get' | 'head';
|
||||
|
||||
type NeedleReadWriteHttpVerbs = 'delete' | 'patch' | 'post' | 'put';
|
||||
|
||||
type NeedleHttpVerbs = NeedleReadonlyHttpVerbs | NeedleReadWriteHttpVerbs;
|
||||
|
||||
interface RequestOptions {
|
||||
/**
|
||||
* Returns error if connection takes longer than X milisecs to establish.
|
||||
* Defaults to 10000 (10 secs). 0 means no timeout.
|
||||
*/
|
||||
open_timeout?: number;
|
||||
/**
|
||||
* Alias for open_timeout
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
/**
|
||||
* Returns error if data transfer takes longer than X milisecs,
|
||||
* after connection is established. Defaults to 0 (no timeout).
|
||||
*/
|
||||
read_timeout?: number;
|
||||
/**
|
||||
* Number of redirects to follow. Defaults to 0.
|
||||
*/
|
||||
follow_max?: number;
|
||||
/**
|
||||
* Alias for follow_max
|
||||
*/
|
||||
follow?: number;
|
||||
|
||||
/**
|
||||
* Enables multipart/form-data encoding. Defaults to false.
|
||||
* Use it when uploading files.
|
||||
*/
|
||||
multipart?: boolean;
|
||||
/**
|
||||
* Uses an http.Agent of your choice, instead of the global, default one.
|
||||
* Useful for tweaking the behaviour at the connection level, such as when doing tunneling.
|
||||
*/
|
||||
agent?: http.Agent | boolean;
|
||||
/**
|
||||
* Forwards request through HTTP(s) proxy.
|
||||
* Eg. proxy: 'http://user:pass@proxy.server.com:3128'.
|
||||
* For more advanced proxying/tunneling use a custom agent.
|
||||
*/
|
||||
proxy?: string;
|
||||
/**
|
||||
* Object containing custom HTTP headers for request.
|
||||
*/
|
||||
headers?: {};
|
||||
/**
|
||||
* Determines what to do with provided username/password.
|
||||
* Options are auto, digest or basic (default).
|
||||
* auto will detect the type of authentication depending on the response headers.
|
||||
*/
|
||||
auth?: "auto" | "digest" | "basic";
|
||||
/**
|
||||
* When true, sets content type to application/json and sends request body as JSON string,
|
||||
* instead of a query string.
|
||||
*/
|
||||
json?: boolean;
|
||||
/**
|
||||
* When sending streams, this lets manually set the Content-Length header
|
||||
* --if the stream's bytecount is known beforehand--,
|
||||
* preventing ECONNRESET (socket hang up) errors on some servers that misbehave
|
||||
* when receiving payloads of unknown size.
|
||||
* Set it to 0 and Needle will get and set the stream's length,
|
||||
* or leave unset for the default behavior,
|
||||
* which is no Content-Length header for stream payloads.
|
||||
*/
|
||||
stream_length?: number;
|
||||
|
||||
// These properties are overwritten by those in the 'headers' field
|
||||
/**
|
||||
* Builds and sets a Cookie header from a { key: 'value' } object.
|
||||
*/
|
||||
cookies?: Cookies;
|
||||
/**
|
||||
* If true, sets 'Accept-Encoding' header to 'gzip,deflate',
|
||||
* and inflates content if zipped.
|
||||
* Defaults to false.
|
||||
*/
|
||||
compressed?: boolean;
|
||||
// Overwritten if present in the URI
|
||||
/**
|
||||
* For HTTP basic auth.
|
||||
*/
|
||||
username?: string;
|
||||
/**
|
||||
* For HTTP basic auth. Requires username to be passed, but is optional.
|
||||
*/
|
||||
password?: string;
|
||||
/**
|
||||
* Sets 'Accept' HTTP header. Defaults to */*.
|
||||
*/
|
||||
accept?: string;
|
||||
/**
|
||||
* Sets 'Connection' HTTP header.
|
||||
* Not set by default, unless running Node < 0.11.4
|
||||
* in which case it defaults to close.
|
||||
*/
|
||||
connection?: string;
|
||||
/**
|
||||
* Sets the 'User-Agent' HTTP header.
|
||||
* Defaults to Needle/{version} (Node.js {node_version}).
|
||||
*/
|
||||
user_agent?: string;
|
||||
/**
|
||||
* Sets the 'Content-Type' header.
|
||||
* Unset by default, unless you're sending data
|
||||
* in which case it's set accordingly to whatever is being sent
|
||||
* (application/x-www-form-urlencoded, application/json or multipart/form-data).
|
||||
* That is, of course, unless the option is passed,
|
||||
* either here or through options.headers.
|
||||
*/
|
||||
content_type?: string;
|
||||
}
|
||||
|
||||
interface ResponseOptions {
|
||||
/**
|
||||
* Whether to decode the text responses to UTF-8,
|
||||
* if Content-Type header shows a different charset. Defaults to true.
|
||||
*/
|
||||
decode_response?: boolean;
|
||||
/**
|
||||
* Alias for decode_response
|
||||
*/
|
||||
decode?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to parse XML or JSON response bodies automagically.
|
||||
* Defaults to true.
|
||||
* You can also set this to 'xml' or 'json' in which case Needle
|
||||
* will only parse the response if the content type matches.
|
||||
*/
|
||||
parse_response?: boolean;
|
||||
/**
|
||||
* Alias for parse_response
|
||||
*/
|
||||
parse?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to parse response’s Set-Cookie header.
|
||||
* Defaults to true.
|
||||
* If parsed, response cookies will be available at resp.cookies.
|
||||
*/
|
||||
parse_cookies?: boolean;
|
||||
/**
|
||||
* Dump response output to file.
|
||||
* This occurs after parsing and charset decoding is done.
|
||||
*/
|
||||
output?: string;
|
||||
}
|
||||
|
||||
interface RedirectOptions {
|
||||
/**
|
||||
* Sends the cookies received in the set-cookie header
|
||||
* as part of the following request.
|
||||
* false by default.
|
||||
*/
|
||||
follow_set_cookie?: boolean;
|
||||
/**
|
||||
* Sets the 'Referer' header to the requested URI
|
||||
* when following a redirect.
|
||||
* false by default.
|
||||
*/
|
||||
follow_set_referer?: boolean;
|
||||
/**
|
||||
* If enabled, resends the request using the original verb
|
||||
* instead of being rewritten to get with no data.
|
||||
* false by default.
|
||||
*/
|
||||
follow_keep_method?: boolean;
|
||||
/**
|
||||
* When true, Needle will only follow redirects that point to the same host
|
||||
* as the original request.
|
||||
* false by default.
|
||||
*/
|
||||
follow_if_same_host?: boolean;
|
||||
/**
|
||||
* When true, Needle will only follow redirects that point to the same protocol
|
||||
* as the original request.
|
||||
* false by default.
|
||||
*/
|
||||
follow_if_same_protocol?: boolean;
|
||||
}
|
||||
|
||||
interface KeyValue {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type BodyData = Buffer | KeyValue | NodeJS.ReadableStream | string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling needle() directly returns a Promise.
|
||||
*
|
||||
* Since needle 2.0
|
||||
* @param method Designates an HTTP verb for the request.
|
||||
*/
|
||||
declare function needle(method: core.NeedleReadonlyHttpVerbs, url: string, options?: core.NeedleOptions): Promise<core.NeedleResponse>;
|
||||
/**
|
||||
* Calling needle() directly returns a Promise.
|
||||
*
|
||||
* Since needle 2.0
|
||||
* @param method Designates an HTTP verb for the request.
|
||||
* @param data May be null when issuing an HTTP DELETE request, but you need to explicity pass it.
|
||||
*/
|
||||
declare function needle(method: core.NeedleHttpVerbs, url: string, data: core.BodyData, options?: core.NeedleOptions): Promise<core.NeedleResponse>;
|
||||
|
||||
declare namespace needle {
|
||||
type BodyData = core.BodyData;
|
||||
type NeedleCallback = core.NeedleCallback;
|
||||
type NeedleHttpVerbs = core.NeedleHttpVerbs;
|
||||
export type NeedleOptions = core.NeedleOptions;
|
||||
type ReadableStream = core.ReadableStream;
|
||||
|
||||
/**
|
||||
* Lets override the defaults for all future requests.
|
||||
*/
|
||||
export function defaults(options: NeedleOptions): NeedleOptions;
|
||||
|
||||
/**
|
||||
* Issues an HTTP HEAD request.
|
||||
*/
|
||||
export function head(url: string, callback?: NeedleCallback): ReadableStream;
|
||||
/**
|
||||
* Issues an HTTP HEAD request.
|
||||
*/
|
||||
export function head(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
/**
|
||||
* Issues an HTTP GET request.
|
||||
*/
|
||||
export function get(url: string, callback?: NeedleCallback): ReadableStream;
|
||||
/**
|
||||
* Issues an HTTP GET request.
|
||||
*/
|
||||
export function get(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
/**
|
||||
* Issues an HTTP POST request.
|
||||
*/
|
||||
export function post(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
/**
|
||||
* Issues an HTTP POST request.
|
||||
*/
|
||||
export function post(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
/**
|
||||
* Issues an HTTP PUT request.
|
||||
*/
|
||||
export function put(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
/**
|
||||
* Issues an HTTP PUT request.
|
||||
*/
|
||||
export function put(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
/**
|
||||
* Same behaviour as PUT.
|
||||
*/
|
||||
export function patch(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
/**
|
||||
* Same behaviour as PUT.
|
||||
*/
|
||||
export function patch(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
/**
|
||||
* Issues an HTTP DELETE request.
|
||||
*/
|
||||
function deleteFunc(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
/**
|
||||
* Issues an HTTP DELETE request.
|
||||
*/
|
||||
function deleteFunc(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
// See https://github.com/Microsoft/TypeScript/issues/1784#issuecomment-258720219
|
||||
export { deleteFunc as delete };
|
||||
|
||||
/**
|
||||
* Generic request.
|
||||
* This not only allows for flexibility, but also lets you perform a GET request with data,
|
||||
* in which case will be appended to the request as a query string,
|
||||
* unless you pass a json: true option.
|
||||
* @param method Designates an HTTP verb for the request.
|
||||
*/
|
||||
export function request(method: NeedleHttpVerbs, url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
/**
|
||||
* Generic request.
|
||||
* This not only allows for flexibility, but also lets you perform a GET request with data,
|
||||
* in which case will be appended to the request as a query string,
|
||||
* unless you pass a json: true option.
|
||||
* @param method Designates an HTTP verb for the request.
|
||||
*/
|
||||
export function request(method: NeedleHttpVerbs, url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
}
|
||||
|
||||
export = needle;
|
||||
|
||||
+175
-60
@@ -2,24 +2,37 @@ import * as needle from "needle";
|
||||
import * as fs from "fs";
|
||||
|
||||
function Usage() {
|
||||
// using promises
|
||||
needle('get', 'http://ifconfig.me/all.json')
|
||||
.then((resp) => console.log(resp.body.ip_addr));
|
||||
|
||||
// using callback
|
||||
needle.get('http://ifconfig.me/all.json', function (error, response) {
|
||||
needle.get('http://ifconfig.me/all.json', (error, response) => {
|
||||
if (!error)
|
||||
console.log(response.body.ip_addr); // JSON decoding magic. :)
|
||||
});
|
||||
|
||||
// using streams
|
||||
var out: any; // = fs.createWriteStream('logo.png');
|
||||
const out = fs.createWriteStream('file.txt');
|
||||
needle.get('https://google.com/images/logo.png').pipe(out);
|
||||
}
|
||||
|
||||
function ResponsePipeline() {
|
||||
needle.get('http://stackoverflow.com/feeds', { compressed: true }, function (err, resp) {
|
||||
// using promises
|
||||
needle('get', 'http://stackoverflow.com/feeds', { compressed: true })
|
||||
.then((resp) => {
|
||||
console.log(resp.body); // this little guy won't be a Gzipped binary blob
|
||||
// but a nice object containing all the latest entries
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.get('http://stackoverflow.com/feeds', { compressed: true }, (err, resp) => {
|
||||
console.log(resp.body); // this little guy won't be a Gzipped binary blob
|
||||
// but a nice object containing all the latest entries
|
||||
});
|
||||
|
||||
var options = {
|
||||
// using streams
|
||||
const options = {
|
||||
compressed: true,
|
||||
follow: 5,
|
||||
rejectUnauthorized: true
|
||||
@@ -27,54 +40,69 @@ function ResponsePipeline() {
|
||||
|
||||
// in this case, we'll ask Needle to follow redirects (disabled by default),
|
||||
// but also to verify their SSL certificates when connecting.
|
||||
var stream = needle.get('https://backend.server.com/everything.html', options);
|
||||
const stream = needle.get('https://backend.server.com/everything.html', options);
|
||||
|
||||
stream.on('readable', function () {
|
||||
var data: any;
|
||||
stream.on('readable', () => {
|
||||
let data: any;
|
||||
while (data = stream.read()) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', function(err: any) {
|
||||
stream.on('end', (err: any) => {
|
||||
// if our request had an error, our 'end' event will tell us.
|
||||
if (!err) console.log('Great success!');
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function API_head() {
|
||||
var options = {
|
||||
open_timeout: 5000 // if we don't get a response in 5 seconds, boom.
|
||||
};
|
||||
// using promises
|
||||
needle('head', 'https://my.backend.server.com')
|
||||
.then((resp) => console.log('Yup, still alive.'))
|
||||
.catch((err: Error) => console.log('Shoot! Something is wrong: ' + err.message));
|
||||
|
||||
needle.head('https://my.backend.server.com', function (err, resp) {
|
||||
// using callback
|
||||
needle.head('https://my.backend.server.com', (err, resp) => {
|
||||
if (err) {
|
||||
console.log('Shoot! Something is wrong: ' + err.message);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
console.log('Yup, still alive.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function API_get() {
|
||||
needle.get('google.com/search?q=syd+barrett', function (err, resp) {
|
||||
// using promises
|
||||
needle('get', 'google.com/search?q=syd+barrett')
|
||||
.then((resp) => {
|
||||
// if no http:// is found, Needle will automagically prepend it.
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.get('google.com/search?q=syd+barrett', (err, resp) => {
|
||||
// if no http:// is found, Needle will automagically prepend it.
|
||||
});
|
||||
}
|
||||
|
||||
function API_post() {
|
||||
var options = {
|
||||
const options = {
|
||||
headers: { 'X-Custom-Header': 'Bumbaway atuna' }
|
||||
};
|
||||
|
||||
needle.post('https://my.app.com/endpoint', 'foo=bar', options, function (err, resp) {
|
||||
// using promises
|
||||
needle('post', 'https://my.app.com/endpoint', 'foo=bar', options)
|
||||
.then((resp) => {
|
||||
// you can pass params as a string or as an object.
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.post('https://my.app.com/endpoint', 'foo=bar', options, (err, resp) => {
|
||||
// you can pass params as a string or as an object.
|
||||
});
|
||||
}
|
||||
|
||||
function API_put() {
|
||||
var nested = {
|
||||
const nested = {
|
||||
params: {
|
||||
are: {
|
||||
also: 'supported'
|
||||
@@ -82,87 +110,155 @@ function API_put() {
|
||||
}
|
||||
};
|
||||
|
||||
needle.put('https://api.app.com/v2', nested, function (err, resp) {
|
||||
console.log('Got ' + resp.bytes + ' bytes.') // another nice treat from this handsome fella.
|
||||
// using promises
|
||||
needle('put', 'https://api.app.com/v2', nested)
|
||||
.then((resp) => {
|
||||
console.log(`Got ${resp.bytes} bytes.`); // another nice treat from this handsome fella.
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.put('https://api.app.com/v2', nested, (err, resp) => {
|
||||
console.log(`Got ${resp.bytes} bytes.`); // another nice treat from this handsome fella.
|
||||
});
|
||||
}
|
||||
|
||||
function API_delete() {
|
||||
var options = {
|
||||
const options = {
|
||||
username: 'fidelio',
|
||||
password: 'x'
|
||||
};
|
||||
|
||||
needle.delete('https://api.app.com/messages/123', null, options, function (err, resp) {
|
||||
// using promises
|
||||
needle('delete', 'https://api.app.com/messages/123', null, options)
|
||||
.then((resp) => {
|
||||
// in this case, data may be null, but you need to explicity pass it.
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.delete('https://api.app.com/messages/123', null, options, (err, resp) => {
|
||||
// in this case, data may be null, but you need to explicity pass it.
|
||||
});
|
||||
}
|
||||
|
||||
function API_request() {
|
||||
var params = {
|
||||
const params = {
|
||||
q: 'a very smart query',
|
||||
page: 2,
|
||||
};
|
||||
|
||||
needle.request('get', 'forum.com/search', params, function (err, resp) {
|
||||
if (!err && resp.statusCode == 200)
|
||||
// using promises
|
||||
needle('get', 'forum.com/search', params)
|
||||
.then((resp) => {
|
||||
if (resp.statusCode === 200)
|
||||
console.log(resp.body); // here you go, mister.
|
||||
});
|
||||
|
||||
needle('get', 'forum.com/search', params, { json: true })
|
||||
.then((resp) => {
|
||||
if (resp.statusCode === 200) console.log('It worked!');
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.request('get', 'forum.com/search', params, (err, resp) => {
|
||||
if (!err && resp.statusCode === 200)
|
||||
console.log(resp.body); // here you go, mister.
|
||||
});
|
||||
|
||||
needle.request('get', 'forum.com/search', params, { json: true }, function(err, resp) {
|
||||
if (resp.statusCode == 200) console.log('It worked!');
|
||||
needle.request('get', 'forum.com/search', params, { json: true }, (err, resp) => {
|
||||
if (resp.statusCode === 200) console.log('It worked!');
|
||||
});
|
||||
}
|
||||
|
||||
function HttpGetWithBasicAuth() {
|
||||
needle.get('https://api.server.com', { username: 'you', password: 'secret' }, function(err, resp) {
|
||||
// using promises
|
||||
needle('get', 'https://api.server.com', { username: 'you', password: 'secret' })
|
||||
.then((resp) => {
|
||||
// used HTTP auth
|
||||
});
|
||||
needle('get', 'https://username:password@api.server.com')
|
||||
.then((resp) => {
|
||||
// used HTTP auth from URL
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.get('https://api.server.com', { username: 'you', password: 'secret' }, (err, resp) => {
|
||||
// used HTTP auth
|
||||
});
|
||||
needle.get('https://username:password@api.server.com', function(err, resp) {
|
||||
needle.get('https://username:password@api.server.com', (err, resp) => {
|
||||
// used HTTP auth from URL
|
||||
});
|
||||
}
|
||||
|
||||
function DigestAuth() {
|
||||
needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' }, function(err, resp, body) {
|
||||
// using promises
|
||||
needle('get', 'other.server.com', { username: 'you', password: 'secret', auth: 'digest' })
|
||||
.then((resp) => {
|
||||
// needle prepends 'http://' to your URL, if missing
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' }, (err, resp, body) => {
|
||||
// needle prepends 'http://' to your URL, if missing
|
||||
});
|
||||
}
|
||||
|
||||
function CustomAcceptHeaderDeflate() {
|
||||
var options = {
|
||||
const options: needle.NeedleOptions = {
|
||||
compressed: true,
|
||||
follow: 10,
|
||||
accept: 'application/vnd.github.full+json'
|
||||
}
|
||||
};
|
||||
|
||||
needle.get('api.github.com/users/tomas', options, function(err, resp, body) {
|
||||
// using promises
|
||||
needle('get', 'api.github.com/users/tomas', options)
|
||||
.then((resp) => {
|
||||
// body will contain a JSON.parse(d) object
|
||||
// if parsing fails, you'll simply get the original body
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.get('api.github.com/users/tomas', options, (err, resp, body) => {
|
||||
// body will contain a JSON.parse(d) object
|
||||
// if parsing fails, you'll simply get the original body
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function Various() {
|
||||
// using promises
|
||||
needle('get', 'https://news.ycombinator.com/rss')
|
||||
.then((resp) => {
|
||||
// if xml2js is installed, you'll get a nice object containing the nodes in the RSS
|
||||
});
|
||||
needle('get', 'http://upload.server.com/tux.png', { output: '/tmp/tux.png' })
|
||||
.then((resp) => {
|
||||
// you can dump any response to a file, not only binaries.
|
||||
});
|
||||
needle('get', 'http://search.npmjs.org', { proxy: 'http://localhost:1234' })
|
||||
.then((resp) => {
|
||||
// request passed through proxy
|
||||
});
|
||||
|
||||
needle.get('https://news.ycombinator.com/rss', function(err, resp, body) {
|
||||
// using callback
|
||||
needle.get('https://news.ycombinator.com/rss', (err, resp, body) => {
|
||||
// if xml2js is installed, you'll get a nice object containing the nodes in the RSS
|
||||
});
|
||||
needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, function(err, resp, body) {
|
||||
needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, (err, resp, body) => {
|
||||
// you can dump any response to a file, not only binaries.
|
||||
});
|
||||
needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, function(err, resp, body) {
|
||||
needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, (err, resp, body) => {
|
||||
// request passed through proxy
|
||||
});
|
||||
|
||||
// using streams
|
||||
const stream1 = needle.get('http://www.as35662.net/100.log');
|
||||
stream1.on('readable', function() {
|
||||
stream1.on('readable', () => {
|
||||
let chunk: any;
|
||||
while (chunk = stream1.read()) {
|
||||
console.log('got data: ', chunk);
|
||||
}
|
||||
});
|
||||
const stream2 = needle.get('http://jsonplaceholder.typicode.com/db', { parse: true });
|
||||
stream2.on('readable', function() {
|
||||
stream2.on('readable', () => {
|
||||
let node: any;
|
||||
|
||||
// our stream2 will only emit a single JSON root node.
|
||||
@@ -170,43 +266,54 @@ function Various() {
|
||||
console.log('got data: ', node);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
// Sample omitted, no JSONStream
|
||||
needle.get('http://jsonplaceholder.typicode.com/db', { parse: true })
|
||||
.pipe(new JSONStream.parse('posts.*.title'))
|
||||
.on('data', function (obj) {
|
||||
console.log('got post title: %s', obj);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function FileUpload() {
|
||||
var data = {
|
||||
const data = {
|
||||
foo: 'bar',
|
||||
image: { file: '/home/tomas/linux.png', content_type: 'image/png' }
|
||||
};
|
||||
|
||||
needle.post('http://my.other.app.com', data, { multipart: true }, function(err, resp, body) {
|
||||
// using promises
|
||||
needle('post', 'http://my.other.app.com', data, { multipart: true })
|
||||
.then((resp) => {
|
||||
// needle will read the file and include it in the form-data as binary
|
||||
});
|
||||
needle('put', 'https://api.app.com/v2', fs.createReadStream('myfile.txt'))
|
||||
.then((resp) => {
|
||||
// stream content is uploaded verbatim
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.post('http://my.other.app.com', data, { multipart: true }, (err, resp, body) => {
|
||||
// needle will read the file and include it in the form-data as binary
|
||||
});
|
||||
needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), function(err, resp, body) {
|
||||
needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), (err, resp, body) => {
|
||||
// stream content is uploaded verbatim
|
||||
});
|
||||
}
|
||||
|
||||
function Multipart() {
|
||||
var buffer = fs.readFileSync('/path/to/package.zip');
|
||||
const buffer = fs.readFileSync('/path/to/package.zip');
|
||||
|
||||
var data = {
|
||||
const data = {
|
||||
zip_file: {
|
||||
buffer: buffer,
|
||||
buffer,
|
||||
filename: 'mypackage.zip',
|
||||
content_type: 'application/octet-stream'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, function(err, resp, body) {
|
||||
// using promises
|
||||
needle('post', 'http://somewhere.com/over/the/rainbow', data, { multipart: true })
|
||||
.then((resp) => {
|
||||
// if you see, when using buffers we need to pass the filename for the multipart body.
|
||||
// you can also pass a filename when using the file path method, in case you want to override
|
||||
// the default filename to be received on the other end.
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, (err, resp, body) => {
|
||||
// if you see, when using buffers we need to pass the filename for the multipart body.
|
||||
// you can also pass a filename when using the file path method, in case you want to override
|
||||
// the default filename to be received on the other end.
|
||||
@@ -214,15 +321,23 @@ function Multipart() {
|
||||
}
|
||||
|
||||
function MultipartContentType() {
|
||||
var data = {
|
||||
const data = {
|
||||
token: 'verysecret',
|
||||
payload: {
|
||||
value: JSON.stringify({ title: 'test', version: 1 }),
|
||||
content_type: 'application/json'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, function(err, resp, body) {
|
||||
// using promises
|
||||
needle('post', 'http://test.com/', data, { timeout: 5000, multipart: true })
|
||||
.then((resp) => {
|
||||
// in this case, if the request takes more than 5 seconds
|
||||
// the callback will return a [Socket closed] error
|
||||
});
|
||||
|
||||
// using callback
|
||||
needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, (err, resp, body) => {
|
||||
// in this case, if the request takes more than 5 seconds
|
||||
// the callback will return a [Socket closed] error
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "dtslint/dt.json",
|
||||
"rules": {
|
||||
"no-conditional-assignment": false,
|
||||
"strict-export-declare-modifiers": false
|
||||
}
|
||||
}
|
||||
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
// Type definitions for needle 1.4
|
||||
// Project: https://github.com/tomas/needle
|
||||
// Definitions by: San Chen <https://github.com/bigsan>, Niklas Mollenhauer <https://github.com/nikeee>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
/// <reference types="node" />
|
||||
|
||||
declare module "needle" {
|
||||
import * as http from 'http';
|
||||
import * as Buffer from 'buffer';
|
||||
import * as https from 'https';
|
||||
namespace Needle {
|
||||
interface NeedleResponse extends http.IncomingMessage {
|
||||
body: any;
|
||||
raw: Buffer;
|
||||
bytes: number;
|
||||
}
|
||||
|
||||
type ReadableStream = NodeJS.ReadableStream;
|
||||
|
||||
type NeedleCallback = (error: Error, response: NeedleResponse, body: any) => void;
|
||||
|
||||
interface Cookies {
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
type NeedleOptions = RequestOptions & ResponseOptions & RedirectOptions & https.RequestOptions;
|
||||
|
||||
interface RequestOptions {
|
||||
open_timeout?: number;
|
||||
read_timeout?: number;
|
||||
/**
|
||||
* Alias for open_timeout
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
follow_max?: number;
|
||||
/**
|
||||
* Alias for follow_max
|
||||
*/
|
||||
follow?: number;
|
||||
|
||||
multipart?: boolean;
|
||||
agent?: http.Agent | boolean;
|
||||
proxy?: string;
|
||||
headers?: {};
|
||||
auth?: "auto" | "digest" | "basic";
|
||||
json?: boolean;
|
||||
|
||||
// These properties are overwritten by those in the 'headers' field
|
||||
cookies?: Cookies;
|
||||
compressed?: boolean;
|
||||
// Overwritten if present in the URI
|
||||
username?: string;
|
||||
password?: string;
|
||||
accept?: string;
|
||||
connection?: string;
|
||||
user_agent?: string;
|
||||
}
|
||||
|
||||
interface ResponseOptions {
|
||||
decode_response?: boolean;
|
||||
/**
|
||||
* Alias for decode_response
|
||||
*/
|
||||
decode?: boolean;
|
||||
parse_response?: boolean;
|
||||
/**
|
||||
* Alias for parse_response
|
||||
*/
|
||||
parse?: boolean;
|
||||
|
||||
parse_cookies?: boolean;
|
||||
output?: string;
|
||||
}
|
||||
|
||||
interface RedirectOptions {
|
||||
follow_set_cookie?: boolean;
|
||||
follow_set_referer?: boolean;
|
||||
follow_keep_method?: boolean;
|
||||
follow_if_same_host?: boolean;
|
||||
follow_if_same_protocol?: boolean;
|
||||
}
|
||||
|
||||
interface KeyValue {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type BodyData = Buffer | KeyValue | NodeJS.ReadableStream | string | null;
|
||||
|
||||
interface NeedleStatic {
|
||||
defaults(options: NeedleOptions): void;
|
||||
|
||||
head(url: string, callback?: NeedleCallback): ReadableStream;
|
||||
head(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
get(url: string, callback?: NeedleCallback): ReadableStream;
|
||||
get(url: string, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
post(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
post(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
put(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
put(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
patch(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
patch(url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
|
||||
delete(url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
delete(url: string, data: BodyData, options?: NeedleOptions, callback ?: NeedleCallback): ReadableStream;
|
||||
|
||||
request(method: string, url: string, data: BodyData, callback?: NeedleCallback): ReadableStream;
|
||||
request(method: string, url: string, data: BodyData, options?: NeedleOptions, callback?: NeedleCallback): ReadableStream;
|
||||
}
|
||||
}
|
||||
const needle: Needle.NeedleStatic;
|
||||
export = needle;
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
import * as needle from "needle";
|
||||
import * as fs from "fs";
|
||||
|
||||
function Usage() {
|
||||
// using callback
|
||||
needle.get('http://ifconfig.me/all.json', function (error, response) {
|
||||
if (!error)
|
||||
console.log(response.body.ip_addr); // JSON decoding magic. :)
|
||||
});
|
||||
|
||||
// using streams
|
||||
var out: any; // = fs.createWriteStream('logo.png');
|
||||
needle.get('https://google.com/images/logo.png').pipe(out);
|
||||
}
|
||||
|
||||
function ResponsePipeline() {
|
||||
needle.get('http://stackoverflow.com/feeds', { compressed: true }, function (err, resp) {
|
||||
console.log(resp.body); // this little guy won't be a Gzipped binary blob
|
||||
// but a nice object containing all the latest entries
|
||||
});
|
||||
|
||||
var options = {
|
||||
compressed: true,
|
||||
follow: 5,
|
||||
rejectUnauthorized: true
|
||||
};
|
||||
|
||||
// in this case, we'll ask Needle to follow redirects (disabled by default),
|
||||
// but also to verify their SSL certificates when connecting.
|
||||
var stream = needle.get('https://backend.server.com/everything.html', options);
|
||||
|
||||
stream.on('readable', function () {
|
||||
var data: any;
|
||||
while (data = stream.read()) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', function(err: any) {
|
||||
// if our request had an error, our 'end' event will tell us.
|
||||
if (!err) console.log('Great success!');
|
||||
})
|
||||
}
|
||||
|
||||
function API_head() {
|
||||
var options = {
|
||||
open_timeout: 5000 // if we don't get a response in 5 seconds, boom.
|
||||
};
|
||||
|
||||
needle.head('https://my.backend.server.com', function (err, resp) {
|
||||
if (err) {
|
||||
console.log('Shoot! Something is wrong: ' + err.message);
|
||||
}
|
||||
else {
|
||||
console.log('Yup, still alive.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function API_get() {
|
||||
needle.get('google.com/search?q=syd+barrett', function (err, resp) {
|
||||
// if no http:// is found, Needle will automagically prepend it.
|
||||
});
|
||||
}
|
||||
|
||||
function API_post() {
|
||||
var options = {
|
||||
headers: { 'X-Custom-Header': 'Bumbaway atuna' }
|
||||
};
|
||||
|
||||
needle.post('https://my.app.com/endpoint', 'foo=bar', options, function (err, resp) {
|
||||
// you can pass params as a string or as an object.
|
||||
});
|
||||
}
|
||||
|
||||
function API_put() {
|
||||
var nested = {
|
||||
params: {
|
||||
are: {
|
||||
also: 'supported'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
needle.put('https://api.app.com/v2', nested, function (err, resp) {
|
||||
console.log('Got ' + resp.bytes + ' bytes.') // another nice treat from this handsome fella.
|
||||
});
|
||||
}
|
||||
|
||||
function API_delete() {
|
||||
var options = {
|
||||
username: 'fidelio',
|
||||
password: 'x'
|
||||
};
|
||||
|
||||
needle.delete('https://api.app.com/messages/123', null, options, function (err, resp) {
|
||||
// in this case, data may be null, but you need to explicity pass it.
|
||||
});
|
||||
}
|
||||
|
||||
function API_request() {
|
||||
var params = {
|
||||
q: 'a very smart query',
|
||||
page: 2,
|
||||
};
|
||||
|
||||
needle.request('get', 'forum.com/search', params, function (err, resp) {
|
||||
if (!err && resp.statusCode == 200)
|
||||
console.log(resp.body); // here you go, mister.
|
||||
});
|
||||
|
||||
needle.request('get', 'forum.com/search', params, { json: true }, function(err, resp) {
|
||||
if (resp.statusCode == 200) console.log('It worked!');
|
||||
});
|
||||
}
|
||||
|
||||
function HttpGetWithBasicAuth() {
|
||||
needle.get('https://api.server.com', { username: 'you', password: 'secret' }, function(err, resp) {
|
||||
// used HTTP auth
|
||||
});
|
||||
needle.get('https://username:password@api.server.com', function(err, resp) {
|
||||
// used HTTP auth from URL
|
||||
});
|
||||
}
|
||||
|
||||
function DigestAuth() {
|
||||
needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' }, function(err, resp, body) {
|
||||
// needle prepends 'http://' to your URL, if missing
|
||||
});
|
||||
}
|
||||
|
||||
function CustomAcceptHeaderDeflate() {
|
||||
var options = {
|
||||
compressed: true,
|
||||
follow: 10,
|
||||
accept: 'application/vnd.github.full+json'
|
||||
}
|
||||
|
||||
needle.get('api.github.com/users/tomas', options, function(err, resp, body) {
|
||||
// body will contain a JSON.parse(d) object
|
||||
// if parsing fails, you'll simply get the original body
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function Various() {
|
||||
|
||||
needle.get('https://news.ycombinator.com/rss', function(err, resp, body) {
|
||||
// if xml2js is installed, you'll get a nice object containing the nodes in the RSS
|
||||
});
|
||||
needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, function(err, resp, body) {
|
||||
// you can dump any response to a file, not only binaries.
|
||||
});
|
||||
needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, function(err, resp, body) {
|
||||
// request passed through proxy
|
||||
});
|
||||
const stream1 = needle.get('http://www.as35662.net/100.log');
|
||||
stream1.on('readable', function() {
|
||||
let chunk: any;
|
||||
while (chunk = stream1.read()) {
|
||||
console.log('got data: ', chunk);
|
||||
}
|
||||
});
|
||||
const stream2 = needle.get('http://jsonplaceholder.typicode.com/db', { parse: true });
|
||||
stream2.on('readable', function() {
|
||||
let node: any;
|
||||
|
||||
// our stream2 will only emit a single JSON root node.
|
||||
while (node = stream2.read()) {
|
||||
console.log('got data: ', node);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
// Sample omitted, no JSONStream
|
||||
needle.get('http://jsonplaceholder.typicode.com/db', { parse: true })
|
||||
.pipe(new JSONStream.parse('posts.*.title'))
|
||||
.on('data', function (obj) {
|
||||
console.log('got post title: %s', obj);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function FileUpload() {
|
||||
var data = {
|
||||
foo: 'bar',
|
||||
image: { file: '/home/tomas/linux.png', content_type: 'image/png' }
|
||||
};
|
||||
|
||||
needle.post('http://my.other.app.com', data, { multipart: true }, function(err, resp, body) {
|
||||
// needle will read the file and include it in the form-data as binary
|
||||
});
|
||||
needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), function(err, resp, body) {
|
||||
// stream content is uploaded verbatim
|
||||
});
|
||||
}
|
||||
|
||||
function Multipart() {
|
||||
var buffer = fs.readFileSync('/path/to/package.zip');
|
||||
|
||||
var data = {
|
||||
zip_file: {
|
||||
buffer: buffer,
|
||||
filename: 'mypackage.zip',
|
||||
content_type: 'application/octet-stream'
|
||||
}
|
||||
}
|
||||
|
||||
needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, function(err, resp, body) {
|
||||
// if you see, when using buffers we need to pass the filename for the multipart body.
|
||||
// you can also pass a filename when using the file path method, in case you want to override
|
||||
// the default filename to be received on the other end.
|
||||
});
|
||||
}
|
||||
|
||||
function MultipartContentType() {
|
||||
var data = {
|
||||
token: 'verysecret',
|
||||
payload: {
|
||||
value: JSON.stringify({ title: 'test', version: 1 }),
|
||||
content_type: 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, function(err, resp, body) {
|
||||
// in this case, if the request takes more than 5 seconds
|
||||
// the callback will return a [Socket closed] error
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"baseUrl": "../../",
|
||||
"typeRoots": [
|
||||
"../../"
|
||||
],
|
||||
"paths": {
|
||||
"needle": ["needle/v1"]
|
||||
},
|
||||
"types": [],
|
||||
"noEmit": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": [
|
||||
"index.d.ts",
|
||||
"needle-tests.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user