Skip to content

Commit 73f6320

Browse files
MasterOdindarrachequesne
authored andcommitted
feat: add option to allow usage of custom parser (#471)
This PR adds a new parser option to the adapter constructor to allow setting a custom parser to use, defaulting to using notepack.io. This would allow someone to use a different msgpack library if they wanted, or even an entirely different protocol altogether (e.g. protobuf). Related: - #462 - #469
1 parent d2977a3 commit 73f6320

File tree

5 files changed

+117
-6
lines changed

5 files changed

+117
-6
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ The following options are allowed:
194194

195195
- `key`: the name of the key to pub/sub events on as prefix (`socket.io`)
196196
- `requestsTimeout`: optional, after this timeout the adapter will stop waiting from responses to request (`5000ms`)
197+
- `parser`: optional, parser to use for encoding and decoding messages passed through Redis ([`notepack.io`](https://www.npmjs.com/package/notepack.io))
197198

198199
### RedisAdapter
199200

@@ -205,6 +206,7 @@ that a regular `Adapter` does not
205206
- `pubClient`
206207
- `subClient`
207208
- `requestsTimeout`
209+
- `parser`
208210

209211
### RedisAdapter#sockets(rooms: Set<String>)
210212

lib/index.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ interface AckRequest {
3737
ack: (...args: any[]) => void;
3838
}
3939

40+
interface Parser {
41+
decode: (msg: any) => any;
42+
encode: (msg: any) => any;
43+
}
44+
4045
const isNumeric = (str) => !isNaN(str) && !isNaN(parseFloat(str));
4146

4247
export interface RedisAdapterOptions {
@@ -62,6 +67,11 @@ export interface RedisAdapterOptions {
6267
* @default false
6368
*/
6469
publishOnSpecificResponseChannel: boolean;
70+
/**
71+
* The parser to use for encoding and decoding messages sent to Redis.
72+
* This option defaults to using `notepack.io`, a MessagePack implementation.
73+
*/
74+
parser: Parser;
6575
}
6676

6777
/**
@@ -87,6 +97,7 @@ export class RedisAdapter extends Adapter {
8797
public readonly uid;
8898
public readonly requestsTimeout: number;
8999
public readonly publishOnSpecificResponseChannel: boolean;
100+
public readonly parser: Parser;
90101

91102
private readonly channel: string;
92103
private readonly requestChannel: string;
@@ -115,6 +126,7 @@ export class RedisAdapter extends Adapter {
115126
this.uid = uid2(6);
116127
this.requestsTimeout = opts.requestsTimeout || 5000;
117128
this.publishOnSpecificResponseChannel = !!opts.publishOnSpecificResponseChannel;
129+
this.parser = opts.parser || msgpack;
118130

119131
const prefix = opts.key || "socket.io";
120132

@@ -181,7 +193,7 @@ export class RedisAdapter extends Adapter {
181193
return debug("ignore unknown room %s", room);
182194
}
183195

184-
const args = msgpack.decode(msg);
196+
const args = this.parser.decode(msg);
185197

186198
const [uid, packet, opts] = args;
187199
if (this.uid === uid) return debug("ignore same uid");
@@ -226,7 +238,7 @@ export class RedisAdapter extends Adapter {
226238
if (msg[0] === 0x7b) {
227239
request = JSON.parse(msg.toString());
228240
} else {
229-
request = msgpack.decode(msg);
241+
request = this.parser.decode(msg);
230242
}
231243
} catch (err) {
232244
debug("ignoring malformed request");
@@ -424,7 +436,7 @@ export class RedisAdapter extends Adapter {
424436

425437
this.publishResponse(
426438
request,
427-
msgpack.encode({
439+
this.parser.encode({
428440
type: RequestType.BROADCAST_ACK,
429441
requestId: request.requestId,
430442
packet: arg,
@@ -467,7 +479,7 @@ export class RedisAdapter extends Adapter {
467479
if (msg[0] === 0x7b) {
468480
response = JSON.parse(msg.toString());
469481
} else {
470-
response = msgpack.decode(msg);
482+
response = this.parser.decode(msg);
471483
}
472484
} catch (err) {
473485
debug("ignoring malformed response");
@@ -596,7 +608,7 @@ export class RedisAdapter extends Adapter {
596608
except: [...new Set(opts.except)],
597609
flags: opts.flags,
598610
};
599-
const msg = msgpack.encode([this.uid, packet, rawOpts]);
611+
const msg = this.parser.encode([this.uid, packet, rawOpts]);
600612
let channel = this.channel;
601613
if (opts.rooms && opts.rooms.size === 1) {
602614
channel += opts.rooms.keys().next().value + "#";
@@ -626,7 +638,7 @@ export class RedisAdapter extends Adapter {
626638
flags: opts.flags,
627639
};
628640

629-
const request = msgpack.encode({
641+
const request = this.parser.encode({
630642
uid: this.uid,
631643
requestId,
632644
type: RequestType.BROADCAST,

test/custom-parser.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { createServer } from "http";
2+
import { Server, Socket as ServerSocket } from "socket.io";
3+
import { io as ioc, Socket as ClientSocket } from "socket.io-client";
4+
import { createAdapter } from "../lib";
5+
import { createClient } from "redis";
6+
import { AddressInfo } from "net";
7+
import { times } from "./util";
8+
import expect = require("expect.js");
9+
10+
const NODES_COUNT = 3;
11+
12+
describe("custom parser", () => {
13+
let servers: Server[] = [],
14+
serverSockets: ServerSocket[] = [],
15+
clientSockets: ClientSocket[] = [],
16+
redisClients: any[] = [];
17+
18+
beforeEach(async () => {
19+
for (let i = 1; i <= NODES_COUNT; i++) {
20+
const httpServer = createServer();
21+
const pubClient = createClient();
22+
const subClient = createClient();
23+
24+
await Promise.all([pubClient.connect(), subClient.connect()]);
25+
26+
redisClients.push(pubClient, subClient);
27+
28+
const io = new Server(httpServer, {
29+
adapter: createAdapter(pubClient, subClient, {
30+
parser: {
31+
decode(msg) {
32+
return JSON.parse(msg);
33+
},
34+
encode(msg) {
35+
return JSON.stringify(msg);
36+
},
37+
},
38+
}),
39+
});
40+
41+
await new Promise((resolve) => {
42+
httpServer.listen(() => {
43+
const port = (httpServer.address() as AddressInfo).port;
44+
const clientSocket = ioc(`http://localhost:${port}`);
45+
46+
io.on("connection", async (socket) => {
47+
clientSockets.push(clientSocket);
48+
serverSockets.push(socket);
49+
servers.push(io);
50+
resolve();
51+
});
52+
});
53+
});
54+
}
55+
});
56+
57+
afterEach(() => {
58+
servers.forEach((server) => {
59+
// @ts-ignore
60+
server.httpServer.close();
61+
server.of("/").adapter.close();
62+
});
63+
clientSockets.forEach((socket) => {
64+
socket.disconnect();
65+
});
66+
redisClients.forEach((redisClient) => {
67+
redisClient.disconnect();
68+
});
69+
});
70+
71+
it("broadcasts", (done) => {
72+
const partialDone = times(3, done);
73+
74+
clientSockets.forEach((clientSocket) => {
75+
clientSocket.on("test", (arg1, arg2, arg3) => {
76+
expect(arg1).to.eql(1);
77+
expect(arg2).to.eql("2");
78+
expect(arg3).to.eql([3]);
79+
partialDone();
80+
});
81+
});
82+
83+
servers[0].emit("test", 1, "2", [3]);
84+
});
85+
});

test/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,5 @@ function cleanup(done) {
562562
}
563563
done();
564564
}
565+
566+
require("./custom-parser");

test/util.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,13 @@ Assertion.prototype.contain = function (...args) {
2020
}
2121
return contain.apply(this, args);
2222
};
23+
24+
export function times(count: number, fn: () => void) {
25+
let i = 0;
26+
return () => {
27+
i++;
28+
if (i === count) {
29+
fn();
30+
}
31+
};
32+
}

0 commit comments

Comments
 (0)