InvalidStateError on websocket object - javascript

I'm just starting learning websockets, andI get a strange error:
<script type="text/javascript">
window.onload = function(){
var service = new WebSocket("ws://localhost:8080/websocket/test");
service.onmessage = function(event){
alert("message");
}
service.onopen = function(){
service.send("hello!");
}
service.onclose = function(){
alert("closed");
}
service.onerror = function(){
alert("error");
}
service.send("test");
service.close();
}
</script>
on the line:
service.send("test");
I get:
InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
Am I missing something important?

The WebSocket.onopen property is an EventHandler that is called when the WebSocket connection's readyState changes to OPEN; this indicates that the connection is ready to send and receive data.
Once you've opened your connection, you can begin transmitting data to the server.
window.onload = function() {
var service = new WebSocket("wss://echo.websocket.org");
service.onmessage = function(event) {
alert("onmessage event: "+event.data);
}
service.onopen = function() {
service.send("test"); //Will work here!
//^^^^^^^^^^^^^^^^^
service.send("hello!");
}
service.onclose = function() {
alert("closed");
}
service.onerror = function() {
alert("error");
}
//Can't close while a connection is still being established.
//service.close();
}

If you use Websocket().send() outside your service.onopen(), you can still check the readyStatebefore sending message, like this:
websocket.sendmessage = async function(msg) {
while (this.readyState === 0) {
await sleep(200);
}
this.send(msg);
};

Related

Event Handler to run only when previous event handling is complete

Attached Event handler to callback like :
$("someSelector").on('click',callBackHandler);
function callBackHandler(){
//Some code
$.ajax({
//Ajax call with success methods
})
}
My success method is manipulating some object properties. Since ajax is involved, it will not wait for the completion and next event handling will start. How can I make sure next click event handling starts only when previous handling is done.
Cannot think of a way of using deferred manually on this because I am triggering event manually on base of some condition in for loop (Not a clean style of coding, but has no other option in particular use case).
$('someSelector').trigger('click');
$("someSelector").on('click', function(event) {
event.preventDefault();
var urAjax = $.ajax({
// Ajax Call here...
});
urAjax.always(function(response) {
$.when( callBackHandler() ).done(function() {
// Handle your ajax response in here!
});
});
});
function callBackHandler() {
// Do Stuff
}
callBackHandler function will fire, and when it's done, your ajax response for .always will fire directly after that. This allows for your ajax to load while the callBackHandler function is running also, but doesn't fire the response until after the function is done! Hopefully I'm understanding what you are asking for here.
You can see an example jsfiddle located here: https://jsfiddle.net/e39oyk8q/11/
Try clicking the submit button multiple times before the AJAX request is finished, you will notice that it will loop over and over again the total amount of clicks you give it on the Submit button. You can see this by the amount of times the Alert box pops up, and also, it adds 100 to the len (that gets outputted on the page) during each call to the callBackHandler function. So, I do believe this is what you asked for.
And, ofcourse, you can still use: $('someSelector').trigger('click');
EDIT
Another approach is to return a json object that can be used within the ajax call or wherever you need it within the click event, like so:
$("someSelector").on('click', function(event) {
event.preventDefault();
var myfunc = callBackHandler();
var urAjax = $.ajax({
type: 'POST',
url: myfunc['url'],
data: myfunc['data']
});
urAjax.always(function(response) {
$.when( myfunc ).done(function() {
console.log(myfunc['time']);
// Handle your ajax response in here!
});
});
});
function callBackHandler() {
var timestamp = new Date().getTime();
return { url: 'my_ajax_post_url', data: {data1: 'testing', data2: 'testing2'}, time: timestamp }
}
fiddle example here: https://jsfiddle.net/e39oyk8q/15/
You can remove the binding on the call of function and bind again when the ajax is done().
function callBackHandler(){
$("someSelector").off('click');
//Some code
$.ajax({
//Ajax call with success methods
}).done(function(){
$("someSelector").on('click',callBackHandler);
})
}
var requestDataButton = document.querySelector('.js-request-data');
var displayDataBox = document.querySelector('.js-display-data');
var displayOperationsBox = document.querySelector('.js-display-operations');
var displayNumberOfRequestsToDo = document.querySelector('.js-display-number-of-requests-to-do');
var isAjaxCallInProgress = false;
var numberOfAjaxRequestsToDo = 0;
var requestUrl = 'http://jsonplaceholder.typicode.com/photos';
requestDataButton.addEventListener('click', handleClick);
function handleClick() {
displayNumberOfRequestsToDo.innerText = numberOfAjaxRequestsToDo;
numberOfAjaxRequestsToDo++;
if(!isAjaxCallInProgress) {
isAjaxCallInProgress = true;
requestData();
}
}
function handleResponse(data) {
displayData(data);
displayOperationsBox.innerHTML = displayOperationsBox.innerHTML + 'request handled <br>';
numberOfAjaxRequestsToDo--;
displayNumberOfRequestsToDo.innerText = numberOfAjaxRequestsToDo;
if(numberOfAjaxRequestsToDo) {
requestData();
} else {
isAjaxCallInProgress = false;
}
}
function displayData(data) {
displayDataBox.textContent = displayDataBox.textContent + JSON.stringify(data[0]);
}
function requestData() {
var request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
var data = JSON.parse(this.response);
handleResponse(data);
} else {
// on error
}
};
request.onerror = function() {
// There was a connection error of some sort
};
request.send();
displayOperationsBox.innerHTML = displayOperationsBox.innerHTML + 'request sent <br>';
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="js-request-data">Request Data</button>
<div>
<h2>Requests wainting in a queue to be sent:
<span class="js-display-number-of-requests-to-do">
0
</span>
</h2>
</div>
<div>
<h2>Operations:</h2>
<div class="js-display-operations"></div>
</div>
<div>
<h2>Response Data:</h2>
<div class="js-display-data"></div>
</div>
edit
here's a utility function that takes similar arguments as $.ajax, and then returns an augmented $.ajax function that keeps an eye on its previous calls and waits for all preceding requests to finish before dispatching another ajax call (are fired sequentially):
function sequentialize(requestData, responseHandler) {
let inProgress = false;
let deferredRequests = 0;
const makeRequest = () => {
$.ajax(requestData).then(processManager);
};
const processManager = (data) => {
responseHandler(data);
if (deferredRequests) {
deferredRequests--;
makeRequest();
} else {
inProgress = false;
}
};
return function () {
if (!inProgress) {
inProgress = true;
makeRequest();
} else {
deferredRequests++;
}
};
}

