diff --git a/CHANGELOG.md b/CHANGELOG.md index a7dd8574..640f74ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## next - feat: async router ([#379](https://github.com/chimurai/http-proxy-middleware/issues/335)) ([LiranBri](https://github.com/LiranBri)) +- feat(typescript): types support ([#369](https://github.com/chimurai/http-proxy-middleware/pull/369)) ## [v0.20.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.20.0) diff --git a/package.json b/package.json index 3490e3ff..0403123e 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "http-proxy-middleware", - "version": "0.21.0-beta.1", + "version": "0.21.0-beta.2", "description": "The one-liner node.js proxy middleware for connect, express and browser-sync", "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist" ], @@ -51,7 +52,6 @@ "@commitlint/cli": "^8.0.0", "@commitlint/config-conventional": "^8.0.0", "@types/express": "^4.17.0", - "@types/http-proxy": "^1.17.0", "@types/is-glob": "^4.0.0", "@types/jest": "^24.0.15", "@types/lodash": "^4.14.136", @@ -72,6 +72,7 @@ "ws": "^7.1.0" }, "dependencies": { + "@types/http-proxy": "^1.17.2", "http-proxy": "^1.17.0", "is-glob": "^4.0.1", "lodash": "^4.17.14", diff --git a/src/config-factory.ts b/src/config-factory.ts index 7240b50a..6dfe632d 100644 --- a/src/config-factory.ts +++ b/src/config-factory.ts @@ -1,7 +1,8 @@ -import * as _ from 'lodash'; -import * as url from 'url'; +import _ from 'lodash'; +import url from 'url'; import { ERRORS } from './errors'; import { getInstance } from './logger'; +import { Filter, Options } from './types'; const logger = getInstance(); @@ -9,7 +10,7 @@ export function createConfig(context, opts?) { // structure of config object to be returned const config = { context: undefined, - options: {} as any + options: {} as Options }; // app.use('/api', proxy({target:'http://localhost:9000'})); @@ -55,7 +56,7 @@ export function createConfig(context, opts?) { * @param {String} context [description] * @return {Boolean} [description] */ -function isStringShortHand(context) { +function isStringShortHand(context: Filter) { if (_.isString(context)) { return !!url.parse(context).host; } @@ -72,11 +73,11 @@ function isStringShortHand(context) { * @param {*} opts [description] * @return {Boolean} [description] */ -function isContextless(context, opts) { +function isContextless(context: Filter, opts: Options) { return _.isPlainObject(context) && _.isEmpty(opts); } -function configureLogger(options) { +function configureLogger(options: Options) { if (options.logLevel) { logger.setLevel(options.logLevel); } diff --git a/src/context-matcher.ts b/src/context-matcher.ts index f04f9365..a2173a97 100644 --- a/src/context-matcher.ts +++ b/src/context-matcher.ts @@ -1,7 +1,7 @@ -import * as isGlob from 'is-glob'; -import * as _ from 'lodash'; -import * as micromatch from 'micromatch'; -import * as url from 'url'; +import isGlob from 'is-glob'; +import _ from 'lodash'; +import micromatch from 'micromatch'; +import url from 'url'; import { ERRORS } from './errors'; export function match(context, uri, req) { diff --git a/src/handlers.ts b/src/handlers.ts index 591a1b9a..db441e20 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash'; +import _ from 'lodash'; import { getInstance } from './logger'; const logger = getInstance(); diff --git a/src/http-proxy-middleware.ts b/src/http-proxy-middleware.ts index f3f6634f..e3c95724 100644 --- a/src/http-proxy-middleware.ts +++ b/src/http-proxy-middleware.ts @@ -1,21 +1,23 @@ -import * as httpProxy from 'http-proxy'; -import * as _ from 'lodash'; +import express from 'express'; +import httpProxy from 'http-proxy'; +import _ from 'lodash'; import { createConfig } from './config-factory'; import * as contextMatcher from './context-matcher'; import * as handlers from './handlers'; import { getArrow, getInstance } from './logger'; import * as PathRewriter from './path-rewriter'; import * as Router from './router'; +import { Filter, IRequest, IRequestHandler, IResponse, Options } from './types'; export class HttpProxyMiddleware { private logger = getInstance(); private config; private wsInternalSubscribed = false; - private proxyOptions; - private proxy; + private proxyOptions: Options; + private proxy: httpProxy; private pathRewriter; - constructor(context, opts) { + constructor(context: Filter | Options, opts?: Options) { this.config = createConfig(context, opts); this.proxyOptions = this.config.options; @@ -45,7 +47,11 @@ export class HttpProxyMiddleware { } // https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this - public middleware = async (req, res, next) => { + public middleware: IRequestHandler = async ( + req: IRequest, + res: IResponse, + next: express.NextFunction + ) => { if (this.shouldProxy(this.config.context, req)) { const activeProxyOptions = await this.prepareProxyRequest(req); this.proxy.web(req, res, activeProxyOptions); @@ -55,7 +61,7 @@ export class HttpProxyMiddleware { if (this.proxyOptions.ws === true) { // use initial request to access the server object to subscribe to http upgrade event - this.catchUpgradeRequest(req.connection.server); + this.catchUpgradeRequest((req.connection as any).server); } }; @@ -68,7 +74,7 @@ export class HttpProxyMiddleware { } }; - private handleUpgrade = async (req, socket, head) => { + private handleUpgrade = async (req: IRequest, socket, head) => { if (this.shouldProxy(this.config.context, req)) { const activeProxyOptions = await this.prepareProxyRequest(req); this.proxy.ws(req, socket, head, activeProxyOptions); @@ -84,7 +90,7 @@ export class HttpProxyMiddleware { * @param {Object} req [description] * @return {Boolean} */ - private shouldProxy = (context, req) => { + private shouldProxy = (context, req: IRequest) => { const path = req.originalUrl || req.url; return contextMatcher.match(context, path, req); }; @@ -97,7 +103,7 @@ export class HttpProxyMiddleware { * @param {Object} req * @return {Object} proxy options */ - private prepareProxyRequest = async req => { + private prepareProxyRequest = async (req: IRequest) => { // https://github.com/chimurai/http-proxy-middleware/issues/17 // https://github.com/chimurai/http-proxy-middleware/issues/94 req.url = req.originalUrl || req.url; @@ -133,7 +139,7 @@ export class HttpProxyMiddleware { }; // Modify option.target when router present. - private applyRouter = async (req, options) => { + private applyRouter = async (req: IRequest, options) => { let newTarget; if (options.router) { @@ -151,7 +157,7 @@ export class HttpProxyMiddleware { }; // rewrite path - private applyPathRewrite = (req, pathRewriter) => { + private applyPathRewrite = (req: IRequest, pathRewriter) => { if (pathRewriter) { const path = pathRewriter(req.url, req); @@ -166,10 +172,11 @@ export class HttpProxyMiddleware { } }; - private logError = (err, req, res) => { + private logError = (err, req: IRequest, res: IResponse) => { const hostname = (req.headers && req.headers.host) || (req.hostname || req.host); // (websocket) || (node0.10 || node 4/5) - const target = this.proxyOptions.target.host || this.proxyOptions.target; + const target = + (this.proxyOptions.target as any).host || this.proxyOptions.target; const errorMessage = '[HPM] Error occurred while trying to proxy request %s from %s to %s (%s) (%s)'; const errReference = diff --git a/src/index.ts b/src/index.ts index 2e510921..d4ec7d66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ import { HttpProxyMiddleware } from './http-proxy-middleware'; +import { Filter, Options } from './types'; -function proxy(context, opts) { - const { middleware } = new HttpProxyMiddleware(context, opts); +function middleware(context: Filter | Options, options?: Options) { + const { middleware } = new HttpProxyMiddleware(context, options); return middleware; } -export = proxy; +export = middleware; diff --git a/src/logger.ts b/src/logger.ts index 6f7f381d..50aece48 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,5 @@ -import * as _ from 'lodash'; -import * as util from 'util'; +import _ from 'lodash'; +import util from 'util'; let loggerInstance; diff --git a/src/path-rewriter.ts b/src/path-rewriter.ts index dd0aaed8..160a7f6d 100644 --- a/src/path-rewriter.ts +++ b/src/path-rewriter.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash'; +import _ from 'lodash'; import { ERRORS } from './errors'; import { getInstance } from './logger'; const logger = getInstance(); diff --git a/src/router.ts b/src/router.ts index 37200b94..fcee4dcf 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash'; +import _ from 'lodash'; import { getInstance } from './logger'; const logger = getInstance(); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..2a1171e1 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,56 @@ +import express from 'express'; +import http from 'http'; +import httpProxy from 'http-proxy'; +import net from 'net'; + +export interface IRequest extends express.Request {} +export interface IResponse extends express.Response {} + +export interface IRequestHandler extends express.RequestHandler { + upgrade?: (req: IRequest, socket: net.Socket, head: any) => void; +} + +export type Filter = string | string[] | ((pathname: string, req: IRequest) => boolean); + +export interface Options extends httpProxy.ServerOptions { + pathRewrite?: + | { [regexp: string]: string } + | ((path: string, req: IRequest) => string); + router?: + | { [hostOrPath: string]: string } + | ((req: IRequest) => string) + | ((req: IRequest) => Promise); + logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent'; + logProvider?(provider: LogProvider): LogProvider; + + onError?(err: Error, req: IRequest, res: IResponse): void; + onProxyRes?( + proxyRes: http.ServerResponse, + req: IRequest, + res: IResponse + ): void; + onProxyReq?( + proxyReq: http.ClientRequest, + req: IRequest, + res: IResponse + ): void; + onProxyReqWs?( + proxyReq: http.ClientRequest, + req: IRequest, + socket: net.Socket, + options: httpProxy.ServerOptions, + head: any + ): void; + onOpen?(proxySocket: net.Socket): void; + onClose?(res: IResponse, socket: net.Socket, head: any): void; +} + +interface LogProvider { + log: Logger; + debug?: Logger; + info?: Logger; + warn?: Logger; + error?: Logger; +} + +type Logger = (...args: any[]) => void; diff --git a/test/e2e/_utils.ts b/test/e2e/_utils.ts index 5eaed460..d3e92416 100644 --- a/test/e2e/_utils.ts +++ b/test/e2e/_utils.ts @@ -1,4 +1,4 @@ -import * as express from 'express'; +import express from 'express'; // tslint:disable-next-line: no-var-requires export const proxyMiddleware = require('../../dist/index'); diff --git a/test/e2e/express-router.spec.ts b/test/e2e/express-router.spec.ts index 09f24c1b..3913981d 100644 --- a/test/e2e/express-router.spec.ts +++ b/test/e2e/express-router.spec.ts @@ -1,5 +1,5 @@ -import * as express from 'express'; -import * as http from 'http'; +import express from 'express'; +import http from 'http'; import { proxyMiddleware as proxy } from './_utils'; describe('Usage in Express', () => { diff --git a/test/e2e/http-proxy-middleware.spec.ts b/test/e2e/http-proxy-middleware.spec.ts index 196e0de8..3211f79c 100644 --- a/test/e2e/http-proxy-middleware.spec.ts +++ b/test/e2e/http-proxy-middleware.spec.ts @@ -1,4 +1,4 @@ -import * as http from 'http'; +import http from 'http'; import { createServer, proxyMiddleware } from './_utils'; describe('E2E http-proxy-middleware', () => { diff --git a/test/e2e/path-rewriter.spec.ts b/test/e2e/path-rewriter.spec.ts index 62449a65..4699ae0b 100644 --- a/test/e2e/path-rewriter.spec.ts +++ b/test/e2e/path-rewriter.spec.ts @@ -1,4 +1,4 @@ -import * as http from 'http'; +import http from 'http'; import { createServer, proxyMiddleware } from './_utils'; describe('E2E pathRewrite', () => { diff --git a/test/e2e/router.spec.ts b/test/e2e/router.spec.ts index e42887e4..bd89e5a7 100644 --- a/test/e2e/router.spec.ts +++ b/test/e2e/router.spec.ts @@ -1,4 +1,4 @@ -import * as http from 'http'; +import http from 'http'; import { createServer, proxyMiddleware } from './_utils'; describe('E2E router', () => { diff --git a/test/e2e/websocket.spec.ts b/test/e2e/websocket.spec.ts index 092d32a4..53fce0a9 100644 --- a/test/e2e/websocket.spec.ts +++ b/test/e2e/websocket.spec.ts @@ -1,5 +1,5 @@ -import * as http from 'http'; -import * as WebSocket from 'ws'; +import http from 'http'; +import WebSocket from 'ws'; // tslint:disable-next-line: no-duplicate-imports import { Server as WebSocketServer } from 'ws'; import { createServer, proxyMiddleware } from './_utils'; diff --git a/test/types.spec.ts b/test/types.spec.ts new file mode 100644 index 00000000..64a0fdba --- /dev/null +++ b/test/types.spec.ts @@ -0,0 +1,154 @@ +import middleware from '../src'; +import { Options } from '../src/types'; + +describe('http-proxy-middleware TypeScript Types', () => { + let options: Options; + + beforeEach(() => { + options = { + target: 'http://example.org' + }; + }); + + it('should create proxy with just options', () => { + const proxy = middleware(options); + expect(proxy).toBeDefined(); + }); + + describe('HPM Filters', () => { + it('should create proxy with path filter', () => { + const proxy = middleware('/path', options); + expect(proxy).toBeDefined(); + }); + + it('should create proxy with glob filter', () => { + const proxy = middleware(['/path/**'], options); + expect(proxy).toBeDefined(); + }); + + it('should create proxy with custom filter', () => { + const proxy = middleware((path, req) => true, options); + expect(proxy).toBeDefined(); + }); + + it('should create proxy with manual websocket upgrade function', () => { + const proxy = middleware((path, req) => true, options); + expect(proxy.upgrade).toBeDefined(); + }); + }); + + describe('http-proxy options', () => { + it('should extend from http-proxy options', () => { + options = { + target: 'http://example', + ws: true + }; + expect(options).toBeDefined(); + }); + }); + + describe('http-proxy-middleware options', () => { + describe('pathRewrite', () => { + it('should have pathRewrite Type with table', () => { + options = { pathRewrite: { '^/from': '/to' } }; + expect(options).toBeDefined(); + }); + + it('should have pathRewrite Type with function', () => { + options = { pathRewrite: (path, req) => '/path' }; + expect(options).toBeDefined(); + }); + }); + + describe('router', () => { + it('should have router Type with table', () => { + options = { router: { '^/from': '/to' } }; + expect(options).toBeDefined(); + }); + + it('should have router Type with function', () => { + options = { router: path => '/path' }; + expect(options).toBeDefined(); + }); + + it('should have router Type with async function', () => { + options = { router: async path => '/path' }; + expect(options).toBeDefined(); + }); + }); + + describe('logLevel', () => { + it('should have logLevel Type', () => { + options = { logLevel: 'info' }; + expect(options).toBeDefined(); + }); + }); + + describe('logProvider', () => { + it('should have logProvider Type', () => { + options = { + logProvider: currentProvider => { + return { + log: () => { + return; + }, + debug: () => { + return; + }, + info: () => { + return; + }, + warn: () => { + return; + }, + error: () => { + return; + } + }; + } + }; + expect(options).toBeDefined(); + }); + }); + + describe('HPM http-proxy events', () => { + it('should have onError type', () => { + // tslint:disable no-empty + options = { onError: (err, req, res) => {} }; + expect(options).toBeDefined(); + }); + + it('should have onProxyReq type', () => { + // tslint:disable no-empty + options = { onProxyReq: (proxyReq, req, res) => {} }; + expect(options).toBeDefined(); + }); + + it('should have onProxyRes type', () => { + // tslint:disable no-empty + options = { onProxyRes: (proxyRes, req, res) => {} }; + expect(options).toBeDefined(); + }); + + it('should have onProxyReqWs type', () => { + options = { + // tslint:disable no-empty + onProxyReqWs: (proxyReq, req, socket, opts, head) => {} + }; + expect(options).toBeDefined(); + }); + + it('should have onOpen type', () => { + // tslint:disable no-empty + options = { onOpen: proxySocket => {} }; + expect(options).toBeDefined(); + }); + + it('should have onClose type', () => { + // tslint:disable no-empty + options = { onClose: (res, socket, head) => {} }; + expect(options).toBeDefined(); + }); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 5b7d0fdf..39528618 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,8 @@ "module": "commonjs", "moduleResolution": "node", "target": "es2015", + "declaration": true, + "esModuleInterop": true }, "include": ["./src"] } diff --git a/yarn.lock b/yarn.lock index 0b0cf96f..797e9c8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,10 +494,10 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" -"@types/http-proxy@^1.17.0": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.0.tgz#baf82ff6aa2723fd29f90e3ba1384e665006863e" - integrity sha512-l+s0IoxSHqhLFJPDHRfO235kgrCkvFD8JmdV/T9C4BKBYPIjrQopGFH4r7h2e3jQqgJRCthRCAZIxDoFnj1zwQ== +"@types/http-proxy@^1.17.2": + version "1.17.2" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.2.tgz#3b7fb5365a00d47129967b0b2da51c2123692314" + integrity sha512-Qfb7batJJBlI8wcrd48vHpgsOOYzQQa+OZcaIz33jkJPe8A7KktAJFmRAiR42s5BfnErdlFnOyQucq2BKy/98g== dependencies: "@types/node" "*"