WebSocket won't reconnect after dropped internet - websocket

I'm using graphql-ws https://www.npmjs.com/package/graphql-ws to manage my websocket connection, but am unable to figure out how to handle a dropped connection. Once my internet drops (toggling wifi) or computer sleeps, subscriptions all drop and websocket never reconnects.
closed never gets called. Everything else works as expected, just the disconnects an issue.
createClient({
retryAttempts: 5,
shouldRetry: () => true,
url: "ws://localhost:8080",
on: {
connected: () => {
console.log("CONNECTED");
},
closed: () => {
console.log("CLOSED");
},
error: (e) => {
console.log(e);
},
},
})
);

You can use keepAlive, ping, and pong as a trigger to restart your connection, and keep retryAttempt to infinite.
That's my attempt at keeping the socket alive:
createClient({
url: 'wss://$domain/v1/graphql',
retryAttempts: Infinity,
shouldRetry: () => true,
keepAlive: 10000,
connectionParams: () => {
const access_token = getAccessTokenFunction();
return {
headers: {
Authorization: `Bearer ${access_token || ''}`
}
};
},
on: {
connected: (socket) => {
activeSocket = socket; // to be used at pings & pongs
// get the access token expiry time and set a timer to close the socket
// once the token expires... Since 'retryAttempts: Infinity' it will
// try to reconnect again by getting a fresh token.
const token_expiry_time = getTokenExpiryDate();
const current_time = Math?.round(+new Date() / 1000);
const difference_time = (token_expiry_time - current_time) * 1000;
if (difference_time > 0) {
setTimeout(() => {
if (socket?.readyState === WebSocket?.OPEN) {
socket?.close(CloseCode?.Forbidden, "Forbidden");
}
}, difference_time);
}
},
ping: (received) => {
if (!received)
// sent
timedOut = setTimeout(() => {
if (activeSocket?.readyState === WebSocket?.OPEN)
activeSocket?.close(4408, 'Request Timeout');
}, 5000); // wait 5 seconds for the pong and then close the connection
},
pong: (received) => {
if (received) clearTimeout(timedOut); // pong is received, clear connection close timeout
}
}
})

Related

SocketIO 4 - manually try to connect once again if first time doesn't succeed

I have following code for the SocketIO client:
import { io } from "socket.io-client";
const token = window.localStorage.getItem('TOKEN') || window.sessionStorage.getItem('TOKEN')
const ioSocket = io("xxxx", {
autoConnect: false,
'reconnection': true,
'reconnectionDelay': 500,
'reconnectionAttempts': 10,
reconnectionDelayMax: 10000,
reconnectionAttempts: Infinity,
transportOptions: {
polling: {
extraHeaders: {
authorization: `${token}`,
},
},
},
});
export const socket = ioSocket
Before user is not logged in, token is not available, and when user perform login action, the page is not refreshed (I am using Vue 3 with vue-router), and the connection is never established. Once I manually refresh the page, connection is created. Is there a way to try to connect once again after some period of time?
This is the code I tried to manually connect:
socket.on("connect", () => {
console.log(socket.id);
});
onMounted(() => {
if(!socket.connected)
{
socket.connect();
}
socket.emit("join", import.meta.env.VITE_SOCKET_ROOM, (message) => {
console.log(message);
});
})

subscribing to event causes 'ERR_STREAM_WRITE_AFTER_END'

