Server to Server events in Socket.io - redis

I'm using Socket.io with Redis Adapter in GKE where I have two replicas of socket server I want to create something scale able that's why I'm using redis pub/sub but I also need to listen when something happen in other replica, is it possible to send events from one replica to another replica.
Server1.js
import { Server } from "socket.io";
import { createAdapter } from "#socket.io/redis-adapter";
import { createClient } from "redis";
const io = new Server();
const pubClient = createClient({ url: "redis://localhost:6379" });
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
io.listen(3000);
io.on("custom-event", () => {
console.log("Custom event occur");
});
From replica I want to fire event
import { Server } from "socket.io";
import { createAdapter } from "#socket.io/redis-adapter";
import { createClient } from "redis";
const io = new Server();
const pubClient = createClient({ url: "redis://localhost:6379" });
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
io.listen(3000);
io.on("connection", socket => {
socket.emit("custom-event"); // not working
io.emit("custom event") // not working
})
How is it possible or is there any alternative.

Related

Can only send data from the end which initiated the connection. How to send Data from other end (PeerJs)

I am using the peerjs module in my SvelteKit app to create a peer-to-peer file transfer app
Also the custom signaling server using peerjs --port 5000 command and it's connecting successfully
Here's my code typescript from the file sender's end:
import { FormatFileSize } from '../lib/utils/FormatFileSize'; // function to convert file size into a string of kb, MB, GB file sizes
import Peer from 'peerjs';
import type { DataConnection } from 'peerjs'; // had to import separately because no export of DataConnection was shown from the 'peerjs' module I don't know why
import { onMount } from 'svelte';
export let file: FileList; // the file which needs to be transfered
let senderPeerId: string = '';
onMount(() => {
let peer: Peer = new Peer({
host: '/',
port: 5000
});
peer.on('open', function (id) {
console.log('My peer ID is: ' + id);
senderPeerId = id;
});
peer.on('connection', (dataConnection: DataConnection) => {
console.log('connected', dataConnection);
dataConnection.send('Hello'); // trying to send this but it's not being recie
dataConnection.on('data', function (data) {
console.log('Received', data); // I am logging the data received from the other end
});
});
});
And here's my code typescript from the file receiver's end (But this end is initiating the peer connection):
import type { PageData } from './$types';
import Peer from 'peerjs';
import { onMount } from 'svelte';
export let data: PageData; // in the formate of { receiverId: string } because this page params contain the peer id of the sender.
let peer: Peer; // initiating here so that can use it elsewhere
onMount(() => {
peer = new Peer({
host: '/',
port: 5000
});
peer.on('open', function (id) {
console.log('My peer ID is: ' + id);
startPeerConnection();
});
});
function startPeerConnection() {
const dataConnection = peer.connect(data.receiverId);
console.log(dataConnection);
dataConnection.on('open', function () {
console.log('connection started');
dataConnection.on('data', function (data) {
console.log('Received', data); // logging the data, but no data is recieved
});
dataConnection.send('World'); // this data is sent successfully and logged in the console of the other side
});
}
What am I doing wrong here, or only the side which initiates the connection can send data?
I looked through the internet but couldn't find a similar problem and a solution to it, please help !!

How to setup multiple clients in Vue apollo v4?

I have 2 different servers (graphql, rest) and trying to setup multiple clients in my vue app.
Here is my setup apollo.provider.js
import { ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client/core'
import { RestLink } from 'apollo-link-rest';
import { provide } from 'vue'
import { ApolloClients } from '#vue/apollo-composable'
// HTTP connection to the API
const httpLink = createHttpLink({
uri: 'https://reqres.in/graphql'
})
// Set `RestLink` with your endpoint
const restLink = new RestLink({
uri: "https://reqres.in"
});
// Cache implementation
const cache = new InMemoryCache()
// Create the graphql client
const graphqlClient = new ApolloClient({
cache: cache,
link: httpLink
})
// Create the rest client
const restClient = new ApolloClient({
cache: cache,
link: restLink
})
export const provider = provide(ApolloClients, {
default: graphqlClient,
restClient: restClient
})
This is not working, however I can use each client seperately by doing
import { createApolloProvider } from '#vue/apollo-option'
export const provider = createApolloProvider({
defaultClient: graphqlClient // or restClient
})
Please help me understand how to use both clients.
It turned out I mixed up between "options API" and "composition API"
considering my setup uses "options API" this solution works.
export const provider = createApolloProvider({
clients: {
graphqlClient,
restClient
},
defaultClient: graphqlClient,
})

What's the right way to publish Redis message from a Redis based NestJS Microservice

I built a sample Redis based Microservice with NestJS. It's fantastic and works great. In my microservice, after processing the message received over Redis (pub/sub), we publish our result to another Redis channel for a different microservice to pick.
What's the right way to publish? Are there any samples?
For my work, I used Redis package and published it (as opposed to ClientProxyFactory). Works fine and gets the job done.
import {
ClientProxy,
ClientProxyFactory,
Transport,
} from '#nestjs/microservices';
import { Injectable, Logger } from '#nestjs/common';
import * as redis from 'redis';
import { NVResponseDTO } from "../dto/nv.dto";
#Injectable()
export class NVPersistService {
logger = new Logger('NVPersistService');
private client: redis.RedisClient;
constructor() {
this.client = redis.createClient({port: 6379, host: 'localhost'});
this.logger.log('Successfully created client for publish');
}
async publish(result: NVResponseDTO) {
const channel = 'persistence';
try {
await this.client.publish(channel, JSON.stringify(result));
this.logger.log(`Message sent`);
} catch (e) {
this.logger.error(e);
}
}
}
But is this the way to do it or should I use something like below
this.client = ClientProxyFactory.create({
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
}
});
await this.client.connect();
const channel = 'persistence';
const status = await this.client.send<string, NVResponseDTO>(channel, result);
this.logger.log(`Status of send - ${status}`);
Note: Above code did not work for me, hence used Redis directly. Any guidance would be much appreciated

