I am trying to add a new websocket connection to my app (I already have a few and none of them had this issue).
The idea is to send logs to my spring boot application from my reactjs frontend.
Here's my client's code:
connectToLoggingWebsocket = ip => {
if (this.props.loggingWebsocketConnected === false) {
loggingStompClient = Stomp.client(`${this.props.environment.webSocketUrl}/logging`);
loggingStompClient.connect({ name: ip }, frame => this.loggingStompSuccessCallBack(frame, loggingStompClient), err => this.loggingStompClientFailureCallBack(err, ip));
}
}
loggingStompSuccessCallBack = (frame, stompClient) => {
console.log(`Connected: ${frame}`);
this.props.setLoggingWebsocketConnected(true);
stompClient.subscribe(LOGGING_NODE, data => {
console.log(data);
});
}
loggingStompClientFailureCallBack = (error, ip) => {
console.log('Exception: ', error);
console.log('Trying to reconnect in 10 seconds');
this.props.setLoggingWebsocketConnected(false);
setTimeout(() => this.connectToLoggingWebsocket(ip), 10000);
}
/* THIS IS OUTSIDE THE CLASS SCOPE */
export const sendLog = log => {
console.log("TRYING TO SEND")
console.log(loggingStompClient);
if (loggingStompClient) {
loggingStompClient.send("/app/logging", log.message);
}
};
And I am using winston -> it calls the sendLog method via a custom Transport
class CustomTransport extends Transport {
log = (info) => {
console.log("SENDING...")
sendToWebsocket(info);
}
}
// Using transport
const transport = new CustomTransport({});
// Create a logger and consume an instance of your transport
export const logger = winston.createLogger({
level: 'info',
transports: [ transport,
new winston.transports.Console() ]
});
const sendToWebsocket = message => {
console.log(message);
sendLog(message);
}
My sendLog method is NOT in my React class because I have to export it for winston. loggingStompClient is also not in my class since sendLog is calling send off of it.
var loggingStompClient = null;
class Key extends React.Component {
componentDidMount = () => { ... }
...
}
My service looks like this - I am basically sending something from the browser that invokes receivedLog -> it then sends a message to the browser - this works exactly ONCE
#MessageMapping("/logging")
public void receivedLog(String log) {
this.template.convertAndSend("/topic/logging", "logging");
logger.info("Received: {}", log);
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/logging").setAllowedOrigins("*");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay(MESSAGE_PREFIX, "queue", "/amq/").setRelayHost(rabbitmqUri)
.setSystemLogin(rabbitmqUsername).setClientLogin(rabbitmqUsername).setClientPasscode(rabbitmqPassword)
.setSystemPasscode(rabbitmqPassword).setAutoStartup(true)
.setSystemHeartbeatReceiveInterval(10000)
.setSystemHeartbeatSendInterval(10000);
registry.setApplicationDestinationPrefixes("/app");
}
Here's my browser's output:
CONNECT
name:127.0.0.1
accept-version:1.0
heart-beat:10000,10000
----------------------
CONNECTED
server:RabbitMQ/3.7.13
session:session-9l1yyKAGsRZlWwKfOFAqKg
heart-beat:10000,10000
version:1.0
user-name:127.0.0.1
----------------------
SUBSCRIBE
id:sub-1661872200224-169
destination:/topic/logging
----------------------
SEND
destination:/app/logging
content-length:63
ADDING TO ITEM: 6a5dd06d-7a66-4048-91db-a74525f87252
----------------------
MESSAGE
subscription:sub-1661872200224-169
destination:/topic/logging
message-id:T_sub-1661872200224-169##session-9l1yyKAGsRZlWwKfOFAqKg##1
redelivered:false
content-type:text/plain;charset=UTF-8
content-length:7
logging
-----------------------
ERROR
message:Connection to broker closed.
content-length:0
enter image description here
It also sends a bunch of PONG messages and never receives a PING
I've been sitting on this all day and I can't figure it out. Also turned everything off and on again.
Thanks!
EDIT
if I set the following the websocket doesn't disconnect. I am also able to continuously send messages to my frontend, but I still can only send ONCE from my frontend to the service.
loggingStompClient.heartbeat.outgoing = 0;
loggingStompClient.heartbeat.incoming = 0;
This method somehow gets called only ONCE.
export const sendLog = log => {
console.log("TRYING TO SEND")
console.log(loggingStompClient);
if (loggingStompClient) {
loggingStompClient.send("/app/logging", log.message);
}
};
Could it be that my winston logger works only once? I tried binding my websocket send method to a button and spring boot does register the messages.
So am I using winston incorrectly? It does log to console but my custom transport's callback is not being called.
const sendToWebsocket = message => {
console.error('SEND TO WEBSOCKET')
sendLog(message);
};
class CustomTransport extends Transport {
log = info => sendToWebsocket(info);
}
// Using transport
const transport = new CustomTransport({
name: 'custom-transport',
handleExceptions: true
});
// Create a logger and consume an instance of your transport
export const logger = createLogger({
exitOnError: false,
level: 'info',
transports: [ transport,
new transports.Console({
name: 'console',
handleExceptions: true
}) ]
});
In the documentation there is this callback() method which I thought was a placeholder for my own callback. I removed it and added my callback -> this was the problem.
Do NOT omit callback()
Now my code looks like this and it works.
Related
I have a lwc component that subscribes to the event WhatsAppMessage, and I have been trying to filter the event platform but I have not been able to get the expected result, since it does not respect my filter and it brings me all the results
This is my JS Code when I suscribe
import { LightningElement } from 'lwc';
import { subscribe, unsubscribe, onError, setDebugFlag, isEmpEnabled } from
'lightning/empApi';
export default class PlatformEventMonitor extends LightningElement {
channelName = '/event/Sample__e';
isSubscribeDisabled = false;
isUnsubscribeDisabled = !this.isSubscribeDisabled;
subscription = {};
// Tracks changes to channelName text field
handleChannelName(event) {
this.channelName = event.target.value;
}
// Initializes the component
connectedCallback() {
// Register error listener
this.registerErrorListener();
}
// Handles subscribe button click
handleSubscribe() {
// Callback invoked whenever a new event message is received
const messageCallback = function(response) {
console.log('New message received: ', JSON.stringify(response));
// Response contains the payload of the new message received
};
// Invoke subscribe method of empApi. Pass reference to messageCallback
subscribe(this.channelName, -1, messageCallback).then(response => {
// Response contains the subscription information on subscribe call
console.log('Subscription request sent to: ', JSON.stringify(response.channel));
this.subscription = response;
this.toggleSubscribeButton(true);
});
}
// Handles unsubscribe button click
handleUnsubscribe() {
this.toggleSubscribeButton(false);
// Invoke unsubscribe method of empApi
unsubscribe(this.subscription, response => {
console.log('unsubscribe() response: ', JSON.stringify(response));
// Response is true for successful unsubscribe
});
}
toggleSubscribeButton(enableSubscribe) {
this.isSubscribeDisabled = enableSubscribe;
this.isUnsubscribeDisabled = !enableSubscribe;
}
registerErrorListener() {
// Invoke onError empApi method
onError(error => {
console.log('Received error from server: ', JSON.stringify(error));
// Error contains the server-side error
});
}}
What makes you think this would work? I don't recognise syntax for filtering like that? From what doc you took it?
You can set replay id to -1, -2 but you'll get all messages. https://developer.salesforce.com/docs/component-library/bundle/lightning-emp-api/documentation
You can filter them out manually in your app but it'll waste the daily limit of the events to can receive...
The proper way would be to define custom channel on top of your event. It's bit like writing a query/listview/report. But there is no UI for it, you'd have to craft a special JSON and send it to ord using tooling API.
https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_filter_section.htm
I am using Stripe's prebuilt checkout with react and firebase. The checkout process works fine and directs the user to the succes_url, but I would like to update a field under the user in the database as well. I don't understand how I can include a function that updates the DB that runs upon a successful checkout.
export const checkoutWithStripe = async(user) => {
const checkoutSessionsRef = collection(db, 'users', user.uid, 'checkout_sessions');
const singleCheckoutSessionRef = await addDoc(checkoutSessionsRef, {
price: 'price_xyz',
allow_promotion_codes: true,
success_url: `${window.location.origin}/dashboard/app?success=true`,
cancel_url: `${window.location.origin}/dashboard/app?canceled=true`
});
onSnapshot(singleCheckoutSessionRef, (snap) => {
const { error, url: checkoutUrl } = snap.data();
if (error) {
console.error(`An checkout error occured: ${error.message}`);
}
if (checkoutUrl) {
window.location.assign(checkoutUrl);
}
});
// TODO: Update user type in firebase from free to starter on successful checkout
};
Thankful for any help.
Update:
I solved it, in 2 parts.
In Stripe I created a new webhook that points to a exported firebase function (2) that fires when "checkout.session.completed" is fired.
In Firebase i created a function that listens for the "checkout.session.completed" event and then calls a function that updates the DB based on the user email that I get from the Stripe event.
This is the Firebase function that listens to the event:
/**
* A webhook handler function for the relevant Stripe events.
*/
exports.updateCustomer = functions.https.onRequest((req, resp) => {
functions.logger.log("updateCustomer body", req);
const relevantEvents = new Set([
'checkout.session.completed'
]);
let event;
// Instead of getting the `Stripe.Event`
// object directly from `req.body`,
// use the Stripe webhooks API to make sure
// this webhook call came from a trusted source
try {
event = stripe.webhooks.constructEvent(
req.rawBody,
req.headers['stripe-signature'],
endpointSecret
);
} catch (error) {
functions.logger.log(`Webhook Error: Invalid Secret`);
resp.status(401).send('Webhook Error: Invalid Secret');
return;
}
functions.logger.log("updateCustomer", event.type);
if (relevantEvents.has(event.type)) {
// logs.startWebhookEventProcessing(event.id, event.type);
try {
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object;
functions.logger.log("checkout.session.completed:", session);
updatePlan(session.customer_details.email);
break;
default:
functions.logger.log(`Unhandled event type ${event.type}`);
}
} catch (error) {
functions.logger.log(`Unhandled event error ${event.type}`);
resp.json({
error: 'Webhook handler failed. View function logs in Firebase.',
});
return;
}
}
// Return a response to Stripe to acknowledge receipt of the event.
resp.json({ received: true });
});
If you need to run some code when the Checkout Session is successful, then you should use Stripe webhooks and listen to the checkout.session.completed event. This is covered in the Stripe documentation.
I am developing an Electron application with the integration of React.js as a front-end framework, which will be more like a calling application.
In that application-specific users can have multiple calls incoming, outgoing, mute | unmute calls, hold | unhold calls, etc.
For this functionality to be achieved we have our own sip server, and for integrating that SIP server, on the frontend we are using a library which is known as SIP.JS.
SIP.JS provides us mostly all the predefined functions to make a call, receive a call, mute, unmute, blind transfer, attended transfer, etc.
But when it comes to having a call conference, it doesn't have proper documentation for that.
SIP.JS specifies to us that we can use FreeSWITCH as well as ASTERISK in order to achieve the functionality, but with our specific requirements, no additional server needs to be integrated.
We have also referred to rfc documentation for the call conference, but no such progress was there.
So far what we did is:
Registered the userAgent
Code for Incoming call integrated
Code for outgoing calls integrated
multiple session handling is achieved, for multiple calls
mute | unmute, hold | unhold.
DTMF functionality
Blind Transfer, Attended Transfer
Ring all Devices
In this scenario of call conference, I guess we have to make changes in Incoming and outgoing session handling functions.
For registration and incoming call in context:
const getUAConfig = async (_extension, _name) => {
let alreadyLogin = '';
try {
alreadyLogin = 'yes';
if (alreadyLogin == 'yes') {
_displayname = _name;
_sipUsername = _extension;
_sipServer = 'SIP SERVER';
_sipPassword = 'SIP PASSWORD';
_wssServer = 'WSS SERVER;
const uri = UserAgent.makeURI('sip:' + _sipUsername + '#' + _sipServer);
const transportOptions = {
wsServers: 'WSS SERVER',
traceSip: true,
maxReconnectionAttempts: 1,
};
const userAgentOptions = {
uri: uri,
transportOptions: transportOptions,
userAgentString: 'App name',
authorizationPassword: _sipPassword,
sipExtension100rel: 'Supported',
sipExtensionReplaces: 'Supported',
register: true,
contactTransport: 'wss',
dtmfType: 'info',
displayName: _name,
sessionDescriptionHandlerFactoryOptions: {
peerConnectionOptions: {
rtcpMuxPolicy: 'negotiate',
iceCheckingTimeout: 1000,
iceTransportPolicy: 'all',
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
},
},
};
userAgent = await new UserAgent(userAgentOptions);
const registerOptions = {
extraContactHeaderParams: [],
};
registerer = await new Registerer(userAgent, registerOptions);
registerer.stateChange.addListener((newState) => {
});
userAgent.start().then(async () => {
console.log('Connected with WebSocket.');
// Send REGISTER
await registerer
.register()
.then((request) => {
console.log('Successfully sent REGISTER, object is here');
dispatch({
type: USER_REGISTERED,
payload: true,
});
})
.catch((error) => {
console.log('Failed to send REGISTER');
});
});
return { userAgent, registerer };
} else {
return null;
}
} catch (error) {
console.log(error.message + '');
return null;
}
};
Outgoing functionality:
const dilaerFun = (inputNumber, userAgentInfo) => {
var session;
var uri = UserAgent.makeURI(
`URI which we wanna call (sip number)`
);
session = new Inviter(userAgentInfo, uri);
session
.invite()
.then((request) => {
console.log('Successfully sent INVITE');
sessionInfoAdd(session);
session.stateChange.addListener(async (state) => {
switch (state) {
case 'Established':
setMissedStatus(null);
console.log('established outgoing....');
//outgoing call log-----
const mediaElement = document.getElementById(
`mediaElement${session._id}`
);
const remoteStream = new MediaStream();
session.sessionDescriptionHandler.peerConnection
.getReceivers()
.forEach((receiver) => {
if (receiver.track) {
remoteStream.addTrack(receiver.track);
}
});
mediaElement.srcObject = remoteStream;
mediaElement.play();
break;
case 'Terminated':
console.log('terminated');
dispatch({
type: DEMO_STATE,
payload: session._id,
});
break;
default:
break;
}
});
})
.catch((error) => {
console.error(' Failed to INVITE');
console.error(error.toString());
});
};
Array of sessions are maintained by:
const sessionInfoAdd = (session) => {
dispatch({
type: SESSION_STORE,
payload: session,
});
};
Variable in which all sessions are stored is:
sessionInfo:[]
NOTE: getUAConfig() is called as soon as the application is started.
dialerFun() is called when we want to dial a specific number.
sessionInfoAdd() is called in both getUAConfig and dialerFun, as they are codes for incoming and outgoing calls.
when sessionInfoAdd() is triggered, the particular session which we get in return is added in the sessionInfo (Array) for the maintenance of sessions.
SIP.JS is just a library so you will have to get the conference setup on the FreeSWITCH or Asterisk (FreeSWITCH is the better in my opinion)
Doing this is fairly straight forward, at your app level you need a way to get calls across to the box after checking the details like access ID and any auth you want to add, (like a PIN.)
Once you have that done, you can forward that to an extension specifically set for conferencing or have a dynamic conference setup by send from the app towards a specific gateway/dialplan to do this.
The FreeSWITCH software has a steep learning curve on it but this helped me when I was doing something similar: https://freeswitch.org/confluence/display/FREESWITCH/mod_conference
You can also code you own conf if you wish.
I've got a React web app subscribing to a Pusher channel that receives events triggered by an API - in this scenario, everything works fine. I now want to trigger events from the React web app itself and not the API, however, and I'm doing so via a presence channel as I used to get warnings saying only allowed on private or presence channels. Nothing happens though when I trigger the event from the React web app. And I can't see the channel is being subscribed to on the dashboard.
AccountPage.js
const videoPusherChannel = useRef<any>();
useEffect(() => {
var pusher = new Pusher('<PUSHER_KEY>', {
cluster: 'eu',
});
const videoChannel = pusher.subscribe(`presence-video-starting.${authState.userId}`);
videoChannel.bind('client-video-10-mins', function (data: any) {
alert("test");
return true;
});
videoPusherChannel.current = videoChannel;
}, []);
useEffect(() => {
videoPusherChannel.current.trigger("client-video-10-mins", {
type: 'video-starting',
user: authenticatedUser,
data: []
});
}, [videoPusherChannel.current]);
First render, pusher instance is created and channels are subscribed to. Channels are assigned to refs to ensure access outside the hook
Once ref has a value i.e. channels are subscribed, trigger the client-video-10-mins event
Nothing happens - no error. The Pusher dashboard doesn't show a subscribe event for the channel, and I don't see the alert 'test' on the browser.
Where am I going wrong??
*** UPDATED ***
Screenshot from dev console:
Authenticate function from my API (with the user's hardcoded values for the sake of testing):
public function authenticate(Request $request)
{
$socketId = $request->socket_id;
$channelName = $request->channel_name;
$pusher = new Pusher(<PUSHER_KEY>, <PUSHER_INFO>, <PUSHER_INFO>, [
'cluster' => 'eu',
'encrypted' => true
]);
$presence_data = ['name' => "Tom Tom"];
$key = $pusher->presence_auth($channelName, $socketId, 6, $presence_data);
return response($key);
}
I can establish a websocket connection with my springboot server but I can't access the endpoint from #MessageMapping when I'm trying to send a message. Here are my configurations:
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/simulator")
.setAllowedOrigins("http://myiphere:3000")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/endpoint");
registry.setApplicationDestinationPrefixes("/app");
}
And a simple controller :
#RestController
#RequestMapping("/api")
public class MyController {
#MessageMapping("/hello/")
#SendTo("/endpoint/greeting")
public Greeting getCurrentLocation() {
System.out.println("hello here");
return GenericBuilder.of(Greeting::new)
.with(Greeting::setContent, "hello from server")
.build();
}
}
I'm using the socketjs-client library in ReactJS by following this tutorial :
import SockJS from "sockjs-client";
import Stomp from "stompjs";
let stompClient;
const connect = () => {
const socket = new SockJS("http://myiphere:8081/simulator");
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log("Connected " + frame);
stompClient.subscribe("http://myiphere:8081/endpoint/greeting", function (greeting) {
console.log("hi" + JSON.parse(greeting.body).content);
});
});
};
const sendSomething = () => {
stompClient.send("http://myiphere:8081/app/hello/", {});
};
And some buttons with onClick events bound to the methods above. The connection is working, I'm getting "connected" messages in browser console but when I'm trying to click the button with sendSomething() I'm not getting anything in the browser's console nor server's console.
Solved.
The problem was the absolute url path in the send() method.
P.S.: And I've been looking for an answer for this problem on many sites and found out that there is no need to use absolute path for subscribe() url.
P.P.S.: In case that someone else have these problems, look for extra / too. You have to be careful when you're setting the url. The pattern from JS should match the one from SpringBoot.