I am initiating socket.io between nodejs (express), and front end. Basically, from nodejs, I am emitting a broadcast to private channel. Then, I want to receive the call back of this broadcast, on the same channel as well.
This is the setup in server.js:
const port = process.env.PORT || 3000;
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
const api = require('../api/AuthController')
var server = http.listen(3000, () => {
console.log('server is running on port', server.address().port);
});
const routes = require('../api/routes');
routes(app,io);
//socket config
global.io = io; //added
Then in AuthController, I am using global.io to emit, and receive in private channel:
global.io.emit(`news${user._id}`, { hello: 'new request');
global.io.on('conection', function (socket) {
socket.on(`news${user._id}`, function (message) {
console.log('from console', message.value);
});
});
I am able to emit perfectly fine to frontend, but I am not able to receive anything when the front end emits back on the same channel.
Appreciate your help.
Thanks,
Edit
FrontEnd Code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adhara_socket_io/adhara_socket_io.dart';
import 'package:geocoder/geocoder.dart';
const String URI = "http://10.0.2.2:3000/";
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState();
List<String> toPrint = ["trying to connect"];
SocketIOManager manager;
Map<String, SocketIO> sockets = {};
Map<String, bool> _isProbablyConnected = {};
bool newtripRequest = false;
var pickupController;
var dropoffController;
SocketIO socketController;
var driver = 'driver';
String socketIdentifier;
#override
void initState() {
super.initState();
manager = SocketIOManager();
initSocket("default");
}
#override
void dispose() {
super.dispose();
}
initSocket(String identifier) async {
setState(() => _isProbablyConnected[identifier] = true);
SocketIO socket = await manager.createInstance(SocketOptions(
//Socket IO server URI
URI,
nameSpace: (identifier == "namespaced") ? "/adhara" : "/",
//Query params - can be used for authentication
query: {
"auth": "--SOME AUTH STRING---",
"info": "new connection from adhara-socketio",
"timestamp": DateTime.now().toString()
},
//Enable or disable platform channel logging
enableLogging: false,
transports: [
Transports.WEB_SOCKET /*, Transports.POLLING*/
] //Enable required transport
));
setState(() {
socketIdentifier = identifier;
});
socket.onConnect((data) {
pprint("connected...");
pprint(data);
sendMessage('news', 'yes', socketIdentifier);
});
socket.onConnectError(pprint);
socket.onConnectTimeout(pprint);
socket.onError(pprint);
socket.onDisconnect(pprint);
socket.on("news", (data) => newTripRquest(data));
socket.connect();
sockets[identifier] = socket;
}
bool isProbablyConnected(String identifier) {
return _isProbablyConnected[identifier] ?? false;
}
disconnect(String identifier) async {
await manager.clearInstance(sockets[identifier]);
setState(() => _isProbablyConnected[identifier] = false);
}
sendMessage(privateChannel, messageBody, identifier) {
//pprint("sending message from '$identifier'...");
sockets[identifier].emit(driverChannel, [
{'response' : messageBody}]);
//pprint("Message emitted from '$identifier'...");
}
pprint(data) {
setState(() {
if (data is Map) {
data = json.encode(data);
}
print(data);
toPrint.add(data);
});
}
You got a typo ('conection'), and there's no closing brace in the emit's object.
Here's the fixed version of your AuthController's code:
global.io.emit(`news${user._id}`, { hello: 'new request' });
global.io.on('connection', function (socket) {
socket.on(`news${user._id}`, function (message) {
console.log('from console', message.value);
});
});
I hope it helps!
Related
I'm trying to receive a message from a post request and forward it to a socket id.
I defined the socket logic so that when a user connects, they get a socket id matched with their phone number in a Map global variable called users
Socket logic
const socket = (io) => {
global.users = new Map()
io.on("connection", (socket) => {
global.rooms = [socket.id]
socket.join(global.rooms)
socket.on("introduce", function (phone) {
io.emit("new_connection", phone)
global.users.set(phone, socket.id)
console.log("users map --> ", global.users)
})
socket.on("disconnect", async () => {
console.log("user has disconnected")
})
})
}
API POST requests
messageHandler = async (req,res) => {
const receiver = global.users.get(req.body.To)
const sender = global.users.get(req.body.From)
const io: Server = req.app.get("io")
const message = {
body: req.body.Body,
from: req.body.From,
to: req.body.To,
dateCreated: new Date(),
}
if (receiver) {
const receiverRoom = global.users.get(receiver)
io.to(receiverRoom).emit("message-receive", message)
res.status(200).send("Message recieved")
}
}
according to my logic, I should be able to listen on "message-receive" in the client and receive the messages, but nothing happens when I do so.
Client side
import io from "socket.io-client"
import { domain, socketBase } from "../config"
const socket = io.connect(domain, { path: socketBase })
export class socketService {
static onSmsMessage(message) {
socket.on("message-receive", message)
}
static removeListeners() {
socket.removeAllListeners()
}
}
const testing = useCallback(
(message) => {
console.log(message)
},
[]
)
// Refresh when a new sms message received
useEffect(() => {
// socketService.onSmsMessage(SmsReceived)
socketService.onSmsMessage(testing)
return () => {
socketService.removeListeners()
}
}, [testing])
tinyscrapper.js
I am emitting scrapeStarted event in scrap function
const EventEmitter = require("events");
const axios = require("axios");
const cheerio = require("cheerio");
const { exit } = require("process");
class TinyScraper extends EventEmitter {
constructor(url, timeout) {
super();
this.scrap(url,timeout) }
async scrap(url, timeout) {
this.emit("scrapeStarted");
let results=null;
try {
setTimeout(
() => {
console.log("timeout");
if (!results) {
this.emit("timeout");
exit(1)
}
},
timeout
);
const { data } = await axios.get(url);
// Load HTML we fetched in the previous line
const $ = cheerio.load(data);
const title = $('meta[property="og:title"]').attr("content");
const image = $('meta[property="og:image"]').attr("content");
const desc = $('meta[property="og:description"]').attr("content");
results={ title: title,
image: `strong text`image,
description:desc}
this.emit("scrapeSuccess", results);
} catch (err) {
this.emit("error", err);
}
}
}
module.exports = TinyScraper;
I am listening scrapeStarted event in index.js but the event does not listen while it is called in TinyScrapper class. An exciting thing is an error, timeout and scrapeSuccess event is working fine
Index.js
// index.js
const TinyScraper = require('./tiny-scraper');
const scraper = new TinyScraper('http://localhost:8000/url1',20000);
scraper.on('scrapeSuccess', (data) => {
console.log('JSON Data received scrapping:', data);
});
scraper.on('scrapeStarted', (data) => {
console.log('Started Scraping:', data);
});
scraper.on('error', () => {
console.log('The URL is not valid.');
});
scraper.on('timeout', () => {
console.log('Scraping timed out');
});
Your scraper starts scraping before you've registered event listeners. You'll need to register event listeners and then call scrap yourself.
class TinyScraper extends EventEmitter {
constructor(url, timeout) {
this.url = url;
this.timeout = timeout
super();
}
async scrape() {
const {url, timeout} = this;
this.emit("scrapeStarted");
// ... rest of method
}
}
And then your index.js needs to be refactored to
const TinyScraper = require('./tiny-scraper');
const scraper = new TinyScraper('http://localhost:8000/url1',20000);
scraper.on('scrapeSuccess', (data) => {
console.log('JSON Data received scrapping:', data);
});
// other .on handlers
scraper.scrape();
I'm trying to create a unit test for the following service, using Sinon.
as you can see the "_createRedisConnection" is called on the constructor, so in the unit test I must mock the Redis connection.
import { inject, injectable } from "inversify";
import { TYPES } from "../../inversify/types";
import { Logger } from "winston";
import { Config } from "../../interfaces/config.interface";
import { BaseService } from "../base.service";
import * as Redis from "ioredis";
import { HttpResponseError } from "../../interfaces/HttpResponseError.interface";
import { BaseResponse } from "../../interfaces/BaseResponse.interface";
#injectable()
export class RedisService extends BaseService {
private _redisClient;
private _isRedisConnected: boolean;
constructor(#inject(TYPES.Logger) private logger: Logger,
#inject(TYPES.Config) private config: Config) {
super(logger, config);
this._isRedisConnected = false;
this._createRedisConnection();
}
public async set(key, value, epu, receivedTtl): Promise<BaseResponse> {
if (this._isRedisConnected) {
const encryptedKey = this.createEncryptedKey(epu, key);
if (!encryptedKey || !value) {
throw new HttpResponseError("General error", "Missing attributes in request body", 422);
}
const ttl = this.limitTtl(receivedTtl);
let response;
if (ttl >= 0) {
await this._redisClient.setex(encryptedKey, ttl, value)
.then(() => {
response = new BaseResponse("success", "Data saved successfully", ttl);
})
.catch((errorMessage: string) => {
throw new HttpResponseError("General error", `Error while saving data. err = ${errorMessage}`, 500);
});
} else {
await this._redisClient.set(encryptedKey, value)
.then(() => {
response = new BaseResponse("success", "Data saved successfully", ttl);
})
.catch((errorMessage: string) => {
throw new HttpResponseError("General error", `Error while saving data. err = ${errorMessage}`, 500);
});
}
return response;
}
throw new HttpResponseError("General error", "Cache is not responding", 503);
}
private _createRedisConnection(): void {
this._redisClient = new Redis({
sentinels: [{ host: this.config.redisConfig.host, port: this.config.redisConfig.port }],
name: "mymaster",
dropBufferSupport: true,
});
this._redisClient.on("connect", () => {
this._isRedisConnected = true;
});
this._redisClient.on("error", (errorMessage: string) => {
this._isRedisConnected = false;
});
}
}
My problem is with mocking the Redis connection. I'm trying stub the 'connect' event, but while debugging it I see that the event never triggered (even not the error event).
import "reflect-metadata";
import { expect } from "chai";
import { Logger } from "winston";
import * as Redis from "ioredis";
import { stub } from "sinon";
import { RedisService } from "./redis.service";
import { config } from "../../config";
class LoggerMock {
public info(str: string) { }
public error(str: string) { }
}
describe("RedisService Service", () => {
const redisStub = stub(Redis.prototype, "connect").returns(Promise.resolve());
const logger = new LoggerMock() as Logger;
const redisService = new RedisService(logger, config);
it("Should success set data", async () => {
const redisClientStub = stub(Redis.prototype, "set").resolves(new Promise((resolve, reject) => { resolve('OK'); }));
const result = await redisService.set("key", "value", "epu", -1);
expect(result.message).to.equals("success");
expect(result.response).to.equals("Data saved successfully");
redisClientStub.restore();
redisStub.restore();
});
});
What is the right way to test this service? why no event is triggered when stubbing this way?
Thanks
This is an example to how to stub ioredis Redis.prototype.connect.
// File test.js
const { expect } = require('chai');
const Redis = require('ioredis');
const sinon = require('sinon');
describe('connection', function () {
it('should emit "connect" when connected', function (done) {
// Create stub on connect.
const stubRedisConnect = sinon.stub(Redis.prototype, 'connect');
stubRedisConnect.callsFake(async function () {
// This will trigger connect event.
this.setStatus('connect');
});
const redis = new Redis();
redis.on('connect', function () {
// Do not forget to restore the stub.
stubRedisConnect.restore();
done();
});
});
});
When I run it on my terminal:
$ npx mocha test.js
connection
✓ should emit "connect" when connected
1 passing (6ms)
If the test stub failed, there will be default timeout error for 2000ms because done not get called.
I have got a node server running locally and setting up a Socket.IO instance.
const http = require('http');
const socket = require('socket.io');
const path = require('path');
class Controller
{
constructor() {
this.localURL = path.resolve(process.cwd() + '/themes/');
this.theme = null;
const server = http.createServer();
this.io = new socket.Server(server, {
transports: ['websocket'],
});
this.io.on("connection", socket => {
// Wait for the client to send the website theme
socket.on('init', theme => {
// Inform current running client that the server is changing projects.
if (this.theme && this.theme !== theme) {
socket.emit(`message-${this.theme}`, {
type: 'message',
message: `Project changed to ${theme}, stopping ${this.theme}.`
});
return;
}
// Set the theme
this.theme = theme;
});
});
server.listen(8080);
}
}
new Controller();
Then on my website I have got a Vue component, but sometimes I could have 2 of the components, so I wanted to emit messages to BOTH of these component's from my server, I will handle accepting the messages in either Vue Instance myself.
This was working, all of a sudden it's not now, not too sure what I changed.
import { io } from 'socket.io-client';
export default {
props: [ 'code' ],
mounted: function () {
this.socket = io('ws://localhost:8080', {
forceNew: true,
timeout: 10000,
transports: ['websocket']
});
this.socket.on('connect', () => {
this.connected = true;
});
this.socket.on('disconnect', () => {
this.connected = false;
this.initiated = false;
});
this.socket.on(`stop-${this.code}`, () => {
this.started = '';
});
this.socket.on(`message-${this.code}`, message => {
console.log(message);
message.time = 'N/A';
this.messages.unshift(message);
})
this.socket.onAny((event, data) => {
if (event.indexOf(this.code) > -1) {
return;
}
event = event.replace(`-${this.code}`, '');
this[event] = data;
});
},
methods: {
initiate() {
this.messages = [];
this.socket.emit('init', this.code);
this.socket.on('started', code => {
if (code !== this.code) {
console.log('Themes don\'t match...');
this.initiated = false;
return;
}
So initially I would run initiate on one of the components, this sends some the theme name to the server and the server stores the theme in a property. Then I would run initiate on the second component, which would send a different theme, so this should hit the this.theme && this.theme !== theme if, and send a message back to the initial theme.
This message is being sent and the event names are as expected, but nothing comes through on the component.
I am trying to put together a very simply multiplayer tic-tac-toe game in swift with a NodeJS backend. When I try and do socket.emit() from my swift client, however, the server does not recognize it.
Client Code:
SocketIOManager:
import UIKit
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
override init() {
super.init()
}
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://10.0.1.30:2000")! as URL)
func connectToServer(completionHandler: #escaping (_ userList: [[String: AnyObject]]?) -> Void) {
socket.emit("connectUser")
socket.on("userList") { ( dataArray, ack) -> Void in
completionHandler(_: dataArray[0] as? [[String: AnyObject]])
}
}
func establishConnection() {
socket.connect()
}
func closeConnection() {
socket.disconnect()
}
}
Game Scene:
import SpriteKit
class GameScene: SKScene {
let screenSize = UIScreen.main.bounds
var board = SKSpriteNode(imageNamed: "Board.png")
var users = [[String: AnyObject]]()
override func didMove(to view: SKView) {
SocketIOManager.sharedInstance.connectToServer(completionHandler: { (userList) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
if userList != nil {
self.users = userList!
}
})
})
board.size = CGSize(width: screenSize.width * 2/3, height: screenSize.width * 2/3)
board.position = CGPoint(x: screenSize.width/2, y: screenSize.height/2)
self.backgroundColor = UIColor.white
self.addChild(board)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
}
}
Server Code:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var userList = [];
app.get('/', function(req, res){
res.send('<h1>Tic-Tac-Toe Server</h1>');
});
http.listen(2000, function(){
console.log('Listening on *:2000');
});
io.on('connection', function(clientSocket) {
console.log('a user connected');
clientSocket.on('disconnect', function() {
console.log('user disconnected');
});
clientSocket.on('connectUser', function() {
console.log('User with id ' + clientSocket.id + ' connected');
var userInfo = {};
var foundUser = false;
for (var i = 0; i < userList.length; i++) {
if (userList[i]["id"] == clientSocket.id) {
userInfo = userList[i];
foundUser = true;
break;
}
}
if (!foundUser) {
userInfo["id"] = clientSocket.id;
userList.push(userInfo);
}
io.emit("userList", userList);
io.emit("userConnectUpdate", userInfo);
});
});
The message in particular that is not working is the "connectUser" one, but I have tried to create others to test it and none of them work. It appears as if the server never receives them.
Since you are not using a https (http://10.0.1.30:2000), probably you forgot to set Allow Arbitrary Loads to YES in your Info.plist file at your iOS project:
Anyways I've built a basic example that's working:
Server side:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var port = 5000;
io.on('connection', function(socket) {
socket.on('message', function(data) {
console.log('client sent a message: ' + data);
});
});
http.listen(port, function() {
console.log('server up and running at %s port', port);
});
Client side:
import UIKit
import SocketIO
class ViewController: UIViewController {
let socket = SocketIOClient(socketURL: URL(string: "http://localhost:5000")!)
override func viewDidLoad() {
super.viewDidLoad()
socket.on("connect") { data, ack in
print("socket connected")
self.socket.emit("message", "Hello dear server from iOS.")
}
socket.connect()
}
}
Logs from Server