PeerJS Peer.on('call') with Socket.io not Triggering - node.js

I'm trying to create a video call app using Socket.io, PeerJS, Node, Express and Angular.
The issue is that, while I can connect my own video just fine, I can't see the other user's video. In fact, the Peer.on('call') code doesn't seem to trigger at all.
I think there might also be an issue with my index.js code, because the console.log()s I've added to that file never appear either and I get the following error message:
Failed to load resource: the server responded with a status of 404
(Not Found)
My code looks like this:
// --- index.js:
const express = require("express");
const app = express();
const PORT = 3000;
const path = require('path');
app.set('src', path.join(__dirname, '../src'));
const server = require('http').Server(app)
const io = require('socket.io')(server)
io.on('connection',(socket)=>{
console.log('backend video test 1') // THIS NEVER TRIGGERS
socket.on('join-room',(roomId,userId)=>{
//join the room
console.log('backend video test 2') // THIS NEVER TRIGGERS
socket.join(roomId)
socket.to(roomId).broadcast.emit('user-connected',userId)
//leave room
socket.on('disconnect',()=>{
console.log('backend video test 3') // THIS NEVER TRIGGERS
socket.to(roomId).broadcast.emit('user-diconncected',userId)
})
})
})
app.listen(PORT, console.log(`Your app is running on port ${PORT}`))
// --- component ts file:
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Socket } from 'ngx-socket-io';
import { Peer } from "peerjs";
interface VideoElement{
muted:boolean;
srcObject:MediaStream;
userId:string;
}
#Component({
selector: 'app-video-call-v2',
templateUrl: './video-call-v2.component.html',
styleUrls: ['./video-call-v2.component.css']
})
export class VideoCallV2Component implements OnInit {
currentUserId:string='testUser'+Math.floor(Math.random()*1000);
videos:VideoElement[]=[];
constructor(
private route: ActivatedRoute,
private socket: Socket,
) {}
ngOnInit() {
console.log(`Init Peer with id ${this.currentUserId}`) // this works fine.
//------------------------------------
// --- Access user video and audio ---
//------------------------------------
navigator.mediaDevices.getUserMedia({
audio:true,
video:true
}).catch((err)=>{
console.log('user media error: ',err);
return null
}).then((stream:any)=>{
const myPeer = new Peer(this.currentUserId,{
host:'/',
port:3001,
});
console.log('myPeer =');
console.log(myPeer) // this works fine.
myPeer.on('open',(userId: any)=>{
console.log('test2') // this works fine.
console.log(userId) // this works fine.
this.socket.emit('join-room','lessonvideo2',userId)
});
if (stream){
this.addMyVideo(stream);
console.log(stream) // this works fine.
} else{
console.log('no stream found')
}
//-------------------------------
// --- Receieve incoming call ---
//-------------------------------
myPeer.on('call',call=>{
console.log(`receiving call from... ${call}`); // THIS NEVER TRIGGERS!
call.answer(stream)
call.on('stream',(otherUserVideoStream: MediaStream)=>{
console.log('receiving other user stream ' + otherUserVideoStream); // THIS NEVER RUNS
this.addOtherUserVideo(call.metadata.userId,otherUserVideoStream);
});
call.on('error',(err:any)=>{
console.log(err)
})
});
//------------------------------
// --- Connecting other user ---
//------------------------------
this.socket.on('user-connected',(userId:string)=>{
console.log('receiving user-connected event', 'Calling ' + userId); // THIS NEVER RUNS
setTimeout(()=>{ // Allow some time for new peers to connect
console.log("test3") // THIS NEVER RUNS
const call = myPeer.call(userId,stream,{
metadata:{userId:this.currentUserId},
});
call.on('stream',(otherUserVideoStream: MediaStream) =>{
console.log('receiving other stream after...') // THIS NEVER RUNS
this.addOtherUserVideo(userId,otherUserVideoStream)
});
call.on('close',()=>{
this.videos=this.videos.filter((video)=>video.userId!==userId);
});
},10000);
});
});
//------------------------------
// --- Disconnect other user ---
//------------------------------
this.socket.on('user-disconnected',(userId:string)=>{
console.log('receiving user-doscconected event from '+ userId) // THIS NEVER RUNS
this.videos = this.videos.filter((video)=>video.userId!==userId);
})
}
addMyVideo(stream:MediaStream){
console.log('added') // This works fine
this.videos.push({
muted:true,
srcObject:stream,
userId:this.currentUserId,
});
}
addOtherUserVideo(userId:string, stream:MediaStream){
console.log('second video added')
const alreadyExisting = this.videos.some(video => video.userId === userId)
if (alreadyExisting){
console.log(this.videos, userId);
return;
}
this.videos.push({
muted:false,
srcObject:stream,
userId,
})
}
onLoadedMetadata(event:Event){
(event.target as HTMLVideoElement).play()
}
}
I've also put the following script into the body of my index.html document:
<script src="http://localhost:3001/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:3001');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
</script>
I'm importing Socket.Io into my app.module.ts file like this:
import{SocketIoModule} from 'ngx-socket-io';
//...
//...
imports: [
SocketIoModule.forRoot({
url:'http://localhost:3001',options: {}
})
]
I'm running my peerjs port with the following command:
peerjs --port 3001
My backend is running on port 3000 and my frontend on 4200, and they're working just fine.
NOTE: I've seen many other Stack Overflow posts on this topic, like these ones, but I've tried everything mentioned and none of them have worked for me:
peer.on('calll') is never being called
Peerjs call.on "stream" event isn't firing but peer.on "call" is