Serving public and private ports using Nestjs

I'm building a that aims to serve a mobile application. Besides serving the client, it will have several back-office functionalities.
We are using swagger and we do want to be able to access the swagger docs of our back-office endpoints. However, we do not want to expose all of our endpoints publicly.
Assuming that having all endpoints public is a bad option one solutions we are thinking of is letting our server serve two ports, and then only exposing one port to the public. We have created a small sample repo that that serves a client module and a back-office module on two different ports.
The main.ts looks like the following:
import { NestFactory } from '#nestjs/core';
import { ClientModule } from './modules/client/client.module';
import * as express from 'express';
import * as http from 'http';
import {ExpressAdapter} from '#nestjs/platform-express';
import { BackOfficeModule } from './modules/backoffice/backoffice.module';
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger';
async function bootstrap() {
const clientServer = express();
const clientApp = await NestFactory.create(
ClientModule,
new ExpressAdapter(clientServer),
);
const clientOptions = new DocumentBuilder()
.setTitle('ClientServer')
.setDescription('The client server API description')
.setVersion('1.0')
.addTag('client')
.build();
const clientDocument = SwaggerModule.createDocument(clientApp, clientOptions);
SwaggerModule.setup('api', clientApp, clientDocument);
await clientApp.init();
const backOfficeServer = express();
const backOfficeApp = await NestFactory.create(
BackOfficeModule,
new ExpressAdapter(backOfficeServer),
);
const backOfficeOptions = new DocumentBuilder()
.setTitle('BackOffice')
.setDescription('The back office API description')
.setVersion('1.0')
.addTag('backOffice')
.build();
const backOfficeDocument = SwaggerModule.createDocument(backOfficeApp, backOfficeOptions);
SwaggerModule.setup('api', backOfficeApp, backOfficeDocument);
await backOfficeApp.init();
http.createServer(clientServer).listen(3000); // The public port (Load balancer will route traffic to this port)
http.createServer(backOfficeServer).listen(4000); // The private port (Will be accessed through a bastian host or similar)
}
bootstrap();
Another option would be to create a bigger separation of the codebase and infrastructure, however as this is a very early stage we feel that is unnecessary.
Our question to the Nest community is thus, has anyone done this? If so, what is are your experience? What are the drawbacks to separating our backend code like this?
Disclaimer: this solution is for express+REST combination.
Routing
Even thought nestjs can't separate controller's based on port, it can separate them based on host. Using that, you can add a reverse proxy in front of your application, that modifies the host header based on the port. Or, you can do that in an express middleware, to make things even more simpe. This is what I did:
async function bootstrap() {
const publicPort = 3000
const privatePort = 4000
const server = express()
server.use((req, res, next) => {
// act as a proper reverse proxy and set X-Forwarded-Host header if it hasn't been set
req.headers['x-forwarded-host'] ??= req.headers.host
switch (req.socket.localPort) {
case publicPort:
req.headers.host = 'public'
break
case privatePort:
req.headers.host = 'private'
break
default:
// this shouldn't be possible
res.sendStatus(500)
return
}
next()
})
const app = await NestFactory.create(AppModule, new ExpressAdapter(server))
http.createServer(server).listen(publicPort)
http.createServer(server).listen(privatePort)
}
Controllers:
#Controller({ path: 'cats', host: 'public' })
export class CatsController {...}
#Controller({ path: 'internal' host: 'private' })
export class InternalController {...}
Alternatively, you can simplify by creating your own PublicController and PrivateController decorators:
// decorator for public controllers, also sets guard
export const PublicController = (path?: string): ClassDecorator => {
return applyDecorators(Controller({ path, host: 'public' }), UseGuards(JwtAuthGuard))
}
// decorator for private controllers
export const PrivateController = (path?: string): ClassDecorator => {
return applyDecorators(Controller({ path, host: 'private' }))
}
#PublicController('cats')
export class CatsController {...}
#PrivateController('internal')
export class InternalController {...}
Swagger
For swagger, SwaggerModule.createDocument has an option "include", which accepts a list of modules to include in the swagger docs. With a bit of effort we can also turn the swagger serving part into an express Router, so both the private and public swagger can be served on the same path, for the different ports:
async function bootstrap() {
const publicPort = 3000
const privatePort = 4000
const server = express()
server.use((req, res, next) => {
// act as a proper reverse proxy and set X-Forwarded-Host header if it hasn't been set
req.headers['x-forwarded-host'] ??= req.headers.host
switch (req.socket.localPort) {
case publicPort:
req.headers.host = 'public'
break
case privatePort:
req.headers.host = 'private'
break
default:
// this shouldn't be possible
res.sendStatus(500)
return
}
next()
})
const app = await NestFactory.create(AppModule, new ExpressAdapter(server))
// setup swagger
let publicSwaggerRouter = await createSwaggerRouter(app, [CatsModule])
let privateSwaggerRouter: await createSwaggerRouter(app, [InternalModule])
server.use('/api', (req: Request, res: Response, next: NextFunction) => {
switch (req.headers.host) {
case 'public':
publicSwaggerRouter(req, res, next)
return
case 'private':
privateSwaggerRouter(req, res, next)
return
default:
// this shouldn't be possible
res.sendStatus(500)
return
}
})
http.createServer(server).listen(publicPort)
http.createServer(server).listen(privatePort)
}
async function createSwaggerRouter(app: INestApplication, modules: Function[]): Promise<Router> {
const swaggerConfig = new DocumentBuilder().setTitle('MyApp').setVersion('1.0').build()
const document = SwaggerModule.createDocument(app, swaggerConfig, { include: modules })
const swaggerUi = loadPackage('swagger-ui-express', 'SwaggerModule', () => require('swagger-ui-express'))
const swaggerHtml = swaggerUi.generateHTML(document)
const router = Router()
.use(swaggerUi.serveFiles(document))
.get('/', (req: Request, res: Response, next: NextFunction) => {
res.send(swaggerHtml)
})
return router
}
That's ok, but if you want to run two servers on 1 host, I would recommend to create two files like main-client.ts and main-back-office.ts and run them in different processes, because in that case failures of one server would not affect work of another.
Also if you are not run this in Docker I would suggest tools like forever, pm2, supervisor or my own very small library workers-cluster
If you run it in Docker and don't want big refactoring, I would recommend to create
single Dockerfile with running different CMD or ENTRYPOINT commands
The NestJS docs cover how to let one server serve multiple ports:
https://docs.nestjs.com/faq/multiple-servers#multiple-simultaneous-servers
The following recipe shows how to instantiate a Nest application that listens on multiple ports (for example, on a non-HTTPS port and an HTTPS port) simultaneously.
const httpsOptions = {
key: fs.readFileSync('./secrets/private-key.pem'),
cert: fs.readFileSync('./secrets/public-certificate.pem'),
};
const server = express();
const app = await NestFactory.create(
ApplicationModule,
new ExpressAdapter(server),
);
await app.init();
http.createServer(server).listen(3000);
https.createServer(httpsOptions, server).listen(443);

StompJs only works in debugging mode - React Native

import * as Stomp from "stompjs";
import _ from "lodash";
export const MESSAGE_TYPE_CHAT_TYPING = "ChatTyping";
export const MESSAGE_TYPE_CHAT_MESSAGE = "ChatMessage";
export const RECONNECT_DELAY = 3000;
export function wsConnect(user, callback, ondisconnected) {
const webSocket = Stomp.client(url);
webSocket.debug = () => {};
webSocket.connect({},() => {
callback ? callback(webSocket) : _.noop();
},
error => {
// console.log(error);
console.log("Connection lost...");
if (ondisconnected) {
ondisconnected();
}
}
);
return webSocket;
}
The connection between the StompJs over WebSocket is established only when the app is in debugging mode.
I recently came across the same problem and fixed it by installing text-encoding package and enabling forceBinaryWSFrames and appendMissingNULLonIncoming properties
npm install text-encoding --save
Add import * as encoding from 'text-encoding'; to App.js
const client = Stomp.over(socket);
client. Connect({
forceBinaryWSFrames:true,
appendMissingNULLonIncoming:true
}, (frame) => {
console.log('Connected: ' + frame);
});