Let HTML5 Notification disappear after a delay?

I want to use HTML5 notifications which work great.
The problem is that they never disappear.
How can I set a delay after which the HTML5 notification disappear?
You can just call the .close() method:
var n = new Notification("Hello");
setTimeout(n.close.bind(n), 2000);
See here on MDN for details.
The notification should have a built in close button, no?
HTML
Notify me!
JS
<script>
var Notification = window.Notification || window.mozNotification || window.webkitNotification;
Notification.requestPermission(function (permission) {
// console.log(permission);
});
function show() {
var instance = new Notification(
"Hey", {
body: "this is a message"
}
);
instance.onclick = function () {
// Something to do
};
instance.onerror = function () {
// Something to do
};
instance.onshow = function () {
// Something to do
};
instance.onclose = function () {
// Something to do
};
return false;
}
</script>

Why does this WebSocket function return too many messages?

I'm using Ruby gems sinatra and sinatra-websocket on the backend, with JS on the frontend.
My backend:
get '/' do
if(!(request.websocket?))
erb :index
else
request.websocket do |ws|
ws.onopen do
settings.sockets << ws
end
ws.onmessage do |msg|
EM.next_tick {
settings.sockets.each{|s|
# p s
s.send(msg)
}
}
end
ws.onclose do
warn("connection closed")
end
end
end
end
And on the frontend...
window.onload = function(){
function someFunction(){
var ws = new WebSocket('ws://'+window.location.host+window.location.pathname);
ws.onopen = function(){
var buttons = document.getElementsByClassName('buttons');
[].forEach.call(
buttons,
function(btn){
if(btn!=null){
btn.onclick = function(){
var amt = 11;
ws.send(amt);
console.log(amt);
return false;
};
}
}
);
};
ws.onmessage = function(msg){
console.log(msg);
return false;
};
ws.onclose = function(){
console.log("Connection closed");
return false;
};
}
};
The first console.log() prints test once, but the one in onmessage gives the response twice at first, three times the second time I click one of the buttons, and more times every time I click afterward.
Why is this? How do I make it so the function in onmessage gives me msg just once each time I click one of the buttons?

YouTube API - iframe onStateChange events