Related

Trouble configuring NextAuth and tRPC's Websockets when deploying

I have built an app with tRPCv10 and NextAuth. As my app requires realtime updates, I have followed tRPC's docs on implementing subscriptions with websockets. tRPC docs on subscription tRPC example app.
From what I understand, to use websockets in tRPC, I need to create a standalone http server and run it alongside my Nextjs app. When I emit data through EventEmitter, the data is proxied through this http server and sent to all other subscribers. Thus, I have deployed my standalone http server on Railway with port 6957, and my Nextjs app on Vercel
Everything is working well when I am developing, through localhost. However, when I'm trying to deploy it, there is an error trying to connect to the websocket server and I'm receiving a NextAuth error when logging in too.
For example, my server name is "https://xxx-server.up.railway.app/" and my Nextjs app is "https://xxx-client.vercel.app/".
On the client side, I'm receiving an error: WebSocket connection to 'wss://xxx-server.up.railway.app:6957/' failed. When I hit the login button which runs the authorize function in NextAuth, the console returns the error: POST https://xxx-client.vercel.app/api/auth/calback/credentials? 401.
For reference, here are the file for _app.tsx and my websocket server:
// _app.tsx
const MyApp: AppType = ({
Component,
pageProps: { session, ...pageProps },
}) => {
return (
<SessionProvider session={session} refetchOnWindowFocus={false}>
<Component {...pageProps} />
</SessionProvider>
);
};
const getBaseUrl = () => {
if (typeof window !== "undefined") {
return "";
}
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};
function getEndingLink() {
if (typeof window === "undefined") {
return httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
});
}
const client = createWSClient({
url: "wss://xxx-server.up.railway.app:6957"
});
return wsLink<AppRouter>({
client,
});
}
export default withTRPC<AppRouter>({
config({ ctx }) {
/**
* If you want to use SSR, you need to use the server's full URL
* #link https://trpc.io/docs/ssr
*/
const url = `${getBaseUrl()}/api/trpc`;
return {
url,
transformer: superjson,
links: [getEndingLink()],
/**
* #link https://react-query.tanstack.com/reference/QueryClient
*/
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
};
},
/**
* #link https://trpc.io/docs/ssr
*/
ssr: true,
})(MyApp);
// prodServer.ts
const port = parseInt(process.env.PORT || "3000", 10);
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = http.createServer((req, res) => {
const proto = req.headers["x-forwarded-proto"];
if (proto && proto === "http") {
// redirect to ssl
res.writeHead(303, {
location: `https://` + req.headers.host + (req.headers.url ?? ""),
});
res.end();
return;
}
const parsedUrl = parse(req.url!, true);
handle(req, res, parsedUrl);
});
const wss = new ws.Server({ server });
const handler = applyWSSHandler({ wss, router: appRouter, createContext });
process.on("SIGTERM", () => {
console.log("SIGTERM");
handler.broadcastReconnectNotification();
});
server.listen(port);
// tslint:disable-next-line:no-console
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? "development" : process.env.NODE_ENV
}`
);
});

Socket.io socket listener not updating in nextjs api

I have a Next.js project that has the simplest Socket.IO implementation set up. Below is the code.
// pages/index.tsx
let socket: Socket;
const Home: NextPage = () => {
useEffect(() => {
async function socketInit() {
//start server
await fetch("http://localhost:3000/api/socket");
// connects to the socket
socket = io();
socket.on("connect", () => {
console.log("hello");
});
}
socketInit();
}, []);
return (
<button
onClick={() => {
socket.emit("test");
}}
>
hello
</button>
);
};
// pages/api/socket.ts
export default function handler(
req: NextApiRequest,
res: Res
) {
if (res.socket.server.io) { res.end(); return; }
const io = new IOServer(res.socket.server);
res.socket.server.io = io;
io.on('connection', socket => {
socket.on('test', () => {
console.log("1"); //Changing it to "2" doesn't do anything until dev is restarted.
});
});
res.end();
}
For some reason, the listener in the server would not update from hot reload. Restarting the dev is the only way. Why is that?
I think there are 2 issues here:
The response object that you instantiate the IOServer on is not recreated after a HMR, it is still referring to the callback function that prints 1, which is hanging around in memory somewhere.
To fix this, you need to actively call the handler method, unsubscribe the old callback function and resubscribe the new (replaced) callback function. Just interacting with through the socket is not enough. Unfortunately, all tutorials I have seen call the handler socket, which is misleading. It should be called setup-socket-handler instead. What it does is retrieve the actual server from the response object of this handler and register a IOSocket server with the underlying server, which will then register a new handler/endpoint /socket.io that will be used for the communication between client and server.
Here is what I came up with. This should not be used as is in production (make sure the replacement happens only once in production, as it did in the original):
const SocketHandler = (
req: NextApiRequest,
res: NextApiResponseWithSocket
): void => {
if (res.socket.server.io != null) {
logger.info("Socket is already running");
res.socket.server.io.removeAllListeners("connection");
res.socket.server.io.on("connection", onConnection);
} else {
logger.info("Socket is initializing");
const io = new Server<ClientToServerEvents, ServerToClientEvents>(
res.socket.server
);
io.engine.on("connection_error", (err: unknown) => {
logger.error(`Connection error: ${err}`);
});
res.socket.server.io = io;
io.on("connection", onConnection);
}
res.end();
};
After changing the callback function and nextjs doing its HMR, it is required to call the handler once as described in 2. I do this by reloading my frontend page which sends a request to the socket handler.

Stream interactive shell session with socket.io

I have 3 components device, server and frontend (admin).
Server
Starts socket.io server with 2 namespaces /admin and /client.
If socket from /admin namespace sends data, server passes it along to /client namespace. If socket from /client namespace sends data, server passes it along to /admin namespace.
const io = require('socket.io');
const device = io.of('/device');
const admin = io.of('/admin');
device.on('connection', (socket) => {
socket.on('data', (data) => {
console.log("PASSING DATA FROM [DEVICE] TO [ADMIN]")
admin.emit('data', data);
})
});
admin.on('connection', (socket) => {
socket.on('data', (data) => {
console.log("PASSING DATA FROM [ADMIN] TO [DEVICE]")
device.emit('data', data);
});
});
io.listen(80);
Device
Uses socket.io-client to connect to socket.io server.
Starts interactive shell session using node-pty.
const io = require('socket.io-client');
const socket = io('http://localhost:80/client');
const os = require('os');
const pty = require('node-pty');
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-color',
cols: 80,
rows: 30
});
socket.on('connect', () => {
});
// INPUT DATA
socket.on('data', (data) => {
ptyProcess.write(data);
});
// OUTPUTING DATA
ptyProcess.onData = (data) => {
socket.emit('data', data)
}
Frontend
Finally I have the frontend which uses xterm.js to create a terminal inside the browser. I am using vue. The browser client as well connects to socket.io server on the /admin namespace. Basically I have this :
<template>
<div id="app">
<div id="terminal" ref="terminal"></div>
</div>
</template>
<script>
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { io } from 'socket.io-client';
export default {
mounted() {
const term = new Terminal({ cursorBlink : true });
term.open(this.$refs.terminal);
const socket = io('http://localhost:80/admin');
socket.on('connect', () => {
term.write('\r\n*** Connected to backend***\r\n');
term.onData((data) => {
socket.emit('data', data);
})
socket.on('data', (data) => {
term.write(data);
});
socket.on('disconnect', () => {
term.write('\r\n*** Disconnected from backend***\r\n');
});
});
}
}
</script>
Problem
❌ Starting the pty session seems to work, at least there are now errors reported. However it seems the onData listener callback is never fired, even when I ptyProcess.write() something.
❌ Getting input from xterm all the way to the device ptyProcess.write does not seem to work. I can see the data passed along through the socket.io sockets all the way to the device. But from there nothing happens. What do I miss ? Also I don't see my input in the xterm window as well.
After switching from child_process to using node-pty to create an interactive shell session I almost had it right. Following the node-pty documentation it marked the on('data') eventhandler as deprecated. Instead I should use .onData property of the process to register a callback. Like this:
ptyProcess.onData = function(data) {
socket.emit('data', data);
};
But that didn't do anything. So I switched back to the depracated way of adding an event listener:
ptyProcess.on('data', function(data) {
socket.emit('data', data);
});
Now I have a working interactive shell session forwarded from a remote device through websocket inside my browser ✅.
UPDATE
Did more digging for onData property. Realized it's not a property but a method so I used it wrong. This would be the prefered way :
ptyProcess.onData(function(data) {
socket.emit('data', data);
});
Which also works as expected 👍

Socket.io-client not connecting to server when path server option is specified

I have an express 4 server I'm trying to integrate with Socket.IO.
If I do io.attach(serverInstance) on server and io.connect("http://localhost:3002") on a react client using socket.io-client connection is established and the server logs something like socket connection active, id: 3IjhKYkpY19D6nWHAAAB, client ip: ::ffff:127.0.0.1.
But when I pass {path: "/socket/socket.io"} as io.attach(serverInstance, {path: "/socket/socket.io"}) and io.connect("http://localhost:3002", {path: "/socket/socket.io"}) nothing is logged, i.e., connection is not being made. I recently started learning socket.io so i don't know much.
I want that incoming socket connection should be requested at localhost:3002/socket and not localhost:3002/.
server side:
class SocketInterface {
constructor() {
this.server = undefined; // will hold the server instance once defined.
this.io = socket(); // referencing socket interface.
this.getServerReference = this.getServerReference.bind(this);
this.ioDotOn = this.ioDotOn.bind(this);
}
/**
* this method defines the sever reference once the server starts.
* #param {http.Server} server: reference to the http server.
*/
getServerReference(server) {
this.server = server;
this.io.attach(server, {path: "/socket/socket.io"});
}
/**
* this method executes io.on()
*/
ioDotOn() {
if (this.server) {
this.io.on("connection", socket => {
// when connection is established
const ip =
socket.handshake.headers["x-forwarded-for"] ||
socket.conn.remoteAddress;
console.log(
`socket connection active, id: ${socket.id}, client ip: ${ip}`
);
// when connection breaks
socket.on("disconnect", () => {
console.log("user gone");
});
});
} else throw new Error("Server is undefined");
}
}
I require this class in server.js as
const socketInterface = require("./socket/socket-io");
let server = undefined;
// first connect to local db.
MongoInterface.mongoConnect(err => {
if (err) {
// if error, occurs terminate.
console.log(err);
process.exit(1);
} else {
// if connected to MongoDB, get a reference to the taskDbOps singleton object.
console.log("Connected to MongoDB server");
// start the server and store a reference to the server.
server = app.listen(config.PORT, err => {
if (err) console.log(err);
else console.log("Application Server Running on Port: ", config.PORT);
});
// starting the socket interface.
if (server) {
socketInterface.getServerReference(server);
socketInterface.ioDotOn(); // socket service.
}
}
});
react client
It is a mock client that has an api.js for socket connection like so:
api.js
import io from "socket.io-client";
function x() {
const socketConn = io.connect("http://localhost:3002", {
path: "/socket/socket.io"
});
}
export { x };
this is imported in app.js like so:
import React from "react";
import { x } from "./api";
import logo from "./logo.svg";
import "./App.css";
function App() {
x(); //connecting to socket
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
I'm using socket.io 2.3
Thanks
const socketInterface = require("./socket/socket-io");
// first connect to local db.
MongoInterface.mongoConnect(err => {
if (err) {
// if error, occurs terminate.
console.log(err);
process.exit(1);
} else {
// if connected to MongoDB, get a reference to the taskDbOps singleton object.
console.log("Connected to MongoDB server");
// start the server and store a reference to the server.
// binding the server variable here resolves the problem
let server = app.listen(config.PORT, err => {
if (err) console.log(err);
else console.log("Application Server Running on Port: ", config.PORT);
});
// starting the socket interface.
if (server) {
socketInterface.getServerReference(server);
socketInterface.ioDotOn(); // socket service.
}
}
});

Why is my Parse subscription not receiving any events (not even 'open')?

I am trying to host my parse server locally, but on my frontend I do not receive any events, not even the 'open' (connection opened) event. I also do not receive any errors that could help me solve the problem.
On my server I am using the following code:
var api = new ParseServer(
{
(... more properties and keys)
liveQuery:
{
classNames: ['Sticky', 'Canvas']
}
});
var app = express();
var mountPath = something;
app.use(mountPath, api);
var httpServer = require('http').createServer(app);
httpServer.listen(port, function(){ console.log('Running on http://localhost:' + port); });
var parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer);
On the frontend I am using the following code:
const stickyQuery = new Parse.Query(Sticky);
this.stickySubscription = await stickyQuery.subscribe();
console.log(this.stickySubscription); // This gets printed, nothing weird
this.stickySubscription.on('open', () => {
console.log('SUBSCRIPTION: opened'); // This is not printed
});
this.stickySubscription.on('create', (sticky) => {
console.log('SUBSCRIPTION: Sticky created, ', sticky); // This is also not printed
});
this.stickySubscription.on('update', (sticky) => {
console.log('SUBSCRIPTION: Sticky updated, ', sticky); // This is not printed
});
The subscription gets printed, and I don't see anything weird. It seems like connecting with the Parse server is going wrong. Does someone know what I'm missing or doing wrong?
Update: I added the following code to the frontend to show the websocket status and whether error events were triggered, but these events are also not triggered:
this.stickySubscription.on('close', () => {
console.log('SUBSCRIPTION: closed'); // This is not printed
});
Parse.LiveQuery.on('open', () => {
console.log('socket connection established'); // Gets printed
});
Parse.LiveQuery.on('close', () => {
console.log('socket connection closed'); // Is not printed
});
Parse.LiveQuery.on('error', (error) => {
console.log('socket error: ', error); // Is not printed
});
In your subscription query , write your className inside quotations:
const stickyQuery = new Parse.Query('Sticky');

Resources