Skip to content

Commit

Permalink
Uses a new "createInterceptor" API
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Mar 1, 2021
1 parent 11ba9b5 commit 0db6518
Show file tree
Hide file tree
Showing 42 changed files with 634 additions and 665 deletions.
90 changes: 25 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,37 +54,40 @@ npm install node-request-interceptor

## API

### `RequestInterceptor(interceptors: Interceptor[])`
### `createInterceptor(options: CreateInterceptorOptions)`

```js
import { RequestInterceptor } from 'node-request-interceptor'
import withDefaultInterceptors from 'node-request-interceptor/lib/presets/default'

const interceptor = new RequestInterceptor({
modules: withDefaultInterceptors,
import { createInterceptor } from 'node-request-interceptor'
import nodeInterceptors from 'node-request-interceptor/lib/presets/node'

const interceptor = createInterceptor({
modules: nodeInterceptors,
resolver(request, ref) {
// Optionally, return a mocked response.
},
})
```

> Using the `/presets/default` interceptors preset is the recommended way to ensure all requests get intercepted, regardless of their origin.
> Using the `/presets/node` interceptors preset is the recommended way to ensure all requests get intercepted, regardless of their origin.
### Interceptors

This library utilizes a concept of an _interceptor_–a module that performs necessary patching, handles a mocked response, and restores patched instances.
This library utilizes a concept of _interceptors_–functions that patch necessary modules, handle mocked responses, and restore patched modules.

**The list of interceptors:**
**List of interceptors:**

- `/interceptors/ClientRequest`
- `/interceptors/XMLHttpRequest`

To use one, or multiple interceptors, import and provide them to the `RequestInterceptor` constructor.
To use a single, or multiple interceptors, import and provide them to the `RequestInterceptor` constructor.

