@wooksjs/http-proxy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # Wooks Proxy
2
+
3
+ **!!! This is work-in-progress library, breaking changes are expected !!!**
4
+
5
+ <p align="center">
6
+ <img src="../../logo.png" width="128px"><br>
7
+ <a href="https://github.com/prostojs/wooks/blob/main/LICENSE">
8
+ <img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge" />
9
+ </a>
10
+ </p>
11
+
12
+
13
+ Wooks Proxy is composable proxy for [@wooksjs/event-http](https://github.com/wooksjs/wooksjs/tree/main/packages/event-http)
14
+
15
+ 🔥 An easy way to proxy request!
16
+
17
+ ## Install
18
+
19
+ `npm install @wooksjs/http-proxy`
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { useProxy } from '@wooksjs/http-proxy'
25
+ app.get('/to-proxy', () => {
26
+ const proxy = useProxy()
27
+ return proxy('https://target-website.com/target-path?query=123')
28
+ })
29
+
30
+ ```
31
+
32
+ ### Restrict cookies/headers to pass
33
+
34
+ ```ts
35
+ import { useProxy } from '@wooksjs/http-proxy'
36
+ app.get('/to-proxy', () => {
37
+ const proxy = useProxy()
38
+ return proxy('https://target-website.com/target-path?query=123', {
39
+ reqHeaders: { block: ['referer'] }, // block referer header
40
+ reqCookies: { block: '*' }, // block all req cookies
41
+ })
42
+ })
43
+
44
+ ```
45
+
46
+ ### Change response
47
+
48
+ It's easy as `proxy` returns fetch response
49
+
50
+ ```ts
51
+ import { useProxy } from '@wooksjs/http-proxy'
52
+ app.get('/to-proxy', async () => {
53
+ const proxy = useProxy()
54
+ const response = proxy('https://mayapi.com/json-api')
55
+ const data = { ...(await response.json()), newField: 'new value' }
56
+ return data
57
+ })
58
+
59
+ ```
60
+
61
+ ## Proxy advanced options
62
+ ```ts
63
+ import { useProxy } from '@wooksjs/http-proxy'
64
+ import { useRequest } from '@wooksjs/composables'
65
+ //...
66
+ app.get('*', async () => {
67
+ const proxy = useProxy()
68
+ const { url } = useRequest()
69
+ const fetchResponse = await proxy('https://www.google.com' + url, {
70
+ // optional method, be default is set with
71
+ // the original request method
72
+ method: 'GET',
73
+
74
+ // the next four options help to filter out
75
+ // request/response headers/cookies
76
+ // each of the option accepts an object with:
77
+ // - allow: '*' | (string | RegExp)[] - a list to allow (default '*')
78
+ // - block: '*' | (string | RegExp)[] - a list to block
79
+ // - overwrite: Record<string| string> | ((data: object) -> object) - object or fn to overwrite data
80
+ reqHeaders: { block: ['referer'] },
81
+ reqCookies: { allow: ['cookie-to-pass-upstream'] },
82
+ resHeaders: { overwrite: { 'x-proxied-by': 'wooks-proxy' } },
83
+ resCookies: { allow: ['cookie-to-pass-downstream'] },
84
+
85
+ // debug: true - will print proxy paths and headers/cookies
86
+ debug: true,
87
+ })
88
+ return fetchResponse // fetch response is supported, the body will be downstreamed
89
+
90
+ // > you can also return fully buffered body as Uint8Array
91
+ // return new Uint8Array(await fetchResponse.arrayBuffer())
92
+
93
+ // > or as string
94
+ // return fetchResponse.text()
95
+
96
+ // > or change response before return
97
+ // const data = await fetchResponse.text() + '<new data>'
98
+ // return data
99
+ })
100
+ //...
101
+ ```
102
+
package/dist/index.cjs ADDED
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ var nodeFetchNative = require('node-fetch-native');
4
+ var eventHttp = require('@wooksjs/event-http');
5
+
6
+ /******************************************************************************
7
+ Copyright (c) Microsoft Corporation.
8
+
9
+ Permission to use, copy, modify, and/or distribute this software for any
10
+ purpose with or without fee is hereby granted.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
13
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
14
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
15
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
16
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
17
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
18
+ PERFORMANCE OF THIS SOFTWARE.
19
+ ***************************************************************************** */
20
+
21
+ function __awaiter(thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ }
30
+
31
+ const banner = () => `[${"@wooksjs/http-proxy"}][${new Date().toISOString().replace('T', ' ').replace(/\.\d{3}z$/i, '')}] `;
32
+
33
+ /* istanbul ignore file */
34
+ function warn(text) {
35
+ console.warn('' + banner() + text + '');
36
+ }
37
+
38
+ class IterableRecords {
39
+ constructor() {
40
+ this.index = 0;
41
+ }
42
+ [Symbol.iterator]() {
43
+ return this;
44
+ }
45
+ next() {
46
+ return { value: undefined, done: true };
47
+ }
48
+ }
49
+ class CookiesIterable extends IterableRecords {
50
+ constructor(cookiesString) {
51
+ super();
52
+ this.cookies = cookiesString.split(/,\s(?!\d{2}[\s-])/);
53
+ }
54
+ next() {
55
+ const str = this.cookies[this.index++];
56
+ const ind = str ? str.indexOf('=') : 0;
57
+ return this.index <= this.cookies.length ?
58
+ { value: [str.slice(0, ind), str.slice(ind + 1)], done: false } :
59
+ { value: undefined, done: true };
60
+ }
61
+ }
62
+ class HeadersIterable extends IterableRecords {
63
+ constructor(headers) {
64
+ super();
65
+ this.entries = Object.entries(headers);
66
+ }
67
+ next() {
68
+ return this.index < this.entries.length ?
69
+ { value: this.entries[this.index++], done: false } :
70
+ { value: undefined, done: true };
71
+ }
72
+ }
73
+ function applyProxyControls(records, controls, additionalBlockers) {
74
+ let result = {};
75
+ const { allow, block, overwrite } = controls;
76
+ const defaultedAllow = allow || '*';
77
+ if (defaultedAllow) {
78
+ for (const [name, value] of records) {
79
+ const add = block !== '*' &&
80
+ (!additionalBlockers || !additionalBlockers.includes(name)) && (defaultedAllow === '*' ||
81
+ (defaultedAllow.find(item => typeof item === 'string' && name.toLowerCase() === item.toLowerCase() || item instanceof RegExp && item.test(name)))) && (!block || !block.find(item => typeof item === 'string' && name.toLowerCase() === item.toLowerCase() || item instanceof RegExp && item.test(name)));
82
+ if (add) {
83
+ result[name] = value;
84
+ }
85
+ }
86
+ }
87
+ if (overwrite) {
88
+ if (typeof overwrite === 'function') {
89
+ result = overwrite(result);
90
+ }
91
+ else {
92
+ result = Object.assign(Object.assign({}, result), overwrite);
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+
98
+ const reqHeadersToBlock = [
99
+ 'connection',
100
+ 'accept-encoding',
101
+ 'content-length',
102
+ 'upgrade-insecure-requests',
103
+ 'cookie',
104
+ ];
105
+ const resHeadersToBlock = [
106
+ 'transfer-encoding',
107
+ 'content-encoding',
108
+ 'set-cookie',
109
+ ];
110
+ function useProxy() {
111
+ const status = eventHttp.useStatus();
112
+ const { setHeader, headers: getSetHeaders } = eventHttp.useSetHeaders();
113
+ const { req } = eventHttp.useHttpContext().getCtx().event;
114
+ const setHeadersObject = getSetHeaders();
115
+ return function proxy(target, opts) {
116
+ return __awaiter(this, void 0, void 0, function* () {
117
+ const targetUrl = new URL(target);
118
+ const path = targetUrl.pathname || '/';
119
+ const url = new URL(path, targetUrl.origin).toString() + (targetUrl.search);
120
+ // preparing request headers and cookies
121
+ const modifiedHeaders = Object.assign(Object.assign({}, req.headers), { host: targetUrl.hostname });
122
+ const headers = (opts === null || opts === void 0 ? void 0 : opts.reqHeaders) ? applyProxyControls(new HeadersIterable(modifiedHeaders), opts === null || opts === void 0 ? void 0 : opts.reqHeaders, reqHeadersToBlock) : {};
123
+ const cookies = (opts === null || opts === void 0 ? void 0 : opts.reqCookies) && req.headers.cookie ? applyProxyControls(new CookiesIterable(req.headers.cookie), opts === null || opts === void 0 ? void 0 : opts.reqCookies) : null;
124
+ if (cookies) {
125
+ headers.cookie = Object.entries(cookies).map(v => v.join('=')).join('; ');
126
+ }
127
+ const method = (opts === null || opts === void 0 ? void 0 : opts.method) || req.method;
128
+ // actual request
129
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
130
+ console.log();
131
+ warn(`[proxy] ${''}${req.method} ${req.url}${''} → ${''}${method} ${url}${''}`);
132
+ console.log('' + 'headers:', JSON.stringify(headers, null, ' '), '');
133
+ }
134
+ const resp = yield nodeFetchNative.fetch(url, {
135
+ method,
136
+ body: ['GET', 'HEAD'].includes(method) ? undefined : req,
137
+ headers: headers,
138
+ });
139
+ // preparing response
140
+ status.value = resp.status;
141
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
142
+ console.log();
143
+ warn(`[proxy] ${resp.status} ${''}${req.method} ${req.url}${''} → ${''}${method} ${url}${''}`);
144
+ console.log(`${''}response headers:${''}`);
145
+ }
146
+ // preparing response headers
147
+ const resHeaders = (opts === null || opts === void 0 ? void 0 : opts.resHeaders) ? applyProxyControls(resp.headers.entries(), opts === null || opts === void 0 ? void 0 : opts.resHeaders, resHeadersToBlock) : null;
148
+ const resCookies = (opts === null || opts === void 0 ? void 0 : opts.resCookies) ? applyProxyControls(new CookiesIterable(resp.headers.get('set-cookie') || ''), opts === null || opts === void 0 ? void 0 : opts.resCookies) : null;
149
+ if (resHeaders) {
150
+ for (const [name, value] of Object.entries(resHeaders)) {
151
+ if (name) {
152
+ setHeader(name, value);
153
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
154
+ console.log(`\t${''}${name}=${''}${value}${''}`);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ if (resCookies) {
160
+ setHeadersObject['set-cookie'] = (setHeadersObject['set-cookie'] || []);
161
+ for (const [name, value] of Object.entries(resCookies)) {
162
+ if (name) {
163
+ setHeadersObject['set-cookie'].push(`${name}=${value}`);
164
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
165
+ console.log(`\t${''}${''}set-cookie=${''}${name}=${value}${''}`);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ return resp;
171
+ });
172
+ };
173
+ }
174
+
175
+ exports.useProxy = useProxy;
@@ -0,0 +1,18 @@
1
+ export declare interface TWooksProxyControls {
2
+ overwrite?: Record<string, string> | ((data: Record<string, string>) => Record<string, string>);
3
+ allow?: (string | RegExp)[] | '*';
4
+ block?: (string | RegExp)[] | '*';
5
+ }
6
+
7
+ export declare interface TWooksProxyOptions {
8
+ method?: string;
9
+ reqHeaders?: TWooksProxyControls;
10
+ reqCookies?: TWooksProxyControls;
11
+ resHeaders?: TWooksProxyControls;
12
+ resCookies?: TWooksProxyControls;
13
+ debug?: boolean;
14
+ }
15
+
16
+ export declare function useProxy(): (target: string, opts?: TWooksProxyOptions) => Promise<Response>;
17
+
18
+ export { }
package/dist/index.mjs ADDED
@@ -0,0 +1,173 @@
1
+ import { fetch } from 'node-fetch-native';
2
+ import { useStatus, useSetHeaders, useHttpContext } from '@wooksjs/event-http';
3
+
4
+ /******************************************************************************
5
+ Copyright (c) Microsoft Corporation.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ PERFORMANCE OF THIS SOFTWARE.
17
+ ***************************************************************************** */
18
+
19
+ function __awaiter(thisArg, _arguments, P, generator) {
20
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
21
+ return new (P || (P = Promise))(function (resolve, reject) {
22
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
23
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
24
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
25
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
26
+ });
27
+ }
28
+
29
+ const banner = () => `[${"@wooksjs/http-proxy"}][${new Date().toISOString().replace('T', ' ').replace(/\.\d{3}z$/i, '')}] `;
30
+
31
+ /* istanbul ignore file */
32
+ function warn(text) {
33
+ console.warn('' + banner() + text + '');
34
+ }
35
+
36
+ class IterableRecords {
37
+ constructor() {
38
+ this.index = 0;
39
+ }
40
+ [Symbol.iterator]() {
41
+ return this;
42
+ }
43
+ next() {
44
+ return { value: undefined, done: true };
45
+ }
46
+ }
47
+ class CookiesIterable extends IterableRecords {
48
+ constructor(cookiesString) {
49
+ super();
50
+ this.cookies = cookiesString.split(/,\s(?!\d{2}[\s-])/);
51
+ }
52
+ next() {
53
+ const str = this.cookies[this.index++];
54
+ const ind = str ? str.indexOf('=') : 0;
55
+ return this.index <= this.cookies.length ?
56
+ { value: [str.slice(0, ind), str.slice(ind + 1)], done: false } :
57
+ { value: undefined, done: true };
58
+ }
59
+ }
60
+ class HeadersIterable extends IterableRecords {
61
+ constructor(headers) {
62
+ super();
63
+ this.entries = Object.entries(headers);
64
+ }
65
+ next() {
66
+ return this.index < this.entries.length ?
67
+ { value: this.entries[this.index++], done: false } :
68
+ { value: undefined, done: true };
69
+ }
70
+ }
71
+ function applyProxyControls(records, controls, additionalBlockers) {
72
+ let result = {};
73
+ const { allow, block, overwrite } = controls;
74
+ const defaultedAllow = allow || '*';
75
+ if (defaultedAllow) {
76
+ for (const [name, value] of records) {
77
+ const add = block !== '*' &&
78
+ (!additionalBlockers || !additionalBlockers.includes(name)) && (defaultedAllow === '*' ||
79
+ (defaultedAllow.find(item => typeof item === 'string' && name.toLowerCase() === item.toLowerCase() || item instanceof RegExp && item.test(name)))) && (!block || !block.find(item => typeof item === 'string' && name.toLowerCase() === item.toLowerCase() || item instanceof RegExp && item.test(name)));
80
+ if (add) {
81
+ result[name] = value;
82
+ }
83
+ }
84
+ }
85
+ if (overwrite) {
86
+ if (typeof overwrite === 'function') {
87
+ result = overwrite(result);
88
+ }
89
+ else {
90
+ result = Object.assign(Object.assign({}, result), overwrite);
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+
96
+ const reqHeadersToBlock = [
97
+ 'connection',
98
+ 'accept-encoding',
99
+ 'content-length',
100
+ 'upgrade-insecure-requests',
101
+ 'cookie',
102
+ ];
103
+ const resHeadersToBlock = [
104
+ 'transfer-encoding',
105
+ 'content-encoding',
106
+ 'set-cookie',
107
+ ];
108
+ function useProxy() {
109
+ const status = useStatus();
110
+ const { setHeader, headers: getSetHeaders } = useSetHeaders();
111
+ const { req } = useHttpContext().getCtx().event;
112
+ const setHeadersObject = getSetHeaders();
113
+ return function proxy(target, opts) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ const targetUrl = new URL(target);
116
+ const path = targetUrl.pathname || '/';
117
+ const url = new URL(path, targetUrl.origin).toString() + (targetUrl.search);
118
+ // preparing request headers and cookies
119
+ const modifiedHeaders = Object.assign(Object.assign({}, req.headers), { host: targetUrl.hostname });
120
+ const headers = (opts === null || opts === void 0 ? void 0 : opts.reqHeaders) ? applyProxyControls(new HeadersIterable(modifiedHeaders), opts === null || opts === void 0 ? void 0 : opts.reqHeaders, reqHeadersToBlock) : {};
121
+ const cookies = (opts === null || opts === void 0 ? void 0 : opts.reqCookies) && req.headers.cookie ? applyProxyControls(new CookiesIterable(req.headers.cookie), opts === null || opts === void 0 ? void 0 : opts.reqCookies) : null;
122
+ if (cookies) {
123
+ headers.cookie = Object.entries(cookies).map(v => v.join('=')).join('; ');
124
+ }
125
+ const method = (opts === null || opts === void 0 ? void 0 : opts.method) || req.method;
126
+ // actual request
127
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
128
+ console.log();
129
+ warn(`[proxy] ${''}${req.method} ${req.url}${''} → ${''}${method} ${url}${''}`);
130
+ console.log('' + 'headers:', JSON.stringify(headers, null, ' '), '');
131
+ }
132
+ const resp = yield fetch(url, {
133
+ method,
134
+ body: ['GET', 'HEAD'].includes(method) ? undefined : req,
135
+ headers: headers,
136
+ });
137
+ // preparing response
138
+ status.value = resp.status;
139
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
140
+ console.log();
141
+ warn(`[proxy] ${resp.status} ${''}${req.method} ${req.url}${''} → ${''}${method} ${url}${''}`);
142
+ console.log(`${''}response headers:${''}`);
143
+ }
144
+ // preparing response headers
145
+ const resHeaders = (opts === null || opts === void 0 ? void 0 : opts.resHeaders) ? applyProxyControls(resp.headers.entries(), opts === null || opts === void 0 ? void 0 : opts.resHeaders, resHeadersToBlock) : null;
146
+ const resCookies = (opts === null || opts === void 0 ? void 0 : opts.resCookies) ? applyProxyControls(new CookiesIterable(resp.headers.get('set-cookie') || ''), opts === null || opts === void 0 ? void 0 : opts.resCookies) : null;
147
+ if (resHeaders) {
148
+ for (const [name, value] of Object.entries(resHeaders)) {
149
+ if (name) {
150
+ setHeader(name, value);
151
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
152
+ console.log(`\t${''}${name}=${''}${value}${''}`);
153
+ }
154
+ }
155
+ }
156
+ }
157
+ if (resCookies) {
158
+ setHeadersObject['set-cookie'] = (setHeadersObject['set-cookie'] || []);
159
+ for (const [name, value] of Object.entries(resCookies)) {
160
+ if (name) {
161
+ setHeadersObject['set-cookie'].push(`${name}=${value}`);
162
+ if (opts === null || opts === void 0 ? void 0 : opts.debug) {
163
+ console.log(`\t${''}${''}set-cookie=${''}${name}=${value}${''}`);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ return resp;
169
+ });
170
+ };
171
+ }
172
+
173
+ export { useProxy };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@wooksjs/http-proxy",
3
+ "version": "0.1.0",
4
+ "description": "Proxy Wooks composable",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/wooksjs/wooksjs.git",
14
+ "directory": "packages/http-proxy"
15
+ },
16
+ "keywords": [
17
+ "http",
18
+ "wooks",
19
+ "composables",
20
+ "web",
21
+ "framework",
22
+ "app",
23
+ "api",
24
+ "rest",
25
+ "restful",
26
+ "prostojs"
27
+ ],
28
+ "author": "Artem Maltsev",
29
+ "license": "MIT",
30
+ "bugs": {
31
+ "url": "https://github.com/wooksjs/wooksjs/issues"
32
+ },
33
+ "peerDependencies": {
34
+ "@wooksjs/event-http": "0.1.0"
35
+ },
36
+ "dependencies": {
37
+ "node-fetch-native": "^0.1.8"
38
+ },
39
+ "homepage": "https://github.com/wooksjs/wooksjs/tree/main/packages/http-proxy#readme"
40
+ }