|
1 | | -'use strict'; |
2 | | - |
3 | | -const config = require('./lib/config'); |
4 | 1 | const log = require('./lib/log'); |
| 2 | +const config = require('./lib/config'); |
5 | 3 | const format = require('util').format; |
6 | | -const app = require('express')(); |
7 | | -const server = require('http').Server(app); // eslint-disable-line new-cap |
8 | | -const io = require('socket.io')(server); |
9 | 4 | const TwitchChatClient = require('./lib/twitch_chat'); |
10 | | - |
11 | | -app.get('/', (req, res) => { |
12 | | - res.sendStatus(200); |
13 | | -}); |
14 | | - |
15 | | -const HEARTBEAT_TIMEOUT = 15 * 1000; |
16 | | -const authenticatedSockets = new WeakSet(); |
17 | | - |
18 | | -module.exports = {app, io, HEARTBEAT_TIMEOUT}; |
| 5 | +const {io, HEARTBEAT_TIMEOUT, setupServer} = require('./lib/server'); |
19 | 6 |
|
20 | 7 | // Wait until we've defined module.exports before loading the Twitch IRC and Slack libs |
21 | 8 | const slack = (function () { |
@@ -52,140 +39,18 @@ process.on('SIGINT', () => { |
52 | 39 | // Create the TwitchChatClient, restrieve the internal tmi client, and connect to twitch |
53 | 40 | const client = new TwitchChatClient(io, HEARTBEAT_TIMEOUT, slack.status.bind(slack)); |
54 | 41 | const chatClient = client.chatClient; |
55 | | -client.connect().then(() => { |
56 | | - setupServer(); |
57 | | -}); |
58 | 42 |
|
59 | 43 | // register the twitch chat client to slack (if enabled) |
60 | 44 | slack.register(chatClient); |
61 | 45 |
|
62 | | -function setupServer() { |
63 | | - io.on('connection', socket => { |
64 | | - log.trace('Socket %s connected.', socket.id); |
65 | | - |
66 | | - socket.on('authenticate', (key, fn) => { |
67 | | - log.debug('Socket %s authenticating with key "%s"', socket.id, key); |
68 | | - |
69 | | - if (authenticatedSockets.has(socket)) { |
70 | | - log.debug('Already authenticated'); |
71 | | - fn('already authenticated'); |
72 | | - return; |
73 | | - } |
74 | | - |
75 | | - if (key === config.get('secretKey')) { |
76 | | - log.debug('Accepted key'); |
77 | | - setupAuthenticatedSocket(socket); |
78 | | - fn(null); |
79 | | - } else { |
80 | | - log.info('Rejected key "%s"', key); |
81 | | - fn('invalid key'); |
82 | | - } |
83 | | - }); |
84 | | - }); |
85 | | - |
86 | | - log.info('Socket.IO server initialized'); |
87 | | - |
88 | | - server.listen(config.get('port')); |
89 | | - log.info('Streen running on http://localhost:%s', config.get('port')); |
90 | | -} |
91 | | - |
92 | | -function setupAuthenticatedSocket(socket) { |
93 | | - authenticatedSockets.add(socket); |
94 | | - |
95 | | - /** |
96 | | - * Join a Twitch chat channel. |
97 | | - * @param {String} channel - The name of the channel to join. Do not include a leading "#" character. |
98 | | - * @param {Function} fn - The callback to execute after successfully joining the channel. |
99 | | - */ |
100 | | - socket.on('join', (channel, fn) => { |
101 | | - log.debug('Socket %s requesting to join Twitch chat channel "%s"', socket.id, channel); |
102 | | - client.resetHeartbeat(channel); |
103 | | - |
104 | | - // NOTE 2/1/2017: Rooms are only left when the socket itself is closed. Is this okay? Is this a leak? |
105 | | - // Have the socket join the namespace for the channel in order to receive messages. |
106 | | - const roomName = `channel:${channel}`; |
107 | | - if (Object.keys(socket.rooms).indexOf(roomName) < 0) { |
108 | | - log.trace('Socket %s joined room:', socket.id, roomName); |
109 | | - socket.join(roomName); |
110 | | - } |
111 | | - |
112 | | - if (chatClient.channels.indexOf(`#${channel}`) >= 0) { |
113 | | - // Already in channel, invoke callback with the name |
114 | | - fn(null, channel); |
115 | | - } else { |
116 | | - chatClient.join(channel).then(() => { |
117 | | - fn(null, null); |
118 | | - }).catch(error => { |
119 | | - log.error(`Error attempting to join "${channel}" from join command.\n\t`, error); |
120 | | - fn(error); |
121 | | - }); |
122 | | - } |
123 | | - }); |
124 | | - |
125 | | - /** |
126 | | - * Send a message to a Twitch chat channel as the user specified in the config file. |
127 | | - * @param {String} channel - The name of the channel to send a message to. Do not include a leading "#" character. |
128 | | - * @param {String} message - The message to send. |
129 | | - * @param {Function} fn - The callback to execute after successfully sending the message. |
130 | | - */ |
131 | | - socket.on('say', (channel, message, fn) => { |
132 | | - chatClient.say(channel, message).then(() => { |
133 | | - fn(null, null); |
134 | | - }).catch(error => { |
135 | | - log.error(`Error attempting to "say" in channel "${channel}".\n\tMessage: ${message}\n\t`, error); |
136 | | - fn(error); |
137 | | - }); |
138 | | - }); |
139 | | - |
140 | | - /** |
141 | | - * Timeout a user in a Twitch chat channel for a given number of seconds. |
142 | | - * @param {String} channel - The name of the channel to execute the timeout command in. |
143 | | - * Do not include a leading "#" character. |
144 | | - * @param {String} username - The name of the user to timeout. |
145 | | - * @param {Number} seconds - The number of seconds to time the user out for. |
146 | | - * @param {Function} fn - The callback to execute after successfully timing out the user. |
147 | | - */ |
148 | | - socket.on('timeout', (channel, username, seconds, fn) => { |
149 | | - chatClient.timeout(channel, username, seconds).then(() => { |
150 | | - fn(null, null); |
151 | | - }).catch(error => { |
152 | | - log.error(`Error attempting to timeout user "${username}" in channel "${channel}" for ${seconds} seconds.\n\t`, error); |
153 | | - fn(error); |
154 | | - }); |
155 | | - }); |
156 | | - |
157 | | - /** |
158 | | - * Get the list of chat mods for a Twitch channel. |
159 | | - * @param {String} channel - The Twitch channel to get a list of chat mods from |
160 | | - * @param {Function} fn - The callback to execute after successfully obtaining the list of chat mods. |
161 | | - */ |
162 | | - socket.on('mods', (channel, fn) => { |
163 | | - chatClient.mods(channel).then(mods => { |
164 | | - fn(null, mods); |
165 | | - }).catch(error => { |
166 | | - log.error(`Error attempting to get list of mods in channel "${channel}".\n\t`, error); |
167 | | - fn(error); |
168 | | - }); |
169 | | - }); |
170 | | - |
171 | | - /** |
172 | | - * Tell Streen that you wish for it to remain in this array of channels. |
173 | | - * @param {Array.<string>} channels - The array of channel names. Do not include leading "#" characters. |
174 | | - * @param {heartbeatCallback} fb - The callback to execute after the heartbeat has been registered. |
175 | | - */ |
176 | | - socket.on('heartbeat', (channels, fn) => { |
177 | | - client.heartbeat(channels); |
178 | | - fn(null, HEARTBEAT_TIMEOUT); |
179 | | - }); |
| 46 | +const twitchClientConnected = client.connect(); |
| 47 | +const delayPromise = new Promise(resolve => { |
| 48 | + setTimeout(() => resolve(), 1000); |
| 49 | +}); |
180 | 50 |
|
181 | | - /** |
182 | | - * The type of callback to execute after a successful heartbeat request. |
183 | | - * @callback heartbeatCallback |
184 | | - * @param {Object} err - The error returned, if any. |
185 | | - * @param {Number} heartbeatTimeout - How long to wait (in milliseconds) before sending the next heartbeat. |
186 | | - * Heartbeats can be sent earlier or later if needed. |
187 | | - * A siphon has up to (heartbeatTimeout * 2 + 1000) milliseconds to |
188 | | - * send another heartbeat before it times out. In other words, it can only miss |
189 | | - * one consecutive heartbeat. |
190 | | - */ |
191 | | -} |
| 51 | +// Start the socket server after either connected to twitch, or after one second. |
| 52 | +// Prevents race conditions on server restart. |
| 53 | +Promise.race([twitchClientConnected, delayPromise]).then( |
| 54 | + () => setupServer(client), |
| 55 | + () => setupServer(client) |
| 56 | +); |
0 commit comments