This repository was archived by the owner on Jun 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathservice-api.ts
159 lines (134 loc) · 4.15 KB
/
service-api.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* graasp-plugin-websockets
*
* Fastify plugin for graasp-plugin-websockets
*
* Integrates the {@link WebSocketChannels} abstraction
* in a fastify server plugin with @fastify/websocket
*/
import { RedisOptions } from 'ioredis';
import util from 'util';
import fws from '@fastify/websocket';
import { FastifyLoggerInstance, FastifyPluginAsync } from 'fastify';
import fp from 'fastify-plugin';
import config from './config';
import { AjvMessageSerializer } from './message-serializer';
import { MultiInstanceChannelsBroker } from './multi-instance';
import { WebSocketChannels } from './ws-channels';
import { WebsocketService } from './ws-service';
/**
* Type definition for plugin options
*/
interface PluginOptions {
prefix?: string;
redis?: {
config?: RedisOptions;
channelName?: string;
};
}
/**
* Helper function to destructure options into useful config params
*/
function destructureOptions(options: PluginOptions) {
const prefix = options.prefix ?? '/ws';
const redis = {
config: {
...options.redis?.config,
port: options.redis?.config?.port ?? config.redis.port,
host: options.redis?.config?.host ?? config.redis.host,
password: options.redis?.config?.password ?? config.redis.password,
},
notifChannel: options.redis?.channelName ?? config.redis.notifChannel,
};
return { prefix, redis };
}
/**
* Helper function to log boot message after plugin initialization
*/
function logBootMessage(log: FastifyLoggerInstance, config: PluginOptions) {
// don't log password
const publicRedisConfig = { ...config.redis };
delete publicRedisConfig.config?.password;
const prettyRedisConfig = util.inspect(publicRedisConfig, {
breakLength: Infinity,
});
log.info(
`graasp-plugin-websockets: plugin booted with prefix ${config.prefix} and Redis parameters ${prettyRedisConfig}`,
);
}
const plugin: FastifyPluginAsync<PluginOptions> = async (fastify, options) => {
// destructure passed fastify instance
const { log, validateSession } = fastify;
// configure plugin
const config = destructureOptions(options);
// must await this register call: otherwise decorated properties on `fastify` are not available
await fastify.register(fws, {
errorHandler: (error, conn, req, reply) => {
// remove client if needed
if (wsChannels) {
wsChannels.clientRemove(conn.socket);
}
log.error(
`graasp-plugin-websockets: an error occured: ${error}\n\tDestroying connection`,
);
conn.destroy();
},
});
// Serializer / deserializer instance
const serdes = new AjvMessageSerializer();
// create channels abstraction instance
const wsChannels = new WebSocketChannels(
fastify.websocketServer,
serdes.serialize,
log,
);
// create multi-instance channels broker
const wsMultiBroker = new MultiInstanceChannelsBroker(
wsChannels,
config.redis,
log,
);
// create websockets service
const wsService = new WebsocketService(
wsChannels,
wsMultiBroker,
serdes.parse,
log,
);
// decorate server with service
fastify.decorate('websockets', wsService);
// decorate with debug internals in test mode
if (process.env.NODE_ENV === 'test') {
fastify.decorate('_debug_websocketsChannels', wsChannels);
}
fastify.register(async (fastify) => {
// user must have valid session
fastify.addHook('preHandler', validateSession);
// handle incoming requests
fastify.get(config.prefix, { websocket: true }, (conn, req) => {
// raw websocket client
const client = conn.socket;
// member from valid session
const { member } = req;
wsChannels.clientRegister(client);
client.on('message', (msg) =>
wsService.handleRequest(msg, member, client),
);
client.on('error', log.error);
client.on('close', (code, reason) => {
wsChannels.clientRemove(client);
});
});
});
// cleanup on server close
fastify.addHook('onClose', (instance, done) => {
wsMultiBroker.close();
wsChannels.close();
done();
});
logBootMessage(log, config);
};
export default fp(plugin, {
fastify: '3.x',
name: 'graasp-plugin-websockets',
});