├── .codesandbox ├── Dockerfile └── tasks.json ├── .gitignore ├── package.json ├── README.md ├── test.js ├── LICENSE ├── index.js └── index.html /.codesandbox/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | chat.db 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket-chat-example", 3 | "version": "0.0.1", 4 | "description": "my first socket.io app", 5 | "type": "module", 6 | "dependencies": { 7 | "@socket.io/cluster-adapter": "^0.2.2", 8 | "express": "^4.18.2", 9 | "redis": "^4.6.12", 10 | "socket.io": "^4.7.2" 11 | }, 12 | "license": "MIT", 13 | "scripts": { 14 | "start": "node index.js" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Socket.IO chat example 2 | 3 | This is the source code for a very simple chat example used for the [Tutorial](https://socket.io/docs/v4/tutorial/introduction) guide of the Socket.IO website. 4 | 5 | You can run this example directly in your browser on: 6 | 7 | - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example?file=index.js) 8 | - [StackBlitz](https://stackblitz.com/github/socketio/chat-example?file=index.js) 9 | - [Repl.it](https://repl.it/github/socketio/chat-example) 10 | -------------------------------------------------------------------------------- /.codesandbox/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // These tasks will run in order when initializing your CodeSandbox project. 3 | "setupTasks": [ 4 | { 5 | "name": "Install Dependencies", 6 | "command": "npm install" 7 | } 8 | ], 9 | 10 | // These tasks can be run from CodeSandbox. Running one will open a log in the app. 11 | "tasks": { 12 | "npm start": { 13 | "name": "npm start", 14 | "command": "npm start", 15 | "runAtStart": true, 16 | "preview": { 17 | "port": 3000 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | 3 | const client = createClient({ 4 | password: 'qoBkPF1Tb3W3Cz4rWvswWZTxeNYbGT4h', 5 | socket: { 6 | host: 'redis-18757.c321.us-east-1-2.ec2.cloud.redislabs.com', 7 | port: 18757 8 | } 9 | }); 10 | 11 | client.on('error', err => console.log('Redis Client Error', err)); 12 | await client.connect(); 13 | 14 | // await client.set('weisemail', 'weionstream@gmail.com'); 15 | const value = await client.get('weisemail'); 16 | 17 | console.log(`Wei's Email is ${value}`) 18 | 19 | await client.hSet('user-session:123', { 20 | name: 'Wei', 21 | surname: 'He', 22 | company: 'MLH', 23 | email: 'weionstream@gmail.com' 24 | }) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2023 Guillermo Rauch 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { createServer } from 'node:http'; 3 | import { fileURLToPath } from 'node:url'; 4 | import { dirname, join } from 'node:path'; 5 | import { Server } from 'socket.io'; 6 | import { availableParallelism } from 'node:os'; 7 | import cluster from 'node:cluster'; 8 | import { createAdapter, setupPrimary } from '@socket.io/cluster-adapter'; 9 | import { createClient } from 'redis'; 10 | 11 | const client = createClient({ 12 | password: 'qoBkPF1Tb3W3Cz4rWvswWZTxeNYbGT4h', 13 | socket: { 14 | host: 'redis-18757.c321.us-east-1-2.ec2.cloud.redislabs.com', 15 | port: 18757 16 | } 17 | }); 18 | 19 | if (cluster.isPrimary) { 20 | const numCPUs = availableParallelism(); 21 | for (let i = 0; i < numCPUs; i++) { 22 | cluster.fork({ 23 | PORT: 3000 + i 24 | }); 25 | } 26 | 27 | setupPrimary(); 28 | } else { 29 | await client.connect(); 30 | 31 | const app = express(); 32 | const server = createServer(app); 33 | const io = new Server(server, { 34 | connectionStateRecovery: {}, 35 | adapter: createAdapter() 36 | }); 37 | 38 | const __dirname = dirname(fileURLToPath(import.meta.url)); 39 | 40 | app.get('/', (req, res) => { 41 | res.sendFile(join(__dirname, 'index.html')); 42 | }); 43 | 44 | io.on('connection', async (socket) => { 45 | socket.on('chat message', async (msg, messageId, callback) => { 46 | // Set a string data type. Key being the messageId, Value is the message. 47 | await client.set(`chat:message:${messageId}`, msg); 48 | 49 | // Append new message id to the end of messages list 50 | await client.rPush('chat:messages', messageId); 51 | 52 | // Notify all clients via websockets that a new message has arrived 53 | io.emit('chat message', msg, messageId); 54 | callback(); 55 | }); 56 | 57 | if (!socket.recovered) { 58 | try { 59 | // Retrieve all saved messages from chat:messages list 60 | const messageIds = await client.lRange('chat:messages', 0, -1); 61 | 62 | if (messageIds.length > 0) { 63 | for (const messageId of messageIds) { 64 | const message = await client.get(`chat:message:${messageId}`); 65 | socket.emit('chat message', message, messageId); 66 | } 67 | } 68 | } catch (e) { 69 | // something went wrong 70 | } 71 | } 72 | }); 73 | 74 | const port = process.env.PORT; 75 | 76 | server.listen(port, () => { 77 | console.log(`server running at http://localhost:${port}`); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |