Skip to content

Commit 8b404f4

Browse files
docs(examples): 1st part of the "private messaging" example
1 parent 12221f2 commit 8b404f4

File tree

17 files changed

+631
-0
lines changed

17 files changed

+631
-0
lines changed

examples/private-messaging/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.DS_Store
2+
node_modules
3+
/dist
4+
5+
6+
# local env files
7+
.env.local
8+
.env.*.local
9+
10+
# Log files
11+
npm-debug.log*
12+
yarn-debug.log*
13+
yarn-error.log*
14+
pnpm-debug.log*
15+
16+
# Editor directories and files
17+
.idea
18+
.vscode
19+
*.suo
20+
*.ntvs*
21+
*.njsproj
22+
*.sln
23+
*.sw?
24+
package-lock.json

examples/private-messaging/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Private messaging with Socket.IO
2+
3+
Please read the related guide:
4+
5+
- [Part I](https://socket.io/get-started/private-messaging-part-1/): initial implementation
6+
- [Part II](https://socket.io/get-started/private-messaging-part-2/): persistent user ID
7+
- [Part III](https://socket.io/get-started/private-messaging-part-3/): persistent messages
8+
- [Part IV](https://socket.io/get-started/private-messaging-part-4/): scaling up
9+
10+
## Running the frontend
11+
12+
```
13+
npm install
14+
npm run serve
15+
```
16+
17+
### Running the server
18+
19+
```
20+
cd server
21+
npm install
22+
npm start
23+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
presets: [
3+
'@vue/cli-plugin-babel/preset'
4+
]
5+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "private-messaging",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"serve": "vue-cli-service serve",
7+
"build": "vue-cli-service build",
8+
"lint": "vue-cli-service lint"
9+
},
10+
"dependencies": {
11+
"core-js": "^3.6.5",
12+
"socket.io-client": "^3.1.1",
13+
"vue": "^2.6.11"
14+
},
15+
"devDependencies": {
16+
"@vue/cli-plugin-babel": "~4.5.0",
17+
"@vue/cli-plugin-eslint": "~4.5.0",
18+
"@vue/cli-service": "~4.5.0",
19+
"babel-eslint": "^10.1.0",
20+
"eslint": "^6.7.2",
21+
"eslint-plugin-vue": "^6.2.2",
22+
"vue-template-compiler": "^2.6.11"
23+
},
24+
"eslintConfig": {
25+
"root": true,
26+
"env": {
27+
"node": true
28+
},
29+
"extends": [
30+
"plugin:vue/essential",
31+
"eslint:recommended"
32+
],
33+
"parserOptions": {
34+
"parser": "babel-eslint"
35+
},
36+
"rules": {}
37+
},
38+
"browserslist": [
39+
"> 1%",
40+
"last 2 versions",
41+
"not dead"
42+
]
43+
}
4.19 KB
Binary file not shown.
Binary file not shown.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
7+
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
8+
<title>Private messaging with Socket.IO</title>
9+
</head>
10+
<body>
11+
<noscript>
12+
<strong>We're sorry but this application doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
13+
</noscript>
14+
<div id="app"></div>
15+
<!-- built files will be auto injected -->
16+
</body>
17+
</html>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const httpServer = require("http").createServer();
2+
const io = require("socket.io")(httpServer, {
3+
cors: {
4+
origin: "http://localhost:8080",
5+
},
6+
});
7+
8+
io.use((socket, next) => {
9+
const username = socket.handshake.auth.username;
10+
if (!username) {
11+
return next(new Error("invalid username"));
12+
}
13+
socket.username = username;
14+
next();
15+
});
16+
17+
io.on("connection", (socket) => {
18+
// fetch existing users
19+
const users = [];
20+
for (let [id, socket] of io.of("/").sockets) {
21+
users.push({
22+
userID: id,
23+
username: socket.username,
24+
});
25+
}
26+
socket.emit("users", users);
27+
28+
// notify existing users
29+
socket.broadcast.emit("user connected", {
30+
userID: socket.id,
31+
username: socket.username,
32+
});
33+
34+
// forward the private message to the right recipient
35+
socket.on("private message", ({ content, to }) => {
36+
socket.to(to).emit("private message", {
37+
content,
38+
from: socket.id,
39+
});
40+
});
41+
42+
// notify users upon disconnection
43+
socket.on("disconnect", () => {
44+
socket.broadcast.emit("user disconnected", socket.id);
45+
});
46+
});
47+
48+
const PORT = process.env.PORT || 3000;
49+
50+
httpServer.listen(PORT, () =>
51+
console.log(`server listening at http://localhost:${PORT}`)
52+
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "server",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node index.js"
8+
},
9+
"author": "Damien Arrachequesne <[email protected]>",
10+
"license": "MIT",
11+
"dependencies": {
12+
"socket.io": "^3.1.1"
13+
}
14+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<div id="app">
3+
<select-username
4+
v-if="!usernameAlreadySelected"
5+
@input="onUsernameSelection"
6+
/>
7+
<chat v-else />
8+
</div>
9+
</template>
10+
11+
<script>
12+
import SelectUsername from "./components/SelectUsername";
13+
import Chat from "./components/Chat";
14+
import socket from "./socket";
15+
16+
export default {
17+
name: "App",
18+
components: {
19+
Chat,
20+
SelectUsername,
21+
},
22+
data() {
23+
return {
24+
usernameAlreadySelected: false,
25+
};
26+
},
27+
methods: {
28+
onUsernameSelection(username) {
29+
this.usernameAlreadySelected = true;
30+
socket.auth = { username };
31+
socket.connect();
32+
},
33+
},
34+
created() {
35+
socket.on("connect_error", (err) => {
36+
if (err.message === "invalid username") {
37+
this.usernameAlreadySelected = false;
38+
}
39+
});
40+
},
41+
destroyed() {
42+
socket.off("connect_error");
43+
},
44+
};
45+
</script>
46+
47+
<style>
48+
body {
49+
margin: 0;
50+
}
51+
52+
@font-face {
53+
font-family: Lato;
54+
src: url("/fonts/Lato-Regular.ttf");
55+
}
56+
57+
#app {
58+
font-family: Lato, Arial, sans-serif;
59+
font-size: 14px;
60+
}
61+
</style>
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<template>
2+
<div>
3+
<div class="left-panel">
4+
<user
5+
v-for="user in users"
6+
:key="user.userID"
7+
:user="user"
8+
:selected="selectedUser === user"
9+
@select="onSelectUser(user)"
10+
/>
11+
</div>
12+
<message-panel
13+
v-if="selectedUser"
14+
:user="selectedUser"
15+
@input="onMessage"
16+
class="right-panel"
17+
/>
18+
</div>
19+
</template>
20+
21+
<script>
22+
import socket from "../socket";
23+
import User from "./User";
24+
import MessagePanel from "./MessagePanel";
25+
26+
export default {
27+
name: "Chat",
28+
components: { User, MessagePanel },
29+
data() {
30+
return {
31+
selectedUser: null,
32+
users: [],
33+
};
34+
},
35+
methods: {
36+
onMessage(content) {
37+
if (this.selectedUser) {
38+
socket.emit("private message", {
39+
content,
40+
to: this.selectedUser.userID,
41+
});
42+
this.selectedUser.messages.push({
43+
content,
44+
fromSelf: true,
45+
});
46+
}
47+
},
48+
onSelectUser(user) {
49+
this.selectedUser = user;
50+
user.hasNewMessages = false;
51+
},
52+
},
53+
created() {
54+
socket.on("connect", () => {
55+
this.users.forEach((user) => {
56+
if (user.self) {
57+
user.connected = true;
58+
}
59+
});
60+
});
61+
62+
socket.on("disconnect", () => {
63+
this.users.forEach((user) => {
64+
if (user.self) {
65+
user.connected = false;
66+
}
67+
});
68+
});
69+
70+
const initReactiveProperties = (user) => {
71+
user.connected = true;
72+
user.messages = [];
73+
user.hasNewMessages = false;
74+
};
75+
76+
socket.on("users", (users) => {
77+
users.forEach((user) => {
78+
user.self = user.userID === socket.id;
79+
initReactiveProperties(user);
80+
});
81+
// put the current user first, and sort by username
82+
this.users = users.sort((a, b) => {
83+
if (a.self) return -1;
84+
if (b.self) return 1;
85+
if (a.username < b.username) return -1;
86+
return a.username > b.username ? 1 : 0;
87+
});
88+
});
89+
90+
socket.on("user connected", (user) => {
91+
initReactiveProperties(user);
92+
this.users.push(user);
93+
});
94+
95+
socket.on("user disconnected", (id) => {
96+
for (let i = 0; i < this.users.length; i++) {
97+
const user = this.users[i];
98+
if (user.userID === id) {
99+
user.connected = false;
100+
break;
101+
}
102+
}
103+
});
104+
105+
socket.on("private message", ({ content, from }) => {
106+
for (let i = 0; i < this.users.length; i++) {
107+
const user = this.users[i];
108+
if (user.userID === from) {
109+
user.messages.push({
110+
content,
111+
fromSelf: false,
112+
});
113+
if (user !== this.selectedUser) {
114+
user.hasNewMessages = true;
115+
}
116+
break;
117+
}
118+
}
119+
});
120+
},
121+
destroyed() {
122+
socket.off("connect");
123+
socket.off("disconnect");
124+
socket.off("users");
125+
socket.off("user connected");
126+
socket.off("user disconnected");
127+
socket.off("private message");
128+
},
129+
};
130+
</script>
131+
132+
<style scoped>
133+
.left-panel {
134+
position: fixed;
135+
left: 0;
136+
top: 0;
137+
bottom: 0;
138+
width: 260px;
139+
overflow-x: hidden;
140+
background-color: #3f0e40;
141+
color: white;
142+
}
143+
144+
.right-panel {
145+
margin-left: 260px;
146+
}
147+
</style>

0 commit comments

Comments
 (0)