mongo-realtime 1.0.2 → 1.0.4

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.
Files changed (4) hide show
  1. package/README.md +270 -272
  2. package/index.js +177 -151
  3. package/logo.png +0 -0
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -1,272 +1,270 @@
1
- # Mongo Realtime
2
-
3
- A Node.js package that combines Socket.IO and MongoDB Change Streams to deliver real-time database updates to your WebSocket clients.
4
-
5
- ## 🚀 Features
6
-
7
- - **Real-time updates**: Automatically detects changes in MongoDB and broadcasts them via Socket.IO
8
- - **Granular events**: Emits specific events by operation type, collection, and document
9
- - **Connection management**: Customizable callbacks for socket connections/disconnections
10
- - **TypeScript compatible**: JSDoc annotations for better development experience
11
-
12
- ## 📦 Installation
13
-
14
- ```bash
15
- npm install mongo-realtime
16
- ```
17
-
18
- ## Setup
19
-
20
- ### Prerequisites
21
-
22
- - MongoDB running as a replica set (required for Change Streams)
23
- - Node.js HTTP server (See below how to configure an HTTP server with Express)
24
-
25
- ### Example setup
26
-
27
- ```javascript
28
- const express = require('express');
29
- const http = require('http');
30
- const mongoose = require('mongoose');
31
- const MongoRealtime = require('mongo-realtime');
32
-
33
- const app = express();
34
- const server = http.createServer(app);
35
-
36
- mongoose.connect('mongodb://localhost:27017/mydb').then((c) => {
37
- console.log("Connected to db",c.connection.name);
38
- });
39
-
40
- MongoRealtime.init({
41
- connection: mongoose.connection,
42
- server: server,
43
- ignore: ["posts"], // ignore 'posts' collection
44
- onSocket: (socket) => {
45
- console.log(`Client connected: ${socket.id}`);
46
- socket.emit('welcome', { message: 'Connection successful!' });
47
- },
48
- offSocket: (socket, reason) => {
49
- console.log(`Client disconnected: ${socket.id}, reason: ${reason}`);
50
- }
51
- });
52
-
53
- server.listen(3000, () => {
54
- console.log('Server started on port 3000');
55
- });
56
- ```
57
-
58
- ## 📋 API
59
-
60
- ### `MongoRealtime.init(options)`
61
-
62
- Initializes the socket system and MongoDB Change Streams.
63
-
64
- #### Parameters
65
-
66
- \* means required
67
-
68
- | Parameter | Type | Description |
69
- |-----------|------|-------------|
70
- | `options.connection` | `mongoose.Connection`* | Active Mongoose connection |
71
- | `options.server` | `http.Server`* | HTTP server to attach Socket.IO |
72
- | `options.onSocket` | `Function` | Callback on socket connection |
73
- | `options.offSocket` | `Function` | Callback on socket disconnection |
74
- | `options.watch` | `Array[String]` | Collections to only watch. Listen to all when is empty |
75
- | `options.ignore` | `Array[String]` | Collections to only ignore. Overrides watch array |
76
-
77
- #### Static Properties
78
-
79
- - `MongoRealtime.io`: Socket.IO server instance
80
- - `MongoRealtime.connection`: MongoDB connection
81
- - `MongoRealtime.sockets`: Array of connected sockets
82
-
83
- ## 🎯 Emitted Events
84
-
85
- The package automatically emits six types of events for each database change:
86
-
87
- ### Event Types
88
-
89
- | Event | Description | Example |
90
- |-------|-------------|---------|
91
- | `db:change` | All changes | Any collection change |
92
- | `db:{type}` | By operation type | `db:insert`, `db:update`, `db:delete` |
93
- | `db:change:{collection}` | By collection | `db:change:users`, `db:change:posts` |
94
- | `db:{type}:{collection}` | Type + collection | `db:insert:users`, `db:update:posts` |
95
- | `db:change:{collection}:{id}` | Specific document | `db:change:users:507f1f77bcf86cd799439011` |
96
- | `db:{type}:{collection}:{id}` | Type + document | `db:insert:users:507f1f77bcf86cd799439011` |
97
-
98
- ### Event listeners
99
-
100
- You can add serverside listeners to those db events to trigger specific actions on the server:
101
-
102
- ```js
103
- function sendNotification(change){
104
- const userId = change.docId; // or change.documentKey._id
105
- NotificationService.send(userId,"Welcome to DB");
106
- }
107
-
108
- MongoRealtime.listen("db:insert:users",sendNotification);
109
- ```
110
-
111
- #### Adding many callback to one event
112
-
113
- ```js
114
- MongoRealtime.listen("db:insert:users",anotherAction);
115
- MongoRealtime.listen("db:insert:users",anotherAction2);
116
- ```
117
-
118
- #### Removing event listeners
119
-
120
- ```js
121
- MongoRealtime.removeListener("db:insert:users",sendNotification); // remove this specific action from this event
122
-
123
- MongoRealtime.removeListener("db:insert:users"); // remove all actions from this event
124
-
125
- MongoRealtime.removeAllListeners(); // remove all listeners
126
-
127
- ```
128
-
129
- ### Event Payload Structure
130
-
131
- Each event contains the full MongoDB change object:
132
-
133
- ```javascript
134
- {
135
- "_id": {...},
136
- "col":"users", // same as ns.coll
137
- "docId":"...", // same as documentKey._id
138
- "operationType": "insert|update|delete|replace",
139
- "documentKey": { "_id": "..." },
140
- "ns": { "db": "mydb", "coll": "users" },
141
- "fullDocument": {...},
142
- "fullDocumentBeforeChange": {...}
143
- }
144
- ```
145
-
146
- ## 🔨 Usage Examples
147
-
148
- ### Server-side - Listening to specific events
149
-
150
- ```javascript
151
- MongoRealtime.init({
152
- connection: connection,
153
- server: server,
154
- onSocket: (socket) => {
155
- socket.on('subscribe:users', () => {
156
- socket.join('users-room');
157
- });
158
- },
159
-
160
- });
161
-
162
- MongoRealtime.io.to('users-room').emit('custom-event', data);
163
- ```
164
-
165
- ### Client-side - Receiving updates
166
-
167
- ```html
168
- <!DOCTYPE html>
169
- <html>
170
- <head>
171
- <script src="/socket.io/socket.io.js"></script>
172
- </head>
173
- <body>
174
- <script>
175
- const socket = io();
176
-
177
- socket.on('db:change', (change) => {
178
- console.log('Detected change:', change);
179
- });
180
-
181
- socket.on('db:insert:users', (change) => {
182
- console.log('New user:', change.fullDocument);
183
- });
184
-
185
- const userId = '507f1f77bcf86cd799439011';
186
- socket.on(`db:update:users:${userId}`, (change) => {
187
- console.log('Updated user:', change.fullDocument);
188
- });
189
-
190
- socket.on('db:delete', (change) => {
191
- console.log('Deleted document:', change.documentKey);
192
- });
193
- </script>
194
- </body>
195
- </html>
196
- ```
197
-
198
- ## Error Handling
199
-
200
- ```javascript
201
- MongoRealtime.init({
202
- connection: mongoose.connection,
203
- server: server,
204
- onSocket: (socket) => {
205
- socket.on('error', (error) => {
206
- console.error('Socket error:', error);
207
- });
208
- },
209
- offSocket: (socket, reason) => {
210
- if (reason === 'transport error') {
211
- console.log('Transport error detected');
212
- }
213
- }
214
- });
215
- ```
216
-
217
- ## 🔒 Security
218
-
219
- ### Socket Authentication
220
-
221
- ```javascript
222
- function authenticateSocket(socket){
223
- socket.on('authenticate', (token) => {
224
- if (isValidToken(token)) {
225
- socket.authenticated = true;
226
- socket.emit('authenticated');
227
- } else {
228
- socket.disconnect();
229
- }
230
- });
231
-
232
- socket.use((packet, next) => {
233
- if (socket.authenticated) {
234
- next();
235
- } else {
236
- next(new Error('Unauthenticated'));
237
- }
238
- });
239
- }
240
-
241
- MongoRealtime.init({
242
- connection: mongoose.connection,
243
- server: server,
244
- onSocket: authenticateSocket,
245
- offSocket: (socket, reason) => {
246
- console.log(`Socket ${socket.id} disconnected: ${reason}`);
247
- }
248
- });
249
- ```
250
-
251
- ## 📚 Dependencies
252
-
253
- - `socket.io`: WebSocket management
254
- - `mongoose`: MongoDB ODM with Change Streams support
255
-
256
- ## 🐛 Troubleshooting
257
-
258
- ### MongoDB must be in Replica Set mode
259
-
260
- ```bash
261
- mongod --replSet rs0
262
-
263
- rs.initiate()
264
- ```
265
-
266
- ## 📄 License
267
-
268
- MIT
269
-
270
- ## 🤝 Contributing
271
-
272
- Contributions are welcome! Feel free to open an issue or submit a pull request.****
1
+ # Mongo Realtime
2
+
3
+ A Node.js package that combines Socket.IO and MongoDB Change Streams to deliver real-time database updates to your WebSocket clients.
4
+
5
+ ![Banner](logo.png)
6
+
7
+ ## 🚀 Features
8
+
9
+ - **Real-time updates**: Automatically detects changes in MongoDB and broadcasts them via Socket.IO
10
+ - **Granular events**: Emits specific events by operation type, collection, and document
11
+ - **Connection management**: Customizable callbacks for socket connections/disconnections
12
+ - **TypeScript compatible**: JSDoc annotations for better development experience
13
+
14
+ ## 📦 Installation
15
+
16
+ ```bash
17
+ npm install mongo-realtime
18
+ ```
19
+
20
+ ## Setup
21
+
22
+ ### Prerequisites
23
+
24
+ - MongoDB running as a replica set (required for Change Streams)
25
+ - Node.js HTTP server (See below how to configure an HTTP server with Express)
26
+
27
+ ### Example setup
28
+
29
+ ```javascript
30
+ const express = require('express');
31
+ const http = require('http');
32
+ const mongoose = require('mongoose');
33
+ const MongoRealtime = require('mongo-realtime');
34
+
35
+ const app = express();
36
+ const server = http.createServer(app);
37
+
38
+ mongoose.connect('mongodb://localhost:27017/mydb').then((c) => {
39
+ console.log("Connected to db",c.connection.name);
40
+ });
41
+
42
+ MongoRealtime.init({
43
+ connection: mongoose.connection,
44
+ server: server,
45
+ ignore: ["posts"], // ignore 'posts' collection
46
+ onSocket: (socket) => {
47
+ console.log(`Client connected: ${socket.id}`);
48
+ socket.emit('welcome', { message: 'Connection successful!' });
49
+ },
50
+ offSocket: (socket, reason) => {
51
+ console.log(`Client disconnected: ${socket.id}, reason: ${reason}`);
52
+ }
53
+ });
54
+
55
+ server.listen(3000, () => {
56
+ console.log('Server started on port 3000');
57
+ });
58
+ ```
59
+
60
+ ## 📋 API
61
+
62
+ ### `MongoRealtime.init(options)`
63
+
64
+ Initializes the socket system and MongoDB Change Streams.
65
+
66
+ #### Parameters
67
+
68
+ \* means required
69
+
70
+ | Parameter | Type | Description |
71
+ |-----------|------|-------------|
72
+ | `options.connection` | `mongoose.Connection`* | Active Mongoose connection |
73
+ | `options.server` | `http.Server`* | HTTP server to attach Socket.IO |
74
+ | `options.authentify` | `Function` | Function to authenticate socket connections. Should return true if authenticated |
75
+ | `options.middlewares` | `Array[Function]` | Array of Socket.IO middlewares |
76
+ | `options.onSocket` | `Function` | Callback on socket connection |
77
+ | `options.offSocket` | `Function` | Callback on socket disconnection |
78
+ | `options.watch` | `Array[String]` | Collections to only watch. Listen to all when is empty |
79
+ | `options.ignore` | `Array[String]` | Collections to only ignore. Overrides watch array |
80
+
81
+ #### Static Properties
82
+
83
+ - `MongoRealtime.io`: Socket.IO server instance
84
+ - `MongoRealtime.connection`: MongoDB connection
85
+ - `MongoRealtime.sockets`: Array of connected sockets
86
+
87
+ ## 🎯 Emitted Events
88
+
89
+ The package automatically emits six types of events for each database change:
90
+
91
+ ### Event Types
92
+
93
+ | Event | Description | Example |
94
+ |-------|-------------|---------|
95
+ | `db:change` | All changes | Any collection change |
96
+ | `db:{type}` | By operation type | `db:insert`, `db:update`, `db:delete` |
97
+ | `db:change:{collection}` | By collection | `db:change:users`, `db:change:posts` |
98
+ | `db:{type}:{collection}` | Type + collection | `db:insert:users`, `db:update:posts` |
99
+ | `db:change:{collection}:{id}` | Specific document | `db:change:users:507f1f77bcf86cd799439011` |
100
+ | `db:{type}:{collection}:{id}` | Type + document | `db:insert:users:507f1f77bcf86cd799439011` |
101
+
102
+ ### Event listeners
103
+
104
+ You can add serverside listeners to those db events to trigger specific actions on the server:
105
+
106
+ ```js
107
+ function sendNotification(change){
108
+ const userId = change.docId; // or change.documentKey._id
109
+ NotificationService.send(userId,"Welcome to DB");
110
+ }
111
+
112
+ MongoRealtime.listen("db:insert:users",sendNotification);
113
+ ```
114
+
115
+ #### Adding many callback to one event
116
+
117
+ ```js
118
+ MongoRealtime.listen("db:insert:users",anotherAction);
119
+ MongoRealtime.listen("db:insert:users",anotherAction2);
120
+ ```
121
+
122
+ #### Removing event listeners
123
+
124
+ ```js
125
+ MongoRealtime.removeListener("db:insert:users",sendNotification); // remove this specific action from this event
126
+ MongoRealtime.removeListener("db:insert:users"); // remove all actions from this event
127
+ MongoRealtime.removeAllListeners(); // remove all listeners
128
+
129
+ ```
130
+
131
+ ### Event Payload Structure
132
+
133
+ Each event contains the full MongoDB change object:
134
+
135
+ ```javascript
136
+ {
137
+ "_id": {...},
138
+ "col":"users", // same as ns.coll
139
+ "docId":"...", // same as documentKey._id
140
+ "operationType": "insert|update|delete|replace",
141
+ "documentKey": { "_id": "..." },
142
+ "ns": { "db": "mydb", "coll": "users" },
143
+ "fullDocument": {...},
144
+ "fullDocumentBeforeChange": {...}
145
+ }
146
+ ```
147
+
148
+ ## 🔨 Usage Examples
149
+
150
+ ### Server-side - Listening to specific events
151
+
152
+ ```javascript
153
+ MongoRealtime.init({
154
+ connection: connection,
155
+ server: server,
156
+ onSocket: (socket) => {
157
+ socket.on('subscribe:users', () => {
158
+ socket.join('users-room');
159
+ });
160
+ },
161
+
162
+ });
163
+
164
+ MongoRealtime.io.to('users-room').emit('custom-event', data);
165
+ ```
166
+
167
+ ### Client-side - Receiving updates
168
+
169
+ ```html
170
+ <!DOCTYPE html>
171
+ <html>
172
+ <head>
173
+ <script src="/socket.io/socket.io.js"></script>
174
+ </head>
175
+ <body>
176
+ <script>
177
+ const socket = io();
178
+
179
+ socket.on('db:change', (change) => {
180
+ console.log('Detected change:', change);
181
+ });
182
+
183
+ socket.on('db:insert:users', (change) => {
184
+ console.log('New user:', change.fullDocument);
185
+ });
186
+
187
+ const userId = '507f1f77bcf86cd799439011';
188
+ socket.on(`db:update:users:${userId}`, (change) => {
189
+ console.log('Updated user:', change.fullDocument);
190
+ });
191
+
192
+ socket.on('db:delete', (change) => {
193
+ console.log('Deleted document:', change.documentKey);
194
+ });
195
+ </script>
196
+ </body>
197
+ </html>
198
+ ```
199
+
200
+ ## Error Handling
201
+
202
+ ```javascript
203
+ MongoRealtime.init({
204
+ connection: mongoose.connection,
205
+ server: server,
206
+ onSocket: (socket) => {
207
+ socket.on('error', (error) => {
208
+ console.error('Socket error:', error);
209
+ });
210
+ },
211
+ offSocket: (socket, reason) => {
212
+ if (reason === 'transport error') {
213
+ console.log('Transport error detected');
214
+ }
215
+ }
216
+ });
217
+ ```
218
+
219
+ ## 🔒 Security
220
+
221
+ ### Socket Authentication
222
+
223
+ ```javascript
224
+ function authenticateSocket(token, socket){
225
+ const verify = AuthService.verifyToken(token);
226
+ if(verify){
227
+ socket.user = verify.user; // attach user info to socket
228
+ return true; // should return true to accept the connection
229
+ }
230
+ return false;
231
+ }
232
+
233
+ MongoRealtime.init({
234
+ connection: mongoose.connection,
235
+ server: server,
236
+ authentify: authenticateSocket,
237
+ middlewares: [
238
+ (socket, next) => {
239
+ console.log(`User is authenticated: ${socket.user.email}`);
240
+ next();
241
+ }
242
+ ],
243
+ offSocket: (socket, reason) => {
244
+ console.log(`Socket ${socket.id} disconnected: ${reason}`);
245
+ }
246
+ });
247
+ ```
248
+
249
+ ## 📚 Dependencies
250
+
251
+ - `socket.io`: WebSocket management
252
+ - `mongoose`: MongoDB ODM with Change Streams support
253
+
254
+ ## 🐛 Troubleshooting
255
+
256
+ ### MongoDB must be in Replica Set mode
257
+
258
+ ```bash
259
+ mongod --replSet rs0
260
+
261
+ rs.initiate()
262
+ ```
263
+
264
+ ## 📄 License
265
+
266
+ MIT
267
+
268
+ ## 🤝 Contributing
269
+
270
+ Contributions are welcome! Feel free to open an issue or submit a pull request.****
package/index.js CHANGED
@@ -1,151 +1,177 @@
1
- const { Server } = require("socket.io");
2
-
3
- class MongoRealtime {
4
- /** @type {import("socket.io").Server} */ static io;
5
- /** @type {import("mongoose").Connection} */ static connection;
6
- /** @type {[import("socket.io").Socket]} */ static sockets = [];
7
- /** @type {Record<String, [(change:ChangeStreamDocument)=>void]>} */ static #listeners =
8
- {};
9
-
10
- /**
11
- * Initializes the socket system.
12
- *
13
- * @param {Object} options
14
- * @param {import("mongoose").Connection} options.connection - Active Mongoose connection
15
- * @param {(socket: import("socket.io").Socket) => void} options.onSocket - Callback triggered when a socket connects
16
- * @param {(socket: import("socket.io").Socket, reason: import("socket.io").DisconnectReason) => void} options.offSocket - Callback triggered when a socket disconnects
17
- * @param {import("http").Server} options.server - HTTP server to attach Socket.IO to
18
- * @param {[String]} options.watch - Collections to watch. If empty, will watch all collections
19
- * @param {[String]} options.ignore - Collections to ignore. Can override `watch`
20
- *
21
- */
22
- static init({
23
- connection,
24
- server,
25
- onSocket,
26
- offSocket,
27
- watch = [],
28
- ignore = [],
29
- }) {
30
- if (this.io)
31
- this.io.close(() => {
32
- this.sockets = [];
33
- });
34
- this.io = new Server(server);
35
- this.connection = connection;
36
-
37
- watch = watch.map((s) => s.toLowerCase());
38
- ignore = ignore.map((s) => s.toLowerCase());
39
-
40
- this.io.on("connection", (socket) => {
41
- this.sockets = [...this.io.sockets.sockets.values()];
42
- if (onSocket) onSocket(socket);
43
-
44
- socket.on("disconnect", (r) => {
45
- this.sockets = [...this.io.sockets.sockets.values()];
46
- if (offSocket) offSocket(socket, r);
47
- });
48
- });
49
-
50
- connection.once("open", () => {
51
- let pipeline = [];
52
- if (watch.length !== 0 && ignore.length === 0) {
53
- pipeline = [{ $match: { "ns.coll": { $in: watch } } }];
54
- } else if (watch.length === 0 && ignore.length !== 0) {
55
- pipeline = [{ $match: { "ns.coll": { $nin: ignore } } }];
56
- } else if (watch.length !== 0 && ignore.length !== 0) {
57
- pipeline = [
58
- {
59
- $match: {
60
- $and: [
61
- { "ns.coll": { $in: watch } },
62
- { "ns.coll": { $nin: ignore } },
63
- ],
64
- },
65
- },
66
- ];
67
- }
68
-
69
- const changeStream = connection.watch(pipeline, {
70
- fullDocument: "updateLookup",
71
- fullDocumentBeforeChange: "whenAvailable",
72
- });
73
-
74
- changeStream.on("change", (change) => {
75
- const colName = change.ns.coll.toLowerCase();
76
- change.col = colName;
77
-
78
- const type = change.operationType;
79
- const id = change.documentKey?._id;
80
-
81
- const e_change = "db:change";
82
- const e_change_type = `db:${type}`;
83
- const e_change_col = `${e_change}:${colName}`;
84
- const e_change_type_col = `${e_change_type}:${colName}`;
85
-
86
- const events = [
87
- e_change,
88
- e_change_type,
89
- e_change_col,
90
- e_change_type_col,
91
- ];
92
-
93
- if (id) {
94
- change.docId = id;
95
- const e_change_doc = `${e_change_col}:${id}`;
96
- const e_change_type_doc = `${e_change_type_col}:${id}`;
97
- events.push(e_change_doc, e_change_type_doc);
98
- }
99
- for (let e of events) {
100
- this.io.emit(e, change);
101
- this.notifyListeners(e, change);
102
- }
103
- });
104
- });
105
- }
106
-
107
- /**
108
- * Notify all event listeners
109
- *
110
- * @param {String} e - Name of the event
111
- * @param {ChangeStreamDocument} change - Change Stream
112
- */
113
- static notifyListeners(e, change) {
114
- if (this.#listeners[e]) {
115
- for (let c of this.#listeners[e]) {
116
- c(change);
117
- }
118
- }
119
- }
120
-
121
- /**
122
- * Subscribe to an event
123
- *
124
- * @param {String} key - Name of the event
125
- * @param {(change:ChangeStreamDocument)=>void} cb - Callback
126
- */
127
- static listen(key, cb) {
128
- if (!this.#listeners[key]) this.#listeners[key] = [];
129
- this.#listeners[key].push(cb);
130
- }
131
-
132
- /**
133
- * Remove one or all listeners of an event
134
- *
135
- * @param {String} key - Name of the event
136
- * @param {(change:ChangeStreamDocument)=>void} cb - Callback
137
- */
138
- static removeListener(key, cb) {
139
- if (cb) this.#listeners[key] = this.#listeners[key].filter((c) => c != cb);
140
- else this.#listeners[key] = [];
141
- }
142
-
143
- /**
144
- * Unsubscribe to all events
145
- */
146
- static removeAllListeners() {
147
- this.#listeners = {};
148
- }
149
- }
150
-
151
- module.exports = MongoRealtime;
1
+ const { Server } = require("socket.io");
2
+
3
+ class MongoRealtime {
4
+ /** @type {import("socket.io").Server} */ static io;
5
+ /** @type {import("mongoose").Connection} */ static connection;
6
+ /** @type {[import("socket.io").Socket]} */ static sockets = [];
7
+ /** @type {Record<String, [(change:ChangeStreamDocument)=>void]>} */ static #listeners =
8
+ {};
9
+
10
+ /**
11
+ * Initializes the socket system.
12
+ *
13
+ * @param {Object} options
14
+ * @param {import("mongoose").Connection} options.connection - Active Mongoose connection
15
+ * @param {(token:String, socket: import("socket.io").Socket) => boolean | Promise<boolean>} options.authentify - Auth function that should return true if `token` is valid
16
+ * @param {[( socket: import("socket.io").Socket, next: (err?: ExtendedError) => void) => void]} options.middlewares - Register mmiddlewares on incoming socket
17
+ * @param {(socket: import("socket.io").Socket) => void} options.onSocket - Callback triggered when a socket connects
18
+ * @param {(socket: import("socket.io").Socket, reason: import("socket.io").DisconnectReason) => void} options.offSocket - Callback triggered when a socket disconnects
19
+ * @param {import("http").Server} options.server - HTTP server to attach Socket.IO to
20
+ * @param {[String]} options.watch - Collections to watch. If empty, will watch all collections
21
+ * @param {[String]} options.ignore - Collections to ignore. Can override `watch`
22
+ *
23
+ */
24
+ static init({
25
+ connection,
26
+ server,
27
+ authentify,
28
+ middlewares = [],
29
+ onSocket,
30
+ offSocket,
31
+ watch = [],
32
+ ignore = [],
33
+ }) {
34
+ if (this.io)
35
+ this.io.close(() => {
36
+ this.sockets = [];
37
+ });
38
+ this.io = new Server(server);
39
+ this.connection = connection;
40
+
41
+ watch = watch.map((s) => s.toLowerCase());
42
+ ignore = ignore.map((s) => s.toLowerCase());
43
+
44
+ this.io.use(async (socket, next) => {
45
+ if (!!authentify) {
46
+ try {
47
+ const token = socket.handshake.auth.token || socket.handshake.headers.authorization;
48
+ if (!token) return next(new Error("No token provided"));
49
+
50
+ const authorized =await authentify(token, socket);
51
+ if (authorized===true) return next(); // exactly returns true
52
+
53
+ return next(new Error("Unauthorized"));
54
+ } catch (error) {
55
+ return next(new Error("Authentication error"));
56
+ }
57
+ } else {
58
+ return next();
59
+ }
60
+ });
61
+
62
+ for (let middleware of middlewares) {
63
+ this.io.use(middleware);
64
+ }
65
+
66
+ this.io.on("connection", (socket) => {
67
+ this.sockets = [...this.io.sockets.sockets.values()];
68
+ if (onSocket) onSocket(socket);
69
+
70
+ socket.on("disconnect", (r) => {
71
+ this.sockets = [...this.io.sockets.sockets.values()];
72
+ if (offSocket) offSocket(socket, r);
73
+ });
74
+ });
75
+
76
+ connection.once("open", () => {
77
+ let pipeline = [];
78
+ if (watch.length !== 0 && ignore.length === 0) {
79
+ pipeline = [{ $match: { "ns.coll": { $in: watch } } }];
80
+ } else if (watch.length === 0 && ignore.length !== 0) {
81
+ pipeline = [{ $match: { "ns.coll": { $nin: ignore } } }];
82
+ } else if (watch.length !== 0 && ignore.length !== 0) {
83
+ pipeline = [
84
+ {
85
+ $match: {
86
+ $and: [
87
+ { "ns.coll": { $in: watch } },
88
+ { "ns.coll": { $nin: ignore } },
89
+ ],
90
+ },
91
+ },
92
+ ];
93
+ }
94
+
95
+ const changeStream = connection.watch(pipeline, {
96
+ fullDocument: "updateLookup",
97
+ fullDocumentBeforeChange: "whenAvailable",
98
+ });
99
+
100
+ changeStream.on("change", (change) => {
101
+ const colName = change.ns.coll.toLowerCase();
102
+ change.col = colName;
103
+
104
+ const type = change.operationType;
105
+ const id = change.documentKey?._id;
106
+
107
+ const e_change = "db:change";
108
+ const e_change_type = `db:${type}`;
109
+ const e_change_col = `${e_change}:${colName}`;
110
+ const e_change_type_col = `${e_change_type}:${colName}`;
111
+
112
+ const events = [
113
+ e_change,
114
+ e_change_type,
115
+ e_change_col,
116
+ e_change_type_col,
117
+ ];
118
+
119
+ if (id) {
120
+ change.docId = id;
121
+ const e_change_doc = `${e_change_col}:${id}`;
122
+ const e_change_type_doc = `${e_change_type_col}:${id}`;
123
+ events.push(e_change_doc, e_change_type_doc);
124
+ }
125
+ for (let e of events) {
126
+ this.io.emit(e, change);
127
+ this.notifyListeners(e, change);
128
+ }
129
+ });
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Notify all event listeners
135
+ *
136
+ * @param {String} e - Name of the event
137
+ * @param {ChangeStreamDocument} change - Change Stream
138
+ */
139
+ static notifyListeners(e, change) {
140
+ if (this.#listeners[e]) {
141
+ for (let c of this.#listeners[e]) {
142
+ c(change);
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Subscribe to an event
149
+ *
150
+ * @param {String} key - Name of the event
151
+ * @param {(change:ChangeStreamDocument)=>void} cb - Callback
152
+ */
153
+ static listen(key, cb) {
154
+ if (!this.#listeners[key]) this.#listeners[key] = [];
155
+ this.#listeners[key].push(cb);
156
+ }
157
+
158
+ /**
159
+ * Remove one or all listeners of an event
160
+ *
161
+ * @param {String} key - Name of the event
162
+ * @param {(change:ChangeStreamDocument)=>void} cb - Callback
163
+ */
164
+ static removeListener(key, cb) {
165
+ if (cb) this.#listeners[key] = this.#listeners[key].filter((c) => c != cb);
166
+ else this.#listeners[key] = [];
167
+ }
168
+
169
+ /**
170
+ * Unsubscribe to all events
171
+ */
172
+ static removeAllListeners() {
173
+ this.#listeners = {};
174
+ }
175
+ }
176
+
177
+ module.exports = MongoRealtime;
package/logo.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongo-realtime",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "main": "index.js",
5
5
  "scripts": {},
6
6
  "keywords": [
@@ -16,7 +16,7 @@
16
16
  "author": "D3R50N",
17
17
  "license": "MIT",
18
18
  "repository": {
19
- "url": "https://github.com/D3R50N/mongo-realtime.git"
19
+ "url": "git+https://github.com/D3R50N/mongo-realtime.git"
20
20
  },
21
21
  "description": "A Node.js package that combines Socket.IO and MongoDB Change Streams to deliver real-time database updates to your WebSocket clients.",
22
22
  "dependencies": {