Reference
Table of Contents
Installation
As browser client
Download vibe.js the way you want.
- The compressed for production
- The uncompressed for development
bower install vibe
Then load or link it by using either script tag or Asynchronous Module Definition loader.
Script tag
<script src="/vibe/vibe.min.js"></script>
<script>
var socket = vibe.open("/vibe");
</script>
AMD loader
require(["vibe"], function(vibe) {
var socket = vibe.open("/vibe");
});
As Node.js client
vibe.js is available on npm under the name of vibe-client
. Install the module.
npm install vibe-client --save
It will install the latest version adding it to dependencies entry in package.json
in the current project folder. If you are on Windows, you may have trouble in installing Contextify. See a installation guide from jsdom.
Then load it as a Node.js module.
var vibe = require("vibe-client");
var socket = vibe.open("http://localhost:8080/vibe");
Socket
The feature-rich and flexible interface for two-way communication.
Event
From the semantic point of view, the unit of data to be sent and be received is the event, like the interaction between user and browser. The socket object's events can be classified like the following:
Pseudo event:
connecting
andwaiting
They only exist in the client and have nothing to do with the server. Event handlers belong to those events
Network event:
open
andclose
They are fired in process of connecting and disconnecting by the transport object.
Message event:
message
and everything elseThey are sent and received via the connection effectively. Only this type of event can be extended. All the custom event used by the socket or the user belong to custom message event.
Life Cycle
Socket always is in a specific state that can be accessed by state
method. According to the status of connection to the server, transition between states occurs and this circulating transition makes a life cycle. The following list is a list of state which a socket can be in. A word in parentheses in closed state means close reason.
preparing
As an initial state of life cycle, it's internally used during reinitializing socket state and selecting transport.
State transition occurs to
- connecting: if transport is selected.
- closed(notransport): if there is no available transport in
transports
option in runtime environment.
connecting
The selected transport starts connecting to the server and the
connecting
event is fired. Timer for time-out is activated iftimeout
option is a positive number, environment for connection sharing is constructed and the socket starts to share its connection ifsharing
option istrue
. Theconnecting
event is an initial event which the socket fires so you can handle it.State transition occurs to
- opened: if transport succeeds in establishing a connection.
- closed(error): if transport fails to connect.
- closed(done): if
sse
transport fails to connect. - closed(timeout): if it's timed out.
- closed(aborted): if
close
method is called.
opened
The connection is established successfully and communication is possible. The
open
event is fired. Heartbeat communication starts ifheartbeat
option is number greater than5000
. Only in this state, the socket can send and receive events via connection to the server.State transition occurs to
- closed(error): if heartbeat fails.
- closed(error): if connection is disconnected due to some error.
- closed(done): if connection is closed cleanly.
- closed(done): if connection established by
sse
transport is disconnected due to some error. - closed(aborted): if
close
method is called.
closed
The connection has been closed, has been regarded as closed or could not be opened. The
close
event is fired with the close reason. If thereconnect
handler is set to or returnsfalse
, the socket's life cycle ends here.State transition occurs to
- waiting: if the
reconnect
handler returns a positive number.
- waiting: if the
waiting
The socket waits out the reconnection delay. The
waiting
event is fired with the reconnection delay in milliseconds and the total number of reconnection attempts.State transition occurs to
- preparing: after the reconnection delay.
- closed(aborted): if
close
method is called.
Sending and Receiving Event
You can send event using send(event: string, data?: any)
and receive event using on(event: string, onEvent: (data?: any) => void)
. Any event name can be used, except connecting
, open
, close
and waiting
and any data can be sent and received but it should be able to be marshalled/unmarshalled to JSON.
Note
- Socket must be in
opened
state. - To manage a lot of events easily, use URI as event name format like
/account/update
.
The client sends events and the server echoes back to the client.
Client
vibe.open("http://localhost:8080/vibe", {reconnect: false})
.on("open", function() {
this.send("echo", Math.PI)
.send("echo", "pi")
.send("echo", {"p": "i"})
.send("echo", ["p", "i"]);
})
.on("echo", function(data) {
console.log(data);
});
Server
server.on("socket", function(socket) {
socket.on("echo", function(data) {
console.log(data);
this.send("echo", data);
});
});
The server sends events and the client echoes back to the server.
Client
vibe.open("http://localhost:8080/vibe", {reconnect: false})
.on("echo", function(data) {
console.log(data);
this.send("echo", data);
})
Server
server.on("socket", function(socket) {
socket.on("echo", function(data) {
console.log(data);
})
.send("echo", Math.PI)
.send("echo", "pi")
.send("echo", {"p": "i"})
.send("echo", ["p", "i"]);
});
Getting and Setting Result of Event Processing
You can get the result of event processing from the server in sending event using send(event: string, data?: any, onResolved?: (data?: any) => void, onRejected?: (data?: any) => void)
, and set the result of event processing to the server in receiving event using on(event: string, handler:(data?: any, reply?: {resolve: (data?: any) => void; reject: (data?: any) => void}) => void)
. Either resolved or rejected callback is executed once when the counterpart executes it.
You can apply this functionality to sending events in order, Acknowledgements, Remote Procedure Call and so on. It looks nothing new to traditional Ajax, but comparing to Ajax, WebSocket can reduce unnecessary traffic like HTTP headers and the result can be shared by multiple tabs and windows. To write a low-latency webapp, you can utilize reply in place of Ajax.
Note
- Socket must be in
opened
state. - Beforehand determine whether to use rejected callback or not to avoid writing unnecessary rejected callbacks.
The client sends events attaching callbacks and the server executes one of them with the result of event processing.
Client
vibe.open("http://localhost:8080/vibe", {reconnect: false})
.on("open", function(data) {
this.send("/account/find", "flowersinthesand", function(data) {
console.log("resolved with " + data);
}, function(data) {
console.log("rejected with " + data);
})
.send("/account/find", "flowersits", function(data) {
console.log("resolved with " + data);
}, function(data) {
console.log("rejected with " + data);
});
});
Server
server.on("socket", function(socket) {
socket.on("/account/find", function(id, reply) {
console.log(id);
if (id === "flowersinthesand") {
reply.resolve({name: "Donghwan Kim"});
} else {
reply.reject("¯\(°_o)/¯");
}
});
});
The server sends events attaching callbacks and the client executes one of them with the result of event processing.
Client
vibe.open("http://localhost:8080/vibe", {reconnect: false})
.on("/account/find", function(id, reply) {
console.log(id);
if (id === "flowersinthesand") {
reply.resolve({name: "Donghwan Kim"});
} else {
reply.reject("¯\(°_o)/¯");
}
});
Server
server.on("socket", function(socket) {
socket.send("/account/find", "flowersinthesand", function(data) {
console.log("resolved with " + data);
}, function(data) {
console.log("rejected with " + data);
})
.send("/account/find", "flowersits", function(data) {
console.log("resolved with " + data);
}, function(data) {
console.log("rejected with " + data);
});
});
Heartbeat
Considering a multitude of browsers, transports, servers, networks and their combination, heartbeat is essential to maintain stable connection. For that reason, a socket starts heartbeat communication every 20 seconds on the open event by default. To change the time interval, set heartbeat:number
option to number in ms greater than 5000.
Eavesdropping heartbeat of client and server.
Client
vibe.open("http://localhost:8080/vibe", {reconnect: false})
.on("heartbeat", function() {
console.log("heartbeat at " + Date.now());
});
Server
server.on("socket", function(socket) {
socket.on("heartbeat", function() {
console.log("heartbeat at " + Date.now());
});
});
Connection Sharing
It's common case that user opens multiple tabs of same website and each tab have its persistent connection like vibe. In the case of a web portal consisting of many portlets, if portlet has a persistent connection, it may not be able to send any request, receive and display anything in violation of the simultaneous connection limit. By sharing a connection, it can be avoided.
To enalbe, set sharing:boolean
option to true
. As long as the cookie is enabled, socket will automatically find and use a shared connection if it exists within the cookie's scope and share its connection if there is no corresponding one. Note that if the web page or computer becomes horribly busy, a newly created socket might establish a physical connection but still it doesn't break anything.
Note
- Applies to only browser.
- In the current implementation, the server can't see socket using shared connection apart from one which possesses a real physical connection. Sockets using shared connection are a kind of mirror of socket having shared the connection but there is no restriction in functionalities. In the future, it will be enhanced or replaced with new implementation to allow for the server to recognize a socket using shared connection as a indipendent socket.
- Reply can't be used together with the current implementation but with new implementation, it will be available.
Sending and reciving event via shared connection.
Client A
vibe.open("http://localhost:8080/vibe", {
reconnect: false,
sharing: true
})
.on("open", function() {
this.send("echo", "purple night");
})
.on("echo", function(data) {
console.log(data);
});
Client B
vibe.open("http://localhost:8080/vibe", {
reconnect: false,
sharing: true
})
.on("open", function() {
this.send("echo", "purple night");
})
.on("echo", function(data) {
console.log(data);
});
Server
server.on("socket", function(socket) {
socket.on("echo", function(data) {
this.send("echo", data);
});
});
Reconnection
Reconnection has been disabled in the above code snippets for convenience of test, but it's essential for production so that it's enabled by default. The default strategy generates a geometric progression with initial delay 500
and ratio 2
(500, 1000, 2000, 4000 ...). To change it, set reconnect (delay: number, attempts: number): number
function which receives the last delay in ms or null at first and the total number of reconnection attempts and should return a delay in ms or false
not to reconnect.
Note
- Don't add event handler during dispatch including
open
event. Because reconnection doesn't remove event handlers, it will be duplicated in next life cycle.
Transport
WebSocket is clearly a best transport but in the real world, many coporate proxies, firewalls, antivirus softwares and cloud application platforms block it for some reason. Of course, many users still use browsers not supporting WebSocket, which we can't compel to upgrade it.
Such transport is configurable through transports:string[]
option. The default value is ["ws", "stream", "longpoll"]
, which means try WebSocket, if not possible try HTTP Streaming if not possible try use HTTP Long polling. Specific details about transport are introduced in Transport section.
Note
- Transport availability is calculated by feature detection in runtime environment. It doesn't mean that selected transport will work with the given network and server.
- Whatever transport is selected, it doesn't affect socket's functionalities.
Transport
The private interface that used to establish a connection, send data, receive data and close the connection.
Implementation
According to the technology, available transport implementations can be sorted into three groups like the following:
WebSocket
WebSocket is a protocol designed for a full-duplex communications over a TCP connection. However, many coporate proxies, firewalls and antivirus softwares blocks it for some reason.
ws
: works if browser supportsWebSocket
.
HTTP Streaming
The client performs a HTTP persistent connection and watches changes in response text and the server prints chunk as data over the connection.
sse
: works if browser supportsEventSource
. If the browser is Safari 5 or 6, only same origin connection works. By reason of the spec's ambiguity, there is no way to determine whether a connection closed normally or not so that the close event's reason will be alwaysdone
even though the connection closed due to an error.streamxhr
: in case of same origin connection, works without qualification. In case of cross origin, works ifXMLHttpRequest
supports CORS. However for both cases, if the browser is Internet Explorer, the version should be equal to or higher than 10 and if the browser is Opera, the version should be equal to or higher than 13.streamxdr
: works if browser supportsXDomainRequest
andxdrURL
option is set.streamiframe
: works if it's same origin connection and browser supportsActiveXObject
. This transport differs from the traditional Hidden Iframe in terms of fetching a response text. The traditional transport expects script tags, whereas this transport periodically polls the response text.
For convenience, a special transport,
stream
, is provided, which represents the above implementation with order.HTTP Long polling
The client performs a HTTP persistent connection and the server ends the connection with data. Then, the client receives it and performs a request again.
longpollajax
: in case of same origin connection, works without qualification. In case of cross origin, works ifXMLHttpRequest
supports CORS.longpollxdr
: works if browser supportsXDomainRequest
andxdrURL
option is set.longpolljsonp
: works always.
For convenience, a special transport,
longpoll
, is provided, which represents the above implementation with order.
As specified in the protocol, in case of HTTP Streaming and HTTP Long polling, they establishes read-only connection using GET method. To simulate a full-duplex connection over HTTP, POST
is used to write data and the following ways are used in HTTP transports to perform POST
request.
- XMLHttpRequest: in case of same origin, works without qualification. In case of cross origin, works if browser supports CORS.
- XDomainRequest: works if browser supports
XDomainRequest
andxdrURL
option is set. - Form tag: works alwayas but makes a clicking sound.
Compatibility
The compatiblity of vibe.js depends on transport compatibility.
Browser
The policy for browser support is the same with the one of jQuery 1.x.
Internet Explorer | Chrome | Firefox | Safari | Opera | iOS | Android |
---|---|---|---|---|---|---|
6+ | (Current - 1) or Current | (Current - 1) or Current | 5.1+ | 12.1x, (Current - 1) or Current | 6.0+ | 4.0+ |
Transport list in each cell is ordered by recommendation. As to ws
, a word in parentheses means WebSocket protocol. So in order to use ws
, the server has to be able to support that protocol.
Browser | Version | WebSocket | HTTP Streaming | HTTP Long polling |
---|---|---|---|---|
Firefox | 11 | ws (rfc6455) |
sse , streamxhr |
longpollajax , longpolljsonp |
Chrome | 25 | ws (rfc6455) |
sse , streamxhr |
longpollajax , longpolljsonp |
Internet Explorer | 11 | ws (rfc6455) |
streamxhr |
longpollajax , longpolljsonp |
10 | ws (rfc6455) |
streamxhr , streamxdr 3, streamiframe 1, 2 |
longpollajax , longpollxdr 3, longpolljsonp |
|
8 | streamxdr 3, streamiframe 1 |
longpollajax 1, longpollxdr 3, longpolljsonp |
||
6 | streamiframe 1 |
longpollajax 1, longpolljsonp |
||
Safari | 7.0 | ws (rfc6455) |
sse , streamxhr |
longpollajax , longpolljsonp |
6.0 | ws (rfc6455) |
sse 1, streamxhr |
longpollajax , longpolljsonp |
|
5.1 | ws (hixie-76) |
sse 1, streamxhr |
longpollajax , longpolljsonp |
|
Opera | 15 | ws (rfc6455) |
sse , streamxhr |
longpollajax , longpolljsonp |
12.10 | ws (rfc6455) |
sse |
longpollajax , longpolljsonp |
|
iOS | 7.0 | ws (rfc6455) |
sse , streamxhr |
longpollajax , longpolljsonp |
6.0 | sse 1, streamxhr |
longpollajax , longpolljsonp |
||
Android | 4.4 | ws (rfc6455) |
sse , streamxhr |
longpollajax , longpolljsonp |
4.0 | streamxhr |
longpollajax , longpolljsonp |
Note
- 1: only availabe in same origin connection
- 2: not available in Metro.
- 3:
xdrURL
option is required.
Node.js
Node.js lower than 0.10 may work.
Version | WebSocket | HTTP Streaming | HTTP Long polling |
---|---|---|---|
0.10 | ws (rfc6455) |
sse |
longpollajax |
Quirks
The vibe.js always has tried to deal with any quirks in non-invasive way. However, it is not possible sometimes.
The browser limits the number of simultaneous connections
Applies to: HTTP transport
According to the HTTP/1.1 spec, a single-user client should not maintain more than 2 connections. This restriction actually varies with the browser. If you consider multiple topics to subscribe and publish, utilize the custom event in a single connection.
Pressing ESC key aborts the connection
Applies to: Firefox less than 20
One of default behaviors of pressing ESC key in Firefox is to cancel all open networking requests fired by XMLHttpRequest
, EventSource
, WebSocket
and so on. The workaround is to prevent that behavior, and its drawback is that the user can't expect the default behavior of pressing ESC key. Fixed in Firefox 20.
// With jQuery
$(window).keydown(function(event) {
if (event.which === 27) {
event.preventDefault();
}
});
Sending an event emits a clicking sound
Applies to: cross-origin HTTP connection on browsers not supporting CORS
If a given url is cross-origin and the browser doesn't support CORS such as Internet Explorer 6, an invisible form tag is used to send data to the server. Here, a clicking sound occurs every time the form is submitted. There is no workaround. Sorry...
A blank page pops up when using connection sharing
Applies to: Internet Explorer whose version is higher than 8.0.7601.17514
and lower than 9.0
When sharing
option is true
, if there is trace of a shared connection, new sockets try to utilize it. In doing this, some Internet Explorer versions don't allow to access other windows so that a blank page pops up instead of getting a reference to targeted window. No workaround. Sorry...