```js
import { RequestInterceptor } from 'node-request-interceptor'
import { createInterceptor } from 'node-request-interceptor'
import { interceptXMLHttpRequest } from 'node-request-interceptor/lib/interceptors/XMLHttpRequest'

// This `interceptor` instance would handle only XMLHttpRequest,
// ignoring requests issued via `http`/`https` modules.
const interceptor = new RequestInterceptor({
const interceptor = new createInterceptor({
modules: [interceptXMLHttpRequest],
})
```
Expand All @@ -93,39 +96,24 @@ const interceptor = new RequestInterceptor({
### Methods

#### `.use(middleware: (req: InterceptedRequest, ref: IncomingMessage | XMLHttpRequest) => MockedResponse): void`

Applies a given middleware function to an intercepted request. May return a [`MockedResponse`](#MockedResponse) object that is going to be used to respond to an intercepted request.
#### `.apply(): void`

##### Requests monitoring
Applies module patches and enabled interception of the requests.

```js
interceptor.use((req) => {
// Will print to stdout any outgoing requests
// without affecting their responses
console.log('%s %s', req.method, req.url.href)
})
interceptor.apply()
```

##### Response mocking
#### `.on(event, listener): boolean`

Adds an event listener to one of the following supported events:

When a request middleware returns a [`MockedResponse`](#MockedResponse) object, it will be returned as the response to the intercepted request. This library automatically creates a proper response instance according to the request issuing module (`http`/`XMLHttpRequest`).
- `request`, whenever a new request happens.
- `response`, whenever a request library responds to a request.

```js
interceptor.use((req) => {
if (['https://google.com'].includes(req.url.origin)) {
// Will return a mocked response for any request
// that is issued from the "https://google.com" origin.
return {
status: 301,
headers: {
'x-powered-by': 'node-request-interceptor',
},
body: JSON.stringify({
message: 'Hey, I am a mocked response',
}),
}
}
interceptor.on('request', (request) => {
console.log('[%s] %s', request.method, request.url.toString())
})
```

Expand All @@ -137,34 +125,6 @@ Restores all patched modules and stops the interception of any future requests.
interceptor.restore()
```

---

### `InterceptedRequest`

```ts
interface InterceptedRequest {
url: URL
method: string
headers?: http.OutgoingHttpHeaders
body?: string
}
```

---

### `MockedResponse`

Whenever a `MockedResponse` object is returned from the request middleware function, it's being used to constructs a relevant response for the intercepted request.

```ts
interface MockedResponse {
status?: number
statusText?: string
headers?: Record<string, string | string[]>
body?: string
}
```

## Special mention

The following libraries were used as an inspiration to write this low-level API:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"dependencies": {
"@open-draft/until": "^1.0.3",
"debug": "^4.3.0",
"headers-utils": "^1.2.0",
"headers-utils": "^1.2.4",
"strict-event-emitter": "^0.1.0"
},
"keywords": [
Expand Down
66 changes: 0 additions & 66 deletions src/RequestInterceptor.ts

This file was deleted.

91 changes: 91 additions & 0 deletions src/createInterceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { IncomingMessage } from 'http'
import { Headers, HeadersObject } from 'headers-utils'
import { StrictEventEmitter } from 'strict-event-emitter'

export type Interceptor = (
observer: Observer,
resolver: Resolver
) => InterceptorCleanupFn

export type Observer = StrictEventEmitter<InterceptorEventsMap>

/**
* A side-effect function to restore all the patched modules.
*/
export type InterceptorCleanupFn = () => void

export interface IsomoprhicRequest {
url: URL
method: string
headers: HeadersObject
body?: string
}

export interface IsomoprhicResponse {
status: number
statusText: string
headers: Headers
body?: string
}

export interface MockedResponse
extends Omit<Partial<IsomoprhicResponse>, 'headers'> {
headers?: HeadersObject
}

interface InterceptorEventsMap {
request(request: IsomoprhicRequest): void
response(request: IsomoprhicRequest, response: IsomoprhicResponse): void
}

export type Resolver = (
request: IsomoprhicRequest,
ref: IncomingMessage | XMLHttpRequest
) => MockedResponse | Promise<MockedResponse | void> | void

export interface InterceptorOptions {
modules: Interceptor[]
resolver: Resolver
}

export interface InterceptorApi {
/**
* Apply necessary module patches to provision the interception of requests.
*/
apply(): void
on<Event extends keyof InterceptorEventsMap>(
event: Event,
listener: InterceptorEventsMap[Event]
): void
/**
* Restore all applied module patches and disable the interception.
*/
restore(): void
}

export function createInterceptor(options: InterceptorOptions): InterceptorApi {
const observer = new StrictEventEmitter<InterceptorEventsMap>()
let cleanupFns: InterceptorCleanupFn[] = []

return {
apply() {
cleanupFns = options.modules.map((interceptor) => {
return interceptor(observer, options.resolver)
})
},
on(event, listener) {
observer.addListener(event, listener)
},
restore() {
observer.removeAllListeners()

if (cleanupFns.length === 0) {
throw new Error(
`Failed to restore patched modules: no patches found. Did you forget to run ".apply()"?`
)
}

cleanupFns.forEach((restore) => restore())
},
}
}
49 changes: 0 additions & 49 deletions src/glossary.ts

This file was deleted.

14 changes: 1 addition & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
/* Classes */
export { RequestInterceptor } from './RequestInterceptor'
export * from './createInterceptor'

/* Utils */
export { getCleanUrl } from './utils/getCleanUrl'

/* Typings */
export {
Interceptor,
RequestInterceptorContext,
RequestInterceptorEventsMap,
RequestMiddleware,
InterceptedRequest,
MockedResponse,
ReturnedResponse,
} from './glossary'
10 changes: 10 additions & 0 deletions src/interceptors/ClientRequest/ClientRequest.glossary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IncomingMessage } from 'http'

// Request instance constructed by the `request` library
// has a `self` property that has a `uri` field. This is
// reproducible by performing a `XMLHttpRequest` request (jsdom).
export interface RequestSelf {
uri?: URL
}

export type HttpRequestCallback = (response: IncomingMessage) => void
Loading

0 comments on commit 0db6518

Please sign in to comment.