-
-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add WebSocket class interceptor
- Loading branch information
1 parent
3429931
commit 7c542cf
Showing
10 changed files
with
678 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import type { | ||
WebSocketSendData, | ||
WebSocketTransport, | ||
} from './WebSocketTransport' | ||
|
||
const kEmitter = Symbol('emitter') | ||
|
||
/** | ||
* The WebSocket client instance represents an incoming | ||
* client connection. The user can control the connection, | ||
* send and receive events. | ||
*/ | ||
export class WebSocketClient { | ||
protected [kEmitter]: EventTarget | ||
|
||
constructor( | ||
public readonly url: URL, | ||
protected readonly transport: WebSocketTransport | ||
) { | ||
this[kEmitter] = new EventTarget() | ||
|
||
/** | ||
* Emit incoming server events so they can be reacted to. | ||
* @note This does NOT forward the events to the client. | ||
* That must be done explicitly via "server.send()". | ||
*/ | ||
transport.onIncoming = (event) => { | ||
this[kEmitter].dispatchEvent(event) | ||
} | ||
} | ||
|
||
/** | ||
* Listen for incoming events from the connected client. | ||
*/ | ||
public on( | ||
event: string, | ||
listener: (...data: Array<WebSocketSendData>) => void | ||
): void { | ||
this[kEmitter].addEventListener(event, (event) => { | ||
if (event instanceof MessageEvent) { | ||
listener(event.data) | ||
} | ||
}) | ||
} | ||
|
||
/** | ||
* Send data to the connected client. | ||
*/ | ||
public send(data: WebSocketSendData): void { | ||
this.transport.send(data) | ||
} | ||
|
||
/** | ||
* Emit the given event to the connected client. | ||
*/ | ||
public emit(event: string, data: WebSocketSendData): void { | ||
throw new Error('WebSocketClient#emit is not implemented') | ||
} | ||
|
||
public close(error?: Error): void { | ||
// Don't do any guessing behind the close code's semantics | ||
// and fallback to a generic contrived close code of 3000. | ||
this.transport.close(error ? 3000 : 1000, error?.message) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import type { WebSocketSendData } from './WebSocketTransport' | ||
import type { WebSocketMessageListener } from './implementations/WebSocketClass/WebSocketClassInterceptor' | ||
|
||
/** | ||
* The WebSocket server instance represents the actual production | ||
* WebSocket server connection. It's idle by default but you can | ||
* establish it by calling `server.connect()`. | ||
*/ | ||
export class WebSocketServer { | ||
/** | ||
* Connect to the actual WebSocket server. | ||
*/ | ||
public connect(): Promise<void> { | ||
throw new Error('WebSocketServer#connect is not implemented') | ||
} | ||
|
||
/** | ||
* Send the data to the original server. | ||
* The connection to the original server will be opened | ||
* as a part of the first `server.send()` call. | ||
*/ | ||
public send(data: WebSocketSendData): void { | ||
throw new Error('WebSocketServer#send is not implemented') | ||
} | ||
|
||
/** | ||
* Listen to the incoming events from the original | ||
* WebSocket server. All the incoming events are automatically | ||
* forwarded to the client connection unless you prevent them | ||
* via `event.preventDefault()`. | ||
*/ | ||
public on(event: string, callback: WebSocketMessageListener): void { | ||
throw new Error('WebSocketServer#on is not implemented') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export type WebSocketSendData = | ||
| string | ||
| ArrayBufferLike | ||
| Blob | ||
| ArrayBufferView | ||
|
||
export type WebSocketTransportOnIncomingCallback = ( | ||
event: MessageEvent<WebSocketSendData> | ||
) => void | ||
|
||
export abstract class WebSocketTransport { | ||
/** | ||
* Listener for the incoming server events. | ||
* This is called when the client receives the | ||
* event from the original server connection. | ||
* | ||
* This way, we can trigger the "message" event | ||
* on the mocked connection to let the user know. | ||
*/ | ||
abstract onIncoming: WebSocketTransportOnIncomingCallback | ||
|
||
/** | ||
* Send the data from the server to this client. | ||
*/ | ||
abstract send(data: WebSocketSendData): void | ||
|
||
/** | ||
* Close the client connection. | ||
*/ | ||
abstract close(code: number, reason?: string): void | ||
} |
24 changes: 24 additions & 0 deletions
24
src/interceptors/WebSocket/implementations/WebSocketClass/WebSocketClassClient.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { invariant } from 'outvariant' | ||
import { WebSocketClient } from '../../WebSocketClient' | ||
import type { WebSocketSendData } from '../../WebSocketTransport' | ||
import { WebSocketClassTransport } from './WebSocketClassTransport' | ||
import type { WebSocketClassOverride } from './WebSocketClassInterceptor' | ||
|
||
export class WebSocketClassClient extends WebSocketClient { | ||
constructor( | ||
readonly ws: WebSocketClassOverride, | ||
readonly transport: WebSocketClassTransport | ||
) { | ||
super(new URL(ws.url), transport) | ||
} | ||
|
||
public emit(event: string, data: WebSocketSendData): void { | ||
invariant( | ||
event === 'message', | ||
'Failed to emit unknown WebSocket event "%s": only the "message" event is supported using the standard WebSocket class', | ||
event | ||
) | ||
|
||
this.transport.send(data) | ||
} | ||
} |
Oops, something went wrong.