mockttp
Advanced tools
Comparing version 1.0.1 to 1.0.2
declare module 'websocket-stream' { | ||
import { Duplex } from "stream"; | ||
function connectWebSocketStream(socket: WebSocket, option?: { objectMode?: boolean }): Duplex; | ||
function connectWebSocketStream(url: string, option?: { objectMode?: boolean }): Duplex; | ||
interface WebsocketOptions { | ||
objectMode?: boolean; | ||
headers?: { [key: string]: string } | ||
} | ||
function connectWebSocketStream(socket: WebSocket, options?: WebsocketOptions): Duplex; | ||
function connectWebSocketStream(url: string, options?: WebsocketOptions): Duplex; | ||
export = connectWebSocketStream; | ||
} |
@@ -29,2 +29,6 @@ /** | ||
export interface MockttpClientOptions extends MockttpOptions { | ||
/** | ||
* Options to include on all client requests, e.g. to add extra | ||
* headers for authentication. | ||
*/ | ||
client?: { | ||
@@ -31,0 +35,0 @@ headers?: { |
@@ -230,5 +230,7 @@ "use strict"; | ||
openStreamToMockServer(config) { | ||
var _a; | ||
const standaloneStreamServer = this.mockServerOptions.standaloneServerUrl.replace(/^http/, 'ws'); | ||
const stream = connectWebSocketStream(`${standaloneStreamServer}/server/${config.port}/stream`, { | ||
objectMode: true | ||
objectMode: true, | ||
headers: (_a = this.mockClientOptions) === null || _a === void 0 ? void 0 : _a.headers | ||
}); | ||
@@ -384,3 +386,4 @@ return new Promise((resolve, reject) => { | ||
reconnect: true, | ||
reconnectionAttempts: 8 | ||
reconnectionAttempts: 8, | ||
wsOptionArguments: [this.mockClientOptions] | ||
}, WebSocket); | ||
@@ -387,0 +390,0 @@ // Note the typeHasField checks - these are a quick hack for backward compatibility, |
@@ -16,2 +16,3 @@ /** | ||
private debug; | ||
private requiredOrigin; | ||
private app; | ||
@@ -18,0 +19,0 @@ private server; |
@@ -34,2 +34,31 @@ "use strict"; | ||
const types_1 = require("../types"); | ||
function strictOriginMatch(origin, expectedOrigin) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!origin) | ||
return false; | ||
if (typeof expectedOrigin === 'string') { | ||
return expectedOrigin === origin; | ||
} | ||
if (_.isRegExp(expectedOrigin)) { | ||
return !!origin.match(expectedOrigin); | ||
} | ||
if (_.isArray(expectedOrigin)) { | ||
return _.some(expectedOrigin, (exp) => (typeof exp === 'string') | ||
? exp === origin | ||
: origin.match(exp)); | ||
} | ||
if (_.isFunction(expectedOrigin)) { | ||
return new Promise((resolve, reject) => { | ||
expectedOrigin(origin, (error, result) => { | ||
if (error) | ||
reject(error); | ||
else | ||
resolve(result); | ||
}); | ||
}); | ||
} | ||
// We don't allow boolean or undefined matches | ||
return false; | ||
}); | ||
} | ||
class MockttpStandalone { | ||
@@ -47,7 +76,13 @@ constructor(options = {}) { | ||
this.app.use(cors(options.corsOptions)); | ||
if (options.corsOptions && options.corsOptions.strict) { | ||
// If you use strict CORS, and set a specific origin, we'll enforce it: | ||
this.requiredOrigin = !!options.corsOptions && | ||
!!options.corsOptions.strict && | ||
!!options.corsOptions.origin && | ||
typeof options.corsOptions.origin !== 'boolean' && | ||
options.corsOptions.origin; | ||
if (this.requiredOrigin) { | ||
this.app.use(corsGate({ | ||
strict: true, | ||
allowSafe: false, | ||
origin: '' // No origin - we accept *no* same-origin requests | ||
origin: '' // No base origin - we accept *no* same-origin requests | ||
})); | ||
@@ -107,4 +142,11 @@ } | ||
this.server = destroyable_server_1.destroyable(this.app.listen(listenOptions, resolve)); | ||
this.server.on('upgrade', (req, socket, head) => { | ||
this.server.on('error', reject); | ||
this.server.on('upgrade', (req, socket, head) => __awaiter(this, void 0, void 0, function* () { | ||
var _a; | ||
const reqOrigin = req.headers['origin']; | ||
if (this.requiredOrigin && !(yield strictOriginMatch(reqOrigin, this.requiredOrigin))) { | ||
console.warn(`Websocket request from invalid origin: ${req.headers['origin']}`); | ||
socket.destroy(); | ||
return; | ||
} | ||
let isSubscriptionRequest = req.url.match(/^\/server\/(\d+)\/subscription$/); | ||
@@ -131,3 +173,3 @@ let isStreamRequest = req.url.match(/^\/server\/(\d+)\/stream$/); | ||
} | ||
}); | ||
})); | ||
}); | ||
@@ -134,0 +176,0 @@ }); |
{ | ||
"name": "mockttp", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"description": "Mock HTTP server for testing HTTP clients and stubbing webservices", | ||
@@ -5,0 +5,0 @@ "main": "dist/main.js", |
@@ -70,2 +70,6 @@ /** | ||
export interface MockttpClientOptions extends MockttpOptions { | ||
/** | ||
* Options to include on all client requests, e.g. to add extra | ||
* headers for authentication. | ||
*/ | ||
client?: { | ||
@@ -190,3 +194,4 @@ headers?: { [key: string]: string }; | ||
const stream = connectWebSocketStream(`${standaloneStreamServer}/server/${config.port}/stream`, { | ||
objectMode: true | ||
objectMode: true, | ||
headers: this.mockClientOptions?.headers | ||
}); | ||
@@ -451,3 +456,4 @@ | ||
reconnect: true, | ||
reconnectionAttempts: 8 | ||
reconnectionAttempts: 8, | ||
wsOptionArguments: [this.mockClientOptions] | ||
}, WebSocket); | ||
@@ -454,0 +460,0 @@ |
@@ -38,4 +38,41 @@ /** | ||
async function strictOriginMatch( | ||
origin: string | undefined, | ||
expectedOrigin: cors.CorsOptions['origin'] | ||
): Promise<boolean> { | ||
if (!origin) return false; | ||
if (typeof expectedOrigin === 'string') { | ||
return expectedOrigin === origin; | ||
} | ||
if (_.isRegExp(expectedOrigin)) { | ||
return !!origin.match(expectedOrigin); | ||
} | ||
if (_.isArray(expectedOrigin)) { | ||
return _.some(expectedOrigin, (exp) => | ||
(typeof exp === 'string') | ||
? exp === origin | ||
: origin.match(exp) | ||
); | ||
} | ||
if (_.isFunction(expectedOrigin)) { | ||
return new Promise<boolean>((resolve, reject) => { | ||
expectedOrigin(origin, (error, result) => { | ||
if (error) reject(error); | ||
else resolve(result); | ||
}); | ||
}); | ||
} | ||
// We don't allow boolean or undefined matches | ||
return false; | ||
} | ||
export class MockttpStandalone { | ||
private debug: boolean; | ||
private requiredOrigin: cors.CorsOptions['origin'] | false; | ||
private app = express(); | ||
@@ -51,7 +88,15 @@ private server: DestroyableServer | null = null; | ||
this.app.use(cors(options.corsOptions)); | ||
if (options.corsOptions && options.corsOptions.strict) { | ||
// If you use strict CORS, and set a specific origin, we'll enforce it: | ||
this.requiredOrigin = !!options.corsOptions && | ||
!!options.corsOptions.strict && | ||
!!options.corsOptions.origin && | ||
typeof options.corsOptions.origin !== 'boolean' && | ||
options.corsOptions.origin; | ||
if (this.requiredOrigin) { | ||
this.app.use(corsGate({ | ||
strict: true, // MUST send an allowed origin | ||
allowSafe: false, // Even for HEAD/GET requests (should be none anyway) | ||
origin: '' // No origin - we accept *no* same-origin requests | ||
origin: '' // No base origin - we accept *no* same-origin requests | ||
})); | ||
@@ -130,3 +175,12 @@ } | ||
this.server.on('upgrade', (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { | ||
this.server.on('error', reject); | ||
this.server.on('upgrade', async (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { | ||
const reqOrigin = req.headers['origin'] as string | undefined; | ||
if (this.requiredOrigin && !await strictOriginMatch(reqOrigin, this.requiredOrigin)) { | ||
console.warn(`Websocket request from invalid origin: ${req.headers['origin']}`); | ||
socket.destroy(); | ||
return; | ||
} | ||
let isSubscriptionRequest = req.url!.match(/^\/server\/(\d+)\/subscription$/); | ||
@@ -133,0 +187,0 @@ let isStreamRequest = req.url!.match(/^\/server\/(\d+)\/stream$/); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
731097
13255