vani-meeting-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,451 @@
1
+ import WebSocket, { WebSocketServer } from "ws";
2
+ import { RedisHandler } from "../lib/redis/RedisHandler";
3
+ import { Participant } from "../models/Participant";
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { EventEmitterHandler } from "../utility/EventEmitterHandler";
6
+ import { VaniEvent } from "../models/Event";
7
+ import { SFUHandler } from "../sfu/SFUHandler";
8
+ import { BaseSFUWebsocket } from "../base/BaseSFUWebsocket";
9
+
10
+
11
+ export enum WebSocketBasicEvents {
12
+ Config = "config",
13
+ JoinRoom = "joinRoom",
14
+ IsSetupDone = "setupDone",
15
+ Ping = "ping",
16
+ Pong = "pong",
17
+ AudioVideoPauseResume = "audioVideoPauseResume",
18
+ OnStartMeetingCalled = "startMeetingCalled",
19
+ OnAudioVideoPauseResume = "audioVideoPauseResume",
20
+ OnAudioVideoStatusUpdated = "audioVideoStatusUpdated",
21
+ OnAudioUnblockRequest = "audioUnblock",
22
+ OnVideoUnblockRequest = "videoUnblock",
23
+ OnAudioBlockRequest = "audioBlock",
24
+ OnVideoBlockRequest = "videoBlock",
25
+ UpdateParticipant = "updateParticipant",
26
+ OnParticipantUpdated = "participantUpdated",
27
+ OnNewJoinee = "newJoinee",
28
+ OnRejoined = "rejoined",
29
+ OnUserOnFoundWhileReconnect = "onUserOnFoundWhileReconnect",
30
+
31
+ OnUserLeft = "userLeft",
32
+ GetAllParticipant = "getParticipant",
33
+ OnServerParticipants = "participants",
34
+ GetMeetingStartTime = "getMeetingStartTime",
35
+ OnMeetingStartTime = "meetingStartTime",
36
+ GetOldMessages = "getOldMessages",
37
+ OnOldMessages = "oldMessages",
38
+ Message = "message",
39
+ OnChat = "chat",
40
+ SelfLeft = "selfLeft",
41
+ }
42
+ export enum SFUMessageType {
43
+ SFUMessage = "sfuMessage",
44
+ GetRouterRtpCapabilities = "routerRtpCapabilities",
45
+ OnRouterRtpCapabilities = "onRouterRtpCapabilities",
46
+ OnTransportConnect = "transportConnect",
47
+ OnCreateTransports = "createTransports",
48
+ OnTransportConnectDone = "transportConnectDone",
49
+ OnSendTransport = "onSendTransport",
50
+ OnConsumeTransport = "onConsumeTransport",
51
+ OnTransportProduceSyncRequest = "transportProduceSync",
52
+ OnTransportDataProduceSyncRequest = "dataTransportProduceSync",
53
+ OnReadyToConsume = "readyToConsume",
54
+ OnStartRTMP = "startRtmp",
55
+ OnProducerClosed = "producerClosed",
56
+ OnPauseProducer = "pauseProducer",
57
+ OnResumeProducer = "resumeProducer",
58
+ OnNewProducer = "onNewProducer",
59
+ ConsumeProductId = "consumeProductId",
60
+ OnServerConsumer = "onServerConsumer",
61
+ ResumeConsumer = "resumeConsumer",
62
+ PauseConsumer = "pauseConsumer",
63
+ OnProduceSyncDone = "produceSyncDone",
64
+ OnSpeakerChanged = "onSpeakerChanged",
65
+ OnRestartIceCandidate = "restartIceCandidate",
66
+ OnRestartIceCandidateResponse = "onRestartIceCandidate",
67
+ OnTrackEnded = "onTrackEnded",
68
+ UpdateSpatialConsumer = "updateSpatialConsumer",
69
+ OnTransportNotFound = "onTransportNotFound",
70
+ GetAllProducers = "getAllProducers",
71
+ OnAllProducers = "onAllProducers",
72
+ OnStopProducer = "stopProducer",
73
+
74
+
75
+ }
76
+
77
+ export enum LocalMessageType {
78
+ OnNewUserJoinedRoom = "onNewUserJoinedRoom",
79
+ OnUserLeft = "OnUserLeft",
80
+ SocketMessage = "socketMessage"
81
+
82
+ }
83
+ export type WebSocketEvents = SFUMessageType | WebSocketBasicEvents | LocalMessageType
84
+
85
+
86
+
87
+
88
+ export interface WebSocketMessageBody {
89
+ interfaceName: 'WebSocketMessageBody'
90
+ type: WebSocketEvents;
91
+ data?: any;
92
+
93
+ }
94
+
95
+ export interface ClientMessageBody {
96
+ interfaceName: 'ClientMessageBody'
97
+ shouldSendToSelf: boolean;
98
+ senderParticipant: Participant;
99
+ broadcastMessage: WebSocketMessageBody
100
+ }
101
+
102
+ export interface RediSubsciptionDetail {
103
+ topic: string;
104
+ listener: (data: string) => void
105
+ }
106
+ export class EachSocketConnectionHandler extends BaseSFUWebsocket {
107
+
108
+ private uuid = uuidv4();
109
+ private socket: WebSocket.WebSocket
110
+ private selfParticipant?: Participant
111
+ private appId: string = 'Demo'
112
+ private isActive = false
113
+ private roomIds: string[] = []
114
+ private redisSubscribtionDetails: RediSubsciptionDetail[] = []
115
+ private isReconnectionFromUrl = false
116
+ private numberOfPongWaiting = 0;
117
+ private destoryCallback?: (socketHandler : EachSocketConnectionHandler) => void;
118
+
119
+ constructor(socket: WebSocket.WebSocket, isReconnectionFromUrl: boolean) {
120
+ super()
121
+ this.socket = socket;
122
+ this.isReconnectionFromUrl = isReconnectionFromUrl
123
+ this.init = this.init.bind(this)
124
+ this.onNewMessage = this.onNewMessage.bind(this)
125
+ this.onConfig = this.onConfig.bind(this)
126
+ this.joinRoom = this.joinRoom.bind(this)
127
+ this.subscribeToRedisMessages = this.subscribeToRedisMessages.bind(this)
128
+ this.redisSubscribeToTopic = this.redisSubscribeToTopic.bind(this)
129
+ this.onRedisMessage = this.onRedisMessage.bind(this)
130
+ this.onNewUserJoinedRoomWithSameUserId = this.onNewUserJoinedRoomWithSameUserId.bind(this)
131
+ this.cleanUp = this.cleanUp.bind(this)
132
+ this.onSetupDone = this.onSetupDone.bind(this)
133
+ this.getAllParticipants = this.getAllParticipants.bind(this)
134
+ this.startPingPong = this.startPingPong.bind(this)
135
+ this.getMeetingStartTimeAndInfromIfNotExist = this.getMeetingStartTimeAndInfromIfNotExist.bind(this)
136
+ this.onSocketClosed = this.onSocketClosed.bind(this)
137
+ this.onStartMeetingCalled = this.onStartMeetingCalled.bind(this)
138
+ this.onAudioVideoPauseResume = this.onAudioVideoPauseResume.bind(this)
139
+ }
140
+
141
+ public onDestoryCallback(callBack: (socketHandler : EachSocketConnectionHandler) => void) {
142
+ this.destoryCallback = callBack
143
+ }
144
+ public async init() {
145
+ this.socket.on("message", this.onNewMessage)
146
+ this.socket.on("close", this.onSocketClosed)
147
+ this.subscribeToRedisMessages()
148
+ }
149
+
150
+ private async onSocketClosed(event: WebSocket.CloseEvent) {
151
+ console.log("Socket close code", event)
152
+ if (String(event).includes("1005") || String(event).includes("1001") || String(event).includes("1000")) {
153
+ this.onUserLeft()
154
+ }
155
+ else {
156
+ this.isActive = false
157
+ }
158
+ }
159
+
160
+ private async onUserLeft() {
161
+ this.socket.close()
162
+ if (!this.selfParticipant) {
163
+ return
164
+ }
165
+ const roomSFUHandler = SFUHandler.getInstance().getRoomSFUHandler(this.selfParticipant?.roomId!)
166
+ if (roomSFUHandler) {
167
+ roomSFUHandler.onUserLeft(this.selfParticipant)
168
+ }
169
+ this.sendVaniEvent(VaniEvent.OnUserLeft, this.selfParticipant)
170
+ this.redisBroadcastMessageToTopic(this.selfParticipant.roomId!, this.preapreClientMessageBody(false, this.selfParticipant, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnUserLeft, { participant: this.selfParticipant })))
171
+ RedisHandler.getInstance().removeParticipantForRoom(this.selfParticipant.roomId!, this.selfParticipant)
172
+ SFUHandler.getInstance().checkIfCanCloseRoom(this.selfParticipant.roomId!)
173
+ this.cleanUp()
174
+
175
+ }
176
+
177
+ private async onNewMessage(msgData: string) {
178
+ const paylod: WebSocketMessageBody = JSON.parse(msgData);
179
+ if (paylod.type === WebSocketBasicEvents.Config) {
180
+ this.onConfig(paylod.data)
181
+ }
182
+ else if (paylod.type === WebSocketBasicEvents.JoinRoom) {
183
+ this.joinRoom(paylod.data)
184
+ }
185
+ else if (paylod.type === WebSocketBasicEvents.IsSetupDone) {
186
+ this.onSetupDone(paylod.data)
187
+ }
188
+ else if (paylod.type === WebSocketBasicEvents.SelfLeft) {
189
+ this.onUserLeft()
190
+ }
191
+ else if (paylod.type === WebSocketBasicEvents.Pong) {
192
+ this.numberOfPongWaiting = 0;
193
+ }
194
+ else if (paylod.type === WebSocketBasicEvents.GetMeetingStartTime) {
195
+ const startMeetingTime = await this.getMeetingStartTimeAndInfromIfNotExist()
196
+ this.redisBroadcastMessageToTopic(this.selfParticipant?.userId!, this.preapreClientMessageBody(true, this.selfParticipant!, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnMeetingStartTime, startMeetingTime)))
197
+
198
+ }
199
+ else if (paylod.type === WebSocketBasicEvents.GetAllParticipant) {
200
+ this.getAllParticipants()
201
+ }
202
+ else if (paylod.type === WebSocketBasicEvents.Message) {
203
+
204
+ if (this.selfParticipant) {
205
+ paylod.data.userId = this.selfParticipant.userId;
206
+ }
207
+ if (paylod.data.shouldPresist && paylod.data.shouldPresist === true) {
208
+ RedisHandler.getInstance().storeMesagesForRoom(this.selfParticipant!.roomId!, paylod.data.message);
209
+ }
210
+ this.redisBroadcastMessageToTopic(this.selfParticipant?.roomId!, this.preapreClientMessageBody(false, this.selfParticipant!, this.preapreWebSocketMessageBody(paylod.data.type, paylod.data.message)))
211
+
212
+ }
213
+ else if (paylod.type === WebSocketBasicEvents.GetOldMessages) {
214
+ const messages = await RedisHandler.getInstance().fetchMessagesForRoom(this.selfParticipant?.roomId!)
215
+ console.log(messages)
216
+ this.redisBroadcastMessageToTopic(this.selfParticipant?.userId!, this.preapreClientMessageBody(true, this.selfParticipant!, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnOldMessages, messages)))
217
+
218
+ }
219
+ else if (paylod.type === WebSocketBasicEvents.OnStartMeetingCalled) {
220
+ this.onStartMeetingCalled(paylod.data)
221
+ }
222
+ else if (paylod.type === WebSocketBasicEvents.OnAudioVideoPauseResume) {
223
+ this.onAudioVideoPauseResume(paylod.data)
224
+ }
225
+
226
+ else if (paylod.type === SFUMessageType.SFUMessage) {
227
+ const roomSFUHandler = SFUHandler.getInstance().getRoomSFUHandler(this.selfParticipant?.roomId!)
228
+ if (roomSFUHandler) {
229
+ roomSFUHandler.onNewMessage(paylod.data, this.selfParticipant!)
230
+ }
231
+ }
232
+
233
+ }
234
+ private async onAudioVideoPauseResume(data: any) {
235
+ if (this.selfParticipant) {
236
+ if (data.message && data.message.type && data.message.type === "video") {
237
+ if (data.message.status && data.message.status === "pause") {
238
+ this.selfParticipant.isVideoEnable = false
239
+ }
240
+ else if (data.message.status && data.message.status === "resume") {
241
+ this.selfParticipant.isVideoEnable = true
242
+ }
243
+ }
244
+ else if (data.message && data.message.type && data.message.type === "audio") {
245
+ if (data.message.status && data.message.status === "pause") {
246
+ this.selfParticipant.isAudioEnable = false
247
+ }
248
+ else if (data.message.status && data.message.status === "resume") {
249
+ this.selfParticipant.isAudioEnable = true
250
+ }
251
+ }
252
+ await RedisHandler.getInstance().addUpdateParticipantForRoom(this.selfParticipant.roomId!, this.selfParticipant)
253
+ this.redisBroadcastMessageToTopic(this.selfParticipant.roomId!, this.preapreClientMessageBody(true, this.selfParticipant, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnAudioVideoStatusUpdated, { participant: this.selfParticipant })));
254
+ }
255
+ }
256
+ private async onStartMeetingCalled(data: any) {
257
+ if (this.selfParticipant) {
258
+ this.selfParticipant.isStartMeetingCalled = true
259
+ RedisHandler.getInstance().addUpdateParticipantForRoom(this.selfParticipant.roomId!, this.selfParticipant)
260
+ }
261
+ }
262
+ private async onConfig(data: any) {
263
+ if (data.participant) {
264
+ this.selfParticipant = Object.assign(new Participant(), data.participant)
265
+ if (this.selfParticipant && this.selfParticipant.userId) {
266
+ this.redisSubscribeToTopic(this.selfParticipant.userId)
267
+ }
268
+ }
269
+ if (data.appId) {
270
+ this.appId = data.appId
271
+ }
272
+ }
273
+
274
+ private async joinRoom(data: any) {
275
+ const roomId = data.roomId
276
+ const userId = data.id
277
+ this.isActive = true
278
+ if (this.roomIds.includes(roomId) === false) {
279
+ this.roomIds.push(roomId)
280
+ this.redisSubscribeToTopic(roomId)
281
+ }
282
+ //Send Local Event To Close Old Socket for same User id
283
+ const localEventData = { roomId: roomId, senderUUID: this.uuid, userId: userId }
284
+ this.redisBroadcastMessageToTopic(userId, this.preapreWebSocketMessageBody(LocalMessageType.OnNewUserJoinedRoom, localEventData))
285
+ }
286
+
287
+ private async onSetupDone(data: any) {
288
+ if (!this.selfParticipant) {
289
+ this.cleanUp()
290
+ return;
291
+ }
292
+ await SFUHandler.getInstance().checkAndSetupRoom(this.selfParticipant!.roomId!)
293
+ // if (shouldUseSfu) {
294
+ // sfuRoom = sfuHandler.handleNewUserAdded(
295
+ // {
296
+ // userId: participant.userId,
297
+ // roomId: participant.roomId
298
+ // },
299
+ // socket
300
+ // );
301
+ // }
302
+ this.getMeetingStartTimeAndInfromIfNotExist()
303
+ const checkIfOldUserExist = await RedisHandler.getInstance().getParticipantByUserId(this.selfParticipant.roomId!, this.selfParticipant.userId!)
304
+ let isReconnection = false
305
+ if (this.isReconnectionFromUrl && !checkIfOldUserExist) {
306
+ this.redisBroadcastMessageToTopic(this.selfParticipant.userId!, this.preapreClientMessageBody(true, this.selfParticipant, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnUserOnFoundWhileReconnect, { participant: this.selfParticipant })))
307
+ return
308
+ }
309
+ if (this.isReconnectionFromUrl || checkIfOldUserExist) {
310
+ //send message for rejoin
311
+ this.redisBroadcastMessageToTopic(this.selfParticipant.roomId!, this.preapreClientMessageBody(false, this.selfParticipant, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnRejoined, { participant: this.selfParticipant })))
312
+ isReconnection = true
313
+ }
314
+ else {
315
+ this.redisBroadcastMessageToTopic(this.selfParticipant.roomId!, this.preapreClientMessageBody(false, this.selfParticipant, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnNewJoinee, { participant: this.selfParticipant })))
316
+ this.sendVaniEvent(VaniEvent.OnUserJoined, this.selfParticipant)
317
+ isReconnection = false
318
+ }
319
+ await RedisHandler.getInstance().addUpdateParticipantForRoom(this.selfParticipant.roomId!, this.selfParticipant)
320
+ this.getAllParticipants()
321
+ this.redisBroadcastMessageToTopic(this.selfParticipant.userId!, this.preapreClientMessageBody(true, this.selfParticipant, this.preapreWebSocketMessageBody(WebSocketBasicEvents.IsSetupDone, { isReconnection: isReconnection })));
322
+ this.startPingPong()
323
+ }
324
+
325
+ private async getMeetingStartTimeAndInfromIfNotExist(): Promise<number> {
326
+ const meetingStartTime = await RedisHandler.getInstance().fetchMeetingTimeForRoom(this.selfParticipant!.roomId!)
327
+ if (meetingStartTime === undefined) {
328
+ const meetingTime = new Date().getTime()
329
+ RedisHandler.getInstance().storeMeetingTimeForRoom(this.selfParticipant!.roomId!, meetingTime)
330
+ this.sendVaniEvent(VaniEvent.OnNewMeetingStarted, { roomId: this.selfParticipant!.roomId! })
331
+ return meetingTime
332
+ }
333
+ return meetingStartTime
334
+
335
+ }
336
+
337
+ private async getAllParticipants() {
338
+ const allUsers = await RedisHandler.getInstance().getAllParticipants(this.selfParticipant?.roomId!)
339
+ const userMap: { [key: string]: Participant } = {}
340
+ allUsers.forEach((participant: Participant) => {
341
+ userMap[participant.userId!] = participant
342
+ })
343
+ this.redisBroadcastMessageToTopic(this.selfParticipant!.userId!, this.preapreClientMessageBody(true, this.selfParticipant!, this.preapreWebSocketMessageBody(WebSocketBasicEvents.OnServerParticipants, userMap)))
344
+ }
345
+
346
+ private startPingPong() {
347
+ if (this.isActive && this.socket.OPEN && this.selfParticipant) {
348
+ if (this.numberOfPongWaiting > 10) {
349
+ console.log("No Pong More Than 10 Times start")
350
+ console.log(this.selfParticipant)
351
+ console.log("No Pong More Than 10 Times End")
352
+ // isUserLeftMsgSent = true;
353
+ this.onUserLeft();
354
+ return;
355
+ }
356
+ this.redisBroadcastMessageToTopic(this.selfParticipant!.userId!, this.preapreClientMessageBody(true, this.selfParticipant!, this.preapreWebSocketMessageBody(WebSocketBasicEvents.Ping, { userId: this.selfParticipant.userId })))
357
+ this.numberOfPongWaiting = this.numberOfPongWaiting + 1;
358
+ setTimeout(this.startPingPong, 5000)
359
+ }
360
+
361
+ }
362
+
363
+ private cleanUp() {
364
+ this.isActive = false;
365
+ this.socket.close(3006)
366
+ try {
367
+ this.socket.removeAllListeners()
368
+ }
369
+ catch (err) {
370
+
371
+ }
372
+ if (this.destoryCallback) {
373
+ this.destoryCallback(this)
374
+ this.destoryCallback = undefined
375
+ }
376
+ this.redisSubscribtionDetails.forEach((eachRedisSubscribtionDetails: RediSubsciptionDetail) => {
377
+ RedisHandler.getInstance().redisSubscriber.unsubscribe(eachRedisSubscribtionDetails.topic, eachRedisSubscribtionDetails.listener)
378
+ })
379
+ this.redisSubscribtionDetails = []
380
+ }
381
+
382
+ //Send EVent Emitter
383
+
384
+ private async sendVaniEvent(event: VaniEvent, data: any) {
385
+ EventEmitterHandler.getInstance().vaniEventEmitter.emit(event, data)
386
+ }
387
+
388
+ //Redis
389
+
390
+ private async onNewUserJoinedRoomWithSameUserId(data: any) {
391
+ console.log("onNewUserJoinedRoomWithSameUserId Start")
392
+ const roomId = data.roomId
393
+ const senderUUID = data.senderUUID
394
+ const userId = data.userId
395
+ if (userId === this.selfParticipant?.userId && this.uuid !== senderUUID && this.roomIds.includes(roomId)) {
396
+ this.isActive = false;
397
+ this.socket.close(3006)
398
+ this.cleanUp()
399
+ console.log("onNewUserJoinedRoomWithSameUserId done")
400
+ }
401
+ }
402
+
403
+
404
+
405
+
406
+ private async sendMessageToClient(data: ClientMessageBody) {
407
+ if ((data.shouldSendToSelf === true || this.selfParticipant?.userId !== data.senderParticipant.userId) && this.isActive && this.socket.OPEN) {
408
+ const sendMessageData = { type: data.broadcastMessage.type, data: { message: data.broadcastMessage.data } }
409
+ this.socket.send(JSON.stringify(sendMessageData))
410
+ }
411
+
412
+ }
413
+ private async onRedisMessage(data: WebSocketMessageBody | ClientMessageBody) {
414
+
415
+ if (data.interfaceName === 'WebSocketMessageBody') {
416
+ const webSocketMessageBody: WebSocketMessageBody = data
417
+ if (webSocketMessageBody.type === LocalMessageType.OnNewUserJoinedRoom) {
418
+ this.onNewUserJoinedRoomWithSameUserId(webSocketMessageBody.data)
419
+ }
420
+ }
421
+ else if (data.interfaceName === 'ClientMessageBody') {
422
+ const socketMessageBody: ClientMessageBody = data
423
+ this.sendMessageToClient(socketMessageBody)
424
+ }
425
+
426
+
427
+
428
+ }
429
+
430
+ private async subscribeToRedisMessages() {
431
+ RedisHandler.getInstance().redisSubscriber.on('message', (roomId, messageInString) => {
432
+ const message = JSON.parse(messageInString);
433
+ console.debug(message)
434
+ })
435
+ }
436
+
437
+ private async redisSubscribeToTopic(topic: string) {
438
+ const isListenerAlreadyExist = this.redisSubscribtionDetails.find((eachRedisSubscribtionDetails) => eachRedisSubscribtionDetails.topic === topic)
439
+ if (!isListenerAlreadyExist) {
440
+ const listener = (data: string) => {
441
+ this.onRedisMessage(JSON.parse(data))
442
+
443
+ }
444
+ RedisHandler.getInstance().redisSubscriber.subscribe(topic, listener)
445
+ this.redisSubscribtionDetails.push({ topic, listener })
446
+ }
447
+
448
+ }
449
+
450
+
451
+ }
@@ -0,0 +1,30 @@
1
+ import WebSocket from "ws";
2
+ import { VaniEvent } from "../models/Event";
3
+ import { ServerHandler } from "../ServerHandler";
4
+ import { EventEmitterHandler } from "../utility/EventEmitterHandler";
5
+ import { EachSocketConnectionHandler } from "./EachSocketConnectionHandler";
6
+
7
+ export class WebSocketHandler {
8
+
9
+ private wss: WebSocket.Server;
10
+ private socketConnections: EachSocketConnectionHandler[] = []
11
+ constructor() {
12
+ this.wss = new WebSocket.Server({ port: ServerHandler.getInstance().serverStartRequest.port })
13
+ }
14
+ connect() {
15
+ this.wss.on("connection", (socket, req) => {
16
+ console.log("On New Connection")
17
+ const eachSocketConnection = new EachSocketConnectionHandler(socket, req.url ? req.url.includes('reconnect') : false)
18
+ eachSocketConnection.init();
19
+ eachSocketConnection.onDestoryCallback((socketConnection: EachSocketConnectionHandler) => {
20
+ const index = this.socketConnections.indexOf(socketConnection)
21
+ if (index > -1) {
22
+ this.socketConnections.splice(index, 1)
23
+ }
24
+ })
25
+ this.socketConnections.push(eachSocketConnection)
26
+ })
27
+ EventEmitterHandler.getInstance().vaniEventEmitter.emit(VaniEvent.OnServerStarted, {})
28
+
29
+ }
30
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "module": "commonjs",
5
+ "lib": ["ESNext"],
6
+ "outDir": "dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true
11
+ }
12
+ }