Skip to content

Commit dd96ae9

Browse files
committed
[ECO-5260] feat: initial local proxy implementation
Core functionality includes: - Spying on HTTP Requests and Responses - Inspect outgoing HTTP requests from the client. - Inspect and modify incoming HTTP responses. - Spying on WebSocket Messages - Observe outgoing WebSocket messages from the client. - Inspect and modify incoming WebSocket messages. - Injecting WebSocket Messages - Simulate server messages by injecting WebSocket frames into the client session. - Breaking WebSocket and HTTP Connections - Force WebSocket disconnections to test reconnection behavior. - Simulate slow or broken HTTP responses to test timeout handling.
1 parent 7b754a1 commit dd96ae9

File tree

12 files changed

+871
-8
lines changed

12 files changed

+871
-8
lines changed

.github/workflows/check.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Check
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 20
18+
- run: npm ci
19+
20+
- name: Run test
21+
run: npm run test

README.md

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,92 @@ During SDK testing, it’s often useful to simulate real-world conditions or ins
2222
- Replay server messages for automated or regression testing.
2323
- Monitor raw protocol-level activity to catch subtle bugs or race conditions.
2424

25-
🚀 Getting Started
25+
### 🚀 Getting Started
2626

2727
Install npm dependency:
2828

2929
```bash
30-
npm i -D @ably-labs/local-proxy
30+
npm install --save-dev @ably-labs/local-proxy
3131
```
3232

33+
### 🧪 Basic Usage
3334

35+
```ts
36+
import { createInterceptingProxy } from '@ably-labs/local-proxy';
3437

38+
// 1. Create and start the proxy
39+
const proxy = createInterceptingProxy({
40+
// options here realtimeHost, restHost
41+
});
42+
await proxy.start();
3543

44+
// 2. Configure your SDK to use proxy.options
45+
const client = new Ably.Realtime({
46+
...proxy.options
47+
// aditonal option
48+
});
49+
50+
// 3. Observe an outgoing HTTP request
51+
const request = await proxy.observeNextRequest(req =>
52+
req.url.includes('/channels')
53+
);
54+
55+
// 4. Observe an incoming protocol message
56+
proxy.observeNextIncomingProtocolMessage(msg =>
57+
msg.action === 15 // Presence message
58+
)
59+
60+
// 5. Inject a fake message into the client
61+
proxy.injectProtocolMessage(client.connection.id, {
62+
action: 9, // Example: SYNC
63+
channel: 'room:test',
64+
connectionId: client.connection.id,
65+
msgSerial: 0,
66+
connectionSerial: -1,
67+
data: { custom: 'data' },
68+
});
69+
```
70+
71+
### 🧩 Replace a Server Message
72+
73+
You can also simulate faulty server responses:
74+
75+
```ts
76+
proxy.replaceNextIncomingProtocolMessage(
77+
{
78+
action: 9, // Fake SYNC
79+
channel: 'room:test',
80+
data: [],
81+
},
82+
msg => msg.action === 9 // Replace only SYNC messages
83+
);
84+
```
85+
86+
87+
### 🔌 Drop or Pause a Connection
88+
89+
```ts
90+
// Drop connection by ID (force disconnect)
91+
proxy.dropConnection(client.connection.id);
92+
93+
// Pause and resume connection manually
94+
const resume = proxy.pauseConnection();
95+
// simulate a pause...
96+
setTimeout(() => resume(), 5000);
97+
```
98+
99+
100+
### 🔧 Register Middleware
101+
102+
For more advanced use cases, register middlewares to continuously inspect or modify traffic:
103+
104+
```ts
105+
const unregister = proxy.registerRestMiddleware(req => {
106+
if (req.url.includes('/channels')) {
107+
console.log('Intercepted REST request:', req);
108+
// You can modify headers, body, or response here
109+
}
110+
});
111+
// Later...
112+
unregister();
113+
```

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@ably-labs/local-proxy",
3-
"version": "0.0.6",
4-
"description": "Unified Test Suite for Chat SDKs. An executable npm package designed to provide a consistent testing environment for different Ably Chat SDKs",
3+
"version": "0.1.0",
4+
"description": "Local proxy for SDK testing",
55
"scripts": {
66
"build": "tsc",
77
"prepare": "npm run build",

src/async-queue.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { CompletableDeferred } from './completable-deferred';
2+
3+
type AsyncTask = () => Promise<void>;
4+
5+
export class AsyncQueue {
6+
private readonly queue: AsyncTask[] = [];
7+
private processing: boolean = false;
8+
9+
enqueue(task: AsyncTask): Promise<void> {
10+
const deferredValue = CompletableDeferred<void>();
11+
this.queue.push(async () => {
12+
try {
13+
await task();
14+
} finally {
15+
deferredValue.complete();
16+
}
17+
});
18+
this.processNext();
19+
return deferredValue.get()
20+
}
21+
22+
private async processNext() {
23+
if (this.processing || this.queue.length === 0) return;
24+
25+
this.processing = true;
26+
27+
const task = this.queue.shift();
28+
29+
try {
30+
await task!!();
31+
} finally {
32+
this.processing = false;
33+
this.processNext();
34+
}
35+
}
36+
}

src/completable-deferred.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
type CompletionHandler<T = void> = (value: T) => void
2+
3+
class DefaultCompletableDeferred<T = void> {
4+
private _completeWith!: CompletionHandler<T>;
5+
private value: T | undefined
6+
7+
private valuePromise = new Promise<T>(resolve => {
8+
this._completeWith = resolve;
9+
});
10+
11+
public complete(value: T): void {
12+
this._completeWith(value);
13+
}
14+
15+
public async get(): Promise<T> {
16+
return this.value ?? this.valuePromise;
17+
}
18+
}
19+
20+
export function CompletableDeferred<T>() {
21+
return new DefaultCompletableDeferred<T>()
22+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './proxy.js';

0 commit comments

Comments
 (0)