I'm using the iframe YouTube API and I want to track events, for example, sending data to google analytics, when user start and stop video.
<iframe src="https://www.youtube.com/embed/DjB1OvEYMhY"></iframe>
I looked https://developers.google.com/youtube/iframe_api_reference?csw=1 and did not find an example how to do that. The example creates iframe and defines onReady and onStateChange as well. How would I do same when I've only iframe on page?
This example listens to every play/pause action the user makes, using onPlayerStateChange with its different states, and prints (records) them.
However, you need to create your own record function to do whatever you want with this data.
You also need an ID on your iframe (#player in this case) and to add ?enablejsapi=1 at the end of its URL. And of course, make sure to include the Youtube iframe API.
Note
It's important to declare the API after your code, because it calls onYouTubeIframeAPIReady when it's ready.
<!DOCTYPE html>
<html>
<body>
<iframe id="player" src="https://www.youtube.com/embed/DjB1OvEYMhY?enablejsapi=1"></iframe>
<h5>Record of user actions:</h5>
<script>
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player( 'player', {
events: { 'onStateChange': onPlayerStateChange }
});
}
function onPlayerStateChange(event) {
switch(event.data) {
case 0:
record('video ended');
break;
case 1:
record('video playing from '+player.getCurrentTime());
break;
case 2:
record('video paused at '+player.getCurrentTime());
}
}
function record(str){
var p = document.createElement("p");
p.appendChild(document.createTextNode(str));
document.body.appendChild(p);
}
</script>
<script src="https://www.youtube.com/iframe_api"></script>
</body>
</html>
JS Fiddle Demo
Here is a version that doesn't use Youtubes iframe API script. The only drawback is that the iframe API might change.
<iframe id="player" src="https://www.youtube.com/embed/dQw4w9WgXcQ?enablejsapi=1"></iframe>
var addYoutubeEventListener = (function() {
var callbacks = [];
var iframeId = 0;
return function (iframe, callback) {
// init message listener that will receive messages from youtube iframes
if(iframeId === 0) {
window.addEventListener("message", function (e) {
if(e.origin !== "https://www.youtube.com" || e.data === undefined) return;
try {
var data = JSON.parse(e.data);
if(data.event !== 'onStateChange') return;
var callback = callbacks[data.id];
callback(data);
}
catch(e) {}
});
}
// store callback
iframeId++;
callbacks[iframeId] = callback;
var currentFrameId = iframeId;
// sendMessage to frame to start receiving messages
iframe.addEventListener("load", function () {
var message = JSON.stringify({
event: 'listening',
id: currentFrameId,
channel: 'widget'
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
message = JSON.stringify({
event: "command",
func: "addEventListener",
args: ["onStateChange"],
id: currentFrameId,
channel: "widget"
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
});
}
})();
addYoutubeEventListener(document.getElementById("player"), function(e) {
switch(e.info) {
case 1:
// playing
break;
case 0:
// ended
break;
}
});
Sometimes the event load is not enough to ensure that the document inside the iframe is ready. If the iframe is in a different domain it is not possible to subscribe to see when it is ready.
A possible workaround is to record when an event is received from the iframe, if after subscribing no event was received try again:
var addYoutubeEventListener = (function() {
var callbacks = [];
var iframeId = 0;
var subscribed = [];
return function (iframe, callback) {
// init message listener that will receive messages from youtube iframes
if(iframeId === 0) {
window.addEventListener("message", function (e) {
if(e.origin !== "https://www.youtube.com" || e.data === undefined) return;
try {
var data = JSON.parse(e.data);
subscribed[data.id] = true;
if(data.event !== 'onStateChange') return;
var callback = callbacks[data.id];
callback(data);
}
catch(e) {}
}, true);
}
// store callback
iframeId++;
callbacks[iframeId] = callback;
subscribed[iframeId] = false;
var currentFrameId = iframeId;
//console.log("adding event listener to iframe id " + iframeId);
// sendMessage to frame to start receiving messages
iframe.addEventListener("load", function () {
var tries = 0;
var checkSubscribed = function()
{
if (subscribed[currentFrameId]) {
//console.log("subscribed succesfully " + currentFrameId)
}
else
{
tries++;
//console.log("Try again " + currentFrameId + " (" + tries + ")");
if (tries < 100) {
doSubscribe();
}
else
{
console.log("Unable to subscribe" + currentFrameId );
}
}
}
var doSubscribe = function()
{
var message = JSON.stringify({
event: 'listening',
id: currentFrameId,
channel: 'widget'
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
message = JSON.stringify({
event: "command",
func: "addEventListener",
args: ["onStateChange"],
id: currentFrameId,
channel: "widget"
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
setTimeout(checkSubscribed, 100);
};
doSubscribe();
}, true);
}
})();

Javascript access class methods outside of class?

See my example below:
(function() {
// Initialize the socket & handlers
var connectToServer = function() {
var warbleSocket = new SockJS('http://url.com:5555/warble');
warbleSocket.onopen = function() {
clearInterval(connectRetry);
$('.connect-status')
.removeClass('disconnected')
.addClass('connected')
.text('Connected');
};
warbleSocket.onmessage = function(e) {
$('#warble-msg').text(e.data);
};
warbleSocket.onclose = function() {
clearInterval(connectRetry);
connectRetry = setInterval(connectToServer, 1000);
$('.connect-status')
.removeClass('connected')
.addClass('disconnected')
.text('Disconnected');
};
// Connect the text field to the socket
$('.msg-sender').off('input').on('input', function() {
warbleSocket.send($('.msg-sender input').val());
});
};
var connectRetry = setInterval(connectToServer, 1000);
connectRetry.warbleSocket.send("Hi there");
})();
What i would like is to be able to access warbleSocket.send from outside of connectRetry
How can i accomplish this?
to expose API from IIFE in JS:
...
return {
send: warbleSocket.send
}
})();
http://benalman.com/news/2010/11/immediately-invoked-function-expression/

Categories