Tried to make example-event.ts work as HTTP Server. The example is a simple counter. You can subscribe to the count as an event.
It works until a client asks a second time for the value (longpoll).
EventSource ready
Emitted change 1
[binding-http] HttpServer on port 8080 received 'GET /eventsource' from [::ffff:192.168.0.5]:58268
[binding-http] HttpServer on port 8080 replied with '200' to [::ffff:192.168.0.5]:58268
[binding-http] HttpServer on port 8080 received 'GET /eventsource/events/onchange' from [::ffff:192.168.0.5]:58268
[core/exposed-thing] ExposedThing 'EventSource' subscribes to event 'onchange'
[core/content-serdes] ContentSerdes serializing to application/json
Emitted change 2
[binding-http] HttpServer on port 8080 replied with '200' to [::ffff:192.168.0.5]:58268
[binding-http] HttpServer on port 8080 closed Event connection
[core/exposed-thing] ExposedThing 'EventSource' unsubscribes from event 'onchange'
[binding-http] HttpServer on port 8080 received 'GET /eventsource/events/onchange' from [::ffff:192.168.0.5]:58268
[core/exposed-thing] ExposedThing 'EventSource' subscribes to event 'onchange'
[core/content-serdes] ContentSerdes serializing to application/json
[core/content-serdes] ContentSerdes serializing to application/json
Emitted change 3
events.js:377
throw er; // Unhandled 'error' event
^
Error [ERR_STREAM_WRITE_AFTER_END]: write after end
at new NodeError (internal/errors.js:322:7)
at writeAfterEnd (_http_outgoing.js:694:15)
at ServerResponse.end (_http_outgoing.js:815:7)
at SafeSubscriber._next (C:\xxx\node_modules\#node-wot\binding-http\dist\http-server.js:721:45)
at SafeSubscriber.__tryOrUnsub (C:\xxx\node_modules\rxjs\Subscriber.js:242:16)
at SafeSubscriber.next (C:\xxx\node_modules\rxjs\Subscriber.js:189:22)
at Subscriber._next (C:\xxx\node_modules\rxjs\Subscriber.js:129:26)
at Subscriber.next (C:\xxx\node_modules\rxjs\Subscriber.js:93:18)
at Subject.next (C:\xxx\node_modules\rxjs\Subject.js:55:25)
at Object.ExposedThing.emitEvent (C:\xxx\node_modules\#node-wot\core\dist\exposed-thing.js:53:50)
Emitted 'error' event on ServerResponse instance at:
at writeAfterEndNT (_http_outgoing.js:753:7)
at processTicksAndRejections (internal/process/task_queues.js:83:21) {
code: 'ERR_STREAM_WRITE_AFTER_END'
}
I am a little confused by all the subscribing and unsubscribing, but this is maybe because of longpoll is used.
my code:
server side:
Servient = require('#node-wot/core').Servient;
HttpServer = require('#node-wot/binding-http').HttpServer;
Helpers = require('#node-wot/core').Helpers;
// create Servient add HTTP binding with port configuration
let servient = new Servient();
servient.addServer(new HttpServer({}));
// internal state, not exposed as Property
let counter = 0;
servient.start().then((WoT) => {
WoT.produce({
title: 'EventSource',
events: {
onchange: {
data: { type: 'integer' },
},
},
})
.then((thing) => {
console.log('Produced ' + thing.getThingDescription().title);
thing.expose().then(() => {
console.info(thing.getThingDescription().title + ' ready');
setInterval(() => {
++counter;
thing.emitEvent('onchange', counter);
console.info('Emitted change ', counter);
}, 5000);
});
})
.catch((e) => {
console.log(e);
});
});
cient side:
const servient = new Wot.Core.Servient();
servient.addClientFactory(new Wot.Http.HttpClientFactory());
const helpers = new Wot.Core.Helpers(servient);
const addr ='http://192.168.0.5:8080/eventsource';
getTd(addr);
function getTd(addr) {
servient.start().then((thingFactory) => {
helpers
.fetch(addr)
.then((td) => {
thingFactory.consume(td).then((thing) => {
showEvents(thing);
});
})
.catch((error) => {
window.alert('Could not fetch TD.\n' + error);
});
});
}
function showEvents(thing) {
let td = thing.getThingDescription();
for (let evnt in td.events) {
if (td.events.hasOwnProperty(evnt)) {
document.getElementById("events").innerHTML = "waiting...";
thing
.subscribeEvent(evnt, (res) => {
document.getElementById("events").innerHTML = res;
})
.catch((err) => window.alert('error: ' + err));
}
}
}
The problem also occurs if I'm using example-event-client.ts or just send GET requests to "http://192.168.0.5:8080/eventsource/events/onchange" using the browser.
What do I have to do to make the example work?

Mqtt and Websocket at the same time with Aedes

I am trying to make Aedes works as a MQTT broker AND Websocket server. According to that doc: https://github.com/moscajs/aedes/blob/master/docs/Examples.md
what i am suppose to understand. Ideally, i want the listener fired up whatever if its a websocket client or a mqtt client.
Is it possible to do something like:
server.broadcast('foo/bar', {data:''})
and all client, websockets and mqtt receive the message ? The doc is not very clear and i am very suprised that websocket-stream is used. It is very low lvl right ?
here some server side code:
const port = 1883
const aedes = require('aedes')({
persistence: mongoPersistence({
url: 'mongodb://127.0.0.1/aedes-test',
// Optional ttl settings
ttl: {
packets: 300, // Number of seconds
subscriptions: 300
}
}),
authenticate: (client, username, password, callback) => {
},
authorizePublish: (client, packet, callback) => {
},
authorizeSubscribe: (client, packet, callback) => {
}
});
//const server = require('net').createServer(aedes.handle);
const httpServer = require('http').createServer()
const ws = require('websocket-stream')
ws.createServer({ server: httpServer }, aedes.handle)
httpServer.listen(port, function () {
Logger.debug('Aedes listening on port: ' + port)
aedes.publish({ topic: 'aedes/hello', payload: "I'm broker " + aedes.id })
});
It should just be case of starting both servers with the same aedes object as follows:
const port = 1883
const wsPort = 8883
const aedes = require('aedes')({
persistence: mongoPersistence({
url: 'mongodb://127.0.0.1/aedes-test',
// Optional ttl settings
ttl: {
packets: 300, // Number of seconds
subscriptions: 300
}
}),
authenticate: (client, username, password, callback) => {
},
authorizePublish: (client, packet, callback) => {
},
authorizeSubscribe: (client, packet, callback) => {
}
});
const server = require('net').createServer(aedes.handle);
const httpServer = require('http').createServer()
const ws = require('websocket-stream')
ws.createServer({ server: httpServer }, aedes.handle)
server.listen(port, function() {
Logger.debug('Ades MQTT listening on port: ' + port)
})
httpServer.listen(wsPort, function () {
Logger.debug('Aedes MQTT-WS listening on port: ' + wsPort)
aedes.publish({ topic: 'aedes/hello', payload: "I'm broker " + aedes.id })
});

why the timeout operator that I have in my http request does not throw an error

the timeout that I defined does not throw any error when the duration parameter I defined is greater than 7000 ms. what is strange is that the timeout operator works well in my code from 0 to 7000 ms
pay(billing: Billing): Observable {
const httpOptions = {
headers: new HttpHeaders({
// 'Access-Control-Allow-Origin':'*'
}),
params: new HttpParams()
.append('timezone', billing.timezone)
.append('mode', billing.mode)
.append('responseFailURL', billing.responseFailURL)
.append('responseSuccessURL', billing.responseSuccessURL)
.append('hash', billing.hash)
.append('txndatetime', billing.txndatetime)
.append('chargetotal', billing.chargetotal.toString())
.append('storename', billing.storename.toString())
.append('currency', billing.currency.toString())
};
// Sending required payment infrmations to Authipay host url
return forkJoin(
of(2), timer(2000).pipe(mergeMap(value => this.getPayementStatus(billing.txndatetime))).pipe( timeout(7500))
).pipe(
map(
([articles, authorOfTheMonth]) => {
console.log(authorOfTheMonth);
return authorOfTheMonth;
}
)
).subscribe(
resp => {
this.router.navigate(['success'], { relativeTo: this.route });
} else {
form.setErrors({ paymentFailed: true });
this.alertify.error(this.translate.instant('error.payment'));
}
},
error => {
if (error instanceof TimeoutError) {
this.alertify.error(error.message);
} else {
this.alertify.error(this.translate.instant('error.payment'));
}
}
);
timeout seems to work as expected to me.
I wrote a test here where I replaced your this.getPayementStatus(billing.txndatetime)) function with a :
simulated response
const simulateResponseTime = (timeInMS)=> timer(timeInMS); // in milliseconds
Which will return a response in delayOfResponse milliseconds. With this tool we can test what happens when the response takes more time than timeout threshold:
Simulation parameters
const timeoutThreshold = 7500; // in ms
const delayOfResponse = 200; //in ms
Finally, a minimalist version of
Your code
forkJoin(of(2), timer(2000).pipe(
mergeMap(value => simulateResponseTime(delayOfResponse))
).pipe(timeout(timeoutThreshold))
).pipe(
...
).subscribe(
resp => {
console.log('Success')
},
error => {
console.log('Error message :', error.message)
console.log('Error type :', error.name)
console.log('Is a TimeoutError :', error.name === 'TimeoutError' )
}
);

How to resume RxJs Observable Interval on Error

I am merging two Observables.
The first one gets the current temperature on init.
The second one polls at a certain interval the API.
If the Api call fails, then the Observable interval is not resumed.
How could I resume it?
getCurrentTemp(): Observable<number> {
return this.http.get(this.environmentService.getTemperatureUrl())
.map((res: Response) => res.json())
.switchMap(() => res.temp);
}
pollCurrentTemperature(): Subscription {
const temp$ = this.getCurrentTemp();
const tempInterval$ = Observable
.interval(3000)
.flatMap(() => this.getCurrentTemp());
return temp$
.take(1)
.merge(tempInterval$)
.subscribe((temp: number) => {
console.log('temp', temp);
}, (err) => {
console.log('error', err);
// When the api fails my interval does not resume. How can I retry it?
});
}
Any ideas? Ty
Use catch.
Catch: recover from an onError notification by continuing the sequence without error
getCurrentTemp(): Observable<number> {
return this.http.get(this.environmentService.getTemperatureUrl())
.map((res: Response) => res.json())
.catch(error => {
console.log('Error occured');
return Observable.empty();
});
.switchMap(() => res.temp);
}
It will catch the error and silently return an empty observable in its place. In effect, the switchmap will skip over the failed api call silently as it will not emit for the empty observable.
Of course, you could have an alternate behaviour on error, but you need to catch it to avoid the problem you are facing.
Using the http status codes you can retrieve an observable only if its a 200 let's say:
getCurrentTemp(): Observable<number> {
return Observable.from(
[
{ value: 1, status: 200 },
{ value: 2, status: 200 },
{ value: 3, status: 200 },
{ value: 4, status: 200 },
{ value: 5, status: 200 },
{ value: 6, status: 400 }])
.switchMap((x: any) => {
if (x.status === 200) {
return Observable.of(x.value);
}
return Observable.onErrorResumeNext();
});
}
pollCurrentTemperature(): Subscription {
const temp$ = this.getCurrentTemp();
const tempInterval$ = Observable
.interval(3000)
.flatMap(() => this.getCurrentTemp());
return temp$
.take(1)
.merge(tempInterval$)
.subscribe((temp: number) => {
console.log('temp', temp);
}, (err) => {
console.log('error', err);
// When the api fails my interval does not resume. How can I retry it?
});
}
The important bit is this return Observable.onErrorResumeNext();

Resources