I'm trying to work out how to close my promise based connections after publishing messages.
I've tried to extrapolate away shared code for my sender and my receiver, so I have a connection file like this:
connector.js
const amqp = require('amqplib');
class Connector {
constructor(RabbitMQUrl) {
this.rabbitMQUrl = RabbitMQUrl;
}
connect() {
return amqp.connect(this.rabbitMQUrl)
.then((connection) => {
this.connection = connection;
process.once('SIGINT', () => {
this.connection.close();
});
return this.connection.createChannel();
})
.catch( (err) => {
console.error('Errrored here');
console.error(err);
});
}
}
module.exports = new Connector(
`amqp://${process.env.AMQP_HOST}:5672`
);
Then my publisher/sender looks like this:
publisher.js
const connector = require('./connector');
class Publisher {
constructor(exchange, exchangeType) {
this.exchange = exchange;
this.exchangeType = exchangeType;
this.durabilityOptions = {
durable: true,
autoDelete: false,
};
}
publish(msg) {
connector.connect()
.then( (channel) => {
let ok = channel.assertExchange(
this.exchange,
this.exchangeType,
this.durabilityOptions
);
return ok
.then( () => {
channel.publish(this.exchange, '', Buffer.from(msg));
return channel.close();
})
.catch( (err) => {
console.error(err);
});
});
}
}
module.exports = new Publisher(
process.env.AMQP_EXCHANGE,
process.env.AMQP_TOPIC
);
But as said, I can't quite work out how to close my connection after calling publish().
You could add a close() function to connector:
close() {
if (this.connection) {
console.log('Connector: Closing connection..');
this.connection.close();
}
}
Publisher:
class Publisher {
constructor(exchange, exchangeType) {
this.exchange = exchange;
this.exchangeType = exchangeType;
this.durabilityOptions = {
durable: true,
autoDelete: false,
};
}
connect() {
return connector.connect().then( (channel) => {
console.log('Connecting..');
return channel.assertExchange(
this.exchange,
this.exchangeType,
this.durabilityOptions
).then (() => {
this.channel = channel;
return Promise.resolve();
}).catch( (err) => {
console.error(err);
});;
});
}
disconnect() {
return this.channel.close().then( () => { return connector.close();});
}
publish(msg) {
this.channel.publish(this.exchange, '', Buffer.from(msg));
};
}
Test.js
'use strict'
const connector = require('./connector');
const publisher = require('./publisher');
publisher.connect().then(() => {
publisher.publish('message');
publisher.publish('message2');
publisher.disconnect();
});
Related
I have a class A that is using another class(B)'s method. I am trying to write test for the class B and mocking the methods of the class A. But even if I mock, I am getting TypeError: this.A.getDynamoEntry is not a function. I have followed exactly the same steps given by the docs.
test.js file:
import A from '../../src/dao/A'
import B from '../../src/dao/B'
jest.mock('../../src/dao/A', () => {
return jest.fn().mockImplementation(() => {
return {
getDynamoEntry: jest.fn(()=> Promise.resolve(2)),
putDynamoEntry: jest.fn(),
updateDynamoEntry: jest.fn(),
deleteDynamoEntry: jest.fn()
};
});
});
beforeAll(() => {
initConfig()
})
describe('Test', () => {
let entity
beforeEach(() => {
entity = new B()
})
it('getFraudLogosByOwnerId', async () => {
const resp = await entity.getFraudLogosByOwnerId(913035121393899)
expect(resp).toBe(2)
})
})
class A:
class A {
constructor(tableName, dynamoClient) {
this.dynamoClient = dynamoClient
this.tableName = tableName
}
async getDynamoEntry(query) {
try {
return await this.dynamoClient.query(query).promise()
} catch (error) {
throw error
}
}
async putDynamoEntry(imageEntity) {
try {
return await this.dynamoClient.put({ TableName: this.tableName, Item: imageEntity }).promise()
} catch (error) {
throw error
}
}
async updateDynamoEntry(params) {
try {
return await this.dynamoClient.update(params).promise()
} catch (error) {
throw error
}
}
async deleteDynamoEntry(criteria) {
try {
return await this.dynamoClient.delete(criteria).promise()
} catch (error) {
throw error
}
}
}
module.exports = A
Class B:
const dynamoQueryHelper = require('./DynamoQueryHelper')
const A = require('./A')
class B {
constructor() {
this.A = new A("table1", "client1")
}
async getFraudLogosByOwnerId(ownerId) {
const query = dynamoQueryHelper.getQuery("123", "table1")
try {
return await this.A.getDynamoEntry(query)
} catch (error) {
IPSLogger.logError(
error,
LogConstants.FRAUD_DATA_ACCESS_DAO,
LogConstants.GET_FRAUD_LOGO,
ownerId,
LogConstants.LOG_GET_FRAUD_IMAGE_BY_OWNER_ID.replace(LogConstants.PLACEHOLDER, ownerId)
)
throw error
}
}
}
module.exports = B
I tried the steps given in https://jestjs.io/docs/es6-class-mocks but to no avail
I tried it in Html and Javascript it's working perfect in web (mobile's browser).I am using angular for frontend and node for backend but not getting any proper solution for redirect Playstore or Gpay for mobile browser. Basically I want to implement for mobile browser.
this is my node for backend code & for frontend I already paste static. So, request you that please check & get back to me proper resolution or tutorial. Thanks & Regards--
const canMakePaymentCache = 'canMakePaymentCache';
onBuyClicked();
function readSupportedInstruments() {
let formValue = {};
formValue['pa'] = keys.GPAY_MERCHANT_ID;//merchantId
formValue['pn'] = `Test_Gpay_Name`;//transactionId
formValue['tn'] = 'Testing Messages ';//message
formValue['mc'] = 'merchant Code';//
formValue['tr'] = 'Transaction Reference';
formValue['tid'] = 'Transaction id';
formValue['url'] = 'http://localhost.co/';
return formValue;
}
function readAmount() {
//const pay_amount = parseInt(req.body.amount) * 100;
return parseInt(req.body.amount) * 100;
}
function onBuyClicked() {
// if (!window.PaymentRequest) {
// console.log('Web payments are not supported in this browser.');
// return;
// }
let formValue = readSupportedInstruments();
const supportedInstruments = [
{
supportedMethods: ['https://pwp-server.appspot.com/pay-dev'],
data: formValue,
},
{
supportedMethods: ['https://tez.google.com/pay'],
data: formValue,
},
];
const details = {
total: {
label: 'Total',
amount: {
currency: 'INR',
value: readAmount(),
},
},
displayItems: [
{
label: 'Original amount',
amount: {
currency: 'INR',
value: readAmount(),
},
},
],
};
const options = {
requestShipping: false,
requestPayerName: false,
requestPayerPhone: false,
requestPayerEmail: false,
shippingType: 'shipping',
};
let request = null;
try {
//const PaymentRequest = {};
//request = PaymentRequest(supportedInstruments, details, options);
request ={supportedInstruments, details, options};
} catch (e) {
return;
}
if (!request) {
console.log('Web payments are not supported in this browser.');
return;
}
var canMakePaymentPromise = checkCanMakePayment(request);
canMakePaymentPromise
.then((result) => {
showPaymentUI(request, result);
})
.catch((err) => {
console.log('Error calling checkCanMakePayment: ' + err);
});
}
function checkCanMakePayment(request) {
//if (sessionStorage.hasOwnProperty(canMakePaymentCache)) {
//return Promise.resolve(JSON.parse(sessionStorage[canMakePaymentCache]));
//}
var canMakePaymentPromise = Promise.resolve(true);
if (request.canMakePayment) {
canMakePaymentPromise = request.canMakePayment();
}
return canMakePaymentPromise
.then((result) => {
canMakePaymentCache = result;
return result;
})
.catch((err) => {
console.log('Error calling canMakePayment: ' + err);
});
}
function showPaymentUI(request, canMakePayment) {
if (!canMakePayment) {
redirectToPlayStore();
return;
}
let paymentTimeout = window.setTimeout(function () {
window.clearTimeout(paymentTimeout);
request.abort()
.then(function () {
console.log('Payment timed out after 20 minutes.');
})
.catch(function () {
console.log('Unable to abort, user is in the process of paying.');
});
}, 20 * 60 * 1000); /* 20 minutes */
request.show()
.then(function (instrument) {
window.clearTimeout(paymentTimeout);
processResponse(instrument); // Handle response from browser.
})
.catch(function (err) {
console.log(err);
});
}
function processResponse(instrument) {
var instrumentString = instrumentToJsonString(instrument);
console.log(instrumentString);
fetch('/buy', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: instrumentString,
credentials: 'include',
})
.then(function (buyResult) {
if (buyResult.ok) {
return buyResult.json();
}
console.log('Error sending instrument to server.');
})
.then(function (buyResultJson) {
completePayment(
instrument, buyResultJson.status, buyResultJson.message);
})
.catch(function (err) {
console.log('Unable to process payment. ' + err);
});
}
function completePayment(instrument, result, msg) {
instrument.complete(result)
.then(function () {
console.log('Payment completes.');
console.log(msg);
document.getElementById('inputSection').style.display = 'none'
document.getElementById('outputSection').style.display = 'block'
document.getElementById('response').innerHTML =
JSON.stringify(instrument, undefined, 2);
})
.catch(function (err) {
console.log(err);
});
}
console.log(`in line 448....`);
function redirectToPlayStore() {
//if (confirm('Tez not installed, go to play store and install?')) {
//window.location.href = 'https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user.alpha'
//res.writeHead( "https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user.alpha" );
const response_data = axios
.post(
'https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user.alpha',
)
console.log(`in line no. 459... ${response_data}`);
return response_data;
//};
}
function instrumentToJsonString(instrument) {
var instrumentDictionary = {
methodName: instrument.methodName,
details: instrument.details,
shippingAddress: addressToJsonString(instrument.shippingAddress),
shippingOption: instrument.shippingOption,
payerName: instrument.payerName,
payerPhone: instrument.payerPhone,
payerEmail: instrument.payerEmail,
};
return JSON.stringify(instrumentDictionary, undefined, 2);
}
This code either runs once or a max of 100 times. I have a dummy data file with 6000 records as this is the average that it will have to handle.
Currently using the Blaze plan.
The code was working somewhat, I set up a new project and now I get this issue.
export const uploadPatrons = functions.storage
.object()
.onFinalize((object, context) => {
let patronPromise: any[];
patronPromise = [];
if (object.name === 'patrons/upload.csv') {
admin
.storage()
.bucket()
.file('/patrons/upload.csv')
.download({})
.then(data => {
Papa.parse(data.toString(), {
header: true,
skipEmptyLines: true,
complete: result => {
result.data.forEach(x => {
x.inside = false;
x.arrived = false;
x.img = false;
x.arrivedTime = null;
const newPromise = admin
.firestore()
.collection('patrons')
.add({ ...x })
.then(doc => {
console.log(doc);
})
.catch(err => {
console.log(err);
});
patronPromise.push(newPromise);
});
}
});
})
.catch(err => {
console.log(err);
});
}
return Promise.all(patronPromise)
.catch(err => {
console.log(err);
});
});
All it has to do is read the file from the storage, parse it and add each record to the firebase collection
Function returned undefined, expected Promise or value
This is the error I get in the logs
Because of your first promise may be shutdown even it not finish. So try to follow the rule promise/always-return
export const uploadPatrons = functions.storage
.object()
.onFinalize((object, context) => {
if (object.name === 'patrons/upload.csv') {
return admin.storage().bucket()
.file('/patrons/upload.csv')
.download({})
.then(data => {
let patronPromise: any[];
patronPromise = [];
Papa.parse(data.toString(), {
header: true,
skipEmptyLines: true,
complete: result => {
result.data.forEach(x => {
x.inside = false;
x.arrived = false;
x.img = false;
x.arrivedTime = null;
const newPromise = admin.firestore()
.collection('patrons')
.add({
...x
})
patronPromise.push(newPromise);
});
}
});
return Promise.all(patronPromise)
})
.then(result=>{
//return Promise.resolve or something
})
.catch(err=>{
console.log(err)
})
}
else{
//also return if it's nothing
}
});
You're ignoring the promise that admin.storage().bucket().file('/patrons/upload.csv').download({}) returns, which means that the function may get aborted.
I think it should be closer to this:
export const uploadPatrons = functions.storage
.object()
.onFinalize((object, context) => {
let patronPromise: any[];
patronPromise = [];
if (object.name === 'patrons/upload.csv') {
return admin.storage().bucket()
.file('/patrons/upload.csv')
.download({})
.then(data => {
Papa.parse(data.toString(), {
header: true,
skipEmptyLines: true,
complete: result => {
result.data.forEach(x => {
x.inside = false;
x.arrived = false;
x.img = false;
x.arrivedTime = null;
const newPromise = admin.firestore()
.collection('patrons')
.add({
...x
})
patronPromise.push(newPromise);
});
// TODO: return the Promise.all(patronPromise) here
}
});
})
}
});
I tried to set up a WebRTC P2P video communication with flutter, a node backend as signaling server and kurento media server.
When I run the app in combination with the Kurento Demo everything works fine but as soon as I try to implement my own backend the video stream doesn't start although the log messages indicate that everything is ok.
Please let me know if more input is required to find a solution.
Relevant code snippets
Web Frontend:
call(username) {
const wrapper = this;
const remoteVideo = this.videoOutputFactory();
if (!remoteVideo) {
console.error('videoOutput not found');
}
const options = {
'remoteVideo': document.getElementById('testVideo'),
onicecandidate(candidate) {
console.debug('onicecandidate', candidate);
wrapper.rpc.onIceCandidate(candidate);
},
mediaConstraints: wrapper.remoteMediaConstraints
};
console.debug('WebRtcWrapper.call: options', options);
return new Promise((resolve, reject) => {
console.log('Creating WebRtcPeer');
this.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, (error) => {
if (error) {
console.error('Error while creating WebRtcPeer', error);
reject(error);
return;
}
console.log('Generating WebRtcPeer offer');
this.webRtcPeer.generateOffer((offerError, offerSdp) => {
if (offerError) {
console.error('Error while generating WebRtcPeer offer', error);
reject(error);
return;
}
this.rpc.call(username, offerSdp).then((res) => {
console.log("Got call answer - Generated-SDPOffer: " + offerSdp);
if (res.response === 'rejected') {
console.log('Call rejected by peer');
reject(res.rejectionMessage);
return;
}
console.log('Processing peer SDP answer', res.sdpAnswer);
this.webRtcPeer.processAnswer(res.sdpAnswer);
});
});
});
});
}
App
TestController._()
: _channel = IOWebSocketChannel.connect('wss://server.marcostephan.at:443') {
_peer = jsonrpc.Peer(_channel.cast<String>());
_peer.registerMethod(
'rtc.incomingCall', (jsonrpc.Parameters message) async => await _onIncomingCall(message));
_peer.registerMethod(
'rtc.offerIceCandidate', (jsonrpc.Parameters message) => _onOfferIceCandidate(message));
_peer.registerMethod(
'rtc.startCommunication', (jsonrpc.Parameters message) => _onStartCommunication(message));
_peer.registerMethod('conn.heartbeat', (jsonrpc.Parameters message) => "");
_peer.registerFallback((jsonrpc.Parameters params) =>
print('Unknown request [${params.method}]: ${params.value}'));
_peer.listen();
_peer.sendRequest("auth.login", {'username': 'john.doe', 'role': 'actor'});
_peer.sendNotification("disp.helpMe", {'category': 'spareParts'});
}
_onIncomingCall(jsonrpc.Parameters message) async {
try{
print('Incoming call from ${message['username'].value}');
if (this.onStateChange != null) {
this.onStateChange(SignalingState.CallStateNew);
}
await _createPeerConnection();
RTCSessionDescription s = await _peerConnection
.createOffer(_constraints);
_peerConnection.setLocalDescription(s);
return {
'from': message['username'].value,
'callResponse': 'accept',
'sdpOffer': s.sdp
};
}
catch(e){
print('TestController._onIncomingCall: ERROR: $e');
}
}
_onOfferIceCandidate(jsonrpc.Parameters message) {
try{
var candidateMap = message['candidate'].value;
print('Received IceCandidate $candidateMap');
if (_peerConnection != null) {
RTCIceCandidate candidate = new RTCIceCandidate(candidateMap['candidate'],
candidateMap['sdpMid'], candidateMap['sdpMLineIndex']);
_peerConnection.addCandidate(candidate);
}
}
catch(e){
print('TestController._onOfferIceCandidate: ERROR: $e');
}
}
_onStartCommunication(jsonrpc.Parameters message) {
try{
_peerConnection.setRemoteDescription(
RTCSessionDescription(message['sdpAnswer'].value, 'answer'));
}
catch(e){
print('TestController._onStartCommunication: ERROR: $e');
}
}
_createPeerConnection() async {
_localStream = await _createStream();
RTCPeerConnection pc = await createPeerConnection(_iceServers, _config);
_peerConnection = pc;
pc.addStream(_localStream);
pc.onAddStream = (stream) {
if (this.onRemoteStream != null) this.onRemoteStream(stream);
//_remoteStreams.add(stream);
};
pc.onIceConnectionState = (state) {
print(
'TestController._createPeerConnection: onIceConnectionState: $state');
};
pc.onIceCandidate = (candidate) {
_peer.sendNotification("rtc.onIceCandidate", {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate
}
});
};
}
Future<MediaStream> _createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth': '1980',
'minHeight': '1020',
'minFrameRate': '30',
},
'facingMode': 'environment',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onLocalStream != null) {
this.onLocalStream(stream);
}
return stream;
}
final Map<String, dynamic> _iceServers = {
'iceServers': [
{'url': 'stun:stun.l.google.com:19302'},
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
Logs
Web Frontend
Pastebin
App
Pastebin
I'm building an App deployed to Heroku which uses WebSocket and Redis.
The WebSocket connection is working properly when I use only 1 dyno, but when I scale to 2, then I send event my application do it twice.
const ws = require('ws')
const jwt = require('jsonwebtoken')
const redis = require('redis')
const User = require('../models/user')
function verifyClient (info, callback) {
let token = info.req.headers['sec-websocket-protocol']
if (!token) { callback(false, 401, 'Unauthorized') } else {
jwt.verify(token, Config.APP_SECRET, (err, decoded) => {
if (err) { callback(false, 401, 'Unauthorized') } else {
if (info.req.headers.gameId) { info.req.gameId = info.req.headers.gameId }
info.req.userId = decoded.aud
callback(true)
}
})
}
};
let websocketServer, pub, sub
let clients = {}
let namespaces = {}
exports.initialize = function (httpServer) {
websocketServer = new ws.Server({
server: httpServer,
verifyClient: verifyClient
})
pub = redis.createClient(Config.REDIS_URL, { no_ready_check: true, detect_buffers: true })
pub.auth(Config.REDIS_PASSWORD, function (err) {
if (err) throw err
})
sub = redis.createClient(Config.REDIS_URL, { no_ready_check: true, detect_buffers: true })
sub.auth(Config.REDIS_PASSWORD, function (err) {
if (err) throw err
})
function handleConnection (socket) {
// socket.send(socket.upgradeReq.userId);
socket.userId = socket.upgradeReq.userId // get the user id parsed from the decoded JWT in the middleware
socket.isAlive = true
socket.scope = socket.upgradeReq.url.split('/')[1] // url = "/scope/whatever" => ["", "scope", "whatever"]
console.log('New connection: ' + socket.userId + ', scope: ' + socket.scope)
socket.on('message', (data, flags) => { handleIncomingMessage(socket, data, flags) })
socket.once('close', (code, reason) => { handleClosedConnection(socket, code, reason) })
socket.on('pong', heartbeat)
if (socket.scope === 'gameplay') {
try {
User.findByIdAndUpdate(socket.userId, { $set: { isOnLine: 2, lastSeen: Date.now() } }).select('id').lean()
let key = [socket.userId, socket.scope].join(':')
clients[key] = socket
sub.psubscribe(['dispatch', '*', socket.userId, socket.scope].join(':'))
} catch (e) { console.log(e) }
} else {
console.log('Scope : ' + socket.scope)
}
console.log('Connected Users : ' + Object.keys(clients))
}
function handleIncomingMessage (socket, message, flags) {
let scope = socket.scope
let userId = socket.userId
let channel = ['dispatch', 'in', userId, scope].join(':')
pub.publish(channel, message)
}
function handleClosedConnection (socket, code, reason) {
console.log('Connection with ' + socket.userId + ' closed. Code: ' + code)
if (socket.scope === 'gameplay') {
try {
User.findByIdAndUpdate(socket.userId, { $set: { isOnLine: 1 } }).select('id').lean()
let key = [socket.userId, socket.scope].join(':')
delete clients[key]
} catch (e) {
console.log(e)
}
} else {
console.log('Scope : ' + socket.scope)
}
}
function heartbeat (socket) {
socket.isAlive = true
}
sub.on('pmessage', (pattern, channel, message) => {
let channelComponents = channel.split(':')
let dir = channelComponents[1]
let userId = channelComponents[2]
let scope = channelComponents[3]
if (dir === 'in') {
try {
let handlers = namespaces[scope] || []
if (handlers.length) {
handlers.forEach(h => {
h(userId, message)
})
}
} catch (e) {
console.log(e)
}
} else if (dir === 'out') {
try {
let key = [userId, scope].join(':')
if (clients[key]) { clients[key].send(message) }
} catch (e) {
console.log(e)
}
}
// otherwise ignore
})
websocketServer.on('connection', handleConnection)
}
exports.on = function (scope, callback) {
if (!namespaces[scope]) { namespaces[scope] = [callback] } else { namespaces[scope].push(callback) }
}
exports.send = function (userId, scope, data) {
let channel = ['dispatch', 'out', userId, scope].join(':')
if (typeof (data) === 'object') { data = JSON.stringify(data) } else if (typeof (data) !== 'string') { throw new Error('DispatcherError: Cannot send this type of message ' + typeof (data)) }
pub.publish(channel, data)
}
exports.clients = clients
This is working on localhost.
Please let me know if I need to provide more info or code.Any help on this would be greatly appreciated, thanks in advance!
You have alot of extraneous info in the code you posted, so it's difficult to understand exactly what you mean.
However, if I understand correctly, you currently have multiple worker dyno instances subscribing to the same channels in some kind of pub/sub network. If you don't want all dynos to subscribe to the same channels, you need to put some logic in to make sure that your channels get distributed across dynos.
One simple way to do that might be to use something like the logic described in this answer.
In your case you might be able to use socket.userId as the key to distribute your channels across dynos.