142 lines
5 KiB
JavaScript
142 lines
5 KiB
JavaScript
|
dojo.provide("dojox.cometd.timesync");
|
||
|
dojo.require("dojox.cometd._base");
|
||
|
|
||
|
/**
|
||
|
* this file provides the time synchronization extension to cometd.
|
||
|
* Timesync allows the client and server to exchange time information on every
|
||
|
* handshake and connect message so that the client may calculate an approximate
|
||
|
* offset from it's own clock epoch to that of the server.
|
||
|
*
|
||
|
* With each handshake or connect, the extension sends timestamps within the
|
||
|
* ext field like: <code>{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}</code>
|
||
|
* where:<ul>
|
||
|
* <li>tc is the client timestamp in ms since 1970 of when the message was sent.
|
||
|
* <li>l is the network lag that the client has calculated.
|
||
|
* <li>o is the clock offset that the client has calculated.
|
||
|
* </ul>
|
||
|
* The accuracy of the offset and lag may be calculated with tc-now-l-o,
|
||
|
* which should be zero if the calculated offset and lag are perfectly
|
||
|
* accurate.
|
||
|
* <p>
|
||
|
* A cometd server that supports timesync, should respond only if the
|
||
|
* measured accuracy value is greater than accuracy target. The response
|
||
|
* will be an ext field like: <code>{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}</code>
|
||
|
* where:<ul>
|
||
|
* <li>tc is the client timestamp of when the message was sent,
|
||
|
* <li>ts is the server timestamp of when the message was received
|
||
|
* <li>p is the poll duration in ms - ie the time the server took before sending the response.
|
||
|
* <li>a is the measured accuracy of the calculated offset and lag sent by the client
|
||
|
* </ul>
|
||
|
*
|
||
|
* On receipt of the response, the client is able to use current time to determine
|
||
|
* the total trip time, from which p is subtracted to determine an approximate
|
||
|
* two way network traversal time. The measured accuracy is used to adjust the assumption
|
||
|
* that the network is symmetric for traversal time, so: <ul>
|
||
|
* <li>lag = (now-tc-p)/2-a
|
||
|
* <li>offset = ts-tc-lag
|
||
|
* </ul>
|
||
|
*
|
||
|
* In order to smooth over any transient fluctuations, the extension keeps a sliding
|
||
|
* average of the offsets received. By default this is over 10 messages, but this can
|
||
|
* be changed with the dojox.cometd.timesync._window element.
|
||
|
*/
|
||
|
dojox.cometd.timesync = new function(){
|
||
|
this._window = 10; // The window size for the sliding average of offset samples.
|
||
|
this._lags = []; // The samples used to calculate the average lag.
|
||
|
this._offsets = []; // The samples used to calculate the average offset.
|
||
|
this.lag=0; // The calculated network lag from client to server
|
||
|
this.offset = 0; // The offset in ms between the clients clock and the servers clock.
|
||
|
this.samples = 0; // The number of samples used to calculate the offset. If 0, the offset is not valid.
|
||
|
|
||
|
this.getServerTime = function(){ // return: long
|
||
|
// Summary:
|
||
|
// Calculate the current time on the server
|
||
|
//
|
||
|
return new Date().getTime()+this.offset;
|
||
|
}
|
||
|
|
||
|
this.getServerDate = function(){ // return: Date
|
||
|
// Summary:
|
||
|
// Calculate the current time on the server
|
||
|
//
|
||
|
return new Date(this.getServerTime());
|
||
|
}
|
||
|
|
||
|
this.setTimeout = function(/*function*/call, /*long|Date*/atTimeOrDate){
|
||
|
// Summary:
|
||
|
// Set a timeout function relative to server time
|
||
|
// call:
|
||
|
// the function to call when the timeout occurs
|
||
|
// atTimeOrTime:
|
||
|
// a long timestamp or a Date representing the server time at
|
||
|
// which the timeout should occur.
|
||
|
|
||
|
var ts = (atTimeOrDate instanceof Date) ? atTimeOrDate.getTime() : (0 + atTimeOrDate);
|
||
|
var tc = ts - this.offset;
|
||
|
var interval = tc - new Date().getTime();
|
||
|
if(interval <= 0){
|
||
|
interval = 1;
|
||
|
}
|
||
|
return setTimeout(call,interval);
|
||
|
}
|
||
|
|
||
|
this._in = function(/*Object*/msg){
|
||
|
// Summary:
|
||
|
// Handle incoming messages for the timesync extension.
|
||
|
// description:
|
||
|
// Look for ext:{timesync:{}} field and calculate offset if present.
|
||
|
// msg:
|
||
|
// The incoming bayeux message
|
||
|
|
||
|
var channel = msg.channel;
|
||
|
if(channel && channel.indexOf('/meta/') == 0){
|
||
|
if(msg.ext && msg.ext.timesync){
|
||
|
var sync = msg.ext.timesync;
|
||
|
var now = new Date().getTime();
|
||
|
var l=(now-sync.tc-sync.p)/2-sync.a;
|
||
|
var o=sync.ts-sync.tc-l;
|
||
|
|
||
|
this._lags.push(l);
|
||
|
this._offsets.push(o);
|
||
|
if(this._offsets.length > this._window){
|
||
|
this._offsets.shift();
|
||
|
this._lags.shift();
|
||
|
}
|
||
|
this.samples++;
|
||
|
l=0;
|
||
|
o=0;
|
||
|
for(var i in this._offsets){
|
||
|
l+=this._lags[i];
|
||
|
o+=this._offsets[i];
|
||
|
}
|
||
|
this.offset = parseInt((o / this._offsets.length).toFixed());
|
||
|
this.lag = parseInt((l / this._lags.length).toFixed());
|
||
|
|
||
|
}
|
||
|
}
|
||
|
return msg;
|
||
|
}
|
||
|
|
||
|
this._out = function(msg){
|
||
|
// Summary:
|
||
|
// Handle outgoing messages for the timesync extension.
|
||
|
// description:
|
||
|
// Look for handshake and connect messages and add the ext:{timesync:{}} fields
|
||
|
// msg:
|
||
|
// The outgoing bayeux message
|
||
|
|
||
|
var channel = msg.channel;
|
||
|
if(channel && channel.indexOf('/meta/') == 0){
|
||
|
var now = new Date().getTime();
|
||
|
if(!msg.ext){
|
||
|
msg.ext = {};
|
||
|
}
|
||
|
msg.ext.timesync = {tc:now,l:this.lag,o:this.offset};
|
||
|
}
|
||
|
return msg;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
dojox.cometd._extendInList.push(dojo.hitch(dojox.cometd.timesync, "_in"));
|
||
|
dojox.cometd._extendOutList.push(dojo.hitch(dojox.cometd.timesync, "_out"));
|